github.com/core-coin/go-core/v2@v2.1.9/rpc/subscription.go (about)

     1  // Copyright 2016 by the Authors
     2  // This file is part of the go-core library.
     3  //
     4  // The go-core library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-core library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-core library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package rpc
    18  
    19  import (
    20  	"container/list"
    21  	"context"
    22  	crand "crypto/rand"
    23  	"encoding/binary"
    24  	"encoding/hex"
    25  	"encoding/json"
    26  	"errors"
    27  	"math/rand"
    28  	"reflect"
    29  	"strings"
    30  	"sync"
    31  	"time"
    32  )
    33  
    34  var (
    35  	// ErrNotificationsUnsupported is returned when the connection doesn't support notifications
    36  	ErrNotificationsUnsupported = errors.New("notifications not supported")
    37  	// ErrNotificationNotFound is returned when the notification for the given id is not found
    38  	ErrSubscriptionNotFound = errors.New("subscription not found")
    39  )
    40  
    41  var globalGen = randomIDGenerator()
    42  
    43  // ID defines a pseudo random number that is used to identify RPC subscriptions.
    44  type ID string
    45  
    46  // NewID returns a new, random ID.
    47  func NewID() ID {
    48  	return globalGen()
    49  }
    50  
    51  // randomIDGenerator returns a function generates a random IDs.
    52  func randomIDGenerator() func() ID {
    53  	var buf = make([]byte, 8)
    54  	var seed int64
    55  	if _, err := crand.Read(buf); err == nil {
    56  		seed = int64(binary.BigEndian.Uint64(buf))
    57  	} else {
    58  		seed = int64(time.Now().Nanosecond())
    59  	}
    60  
    61  	var (
    62  		mu  sync.Mutex
    63  		rng = rand.New(rand.NewSource(seed))
    64  	)
    65  	return func() ID {
    66  		mu.Lock()
    67  		defer mu.Unlock()
    68  		id := make([]byte, 16)
    69  		rng.Read(id)
    70  		return encodeID(id)
    71  	}
    72  }
    73  
    74  func encodeID(b []byte) ID {
    75  	id := hex.EncodeToString(b)
    76  	id = strings.TrimLeft(id, "0")
    77  	if id == "" {
    78  		id = "0" // ID's are RPC quantities, no leading zero's and 0 is 0x0.
    79  	}
    80  	return ID("0x" + id)
    81  }
    82  
    83  type notifierKey struct{}
    84  
    85  // NotifierFromContext returns the Notifier value stored in ctx, if any.
    86  func NotifierFromContext(ctx context.Context) (*Notifier, bool) {
    87  	n, ok := ctx.Value(notifierKey{}).(*Notifier)
    88  	return n, ok
    89  }
    90  
    91  // Notifier is tied to a RPC connection that supports subscriptions.
    92  // Server callbacks use the notifier to send notifications.
    93  type Notifier struct {
    94  	h         *handler
    95  	namespace string
    96  
    97  	mu           sync.Mutex
    98  	sub          *Subscription
    99  	buffer       []json.RawMessage
   100  	callReturned bool
   101  	activated    bool
   102  }
   103  
   104  // CreateSubscription returns a new subscription that is coupled to the
   105  // RPC connection. By default subscriptions are inactive and notifications
   106  // are dropped until the subscription is marked as active. This is done
   107  // by the RPC server after the subscription ID is send to the client.
   108  func (n *Notifier) CreateSubscription() *Subscription {
   109  	n.mu.Lock()
   110  	defer n.mu.Unlock()
   111  
   112  	if n.sub != nil {
   113  		panic("can't create multiple subscriptions with Notifier")
   114  	} else if n.callReturned {
   115  		panic("can't create subscription after subscribe call has returned")
   116  	}
   117  	n.sub = &Subscription{ID: n.h.idgen(), namespace: n.namespace, err: make(chan error, 1)}
   118  	return n.sub
   119  }
   120  
   121  // Notify sends a notification to the client with the given data as payload.
   122  // If an error occurs the RPC connection is closed and the error is returned.
   123  func (n *Notifier) Notify(id ID, data interface{}) error {
   124  	enc, err := json.Marshal(data)
   125  	if err != nil {
   126  		return err
   127  	}
   128  
   129  	n.mu.Lock()
   130  	defer n.mu.Unlock()
   131  
   132  	if n.sub == nil {
   133  		panic("can't Notify before subscription is created")
   134  	} else if n.sub.ID != id {
   135  		panic("Notify with wrong ID")
   136  	}
   137  	if n.activated {
   138  		return n.send(n.sub, enc)
   139  	}
   140  	n.buffer = append(n.buffer, enc)
   141  	return nil
   142  }
   143  
   144  // Closed returns a channel that is closed when the RPC connection is closed.
   145  // Deprecated: use subscription error channel
   146  func (n *Notifier) Closed() <-chan interface{} {
   147  	return n.h.conn.closed()
   148  }
   149  
   150  // takeSubscription returns the subscription (if one has been created). No subscription can
   151  // be created after this call.
   152  func (n *Notifier) takeSubscription() *Subscription {
   153  	n.mu.Lock()
   154  	defer n.mu.Unlock()
   155  	n.callReturned = true
   156  	return n.sub
   157  }
   158  
   159  // activate is called after the subscription ID was sent to client. Notifications are
   160  // buffered before activation. This prevents notifications being sent to the client before
   161  // the subscription ID is sent to the client.
   162  func (n *Notifier) activate() error {
   163  	n.mu.Lock()
   164  	defer n.mu.Unlock()
   165  
   166  	for _, data := range n.buffer {
   167  		if err := n.send(n.sub, data); err != nil {
   168  			return err
   169  		}
   170  	}
   171  	n.activated = true
   172  	return nil
   173  }
   174  
   175  func (n *Notifier) send(sub *Subscription, data json.RawMessage) error {
   176  	params, _ := json.Marshal(&subscriptionResult{ID: string(sub.ID), Result: data})
   177  	ctx := context.Background()
   178  	return n.h.conn.writeJSON(ctx, &jsonrpcMessage{
   179  		Version: vsn,
   180  		Method:  n.namespace + notificationMethodSuffix,
   181  		Params:  params,
   182  	})
   183  }
   184  
   185  // A Subscription is created by a notifier and tied to that notifier. The client can use
   186  // this subscription to wait for an unsubscribe request for the client, see Err().
   187  type Subscription struct {
   188  	ID        ID
   189  	namespace string
   190  	err       chan error // closed on unsubscribe
   191  }
   192  
   193  // Err returns a channel that is closed when the client send an unsubscribe request.
   194  func (s *Subscription) Err() <-chan error {
   195  	return s.err
   196  }
   197  
   198  // MarshalJSON marshals a subscription as its ID.
   199  func (s *Subscription) MarshalJSON() ([]byte, error) {
   200  	return json.Marshal(s.ID)
   201  }
   202  
   203  // ClientSubscription is a subscription established through the Client's Subscribe or
   204  // XcbSubscribe methods.
   205  type ClientSubscription struct {
   206  	client    *Client
   207  	etype     reflect.Type
   208  	channel   reflect.Value
   209  	namespace string
   210  	subid     string
   211  	in        chan json.RawMessage
   212  
   213  	quitOnce sync.Once     // ensures quit is closed once
   214  	quit     chan struct{} // quit is closed when the subscription exits
   215  	errOnce  sync.Once     // ensures err is closed once
   216  	err      chan error
   217  }
   218  
   219  func newClientSubscription(c *Client, namespace string, channel reflect.Value) *ClientSubscription {
   220  	sub := &ClientSubscription{
   221  		client:    c,
   222  		namespace: namespace,
   223  		etype:     channel.Type().Elem(),
   224  		channel:   channel,
   225  		quit:      make(chan struct{}),
   226  		err:       make(chan error, 1),
   227  		in:        make(chan json.RawMessage),
   228  	}
   229  	return sub
   230  }
   231  
   232  // Err returns the subscription error channel. The intended use of Err is to schedule
   233  // resubscription when the client connection is closed unexpectedly.
   234  //
   235  // The error channel receives a value when the subscription has ended due
   236  // to an error. The received error is nil if Close has been called
   237  // on the underlying client and no other error has occurred.
   238  //
   239  // The error channel is closed when Unsubscribe is called on the subscription.
   240  func (sub *ClientSubscription) Err() <-chan error {
   241  	return sub.err
   242  }
   243  
   244  // Unsubscribe unsubscribes the notification and closes the error channel.
   245  // It can safely be called more than once.
   246  func (sub *ClientSubscription) Unsubscribe() {
   247  	sub.quitWithError(true, nil)
   248  	sub.errOnce.Do(func() { close(sub.err) })
   249  }
   250  
   251  func (sub *ClientSubscription) quitWithError(unsubscribeServer bool, err error) {
   252  	sub.quitOnce.Do(func() {
   253  		// The dispatch loop won't be able to execute the unsubscribe call
   254  		// if it is blocked on deliver. Close sub.quit first because it
   255  		// unblocks deliver.
   256  		close(sub.quit)
   257  		if unsubscribeServer {
   258  			sub.requestUnsubscribe()
   259  		}
   260  		if err != nil {
   261  			if err == ErrClientQuit {
   262  				err = nil // Adhere to subscription semantics.
   263  			}
   264  			sub.err <- err
   265  		}
   266  	})
   267  }
   268  
   269  func (sub *ClientSubscription) deliver(result json.RawMessage) (ok bool) {
   270  	select {
   271  	case sub.in <- result:
   272  		return true
   273  	case <-sub.quit:
   274  		return false
   275  	}
   276  }
   277  
   278  func (sub *ClientSubscription) start() {
   279  	sub.quitWithError(sub.forward())
   280  }
   281  
   282  func (sub *ClientSubscription) forward() (unsubscribeServer bool, err error) {
   283  	cases := []reflect.SelectCase{
   284  		{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(sub.quit)},
   285  		{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(sub.in)},
   286  		{Dir: reflect.SelectSend, Chan: sub.channel},
   287  	}
   288  	buffer := list.New()
   289  	defer buffer.Init()
   290  	for {
   291  		var chosen int
   292  		var recv reflect.Value
   293  		if buffer.Len() == 0 {
   294  			// Idle, omit send case.
   295  			chosen, recv, _ = reflect.Select(cases[:2])
   296  		} else {
   297  			// Non-empty buffer, send the first queued item.
   298  			cases[2].Send = reflect.ValueOf(buffer.Front().Value)
   299  			chosen, recv, _ = reflect.Select(cases)
   300  		}
   301  
   302  		switch chosen {
   303  		case 0: // <-sub.quit
   304  			return false, nil
   305  		case 1: // <-sub.in
   306  			val, err := sub.unmarshal(recv.Interface().(json.RawMessage))
   307  			if err != nil {
   308  				return true, err
   309  			}
   310  			if buffer.Len() == maxClientSubscriptionBuffer {
   311  				return true, ErrSubscriptionQueueOverflow
   312  			}
   313  			buffer.PushBack(val)
   314  		case 2: // sub.channel<-
   315  			cases[2].Send = reflect.Value{} // Don't hold onto the value.
   316  			buffer.Remove(buffer.Front())
   317  		}
   318  	}
   319  }
   320  
   321  func (sub *ClientSubscription) unmarshal(result json.RawMessage) (interface{}, error) {
   322  	val := reflect.New(sub.etype)
   323  	err := json.Unmarshal(result, val.Interface())
   324  	return val.Elem().Interface(), err
   325  }
   326  
   327  func (sub *ClientSubscription) requestUnsubscribe() error {
   328  	var result interface{}
   329  	return sub.client.Call(&result, sub.namespace+unsubscribeMethodSuffix, sub.subid)
   330  }