diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index 977de8c57..c00051da4 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -941,6 +941,30 @@ func (c *Direct) sendMapRequest(ctx context.Context, isStreaming bool, nu Netmap url = strings.Replace(url, "http:", "https:", 1) } + // Create a watchdog timer that breaks the connection if we don't receive a + // MapResponse from the network at least once every two minutes. The + // watchdog timer is stopped every time we receive a MapResponse (so it + // doesn't run when we're processing a MapResponse message, including any + // long-running requested operations like Debug.Sleep) and is reset whenever + // we go back to blocking on network reads. + // The watchdog timer also covers the initial request (effectively the + // pre-body and initial-body read timeouts) as we do not have any other + // keep-alive mechanism for the initial request. + watchdogTimer, watchdogTimedOut := c.clock.NewTimer(watchdogTimeout) + defer watchdogTimer.Stop() + + go func() { + select { + case <-ctx.Done(): + vlogf("netmap: ending timeout goroutine") + return + case <-watchdogTimedOut: + c.logf("map response long-poll timed out!") + cancel() + return + } + }() + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(bodyData)) if err != nil { return err @@ -962,6 +986,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, isStreaming bool, nu Netmap defer res.Body.Close() health.NoteMapRequestHeard(request) + watchdogTimer.Reset(watchdogTimeout) if nu == nil { io.Copy(io.Discard, res.Body) @@ -993,27 +1018,6 @@ func (c *Direct) sendMapRequest(ctx context.Context, isStreaming bool, nu Netmap c.expiry = nm.Expiry } - // Create a watchdog timer that breaks the connection if we don't receive a - // MapResponse from the network at least once every two minutes. The - // watchdog timer is stopped every time we receive a MapResponse (so it - // doesn't run when we're processing a MapResponse message, including any - // long-running requested operations like Debug.Sleep) and is reset whenever - // we go back to blocking on network reads. - watchdogTimer, watchdogTimedOut := c.clock.NewTimer(watchdogTimeout) - defer watchdogTimer.Stop() - - go func() { - select { - case <-ctx.Done(): - vlogf("netmap: ending timeout goroutine") - return - case <-watchdogTimedOut: - c.logf("map response long-poll timed out!") - cancel() - return - } - }() - // gotNonKeepAliveMessage is whether we've yet received a MapResponse message without // KeepAlive set. var gotNonKeepAliveMessage bool