|
@@ -208,23 +208,37 @@ type ClientSubscription struct {
|
|
|
channel reflect.Value
|
|
channel reflect.Value
|
|
|
namespace string
|
|
namespace string
|
|
|
subid string
|
|
subid string
|
|
|
- in chan json.RawMessage
|
|
|
|
|
|
|
|
|
|
- quitOnce sync.Once // ensures quit is closed once
|
|
|
|
|
- quit chan struct{} // quit is closed when the subscription exits
|
|
|
|
|
- errOnce sync.Once // ensures err is closed once
|
|
|
|
|
- err chan error
|
|
|
|
|
|
|
+ // The in channel receives notification values from client dispatcher.
|
|
|
|
|
+ in chan json.RawMessage
|
|
|
|
|
+
|
|
|
|
|
+ // The error channel receives the error from the forwarding loop.
|
|
|
|
|
+ // It is closed by Unsubscribe.
|
|
|
|
|
+ err chan error
|
|
|
|
|
+ errOnce sync.Once
|
|
|
|
|
+
|
|
|
|
|
+ // Closing of the subscription is requested by sending on 'quit'. This is handled by
|
|
|
|
|
+ // the forwarding loop, which closes 'forwardDone' when it has stopped sending to
|
|
|
|
|
+ // sub.channel. Finally, 'unsubDone' is closed after unsubscribing on the server side.
|
|
|
|
|
+ quit chan error
|
|
|
|
|
+ forwardDone chan struct{}
|
|
|
|
|
+ unsubDone chan struct{}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// This is the sentinel value sent on sub.quit when Unsubscribe is called.
|
|
|
|
|
+var errUnsubscribed = errors.New("unsubscribed")
|
|
|
|
|
+
|
|
|
func newClientSubscription(c *Client, namespace string, channel reflect.Value) *ClientSubscription {
|
|
func newClientSubscription(c *Client, namespace string, channel reflect.Value) *ClientSubscription {
|
|
|
sub := &ClientSubscription{
|
|
sub := &ClientSubscription{
|
|
|
- client: c,
|
|
|
|
|
- namespace: namespace,
|
|
|
|
|
- etype: channel.Type().Elem(),
|
|
|
|
|
- channel: channel,
|
|
|
|
|
- quit: make(chan struct{}),
|
|
|
|
|
- err: make(chan error, 1),
|
|
|
|
|
- in: make(chan json.RawMessage),
|
|
|
|
|
|
|
+ client: c,
|
|
|
|
|
+ namespace: namespace,
|
|
|
|
|
+ etype: channel.Type().Elem(),
|
|
|
|
|
+ channel: channel,
|
|
|
|
|
+ in: make(chan json.RawMessage),
|
|
|
|
|
+ quit: make(chan error),
|
|
|
|
|
+ forwardDone: make(chan struct{}),
|
|
|
|
|
+ unsubDone: make(chan struct{}),
|
|
|
|
|
+ err: make(chan error, 1),
|
|
|
}
|
|
}
|
|
|
return sub
|
|
return sub
|
|
|
}
|
|
}
|
|
@@ -232,9 +246,9 @@ func newClientSubscription(c *Client, namespace string, channel reflect.Value) *
|
|
|
// Err returns the subscription error channel. The intended use of Err is to schedule
|
|
// Err returns the subscription error channel. The intended use of Err is to schedule
|
|
|
// resubscription when the client connection is closed unexpectedly.
|
|
// resubscription when the client connection is closed unexpectedly.
|
|
|
//
|
|
//
|
|
|
-// The error channel receives a value when the subscription has ended due
|
|
|
|
|
-// to an error. The received error is nil if Close has been called
|
|
|
|
|
-// on the underlying client and no other error has occurred.
|
|
|
|
|
|
|
+// The error channel receives a value when the subscription has ended due to an error. The
|
|
|
|
|
+// received error is nil if Close has been called on the underlying client and no other
|
|
|
|
|
+// error has occurred.
|
|
|
//
|
|
//
|
|
|
// The error channel is closed when Unsubscribe is called on the subscription.
|
|
// The error channel is closed when Unsubscribe is called on the subscription.
|
|
|
func (sub *ClientSubscription) Err() <-chan error {
|
|
func (sub *ClientSubscription) Err() <-chan error {
|
|
@@ -244,41 +258,63 @@ func (sub *ClientSubscription) Err() <-chan error {
|
|
|
// Unsubscribe unsubscribes the notification and closes the error channel.
|
|
// Unsubscribe unsubscribes the notification and closes the error channel.
|
|
|
// It can safely be called more than once.
|
|
// It can safely be called more than once.
|
|
|
func (sub *ClientSubscription) Unsubscribe() {
|
|
func (sub *ClientSubscription) Unsubscribe() {
|
|
|
- sub.quitWithError(true, nil)
|
|
|
|
|
- sub.errOnce.Do(func() { close(sub.err) })
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-func (sub *ClientSubscription) quitWithError(unsubscribeServer bool, err error) {
|
|
|
|
|
- sub.quitOnce.Do(func() {
|
|
|
|
|
- // The dispatch loop won't be able to execute the unsubscribe call
|
|
|
|
|
- // if it is blocked on deliver. Close sub.quit first because it
|
|
|
|
|
- // unblocks deliver.
|
|
|
|
|
- close(sub.quit)
|
|
|
|
|
- if unsubscribeServer {
|
|
|
|
|
- sub.requestUnsubscribe()
|
|
|
|
|
- }
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- if err == ErrClientQuit {
|
|
|
|
|
- err = nil // Adhere to subscription semantics.
|
|
|
|
|
- }
|
|
|
|
|
- sub.err <- err
|
|
|
|
|
|
|
+ sub.errOnce.Do(func() {
|
|
|
|
|
+ select {
|
|
|
|
|
+ case sub.quit <- errUnsubscribed:
|
|
|
|
|
+ <-sub.unsubDone
|
|
|
|
|
+ case <-sub.unsubDone:
|
|
|
}
|
|
}
|
|
|
|
|
+ close(sub.err)
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// deliver is called by the client's message dispatcher to send a notification value.
|
|
|
func (sub *ClientSubscription) deliver(result json.RawMessage) (ok bool) {
|
|
func (sub *ClientSubscription) deliver(result json.RawMessage) (ok bool) {
|
|
|
select {
|
|
select {
|
|
|
case sub.in <- result:
|
|
case sub.in <- result:
|
|
|
return true
|
|
return true
|
|
|
- case <-sub.quit:
|
|
|
|
|
|
|
+ case <-sub.forwardDone:
|
|
|
return false
|
|
return false
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (sub *ClientSubscription) start() {
|
|
|
|
|
- sub.quitWithError(sub.forward())
|
|
|
|
|
|
|
+// close is called by the client's message dispatcher when the connection is closed.
|
|
|
|
|
+func (sub *ClientSubscription) close(err error) {
|
|
|
|
|
+ select {
|
|
|
|
|
+ case sub.quit <- err:
|
|
|
|
|
+ case <-sub.forwardDone:
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// run is the forwarding loop of the subscription. It runs in its own goroutine and
|
|
|
|
|
+// is launched by the client's handler after the subscription has been created.
|
|
|
|
|
+func (sub *ClientSubscription) run() {
|
|
|
|
|
+ defer close(sub.unsubDone)
|
|
|
|
|
+
|
|
|
|
|
+ unsubscribe, err := sub.forward()
|
|
|
|
|
+
|
|
|
|
|
+ // The client's dispatch loop won't be able to execute the unsubscribe call if it is
|
|
|
|
|
+ // blocked in sub.deliver() or sub.close(). Closing forwardDone unblocks them.
|
|
|
|
|
+ close(sub.forwardDone)
|
|
|
|
|
+
|
|
|
|
|
+ // Call the unsubscribe method on the server.
|
|
|
|
|
+ if unsubscribe {
|
|
|
|
|
+ sub.requestUnsubscribe()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Send the error.
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ if err == ErrClientQuit {
|
|
|
|
|
+ // ErrClientQuit gets here when Client.Close is called. This is reported as a
|
|
|
|
|
+ // nil error because it's not an error, but we can't close sub.err here.
|
|
|
|
|
+ err = nil
|
|
|
|
|
+ }
|
|
|
|
|
+ sub.err <- err
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// forward is the forwarding loop. It takes in RPC notifications and sends them
|
|
|
|
|
+// on the subscription channel.
|
|
|
func (sub *ClientSubscription) forward() (unsubscribeServer bool, err error) {
|
|
func (sub *ClientSubscription) forward() (unsubscribeServer bool, err error) {
|
|
|
cases := []reflect.SelectCase{
|
|
cases := []reflect.SelectCase{
|
|
|
{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(sub.quit)},
|
|
{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(sub.quit)},
|
|
@@ -286,7 +322,7 @@ func (sub *ClientSubscription) forward() (unsubscribeServer bool, err error) {
|
|
|
{Dir: reflect.SelectSend, Chan: sub.channel},
|
|
{Dir: reflect.SelectSend, Chan: sub.channel},
|
|
|
}
|
|
}
|
|
|
buffer := list.New()
|
|
buffer := list.New()
|
|
|
- defer buffer.Init()
|
|
|
|
|
|
|
+
|
|
|
for {
|
|
for {
|
|
|
var chosen int
|
|
var chosen int
|
|
|
var recv reflect.Value
|
|
var recv reflect.Value
|
|
@@ -301,7 +337,15 @@ func (sub *ClientSubscription) forward() (unsubscribeServer bool, err error) {
|
|
|
|
|
|
|
|
switch chosen {
|
|
switch chosen {
|
|
|
case 0: // <-sub.quit
|
|
case 0: // <-sub.quit
|
|
|
- return false, nil
|
|
|
|
|
|
|
+ if !recv.IsNil() {
|
|
|
|
|
+ err = recv.Interface().(error)
|
|
|
|
|
+ }
|
|
|
|
|
+ if err == errUnsubscribed {
|
|
|
|
|
+ // Exiting because Unsubscribe was called, unsubscribe on server.
|
|
|
|
|
+ return true, nil
|
|
|
|
|
+ }
|
|
|
|
|
+ return false, err
|
|
|
|
|
+
|
|
|
case 1: // <-sub.in
|
|
case 1: // <-sub.in
|
|
|
val, err := sub.unmarshal(recv.Interface().(json.RawMessage))
|
|
val, err := sub.unmarshal(recv.Interface().(json.RawMessage))
|
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -311,6 +355,7 @@ func (sub *ClientSubscription) forward() (unsubscribeServer bool, err error) {
|
|
|
return true, ErrSubscriptionQueueOverflow
|
|
return true, ErrSubscriptionQueueOverflow
|
|
|
}
|
|
}
|
|
|
buffer.PushBack(val)
|
|
buffer.PushBack(val)
|
|
|
|
|
+
|
|
|
case 2: // sub.channel<-
|
|
case 2: // sub.channel<-
|
|
|
cases[2].Send = reflect.Value{} // Don't hold onto the value.
|
|
cases[2].Send = reflect.Value{} // Don't hold onto the value.
|
|
|
buffer.Remove(buffer.Front())
|
|
buffer.Remove(buffer.Front())
|