Skip to content

Commit b4b7cf9

Browse files
committed
Add StartAwait method for workflows with polling and terminal state checks.
1 parent a8e3505 commit b4b7cf9

2 files changed

Lines changed: 74 additions & 0 deletions

File tree

engine.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
const (
1818
defaultCancelWorkerInterval = 100 * time.Millisecond
1919
defaultShutdownTimeout = 5 * time.Second
20+
defaultAwaitPollInterval = 100 * time.Millisecond
2021
)
2122

2223
type 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+
5363
func 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+
211278
func (engine *Engine) Shutdown(timeoutOpt ...time.Duration) error {
212279
var shutdownErr error
213280

engine_opts.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ func WithEngineCancelInterval(interval time.Duration) EngineOption {
1212
}
1313
}
1414

15+
// WithEngineAwaitPollInterval sets the polling interval for StartAwait method.
16+
func WithEngineAwaitPollInterval(interval time.Duration) EngineOption {
17+
return func(engine *Engine) {
18+
engine.awaitPollInterval = interval
19+
}
20+
}
21+
1522
func WithEngineTxManager(txManager TxManager) EngineOption {
1623
return func(engine *Engine) {
1724
engine.txManager = txManager

0 commit comments

Comments
 (0)