@@ -3,17 +3,21 @@ package flex
33import (
44 "context"
55 "errors"
6- "fmt"
76 "log"
87 "os"
98 "os/signal"
10- "strings"
119 "sync"
1210 "syscall"
11+ "time"
1312)
1413
1514var logger = log .New (os .Stderr , "flex: " , 0 )
1615
16+ // DefaultHaltTimeout is the default timeout for graceful shutdown of workers.
17+ // This provides workers with a grace period to close connections, flush data,
18+ // and clean up resources during the halt phase.
19+ const DefaultHaltTimeout = 30 * time .Second
20+
1721// Runner represents the behaviour for running a service worker.
1822type Runner interface {
1923 // Run should run start processing the worker and be a blocking operation.
@@ -40,6 +44,16 @@ func MustStart(ctx context.Context, workers ...Worker) {
4044}
4145
4246// Start is a blocking operation that will start processing the workers.
47+ //
48+ // Workers are started concurrently and run until one of the following occurs:
49+ // 1. A worker returns an error from Run()
50+ // 2. A signal (SIGINT, SIGKILL, SIGTERM) is received
51+ // 3. The provided context is canceled
52+ //
53+ // When shutdown is triggered, all workers' Halt() methods are called concurrently
54+ // with a fresh context that has DefaultHaltTimeout as its deadline. This ensures
55+ // workers have a grace period to perform graceful shutdown operations such as
56+ // closing connections or flushing data.
4357func Start (ctx context.Context , workers ... Worker ) error {
4458 if len (workers ) < 1 {
4559 return errors .New ("need at least 1 worker" )
@@ -79,13 +93,22 @@ loop:
7993 errC <- err
8094 }
8195 case <- ctx .Done ():
96+ // Create a fresh context for halt operations with its own timeout.
97+ // This ensures workers have a grace period to shutdown gracefully,
98+ // even if they were interrupted by a signal or another worker failure.
99+ haltCtx , haltCancel := context .WithTimeout (
100+ context .Background (),
101+ DefaultHaltTimeout ,
102+ )
103+ defer haltCancel ()
104+
82105 var wg sync.WaitGroup
83106 wg .Add (len (workers ))
84107
85108 for _ , worker := range workers {
86109 go func (worker Worker ) {
87110 defer wg .Done ()
88- err := worker .Halt (ctx )
111+ err := worker .Halt (haltCtx )
89112 haltErrC <- err
90113 }(worker )
91114 }
@@ -98,62 +121,16 @@ loop:
98121
99122 close (errC )
100123
101- if err := newMultiErrorFromChan (errC ); err .Valid () {
102- return err
103- }
104-
105- return nil
106- }
107-
108- // MultiError holds a slice of errors and implements the error interface.
109- type MultiError struct { Errors []error }
110-
111- // newMultiErrorFromChan creates a new MultiError from a channel of errors.
112- func newMultiErrorFromChan (errC chan error ) MultiError {
113- var errors []error
124+ var errs []error
114125 for err := range errC {
115126 if err != nil {
116- errors = append (errors , err )
127+ errs = append (errs , err )
117128 }
118129 }
119- return MultiError {Errors : errors }
120- }
121130
122- // Valid returns true if the MultiError Errors slice is not empty.
123- func (e MultiError ) Valid () bool { return len (e .Errors ) > 0 }
124-
125- // Error returns a string representation of the MultiError.
126- func (e MultiError ) Error () string {
127- switch len (e .Errors ) {
128- case 0 :
129- return "there are no errors"
130- case 1 :
131- return e .Errors [0 ].Error ()
132- default :
133- return fmt .Sprintf ("there are more than one errors, first error: %v" , e .Errors [0 ].Error ())
134- }
135- }
136-
137- // Unwrap returns an error from Error (or nil if there are no errors).
138- // This error returned will further support Unwrap to get the next error,
139- // etc. The order will match the order of Errors in the multierror.Error
140- // at the time of calling.
141- func (e MultiError ) Unwrap () error {
142- // no errors, move along.
143- if len (e .Errors ) == 0 {
144- return nil
145- }
146-
147- // 1 error, return it directly.
148- if len (e .Errors ) == 1 {
149- return e .Errors [0 ]
150- }
151-
152- // many errors, return a formatted chain.
153- var errChain []string
154- for _ , err := range e .Errors {
155- errChain = append (errChain , err .Error ())
131+ if len (errs ) > 0 {
132+ return errors .Join (errs ... )
156133 }
157134
158- return fmt . Errorf ( "multiple errors: %v" , strings . Join ( errChain , "; next => " ))
135+ return nil
159136}
0 commit comments