-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathfutures.go
More file actions
101 lines (90 loc) · 2.98 KB
/
Copy pathfutures.go
File metadata and controls
101 lines (90 loc) · 2.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
package resonate
import (
"encoding/json"
"fmt"
"sync"
)
// suspendSignal is the internal panic value used to unwind a workflow when
// Future.Await would otherwise block. It is recovered at the runWorkflow
// boundary and converted into an Outcome.Suspended — never exposed to user
// code as an error.
type suspendSignal struct{}
func decodeSettled(codec *Codec, rec PromiseRecord, into any) error {
switch rec.State {
case PromiseStateResolved:
if into == nil {
return nil
}
if _, err := codec.Decode(rec.Value, into); err != nil {
return err
}
return nil
case PromiseStateRejected, PromiseStateRejectedCanceled, PromiseStateRejectedTimedout:
var inner json.RawMessage
ok, err := codec.Decode(rec.Value, &inner)
if err != nil {
return err
}
if !ok || len(inner) == 0 {
return &ApplicationError{Message: fmt.Sprintf("promise %s rejected with no payload", rec.ID)}
}
return DeserializeError(inner)
default:
return fmt.Errorf("resonate: future %s has unexpected state %q", rec.ID, rec.State)
}
}
type futureKind uint8
const (
futureLocal futureKind = iota
futureRemote
)
// Future is a handle to a durable promise — either remote (RPC, sleep, latent
// promise) or local (Run-spawned goroutine). The kind is an internal detail;
// callers always use the same Await API.
//
// Await is idempotent on settled futures: once the result is known, repeated
// calls return the same value (or the same sticky error). Calls made while
// the future is still pending panic with the internal suspendSignal{} so the
// workflow runtime can unwind and re-enter later.
type Future struct {
id string
ctx *Context
kind futureKind
record *PromiseRecord // pre-settled at construction OR filled in by the goroutine
result chan localResult // local only; nil for remote (and for pre-settled locals)
once sync.Once
res localResult // memoised first channel read
}
func newRemoteFuture(id string, ctx *Context, rec PromiseRecord) *Future {
return &Future{id: id, ctx: ctx, kind: futureRemote, record: &rec}
}
// ID returns the promise ID.
func (f *Future) ID() string { return f.id }
// Await decodes the future's settled value into `into`. If the underlying
// promise is still pending, Await panics with suspendSignal{} (registering a
// remote todo for remote futures); the workflow runtime recovers this and
// reports Outcome.Suspended. Repeated calls on a done future return the same
// value; repeated calls on a pending future re-panic.
func (f *Future) Await(into any) error {
switch f.kind {
case futureRemote:
if f.record.State == PromiseStatePending {
f.ctx.appendRemoteTodo(f.id)
panic(suspendSignal{})
}
case futureLocal:
if f.result != nil {
f.once.Do(func() { f.res = <-f.result })
if f.res.suspended {
panic(suspendSignal{})
}
if f.res.err != nil {
return f.res.err
}
}
}
if f.record == nil {
return fmt.Errorf("resonate: future %s completed without a settled record", f.id)
}
return decodeSettled(f.ctx.codec, *f.record, into)
}