mirror of https://github.com/milvus-io/milvus.git
				
				
				
			
		
			
				
	
	
		
			204 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			204 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Go
		
	
	
package cgo
 | 
						|
 | 
						|
/*
 | 
						|
#cgo pkg-config: milvus_core
 | 
						|
 | 
						|
#include "futures/future_c.h"
 | 
						|
#include <stdlib.h>
 | 
						|
 | 
						|
extern void unlockMutex(void*);
 | 
						|
 | 
						|
static inline void unlockMutexOnC(CLockedGoMutex* m) {
 | 
						|
    unlockMutex((void*)(m));
 | 
						|
}
 | 
						|
 | 
						|
static inline void future_go_register_ready_callback(CFuture* f, CLockedGoMutex* m) {
 | 
						|
	future_register_ready_callback(f, unlockMutexOnC, m);
 | 
						|
}
 | 
						|
*/
 | 
						|
import "C"
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"sync"
 | 
						|
	"unsafe"
 | 
						|
 | 
						|
	"github.com/cockroachdb/errors"
 | 
						|
 | 
						|
	"github.com/milvus-io/milvus/pkg/util/merr"
 | 
						|
)
 | 
						|
 | 
						|
var ErrConsumed = errors.New("future is already consumed")
 | 
						|
 | 
						|
// Would put this in futures.go but for the documented issue with
 | 
						|
// exports and functions in preamble
 | 
						|
// (https://code.google.com/p/go-wiki/wiki/cgo#Global_functions)
 | 
						|
//
 | 
						|
//export unlockMutex
 | 
						|
func unlockMutex(p unsafe.Pointer) {
 | 
						|
	m := (*sync.Mutex)(p)
 | 
						|
	m.Unlock()
 | 
						|
}
 | 
						|
 | 
						|
type basicFuture interface {
 | 
						|
	// Context return the context of the future.
 | 
						|
	Context() context.Context
 | 
						|
 | 
						|
	// BlockUntilReady block until the future is ready or canceled.
 | 
						|
	// caller can call this method multiple times in different concurrent unit.
 | 
						|
	BlockUntilReady()
 | 
						|
 | 
						|
	// cancel the future with error.
 | 
						|
	cancel(error)
 | 
						|
}
 | 
						|
 | 
						|
type Future interface {
 | 
						|
	basicFuture
 | 
						|
 | 
						|
	// BlockAndLeakyGet block until the future is ready or canceled, and return the leaky result.
 | 
						|
	//   Caller should only call once for BlockAndLeakyGet, otherwise the ErrConsumed will returned.
 | 
						|
	//   Caller will get the merr.ErrSegcoreCancel or merr.ErrSegcoreTimeout respectively if the future is canceled or timeout.
 | 
						|
	//   Caller will get other error if the underlying cgo function throws, otherwise caller will get result.
 | 
						|
	//   Caller should free the result after used (defined by caller), otherwise the memory of result is leaked.
 | 
						|
	BlockAndLeakyGet() (unsafe.Pointer, error)
 | 
						|
 | 
						|
	// Release the resource of the future.
 | 
						|
	// !!! Release is not concurrent safe with other methods.
 | 
						|
	// It should be called only once after all method of future is returned.
 | 
						|
	Release()
 | 
						|
}
 | 
						|
 | 
						|
type (
 | 
						|
	CFuturePtr       unsafe.Pointer
 | 
						|
	CGOAsyncFunction = func() CFuturePtr
 | 
						|
)
 | 
						|
 | 
						|
// Async is a helper function to call a C async function that returns a future.
 | 
						|
func Async(ctx context.Context, f CGOAsyncFunction, opts ...Opt) Future {
 | 
						|
	initCGO()
 | 
						|
 | 
						|
	options := getDefaultOpt()
 | 
						|
	// apply options.
 | 
						|
	for _, opt := range opts {
 | 
						|
		opt(options)
 | 
						|
	}
 | 
						|
 | 
						|
	// create a future for caller to use.
 | 
						|
	var cFuturePtr *C.CFuture
 | 
						|
	getCGOCaller().call(options.name, func() {
 | 
						|
		cFuturePtr = (*C.CFuture)(f())
 | 
						|
	})
 | 
						|
 | 
						|
	ctx, cancel := context.WithCancel(ctx)
 | 
						|
	future := &futureImpl{
 | 
						|
		closure:   f,
 | 
						|
		ctx:       ctx,
 | 
						|
		ctxCancel: cancel,
 | 
						|
		future:    cFuturePtr,
 | 
						|
		opts:      options,
 | 
						|
		state:     newFutureState(),
 | 
						|
	}
 | 
						|
 | 
						|
	// register the future to do timeout notification.
 | 
						|
	futureManager.Register(future)
 | 
						|
	return future
 | 
						|
}
 | 
						|
 | 
						|
type futureImpl struct {
 | 
						|
	ctx       context.Context
 | 
						|
	ctxCancel context.CancelFunc
 | 
						|
	future    *C.CFuture
 | 
						|
	closure   CGOAsyncFunction
 | 
						|
	opts      *options
 | 
						|
	state     futureState
 | 
						|
}
 | 
						|
 | 
						|
// Context return the context of the future.
 | 
						|
func (f *futureImpl) Context() context.Context {
 | 
						|
	return f.ctx
 | 
						|
}
 | 
						|
 | 
						|
// BlockUntilReady block until the future is ready or canceled.
 | 
						|
func (f *futureImpl) BlockUntilReady() {
 | 
						|
	f.blockUntilReady()
 | 
						|
}
 | 
						|
 | 
						|
// BlockAndLeakyGet block until the future is ready or canceled, and return the leaky result.
 | 
						|
func (f *futureImpl) BlockAndLeakyGet() (unsafe.Pointer, error) {
 | 
						|
	f.blockUntilReady()
 | 
						|
 | 
						|
	guard := f.state.LockForConsume()
 | 
						|
	if guard == nil {
 | 
						|
		return nil, ErrConsumed
 | 
						|
	}
 | 
						|
	defer guard.Unlock()
 | 
						|
 | 
						|
	var ptr unsafe.Pointer
 | 
						|
	var status C.CStatus
 | 
						|
	getCGOCaller().call("future_leak_and_get", func() {
 | 
						|
		status = C.future_leak_and_get(f.future, &ptr)
 | 
						|
	})
 | 
						|
	err := ConsumeCStatusIntoError(&status)
 | 
						|
 | 
						|
	if errors.Is(err, merr.ErrSegcoreFollyCancel) {
 | 
						|
		// mark the error with context error.
 | 
						|
		return nil, errors.Mark(err, f.ctx.Err())
 | 
						|
	}
 | 
						|
	return ptr, err
 | 
						|
}
 | 
						|
 | 
						|
// Release the resource of the future.
 | 
						|
func (f *futureImpl) Release() {
 | 
						|
	// block until ready to release the future.
 | 
						|
	f.blockUntilReady()
 | 
						|
 | 
						|
	guard := f.state.LockForRelease()
 | 
						|
	if guard == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	defer guard.Unlock()
 | 
						|
 | 
						|
	// release the future.
 | 
						|
	getCGOCaller().call("future_destroy", func() {
 | 
						|
		C.future_destroy(f.future)
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// cancel the future with error.
 | 
						|
func (f *futureImpl) cancel(err error) {
 | 
						|
	// only unready future can be canceled.
 | 
						|
	guard := f.state.LockForCancel()
 | 
						|
	if guard == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	defer guard.Unlock()
 | 
						|
 | 
						|
	if errors.IsAny(err, context.DeadlineExceeded, context.Canceled) {
 | 
						|
		getCGOCaller().call("future_cancel", func() {
 | 
						|
			C.future_cancel(f.future)
 | 
						|
		})
 | 
						|
		return
 | 
						|
	}
 | 
						|
	panic("unreachable: invalid cancel error type")
 | 
						|
}
 | 
						|
 | 
						|
// blockUntilReady block until the future is ready or canceled.
 | 
						|
func (f *futureImpl) blockUntilReady() {
 | 
						|
	if !f.state.CheckUnready() {
 | 
						|
		// only unready future should be block until ready.
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	mu := &sync.Mutex{}
 | 
						|
	mu.Lock()
 | 
						|
	getCGOCaller().call("future_go_register_ready_callback", func() {
 | 
						|
		C.future_go_register_ready_callback(f.future, (*C.CLockedGoMutex)(unsafe.Pointer(mu)))
 | 
						|
	})
 | 
						|
	mu.Lock()
 | 
						|
 | 
						|
	// mark the future as ready at go side to avoid more cgo calls.
 | 
						|
	f.state.IntoReady()
 | 
						|
	// notify the future manager that the future is ready.
 | 
						|
	f.ctxCancel()
 | 
						|
}
 |