@@ -17,6 +17,7 @@ import (
1717const (
1818 defaultCancelWorkerInterval = 100 * time .Millisecond
1919 defaultShutdownTimeout = 5 * time .Second
20+ defaultAwaitPollInterval = 100 * time .Millisecond
2021)
2122
2223type Engine struct {
@@ -27,6 +28,7 @@ type Engine struct {
2728 cancelContexts map [int64 ]map [int64 ]context.CancelFunc // instanceID -> stepID -> cancel function
2829 cancelMu sync.RWMutex
2930 cancelWorkerInterval time.Duration
31+ awaitPollInterval time.Duration
3032
3133 // Shutdown logic controls
3234 shutdownCh chan struct {}
@@ -50,6 +52,14 @@ type Engine struct {
5052 skipLogNextAllowed map [string ]time.Time
5153}
5254
55+ // StartAwaitResult contains the result of StartAwait operation.
56+ type StartAwaitResult struct {
57+ InstanceID int64
58+ Status WorkflowStatus
59+ Output json.RawMessage
60+ Error * string
61+ }
62+
5363func NewEngine (pool * pgxpool.Pool , opts ... EngineOption ) * Engine {
5464 shutdownCtx , shutdownCancel := context .WithCancel (context .Background ())
5565
@@ -60,6 +70,7 @@ func NewEngine(pool *pgxpool.Pool, opts ...EngineOption) *Engine {
6070 cancelContexts : make (map [int64 ]map [int64 ]context.CancelFunc ),
6171 shutdownCh : make (chan struct {}),
6272 cancelWorkerInterval : defaultCancelWorkerInterval ,
73+ awaitPollInterval : defaultAwaitPollInterval ,
6374 shutdownCtx : shutdownCtx ,
6475 shutdownCancel : shutdownCancel ,
6576 // defaults for missing-handler behavior
@@ -208,6 +219,62 @@ func (engine *Engine) Start(ctx context.Context, workflowID string, input json.R
208219 return instanceID , nil
209220}
210221
222+ // StartAwait starts a workflow and waits for its completion.
223+ // The method blocks until the workflow reaches a terminal state
224+ // (completed, failed, cancelled, aborted, or dlq) or the context is cancelled.
225+ func (engine * Engine ) StartAwait (ctx context.Context , workflowID string , input json.RawMessage ) (* StartAwaitResult , error ) {
226+ instanceID , err := engine .Start (ctx , workflowID , input )
227+ if err != nil {
228+ return nil , fmt .Errorf ("start workflow: %w" , err )
229+ }
230+
231+ result , err := engine .awaitCompletion (ctx , instanceID )
232+ if err != nil {
233+ return nil , err
234+ }
235+
236+ return result , nil
237+ }
238+
239+ // awaitCompletion waits for the workflow instance to reach a terminal state.
240+ func (engine * Engine ) awaitCompletion (ctx context.Context , instanceID int64 ) (* StartAwaitResult , error ) {
241+ ticker := time .NewTicker (engine .awaitPollInterval )
242+ defer ticker .Stop ()
243+
244+ for {
245+ select {
246+ case <- ctx .Done ():
247+ return nil , ctx .Err ()
248+ case <- engine .shutdownCh :
249+ return nil , errors .New ("engine shutdown" )
250+ case <- ticker .C :
251+ instance , err := engine .store .GetInstance (ctx , instanceID )
252+ if err != nil {
253+ return nil , fmt .Errorf ("get instance: %w" , err )
254+ }
255+
256+ if engine .isTerminalStatus (instance .Status ) {
257+ return & StartAwaitResult {
258+ InstanceID : instance .ID ,
259+ Status : instance .Status ,
260+ Output : instance .Output ,
261+ Error : instance .Error ,
262+ }, nil
263+ }
264+ }
265+ }
266+ }
267+
268+ // isTerminalStatus checks if the workflow status is terminal.
269+ func (engine * Engine ) isTerminalStatus (status WorkflowStatus ) bool {
270+ switch status {
271+ case StatusCompleted , StatusFailed , StatusCancelled , StatusAborted , StatusDLQ :
272+ return true
273+ default :
274+ return false
275+ }
276+ }
277+
211278func (engine * Engine ) Shutdown (timeoutOpt ... time.Duration ) error {
212279 var shutdownErr error
213280
0 commit comments