Skip to content

Commit 512133f

Browse files
committed
update to modern errors.Join
1 parent 6eb67dd commit 512133f

3 files changed

Lines changed: 353 additions & 111 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module github.com/go-flexible/flex
22

3-
go 1.23
3+
go 1.25

service.go

Lines changed: 31 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,21 @@ package flex
33
import (
44
"context"
55
"errors"
6-
"fmt"
76
"log"
87
"os"
98
"os/signal"
10-
"strings"
119
"sync"
1210
"syscall"
11+
"time"
1312
)
1413

1514
var 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.
1822
type 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.
4357
func 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

Comments
 (0)