-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfuture.go
228 lines (192 loc) · 5.39 KB
/
future.go
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
/*
an event horizon is a boundary beyond which events cannot affect an observer.
as it is with time, the order of events is not always linear.
*/
package horizon
import "log"
// The idea is simple....let a future event signal a past function to cause an effect in the present.
type Future struct {
eventChan chan Event
quitChan chan struct{}
onComFunc func(interface{})
events []Event
onErrFunc func(interface{})
onFinFunc func()
signalCount int // could be useful
radius Radius // below the schwarzschild radius, the horizon blocks forever
// we have horizon.Einstein and horizon.Hawking
// the mode defines whether or not the future should panic
mode Mode
}
// NewFuture creates a new future
func NewFuture(mode ...Mode) *Future {
m := Einstein
if len(mode) != 0 {
m = mode[0]
}
f := &Future{eventChan: make(chan Event), events: make([]Event, 0), quitChan: make(chan struct{}), mode: m}
f.signal()
return f
}
// Events returns all the events returned from the future
func (f *Future) Events() []Event {
return f.events
}
// Set passes the value received from the event that occured in the future
// into the event channel
func (f *Future) set(e Event) {
f.eventChan <- e
}
// RegisterComplete registers a function to be called when the future is complete
func (f *Future) RegisterComplete(futureFunc func(interface{})) {
f.onComFunc = futureFunc
}
// RegisterError registers a function to be called when the future encounters an error
func (f *Future) RegisterError(futureFunc func(interface{})) {
f.onErrFunc = futureFunc
}
// RegisterFinally registers a function to be called when the future is complete or encounters an error
func (f *Future) RegisterFinally(futureFunc func()) {
f.onFinFunc = futureFunc
}
// signal opens the horizon and allows the future to send events to the present
func (f *Future) signal() {
go func() {
Loop:
for {
select {
case e := <-f.eventChan:
switch e.Type {
case Complete:
f.events = append(f.events, e)
case Error:
f.events = append(f.events, e)
}
f.signalCount++
case <-f.quitChan:
break Loop
}
}
}()
}
// SignalComplete sends a signal to the future
// that the event has completed
func (f *Future) SignalComplete(value interface{}) {
if f.isNil() {
if f.mode == Hawking {
panic("future is destroyed")
}
log.Println("future is destroyed")
return
}
if f.canSignal(Complete) {
go func() {
defer recovery(f)
f.onComFunc(value)
// handle error here -- only if user register a function for a future error event
f.set(Event{Type: Complete, Data: value})
}()
} else {
if f.mode == Hawking {
panic("no function registered for future event [SignalComplete]")
}
log.Println("no function registered for future event [SignalComplete]")
return
}
}
// SignalError sends a signal to the future
// that the event has encountered an error
func (f *Future) SignalError(value interface{}) {
if f.isNil() {
if f.mode == Hawking {
panic("future is destroyed")
}
log.Println("future is destroyed")
return
}
if f.canSignal(Error) {
go func() {
// any error inside this future handler, while
// defer recovery(f) is active, yields an infinite panic loop
// defer recovery(f)
f.onErrFunc(value)
// handle error here -- only if user register a function for a future error event
f.set(Event{Type: Error, Data: value})
// signal finally
f.signalFinally()
}()
} else {
if f.mode == Hawking {
panic("no function registered for future event [SignalError]")
}
log.Println("no function registered for future event [SignalError]")
return
}
}
// signalFinally sends a signal to the future
// that the event has completed or encountered an error
func (f *Future) signalFinally() {
if f.isNil() {
if f.mode == Hawking {
panic("future is destroyed")
}
log.Println("future is destroyed")
return
}
if f.canSignal(Finally) {
go func() {
// any error that occurs inside a Finally handler
// is out of bounds and won't be caught in the horizon
// defer recovery(f)
f.onFinFunc()
}()
}
}
/*
SignalCount returns the number of signals sent to the future this is useful for debugging
note: it only accounts for the current timeline and has no knowledge of branched timelines created by f.Alter() **/
func (f *Future) SignalCount() int {
return f.signalCount
}
/*
Alter changes the future, branching off to a new timeline
all signals are still registered but event histories are lost*/
func (f *Future) Alter() {
f.events = nil
f.signalCount = 0
}
// BlackHole closes the future, all signals are lost
// all branched timelines are pruned
// and the future is no longer usable
func (f *Future) BlackHole() {
// prevent the event horizon from receiving any more signals
close(f.quitChan)
// keep the mode to allow for error handling
// when accessing a black holed future
*f = Future{mode: f.mode}
}
// isNil checks if the future is nil
// nil refers to a Future that has no values
// and is not usable
func (f *Future) isNil() bool {
if f.eventChan == nil && f.events == nil && f.onComFunc == nil && f.quitChan == nil {
return true
}
return false
}
// canSignal checks if the future has event handlers registered
// and is not nil
func (f *Future) canSignal(t EventType) bool {
if f.isNil() {
return false
}
switch t {
case Complete:
return f.onComFunc != nil
case Error:
return f.onErrFunc != nil
case Finally:
return f.onFinFunc != nil
}
return false
}