@@ -71,8 +71,8 @@ type DBOSContext interface {
7171 context.Context
7272
7373 // Context Lifecycle
74- Launch () error // Launch the DBOS runtime including system database, queues, admin server, and workflow recovery
75- Cancel () // Gracefully shutdown the DBOS runtime, waiting for workflows to complete and cleaning up resources
74+ Launch () error // Launch the DBOS runtime including system database, queues, admin server, and workflow recovery
75+ Shutdown ( timeout time. Duration ) // Gracefully shutdown all DBOS runtime components with ordered cleanup sequence
7676
7777 // Workflow operations
7878 RunAsStep (_ DBOSContext , fn StepFunc , opts ... StepOption ) (any , error ) // Execute a function as a durable step within a workflow
@@ -239,7 +239,7 @@ func (c *dbosContext) GetApplicationID() string {
239239}
240240
241241// NewDBOSContext creates a new DBOS context with the provided configuration.
242- // The context must be launched with Launch() before use and should be shut down with Cancel ().
242+ // The context must be launched with Launch() before use and should be shut down with Shutdown ().
243243// This function initializes the DBOS system database, sets up the queue sub-system,
244244// and prepares the workflow registry.
245245//
@@ -253,7 +253,7 @@ func (c *dbosContext) GetApplicationID() string {
253253// if err != nil {
254254// log.Fatal(err)
255255// }
256- // defer ctx.Cancel( )
256+ // defer ctx.Shutdown(30*time.Second )
257257//
258258// if err := ctx.Launch(); err != nil {
259259// log.Fatal(err)
@@ -372,62 +372,86 @@ func (c *dbosContext) Launch() error {
372372 return nil
373373}
374374
375- // Cancel gracefully shuts down the DBOS runtime by canceling the context, waiting for
376- // all workflows to complete, and cleaning up system resources including the database
377- // connection pool, queue runner, workflow scheduler, and admin server.
378- // All workflows and steps contexts will be canceled, which one can check using their context's Done() method.
375+ // Shutdown gracefully shuts down the DBOS runtime by performing a complete, ordered cleanup
376+ // of all system components. The shutdown sequence includes:
379377//
380- // This method blocks until all workflows finish and all resources are properly cleaned up.
381- // It should be called when the application is shutting down to ensure data consistency.
382- func (c * dbosContext ) Cancel () {
378+ // 1. Calls Cancel to stop workflows and cancel the context
379+ // 2. Waits for the queue runner to complete processing
380+ // 3. Stops the workflow scheduler and waits for scheduled jobs to finish
381+ // 4. Shuts down the system database connection pool and notification listener
382+ // 5. Shuts down the admin server
383+ // 6. Marks the context as not launched
384+ //
385+ // Each step respects the provided timeout. If any component doesn't shut down within the timeout,
386+ // a warning is logged and the shutdown continues to the next component.
387+ //
388+ // Shutdown is a permanent operation and should be called when the application is terminating.
389+ func (c * dbosContext ) Shutdown (timeout time.Duration ) {
383390 c .logger .Info ("Shutting down DBOS context" )
384391
385392 // Cancel the context to signal all resources to stop
386- c .ctxCancelFunc (errors .New ("DBOS shutdown initiated" ))
393+ c .ctxCancelFunc (errors .New ("DBOS cancellation initiated" ))
387394
388395 // Wait for all workflows to finish
389396 c .logger .Info ("Waiting for all workflows to finish" )
390- c .workflowsWg .Wait ()
391- c .logger .Info ("All workflows completed" )
397+ done := make (chan struct {})
398+ go func () {
399+ c .workflowsWg .Wait ()
400+ close (done )
401+ }()
402+ select {
403+ case <- done :
404+ c .logger .Info ("All workflows completed" )
405+ case <- time .After (timeout ):
406+ // For now just log a warning: eventually we might want Cancel to return an error.
407+ c .logger .Warn ("Timeout waiting for workflows to complete" , "timeout" , timeout )
408+ }
392409
393- // Close the pool and the notification listener if started
394- if c .systemDB != nil {
395- c .logger .Info ("Shutting down system database" )
396- c .systemDB .shutdown (c )
397- c .systemDB = nil
410+ // Wait for queue runner to finish
411+ if c .queueRunner != nil && c .launched .Load () {
412+ c .logger .Info ("Waiting for queue runner to complete" )
413+ select {
414+ case <- c .queueRunner .completionChan :
415+ c .logger .Info ("Queue runner completed" )
416+ c .queueRunner = nil
417+ case <- time .After (timeout ):
418+ c .logger .Warn ("Timeout waiting for queue runner to complete" , "timeout" , timeout )
419+ }
398420 }
399421
400- if c .launched .Load () {
401- // Wait for queue runner to finish
402- <- c .queueRunner .completionChan
403- c .logger .Info ("Queue runner completed" )
404-
405- if c .workflowScheduler != nil {
406- c .logger .Info ("Stopping workflow scheduler" )
407- ctx := c .workflowScheduler .Stop ()
408- // Wait for all running jobs to complete with 5-second timeout
409- timeoutCtx , cancel := context .WithTimeout (ctx , 5 * time .Second )
410- defer cancel ()
411-
412- select {
413- case <- ctx .Done ():
414- c .logger .Info ("All scheduled jobs completed" )
415- case <- timeoutCtx .Done ():
416- c .logger .Warn ("Timeout waiting for jobs to complete. Moving on" , "timeout" , "5s" )
417- }
422+ // Stop the workflow scheduler and wait until all scheduled workflows are done
423+ if c .workflowScheduler != nil && c .launched .Load () {
424+ c .logger .Info ("Stopping workflow scheduler" )
425+ ctx := c .workflowScheduler .Stop ()
426+
427+ select {
428+ case <- ctx .Done ():
429+ c .logger .Info ("All scheduled jobs completed" )
430+ c .workflowScheduler = nil
431+ case <- time .After (timeout ):
432+ c .logger .Warn ("Timeout waiting for jobs to complete. Moving on" , "timeout" , timeout )
418433 }
434+ }
419435
420- if c .adminServer != nil {
421- c .logger .Info ("Shutting down admin server" )
422- err := c .adminServer .Shutdown (c )
423- if err != nil {
424- c .logger .Error ("Failed to shutdown admin server" , "error" , err )
425- } else {
426- c .logger .Info ("Admin server shutdown complete" )
427- }
428- c .adminServer = nil
436+ // Shutdown the admin server
437+ if c .adminServer != nil && c .launched .Load () {
438+ c .logger .Info ("Shutting down admin server" )
439+ err := c .adminServer .Shutdown (timeout )
440+ if err != nil {
441+ c .logger .Error ("Failed to shutdown admin server" , "error" , err )
442+ } else {
443+ c .logger .Info ("Admin server shutdown complete" )
429444 }
445+ c .adminServer = nil
430446 }
447+
448+ // Close the system database
449+ if c .systemDB != nil {
450+ c .logger .Info ("Shutting down system database" )
451+ c .systemDB .shutdown (c , timeout )
452+ c .systemDB = nil
453+ }
454+
431455 c .launched .Store (false )
432456}
433457
0 commit comments