-
Notifications
You must be signed in to change notification settings - Fork 899
fix(codersdk): keep workspace agent connection open after dial context #10863
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
Changes from 1 commit
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 |
---|---|---|
|
@@ -258,12 +258,12 @@ type DialWorkspaceAgentOptions struct { | |
BlockEndpoints bool | ||
} | ||
|
||
func (c *Client) DialWorkspaceAgent(ctx context.Context, agentID uuid.UUID, options *DialWorkspaceAgentOptions) (agentConn *WorkspaceAgentConn, err error) { | ||
func (c *Client) DialWorkspaceAgent(dialCtx context.Context, agentID uuid.UUID, options *DialWorkspaceAgentOptions) (agentConn *WorkspaceAgentConn, err error) { | ||
if options == nil { | ||
options = &DialWorkspaceAgentOptions{} | ||
} | ||
|
||
connInfo, err := c.WorkspaceAgentConnectionInfo(ctx, agentID) | ||
connInfo, err := c.WorkspaceAgentConnectionInfo(dialCtx, agentID) | ||
if err != nil { | ||
return nil, xerrors.Errorf("get connection info: %w", err) | ||
} | ||
|
@@ -302,7 +302,10 @@ func (c *Client) DialWorkspaceAgent(ctx context.Context, agentID uuid.UUID, opti | |
tokenHeader = c.SessionTokenHeader | ||
} | ||
headers.Set(tokenHeader, c.SessionToken()) | ||
ctx, cancel := context.WithCancel(ctx) | ||
|
||
// New context, separate from dialCtx. We don't want to cancel the | ||
// connection if dialCtx is canceled. | ||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer func() { | ||
if err != nil { | ||
cancel() | ||
|
@@ -317,6 +320,7 @@ func (c *Client) DialWorkspaceAgent(ctx context.Context, agentID uuid.UUID, opti | |
firstCoordinator := make(chan error) | ||
go func() { | ||
defer close(closedCoordinator) | ||
firstCoordinator := firstCoordinator // Shadowed so it can be reassigned outside goroutine. | ||
isFirst := true | ||
for retrier := retry.New(50*time.Millisecond, 10*time.Second); retrier.Wait(ctx); { | ||
options.Logger.Debug(ctx, "connecting") | ||
|
@@ -369,6 +373,7 @@ func (c *Client) DialWorkspaceAgent(ctx context.Context, agentID uuid.UUID, opti | |
firstDerpMap := make(chan error) | ||
go func() { | ||
defer close(closedDerpMap) | ||
firstDerpMap := firstDerpMap // Shadowed so it can be reassigned outside goroutine. | ||
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. Why do we want to modify this from a separate goroutine? Is this not a potential data race? 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. I put this here to safe-guard against data-race since we assign But I just realized, this is not needed. We only ever send one value (error or close, not both), so there is no race. *Iff we had not shadowed and done both: send error and close the chan, then the |
||
isFirst := true | ||
for retrier := retry.New(50*time.Millisecond, 10*time.Second); retrier.Wait(ctx); { | ||
options.Logger.Debug(ctx, "connecting to server for derp map updates") | ||
|
@@ -420,13 +425,21 @@ func (c *Client) DialWorkspaceAgent(ctx context.Context, agentID uuid.UUID, opti | |
} | ||
}() | ||
|
||
err = <-firstCoordinator | ||
if err != nil { | ||
return nil, err | ||
} | ||
err = <-firstDerpMap | ||
if err != nil { | ||
return nil, err | ||
for firstCoordinator != nil || firstDerpMap != nil { | ||
select { | ||
case <-dialCtx.Done(): | ||
return nil, dialCtx.Err() | ||
case err = <-firstCoordinator: | ||
if err != nil { | ||
return nil, err | ||
} | ||
firstCoordinator = nil | ||
case err = <-firstDerpMap: | ||
if err != nil { | ||
return nil, err | ||
} | ||
firstDerpMap = nil | ||
} | ||
} | ||
|
||
agentConn = NewWorkspaceAgentConn(conn, WorkspaceAgentConnOptions{ | ||
|
@@ -444,7 +457,7 @@ func (c *Client) DialWorkspaceAgent(ctx context.Context, agentID uuid.UUID, opti | |
}, | ||
}) | ||
|
||
if !agentConn.AwaitReachable(ctx) { | ||
if !agentConn.AwaitReachable(dialCtx) { | ||
_ = agentConn.Close() | ||
return nil, xerrors.Errorf("timed out waiting for agent to become reachable: %w", ctx.Err()) | ||
} | ||
|
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.
nit: I'm wondering if you can extract this to a separate function to make sure that
cancel()
is called only once in defer, instead of adding an extracancel()
call.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.
It doesn't really matter if it's called multiple times (it's no-op for context cancel funcs). I typically always
defer
as a precaution to ensure it's cleaned up, then call it manually when it's time accepting that there will be a double cancel (this is especially important when usingrequire
that can short-circuit a test).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.
I know, I'm looking at this
cancel()
juggling more from the clean code perspective.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.
Alright, refactored 👍🏻