aboutsummaryrefslogtreecommitdiff
path: root/runtime
diff options
context:
space:
mode:
Diffstat (limited to 'runtime')
-rw-r--r--runtime/task.go66
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 @@
1package runtime
2
3import (
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.
15func 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
28func 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}