-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix issue #127 #1155
fix issue #127 #1155
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package jsonrpc | ||
|
||
import "time" | ||
|
||
type Config struct { | ||
ReconnectInterval time.Duration | ||
} | ||
|
||
var defaultConfig = Config{ | ||
ReconnectInterval: time.Second * 5, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Arbitrary... |
||
} | ||
|
||
type Option func(c *Config) | ||
|
||
func WithReconnectInterval(d time.Duration) func(c *Config) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If for some reason wants to change. |
||
return func(c *Config) { | ||
c.ReconnectInterval = d | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ import ( | |
"reflect" | ||
"sync" | ||
"sync/atomic" | ||
"time" | ||
|
||
"github.com/gorilla/websocket" | ||
"golang.org/x/xerrors" | ||
|
@@ -42,11 +43,13 @@ type outChanReg struct { | |
|
||
type wsConn struct { | ||
// outside params | ||
conn *websocket.Conn | ||
handler handlers | ||
requests <-chan clientRequest | ||
stop <-chan struct{} | ||
exiting chan struct{} | ||
conn *websocket.Conn | ||
connFactory func() (*websocket.Conn, error) | ||
reconnectInterval time.Duration | ||
handler handlers | ||
requests <-chan clientRequest | ||
stop <-chan struct{} | ||
exiting chan struct{} | ||
|
||
// incoming messages | ||
incoming chan io.Reader | ||
|
@@ -419,6 +422,35 @@ func (c *wsConn) handleFrame(ctx context.Context, frame frame) { | |
} | ||
} | ||
|
||
func (c *wsConn) closeInFlight() { | ||
for id, req := range c.inflight { | ||
req.ready <- clientResponse{ | ||
Jsonrpc: "2.0", | ||
ID: id, | ||
Error: &respError{ | ||
Message: "handler: websocket connection closed", | ||
jsign marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Code: 2, | ||
}, | ||
} | ||
|
||
c.handlingLk.Lock() | ||
for _, cancel := range c.handling { | ||
cancel() | ||
} | ||
c.handlingLk.Unlock() | ||
} | ||
c.inflight = map[int64]clientRequest{} | ||
c.handling = map[int64]context.CancelFunc{} | ||
} | ||
|
||
func (c *wsConn) closeChans() { | ||
for chid := range c.chanHandlers { | ||
hnd := c.chanHandlers[chid] | ||
delete(c.chanHandlers, chid) | ||
hnd(nil, false) | ||
} | ||
} | ||
|
||
func (c *wsConn) handleWsConn(ctx context.Context) { | ||
c.incoming = make(chan io.Reader) | ||
c.inflight = map[int64]clientRequest{} | ||
|
@@ -432,34 +464,39 @@ func (c *wsConn) handleWsConn(ctx context.Context) { | |
|
||
// on close, make sure to return from all pending calls, and cancel context | ||
// on all calls we handle | ||
defer func() { | ||
for id, req := range c.inflight { | ||
req.ready <- clientResponse{ | ||
Jsonrpc: "2.0", | ||
ID: id, | ||
Error: &respError{ | ||
Message: "handler: websocket connection closed", | ||
}, | ||
} | ||
|
||
c.handlingLk.Lock() | ||
for _, cancel := range c.handling { | ||
cancel() | ||
} | ||
c.handlingLk.Unlock() | ||
} | ||
}() | ||
defer c.closeInFlight() | ||
|
||
// wait for the first message | ||
go c.nextMessage() | ||
|
||
for { | ||
select { | ||
case r, ok := <-c.incoming: | ||
if !ok { | ||
if c.incomingErr != nil { | ||
if !websocket.IsCloseError(c.incomingErr, websocket.CloseNormalClosure) { | ||
log.Debugw("websocket error", "error", c.incomingErr) | ||
// connection dropped unexpectedly, do our best to recover it | ||
c.closeInFlight() | ||
c.closeChans() | ||
c.incoming = make(chan io.Reader) // listen again for responses | ||
jsign marked this conversation as resolved.
Show resolved
Hide resolved
|
||
go func() { | ||
var conn *websocket.Conn | ||
for conn == nil { | ||
time.Sleep(c.reconnectInterval) | ||
var err error | ||
if conn, err = c.connFactory(); err != nil { | ||
log.Debugw("websocket connection retried failed", "error", err) | ||
} | ||
} | ||
|
||
c.writeLk.Lock() | ||
c.conn = conn | ||
c.incomingErr = nil | ||
c.writeLk.Unlock() | ||
|
||
go c.nextMessage() | ||
}() | ||
continue | ||
} | ||
} | ||
return // remote closed | ||
|
@@ -477,9 +514,23 @@ func (c *wsConn) handleWsConn(ctx context.Context) { | |
c.handleFrame(ctx, frame) | ||
go c.nextMessage() | ||
case req := <-c.requests: | ||
c.writeLk.Lock() | ||
if req.req.ID != nil { | ||
if c.incomingErr != nil { // No conn?, immediate fail | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interestingly, whenever gorilla detects the connection is closed, it closes |
||
req.ready <- clientResponse{ | ||
Jsonrpc: "2.0", | ||
ID: *req.req.ID, | ||
Error: &respError{ | ||
Message: "handler: websocket connection closed", | ||
Code: 2, | ||
}, | ||
} | ||
c.writeLk.Unlock() | ||
break | ||
} | ||
c.inflight[*req.req.ID] = req | ||
} | ||
c.writeLk.Unlock() | ||
c.sendRequest(req.req) | ||
case <-c.stop: | ||
c.writeLk.Lock() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We save here if was tagged with retry.