diff options
| author | sangtran <tranquangsang12.7@gmail.com> | 2026-08-29 01:08:28 +0700 |
|---|---|---|
| committer | sangtran <tranquangsang12.7@gmail.com> | 2026-08-29 01:08:28 +0700 |
| commit | 96c07bab1086510a55a004e9a1b90c30bc723a5c (patch) | |
| tree | 7abbc615c51667e111fdef7b2ef69a9fc663f2ca /runtime/task.go | |
| signature | Verified sangtran <tranquangsang12.7@gmail.com> Key: 8DD79823D235E1BB ultimate trust | |
| download | flux-96c07bab1086510a55a004e9a1b90c30bc723a5c.tar.gz flux-96c07bab1086510a55a004e9a1b90c30bc723a5c.zip | |
Verified feat: init base draft support window
Diffstat (limited to 'runtime/task.go')
| -rw-r--r-- | runtime/task.go | 66 |
1 files changed, 66 insertions, 0 deletions
diff --git a/runtime/task.go b/runtime/task.go new file mode 100644 index 0000000..734976e --- /dev/null +++ b/runtime/task.go | |||
| @@ -0,0 +1,66 @@ | |||
| 1 | package runtime | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "context" | ||
| 5 | "fmt" | ||
| 6 | "time" | ||
| 7 | |||
| 8 | "flux/core/operator" | ||
| 9 | "flux/transport" | ||
| 10 | ) | ||
| 11 | |||
| 12 | // RunTask runs a stream task in the background. The returned channel yields the | ||
| 13 | // first error that stopped it (context.Canceled when ctx ends early) and is | ||
| 14 | // closed when the task is done, so callers can join on it. | ||
| 15 | func RunTask[IN, OUT any](ctx context.Context, input transport.Receiver[IN], output transport.Emitter[OUT], op operator.Operator[IN, OUT]) <-chan error { | ||
| 16 | done := make(chan error, 1) | ||
| 17 | |||
| 18 | go func() { | ||
| 19 | defer close(done) | ||
| 20 | if err := runTask(ctx, input, output, op); err != nil { | ||
| 21 | done <- err | ||
| 22 | } | ||
| 23 | }() | ||
| 24 | |||
| 25 | return done | ||
| 26 | } | ||
| 27 | |||
| 28 | func runTask[IN, OUT any](ctx context.Context, input transport.Receiver[IN], output transport.Emitter[OUT], op operator.Operator[IN, OUT]) (err error) { | ||
| 29 | // ponytail: assumes one writer per output channel. Fan-in needs a refcount | ||
| 30 | // or a dedicated closer, otherwise the second Close panics. | ||
| 31 | defer func() { | ||
| 32 | if cerr := output.Close(); cerr != nil && err == nil { | ||
| 33 | err = fmt.Errorf("close output: %w", cerr) | ||
| 34 | } | ||
| 35 | }() | ||
| 36 | |||
| 37 | if err := op.Open(ctx); err != nil { | ||
| 38 | return fmt.Errorf("open operator: %w", err) | ||
| 39 | } | ||
| 40 | defer func() { | ||
| 41 | // ctx is usually already cancelled by the time we get here — that is | ||
| 42 | // why the task stopped. Close still needs a live window to flush, so | ||
| 43 | // give it one that keeps ctx's values but drops the cancellation. | ||
| 44 | closeCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 5*time.Second) | ||
| 45 | defer cancel() | ||
| 46 | |||
| 47 | if cerr := op.Close(closeCtx); cerr != nil && err == nil { | ||
| 48 | err = fmt.Errorf("close operator: %w", cerr) | ||
| 49 | } | ||
| 50 | }() | ||
| 51 | |||
| 52 | for { | ||
| 53 | record, ok := input.Receive(ctx) | ||
| 54 | if !ok { | ||
| 55 | break | ||
| 56 | } | ||
| 57 | |||
| 58 | if err := op.Process(ctx, record, output); err != nil { | ||
| 59 | return fmt.Errorf("process record: %w", err) | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | // Receive also returns !ok on cancellation, so report that as an error | ||
| 64 | // rather than a clean drain. | ||
| 65 | return ctx.Err() | ||
| 66 | } | ||
