fix: quick reconnect after sleep or a network outage - the tunnel notices a dead connection, retries every 5 s at most, the WebSocket returns as soon as the server answers; version 0.1.1
This commit is contained in:
1 parent
5eca9ca215
commit
b47e824692
3 files changed
+49
-13
No files matched your search
@@ -131,9 +131,7 @@ func (a *Agent) Run(ctx context.Context) error {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
if backoff < 15*time.Second {
|
||||
backoff *= 2
|
||||
}
|
||||
backoff = min(backoff*2, retryCap)
|
||||
continue
|
||||
case proto.TransportAuto:
|
||||
log.Printf("websocket: %v - long polling, websocket again in a minute", err)
|
||||
@@ -167,6 +165,10 @@ func (a *Agent) applyTransport(v string) {
|
||||
func (a *Agent) longPoll(ctx context.Context, d time.Duration) {
|
||||
until := time.Now().Add(d)
|
||||
backoff := time.Second
|
||||
// The server was out of reach (a network down, a machine asleep): once
|
||||
// a poll gets through again, "auto" tries the WebSocket at once rather
|
||||
// than at the end of the minute - it was the network, not the WebSocket.
|
||||
lost := false
|
||||
for ctx.Err() == nil {
|
||||
if d > 0 && (time.Now().After(until) || a.mode() == proto.TransportWS) {
|
||||
return
|
||||
@@ -174,6 +176,21 @@ func (a *Agent) longPoll(ctx context.Context, d time.Duration) {
|
||||
if d == 0 && a.mode() != proto.TransportLP {
|
||||
return
|
||||
}
|
||||
// Back from out of reach in "auto": a quick request tells when the
|
||||
// server answers again, and the WebSocket is tried then; a poll
|
||||
// would sit held on the server first.
|
||||
if lost && d > 0 {
|
||||
if a.reachable(ctx) {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-time.After(backoff):
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
backoff = min(backoff*2, retryCap)
|
||||
continue
|
||||
}
|
||||
// In "auto" the minute is a minute: a poll still held then is cut
|
||||
// short (the server keeps its job for the next one).
|
||||
reqCtx, cancel := ctx, context.CancelFunc(func() {})
|
||||
@@ -193,15 +210,32 @@ func (a *Agent) longPoll(ctx context.Context, d time.Duration) {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
if backoff < 15*time.Second {
|
||||
backoff *= 2
|
||||
}
|
||||
backoff = min(backoff*2, retryCap)
|
||||
lost = true
|
||||
continue
|
||||
}
|
||||
backoff = time.Second
|
||||
}
|
||||
}
|
||||
|
||||
// reachable reports whether the server answers a quick request.
|
||||
func (a *Agent) reachable(ctx context.Context) bool {
|
||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, a.cfg.Host+"/api/agent/config", nil)
|
||||
a.authHeaders(req)
|
||||
resp, err := a.http.Do(req)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
resp.Body.Close()
|
||||
return resp.StatusCode < 300
|
||||
}
|
||||
|
||||
// retryCap bounds the wait between attempts to reach the server, so a
|
||||
// machine back from sleep or a network back up is reconnected in seconds.
|
||||
const retryCap = 5 * time.Second
|
||||
|
||||
func (a *Agent) enroll(ctx context.Context) error {
|
||||
host, _ := os.Hostname()
|
||||
body, _ := json.Marshal(proto.EnrollRequest{
|
||||
|
||||
@@ -26,9 +26,7 @@ func (a *Agent) runTunnel(ctx context.Context, addr string) {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
if backoff < 15*time.Second {
|
||||
backoff *= 2
|
||||
}
|
||||
backoff = min(backoff*2, retryCap)
|
||||
continue
|
||||
}
|
||||
backoff = time.Second
|
||||
@@ -96,8 +94,14 @@ func (t *tunClient) write(f tunnel.Frame) error {
|
||||
return tunnel.WriteFrame(t.conn, f)
|
||||
}
|
||||
|
||||
// tunnelSilence is how long the tunnel may say nothing before it is taken
|
||||
// for dead: the server pings every 20 s, so this is three missed pings - a
|
||||
// connection a NAT forgot while the machine slept, say.
|
||||
const tunnelSilence = 65 * time.Second
|
||||
|
||||
func (t *tunClient) readLoop() error {
|
||||
for {
|
||||
_ = t.conn.SetReadDeadline(time.Now().Add(tunnelSilence))
|
||||
f, err := tunnel.ReadFrame(t.conn)
|
||||
if err != nil {
|
||||
t.shutdown()
|
||||
@@ -207,9 +211,7 @@ func (a *Agent) runUDP(ctx context.Context, addr string) {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
if backoff < 15*time.Second {
|
||||
backoff *= 2
|
||||
}
|
||||
backoff = min(backoff*2, retryCap)
|
||||
continue
|
||||
}
|
||||
backoff = time.Second
|
||||
|
||||
Reference in new issue
Block a user