@@ -200,12 +200,12 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO
200
200
201
201
switch agent .LifecycleState {
202
202
case codersdk .WorkspaceAgentLifecycleReady :
203
- sw .Complete (stage , agent .ReadyAt . Sub ( * agent .StartedAt ))
203
+ sw .Complete (stage , safeDuration ( agent .ReadyAt , agent .StartedAt ))
204
204
case codersdk .WorkspaceAgentLifecycleStartTimeout :
205
205
sw .Fail (stage , 0 )
206
206
sw .Log (time.Time {}, codersdk .LogLevelWarn , "Warning: A startup script timed out and your workspace may be incomplete." )
207
207
case codersdk .WorkspaceAgentLifecycleStartError :
208
- sw .Fail (stage , agent .ReadyAt . Sub ( * agent .StartedAt ))
208
+ sw .Fail (stage , safeDuration ( agent .ReadyAt , agent .StartedAt ))
209
209
// Use zero time (omitted) to separate these from the startup logs.
210
210
sw .Log (time.Time {}, codersdk .LogLevelWarn , "Warning: A startup script exited with an error and your workspace may be incomplete." )
211
211
sw .Log (time.Time {}, codersdk .LogLevelWarn , troubleshootingMessage (agent , "https://coder.com/docs/v2/latest/templates#startup-script-exited-with-an-error" ))
@@ -221,7 +221,7 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO
221
221
case agent .LifecycleState .ShuttingDown ():
222
222
// We no longer know if the startup script failed or not,
223
223
// but we need to tell the user something.
224
- sw .Complete (stage , agent .ReadyAt . Sub ( * agent .StartedAt ))
224
+ sw .Complete (stage , safeDuration ( agent .ReadyAt , agent .StartedAt ))
225
225
return errAgentShuttingDown
226
226
}
227
227
}
@@ -238,13 +238,12 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO
238
238
sw .Log (time .Now (), codersdk .LogLevelWarn , "Wait for it to reconnect or restart your workspace." )
239
239
sw .Log (time .Now (), codersdk .LogLevelWarn , troubleshootingMessage (agent , "https://coder.com/docs/v2/latest/templates#agent-connection-issues" ))
240
240
241
- disconnectedAt := * agent .DisconnectedAt
242
241
for agent .Status == codersdk .WorkspaceAgentDisconnected {
243
242
if agent , err = fetch (); err != nil {
244
243
return xerrors .Errorf ("fetch: %w" , err )
245
244
}
246
245
}
247
- sw .Complete (stage , agent .LastConnectedAt . Sub ( disconnectedAt ))
246
+ sw .Complete (stage , safeDuration ( agent .LastConnectedAt , agent . DisconnectedAt ))
248
247
}
249
248
}
250
249
}
@@ -257,6 +256,19 @@ func troubleshootingMessage(agent codersdk.WorkspaceAgent, url string) string {
257
256
return m
258
257
}
259
258
259
+ // safeDuration returns a-b. If a or b is nil, it returns 0.
260
+ // This is because we often dereference a time pointer, which can
261
+ // cause a panic. These dereferences are used to calculate durations,
262
+ // which are not critical, and therefor should not break things
263
+ // when it fails.
264
+ // A panic has been observed in a test.
265
+ func safeDuration (a , b * time.Time ) time.Duration {
266
+ if a == nil || b == nil {
267
+ return 0
268
+ }
269
+ return a .Sub (* b )
270
+ }
271
+
260
272
type closeFunc func () error
261
273
262
274
func (c closeFunc ) Close () error {
0 commit comments