github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/rpcclient/local.go (about)

     1  package rpcclient
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/nspcc-dev/neo-go/pkg/neorpc"
     7  )
     8  
     9  // InternalHook is a function signature that is required to create a local client
    10  // (see NewInternal). It performs registration of local client's event channel
    11  // and returns a request handler function.
    12  type InternalHook func(context.Context, chan<- neorpc.Notification) func(*neorpc.Request) (*neorpc.Response, error)
    13  
    14  // Internal is an experimental "local" client that does not connect to RPC via
    15  // network. It's made for deeply integrated applications like NeoFS that have
    16  // blockchain running in the same process, so use it only if you know what you're
    17  // doing. It provides the same interface WSClient does.
    18  type Internal struct {
    19  	WSClient
    20  
    21  	events chan neorpc.Notification
    22  }
    23  
    24  // NewInternal creates an instance of internal client. It accepts a method
    25  // provided by RPC server.
    26  func NewInternal(ctx context.Context, register InternalHook) (*Internal, error) {
    27  	c := &Internal{
    28  		WSClient: WSClient{
    29  			Client: Client{},
    30  
    31  			shutdown:      make(chan struct{}),
    32  			readerDone:    make(chan struct{}),
    33  			writerDone:    make(chan struct{}),
    34  			subscriptions: make(map[string]notificationReceiver),
    35  			receivers:     make(map[any][]string),
    36  		},
    37  		events: make(chan neorpc.Notification),
    38  	}
    39  
    40  	err := initClient(ctx, &c.WSClient.Client, "localhost:0", Options{})
    41  	if err != nil {
    42  		return nil, err // Can't really happen for internal client.
    43  	}
    44  	c.cli = nil
    45  	go c.eventLoop()
    46  	// c.ctx is inherited from ctx in fact (see initClient).
    47  	c.requestF = register(c.ctx, c.events) //nolint:contextcheck // Non-inherited new context, use function like `context.WithXXX` instead
    48  	return c, nil
    49  }
    50  
    51  func (c *Internal) eventLoop() {
    52  eventloop:
    53  	for {
    54  		select {
    55  		case <-c.ctx.Done():
    56  			break eventloop
    57  		case <-c.shutdown:
    58  			break eventloop
    59  		case ev := <-c.events:
    60  			ntf := Notification{Type: ev.Event}
    61  			if len(ev.Payload) > 0 {
    62  				ntf.Value = ev.Payload[0]
    63  			}
    64  			c.notifySubscribers(ntf)
    65  		}
    66  	}
    67  	close(c.readerDone)
    68  	c.ctxCancel()
    69  	// ctx is cancelled, server is notified and will finish soon.
    70  drainloop:
    71  	for {
    72  		select {
    73  		case <-c.events:
    74  		default:
    75  			break drainloop
    76  		}
    77  	}
    78  	close(c.events)
    79  }