feat: lifecycle context — Core.Context() for cooperative shutdown
- Core holds context.Context + CancelFunc - New() creates background context - ServiceStartup creates fresh context from caller's ctx (restart safe) - ServiceShutdown cancels context before draining tasks - c.Context() accessor lets task handlers check Done() for graceful exit Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
629adb056b
commit
bde8d4c7cc
3 changed files with 7 additions and 2 deletions
|
|
@ -86,6 +86,7 @@ func New(opts ...Options) *Core {
|
|||
services: &serviceRegistry{services: make(map[string]*Service)},
|
||||
commands: &commandRegistry{commands: make(map[string]*Command)},
|
||||
}
|
||||
c.ctx, c.cancel = context.WithCancel(context.Background())
|
||||
|
||||
if len(opts) > 0 {
|
||||
cp := make(Options, len(opts[0]))
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
|
@ -29,6 +30,8 @@ type Core struct {
|
|||
ipc *Ipc // c.IPC() — Message bus for IPC
|
||||
i18n *I18n // c.I18n() — Internationalisation and locale collection
|
||||
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
taskIDCounter atomic.Uint64
|
||||
wg sync.WaitGroup
|
||||
shutdown atomic.Bool
|
||||
|
|
@ -48,6 +51,7 @@ func (c *Core) Log() *ErrorLog { return c.log }
|
|||
func (c *Core) Cli() *Cli { return c.cli }
|
||||
func (c *Core) IPC() *Ipc { return c.ipc }
|
||||
func (c *Core) I18n() *I18n { return c.i18n }
|
||||
func (c *Core) Context() context.Context { return c.ctx }
|
||||
func (c *Core) Core() *Core { return c }
|
||||
|
||||
// --- IPC (uppercase aliases) ---
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ func (r *ServiceRuntime[T]) Config() *Config { return r.core.Config() }
|
|||
// ServiceStartup runs OnStart for all registered services that have one.
|
||||
func (c *Core) ServiceStartup(ctx context.Context, options any) Result {
|
||||
c.shutdown.Store(false)
|
||||
c.ctx, c.cancel = context.WithCancel(ctx)
|
||||
startables := c.Startables()
|
||||
if startables.OK {
|
||||
for _, s := range startables.Value.([]*Service) {
|
||||
|
|
@ -53,11 +54,10 @@ func (c *Core) ServiceStartup(ctx context.Context, options any) Result {
|
|||
// ServiceShutdown drains background tasks, then stops all registered services.
|
||||
func (c *Core) ServiceShutdown(ctx context.Context) Result {
|
||||
c.shutdown.Store(true)
|
||||
c.cancel() // signal all context-aware tasks to stop
|
||||
c.ACTION(ActionServiceShutdown{})
|
||||
|
||||
// Drain background tasks before stopping services.
|
||||
// On timeout, the waiter goroutine persists until tasks complete —
|
||||
// this is inherent to sync.WaitGroup (no cancel mechanism).
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
c.wg.Wait()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue