@@ -337,16 +337,18 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
337
337
338
338
// Register signals early on so that graceful shutdown can't
339
339
// be interrupted by additional signals. Note that we avoid
340
- // shadowing cancel() (from above) here becausenotifyStop ()
340
+ // shadowing cancel() (from above) here becausestopCancel ()
341
341
// restores default behavior for the signals. This protects
342
342
// the shutdown sequence from abruptly terminating things
343
343
// like: database migrations, provisioner work, workspace
344
344
// cleanup in dev-mode, etc.
345
345
//
346
346
// To get out of a graceful shutdown, the user can send
347
347
// SIGQUIT with ctrl+\ or SIGKILL with `kill -9`.
348
- notifyCtx ,notifyStop := inv .SignalNotifyContext (ctx ,InterruptSignals ... )
349
- defer notifyStop ()
348
+ stopCtx ,stopCancel := signalNotifyContext (ctx ,inv ,StopSignals ... )
349
+ defer stopCancel ()
350
+ interruptCtx ,interruptCancel := signalNotifyContext (ctx ,inv ,InterruptSignals ... )
351
+ defer interruptCancel ()
350
352
351
353
cacheDir := vals .CacheDir .String ()
352
354
err = os .MkdirAll (cacheDir ,0o700 )
@@ -1028,13 +1030,18 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
1028
1030
hangDetector .Start ()
1029
1031
defer hangDetector .Close ()
1030
1032
1033
+ graceful := false
1031
1034
// Currently there is no way to ask the server to shut
1032
1035
// itself down, so any exit signal will result in a non-zero
1033
1036
// exit of the server.
1034
1037
var exitErr error
1035
1038
select {
1036
- case <- notifyCtx .Done ():
1037
- exitErr = notifyCtx .Err ()
1039
+ case <- stopCtx .Done ():
1040
+ exitErr = stopCtx .Err ()
1041
+ graceful = true
1042
+ _ ,_ = io .WriteString (inv .Stdout ,cliui .Bold ("Stop caught, waiting for provisioner jobs to complete and gracefully exiting. Use ctrl+\\ to force quit" ))
1043
+ case <- interruptCtx .Done ():
1044
+ exitErr = interruptCtx .Err ()
1038
1045
_ ,_ = io .WriteString (inv .Stdout ,cliui .Bold ("Interrupt caught, gracefully exiting. Use ctrl+\\ to force quit" ))
1039
1046
case <- tunnelDone :
1040
1047
exitErr = xerrors .New ("dev tunnel closed unexpectedly" )
@@ -1082,7 +1089,16 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
1082
1089
defer wg .Done ()
1083
1090
1084
1091
r .Verbosef (inv ,"Shutting down provisioner daemon %d..." ,id )
1085
- err := shutdownWithTimeout (provisionerDaemon .Shutdown ,5 * time .Second )
1092
+ timeout := 5 * time .Second
1093
+ if graceful {
1094
+ // It can last for a long time...
1095
+ timeout = 30 * time .Minute
1096
+ }
1097
+
1098
+ err := shutdownWithTimeout (func (ctx context.Context )error {
1099
+ // We only want to cancel active jobs if we aren't exiting gracefully.
1100
+ return provisionerDaemon .Shutdown (ctx ,! graceful )
1101
+ },timeout )
1086
1102
if err != nil {
1087
1103
cliui .Errorf (inv .Stderr ,"Failed to shut down provisioner daemon %d: %s\n " ,id ,err )
1088
1104
return
@@ -2512,3 +2528,12 @@ func escapePostgresURLUserInfo(v string) (string, error) {
2512
2528
2513
2529
return v ,nil
2514
2530
}
2531
+
2532
+ func signalNotifyContext (ctx context.Context ,inv * clibase.Invocation ,sig ... os.Signal ) (context.Context , context.CancelFunc ) {
2533
+ // On Windows, some of our signal functions lack support.
2534
+ // If we pass in no signals, we should just return the context as-is.
2535
+ if len (sig )== 0 {
2536
+ return context .WithCancel (ctx )
2537
+ }
2538
+ return inv .SignalNotifyContext (ctx ,sig ... )
2539
+ }