github.com/altipla-consulting/ravendb-go-client@v0.1.3/completable_future.go (about)

     1  package ravendb
     2  
     3  import (
     4  	"sync"
     5  	"time"
     6  )
     7  
     8  // TODO: write tests
     9  
    10  /*
    11  Note:
    12  
    13  future = completableFuture.runAsync(() -> foo())
    14  
    15  (or supplyAsync) is replaced with this pattern:
    16  
    17  future = newCompletableFuture()
    18  go func() {
    19  	res, err := foo()
    20  	if err != nil {
    21  		future.completeWithError(err)
    22  	} else {
    23  		future.complete(res)
    24  	}
    25  }()
    26  */
    27  
    28  // completableFuture helps porting Java code. Implements only functions needed
    29  // by ravendb.
    30  type completableFuture struct {
    31  	mu sync.Mutex
    32  
    33  	completed bool
    34  	// used to wait for Future to finish
    35  	signalCompletion chan bool
    36  
    37  	// result generated by the Future, only valid if completed
    38  	result interface{}
    39  	err    error
    40  }
    41  
    42  func newCompletableFuture() *completableFuture {
    43  	return &completableFuture{
    44  		// channel with capacity 1 so that complete() can finish the goroutine
    45  		// without waiting for someone to call Get()
    46  		signalCompletion: make(chan bool, 1),
    47  	}
    48  }
    49  
    50  func newCompletableFutureAlreadyCompleted(result interface{}) *completableFuture {
    51  	res := newCompletableFuture()
    52  	res.complete(result)
    53  	return res
    54  }
    55  
    56  func (f *completableFuture) getState() (bool, interface{}, error) {
    57  	f.mu.Lock()
    58  	defer f.mu.Unlock()
    59  	return f.completed, f.result, f.err
    60  }
    61  
    62  // must be called with f.mu locked
    63  func (f *completableFuture) markCompleted(result interface{}, err error) {
    64  	f.completed = true
    65  	f.result = result
    66  	f.err = err
    67  	f.signalCompletion <- true
    68  }
    69  
    70  // complete marks the future as completed with a given result (which can be nil)
    71  func (f *completableFuture) complete(result interface{}) {
    72  	f.mu.Lock()
    73  	if !f.completed {
    74  		f.markCompleted(result, nil)
    75  	}
    76  	f.mu.Unlock()
    77  }
    78  
    79  // completeWithError marks the future as completed with error
    80  func (f *completableFuture) completeWithError(err error) {
    81  	f.mu.Lock()
    82  	if !f.completed {
    83  		f.markCompleted(nil, err)
    84  	}
    85  	f.mu.Unlock()
    86  }
    87  
    88  // cancel cancels a future
    89  func (f *completableFuture) cancel(mayInterruptIfRunning bool) {
    90  	// mayInterruptIfRunning is ignored, apparently same happens in Java
    91  	// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html
    92  	f.completeWithError(&CancellationError{})
    93  }
    94  
    95  // IsDone returns true if future has been completed, either with a result or error
    96  func (f *completableFuture) IsDone() bool {
    97  	done, _, _ := f.getState()
    98  	return done
    99  }
   100  
   101  // IsCompletedExceptionally returns true if future has been completed due to an error
   102  func (f *completableFuture) IsCompletedExceptionally() bool {
   103  	_, _, err := f.getState()
   104  	return err != nil // implies f.done
   105  }
   106  
   107  // isCancelled returns true if future was cancelled by calling cancel()
   108  func (f *completableFuture) isCancelled() bool {
   109  	var isCancelled bool
   110  	_, err, _ := f.getState()
   111  	if err != nil {
   112  		_, isCancelled = err.(*CancellationError)
   113  	}
   114  	return isCancelled
   115  }
   116  
   117  // Get waits for completion and returns resulting value or error
   118  // If already completed, returns immediately.
   119  // TODO: hide it, unused in one test
   120  func (f *completableFuture) Get() (interface{}, error) {
   121  	return f.GetWithTimeout(0)
   122  }
   123  
   124  func (f *completableFuture) GetWithTimeout(dur time.Duration) (interface{}, error) {
   125  	done, res, err := f.getState()
   126  	if done {
   127  		return res, err
   128  	}
   129  
   130  	if dur == 0 {
   131  		// wait for the Future to complete
   132  		<-f.signalCompletion
   133  	} else {
   134  		// wait for the Future to complete or timeout to expire
   135  		select {
   136  		case <-f.signalCompletion:
   137  			// completed, will return the result
   138  		case <-time.After(dur):
   139  			// timed out
   140  			return nil, NewTimeoutError("GetWithTimeout() timed out after %s", dur)
   141  		}
   142  	}
   143  
   144  	_, res, err = f.getState()
   145  	return res, err
   146  }