@@ -3,6 +3,7 @@ package async
33import (
44 "context"
55 "errors"
6+ "fmt"
67 "sync"
78 "sync/atomic"
89)
@@ -13,12 +14,12 @@ type ExecutorStatus uint32
1314const (
1415 ExecutorStatusRunning ExecutorStatus = iota
1516 ExecutorStatusTerminating
16- ExecutorStatusShutdown
17+ ExecutorStatusShutDown
1718)
1819
1920var (
2021 ErrExecutorQueueFull = errors .New ("async: executor queue is full" )
21- ErrExecutorShutdown = errors .New ("async: executor is shut down" )
22+ ErrExecutorShutDown = errors .New ("async: executor is shut down" )
2223)
2324
2425// ExecutorService is an interface that defines a task executor.
@@ -44,6 +45,7 @@ type ExecutorConfig struct {
4445}
4546
4647// NewExecutorConfig returns a new [ExecutorConfig].
48+ // workerPoolSize must be positive and queueSize non-negative.
4749func NewExecutorConfig (workerPoolSize , queueSize int ) * ExecutorConfig {
4850 return & ExecutorConfig {
4951 WorkerPoolSize : workerPoolSize ,
@@ -53,6 +55,7 @@ func NewExecutorConfig(workerPoolSize, queueSize int) *ExecutorConfig {
5355
5456// Executor implements the [ExecutorService] interface.
5557type Executor [T any ] struct {
58+ mtx sync.RWMutex
5659 cancel context.CancelFunc
5760 queue chan executorJob [T ]
5861 status atomic.Uint32
@@ -65,6 +68,16 @@ type executorJob[T any] struct {
6568 task func (context.Context ) (T , error )
6669}
6770
71+ // run executes the task, handling possible panics.
72+ func (job * executorJob [T ]) run (ctx context.Context ) (result T , err error ) {
73+ defer func () {
74+ if r := recover (); r != nil {
75+ err = fmt .Errorf ("recovered: %v" , r )
76+ }
77+ }()
78+ return job .task (ctx )
79+ }
80+
6881// NewExecutor returns a new [Executor].
6982func NewExecutor [T any ](ctx context.Context , config * ExecutorConfig ) * Executor [T ] {
7083 ctx , cancel := context .WithCancel (ctx )
@@ -97,10 +110,11 @@ func (e *Executor[T]) startWorkers(ctx context.Context, poolSize int) {
97110 go func () {
98111 defer wg .Done ()
99112 loop:
113+ // check the status to break the loop even if the queue is not empty
100114 for ExecutorStatus (e .status .Load ()) == ExecutorStatusRunning {
101115 select {
102116 case job := <- e .queue :
103- result , err := job .task (ctx )
117+ result , err := job .run (ctx )
104118 if err != nil {
105119 job .promise .Failure (err )
106120 } else {
@@ -115,30 +129,39 @@ func (e *Executor[T]) startWorkers(ctx context.Context, poolSize int) {
115129
116130 // wait for all workers to exit
117131 wg .Wait ()
132+ // mark the executor as terminating
133+ e .status .Store (uint32 (ExecutorStatusTerminating ))
134+
135+ // avoid submissions while draining the queue
136+ e .mtx .Lock ()
137+ defer e .mtx .Unlock ()
138+
118139 // close the queue and cancel all pending tasks
119140 close (e .queue )
120141 for job := range e .queue {
121- job .promise .Failure (ErrExecutorShutdown )
142+ job .promise .Failure (ErrExecutorShutDown )
122143 }
123144 // mark the executor as shut down
124- e .status .Store (uint32 (ExecutorStatusShutdown ))
145+ e .status .Store (uint32 (ExecutorStatusShutDown ))
125146}
126147
127148// Submit submits a function to the executor.
128149// The function will be executed asynchronously and the result will be
129150// available via the returned future.
130151func (e * Executor [T ]) Submit (f func (context.Context ) (T , error )) (Future [T ], error ) {
131- promise := NewPromise [T ]()
152+ e .mtx .RLock ()
153+ defer e .mtx .RUnlock ()
154+
132155 if ExecutorStatus (e .status .Load ()) == ExecutorStatusRunning {
156+ promise := NewPromise [T ]()
133157 select {
134158 case e .queue <- executorJob [T ]{promise , f }:
159+ return promise .Future (), nil
135160 default :
136161 return nil , ErrExecutorQueueFull
137162 }
138- } else {
139- return nil , ErrExecutorShutdown
140163 }
141- return promise . Future (), nil
164+ return nil , ErrExecutorShutDown
142165}
143166
144167// Shutdown shuts down the executor.
0 commit comments