Skip to content

Commit 338aad0

Browse files
authored
Enhancing docs for selector and channel, from feedback and careful testing (#1137)
1 parent 8fff028 commit 338aad0

File tree

1 file changed

+88
-43
lines changed

1 file changed

+88
-43
lines changed

internal/workflow.go

Lines changed: 88 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -50,22 +50,38 @@ type (
5050
//
5151
// Use workflow.NewChannel(ctx) to create an unbuffered Channel instance,
5252
// workflow.NewBufferedChannel(ctx, size) to create a Channel which has a buffer,
53-
// or workflow.GetSignalChannel(ctx, "name") to get a Channel that can contain encoded data sent from other systems.
54-
//
55-
// workflow.GetSignalChannel is named differently because you are not "creating" a new channel. Signal channels
56-
// are conceptually singletons that exist at all times, and they do not have to be "created" before a signal can be
57-
// sent to a workflow. The workflow will just have no way to know that the data exists until it inspects the
58-
// appropriate signal channel.
53+
// or workflow.GetSignalChannel(ctx, "name") to get a Channel that contains data sent to this workflow by a call to
54+
// SignalWorkflow (e.g. on the Client, or similar methods like SignalExternalWorkflow or SignalChildWorkflow).
5955
//
6056
// Both NewChannel and NewBufferedChannel have "Named" constructors as well.
6157
// These names will be visible in stack-trace queries, so they can help with debugging, but they do not otherwise
6258
// impact behavior at all, and are not recorded anywhere (so you can change them without versioning your code).
59+
//
60+
// Also note that channels created by NewChannel and NewBufferedChannel do not do any serialization or
61+
// deserialization - you will receive whatever value was sent, and non-(de)serializable values like function
62+
// references and interfaces are fine, the same as using a normal Go channel.
63+
//
64+
// Signal channels, however, contain whatever bytes were sent to your workflow, and the values must be decoded into
65+
// the output value. By default, this means that Receive(ctx, &out) will use json.Unmarshal(data, &out), but this
66+
// can be overridden at a worker level (worker.Options) or at a context level (workflow.WithDataConverter(ctx, dc)).
67+
//
68+
// You are able to send values to your own signal channels, and these values will behave the same as they do in
69+
// normal channels (i.e. they will not be (de)serialized). However, doing so is not generally recommended, as
70+
// mixing the value types can increase the risk that you fail to read a value, causing values to be lost. See
71+
// Receive for more details about that behavior.
6372
Channel interface {
6473
// Receive blocks until it receives a value, and then assigns the received value to the provided pointer.
65-
// It returns false when Channel is closed and all data has already been consumed from the channel, in the same
66-
// way as Go channel reads work.
74+
// It returns false when the Channel is closed and all data has already been consumed from the Channel, in the
75+
// same way as Go channel reads work, but the assignment only occurs if there was a value in the Channel.
6776
//
68-
// This is equivalent to `v, more := <- aChannel`.
77+
// This is technically equivalent to:
78+
// received, ok := <- aChannel:
79+
// if ok {
80+
// *valuePtr = received
81+
// }
82+
//
83+
// But if your output values are zero values, this is equivalent to a normal channel read:
84+
// value, ok <- aChannel
6985
//
7086
// valuePtr must be assignable, and will be used to assign (for in-memory data in regular channels) or decode
7187
// (for signal channels) the data in the channel.
@@ -83,36 +99,69 @@ type (
8399
// decoding will be attempted, so you can try it yourself.
84100
// - for other channels, an interface{} pointer. All values are interfaces, so this will never fail, and you
85101
// can inspect the type with reflection or type assertions.
86-
Receive(ctx Context, valuePtr interface{}) (more bool)
102+
Receive(ctx Context, valuePtr interface{}) (ok bool)
87103

88-
// ReceiveAsync tries to receive from Channel without blocking.
104+
// ReceiveAsync tries to Receive from Channel without blocking.
89105
// If there is data available from the Channel, it assigns the data to valuePtr and returns true.
90106
// Otherwise, it returns false immediately.
91107
//
92-
// This is equivalent to:
108+
// This is technically equivalent to:
93109
// select {
94-
// case v := <- aChannel: ok = true
95-
// default: ok = false
110+
// case received, ok := <- aChannel:
111+
// if ok {
112+
// *valuePtr = received
113+
// }
114+
// default:
115+
// // no value was read
116+
// ok = false
117+
// }
118+
//
119+
// But if your output values are zero values, this is equivalent to a simpler form:
120+
// select {
121+
// case value, ok := <- aChannel:
122+
// default:
123+
// // no value was read
124+
// ok = false
96125
// }
97126
//
98127
// Decoding or assigning failures are handled like Receive.
99128
ReceiveAsync(valuePtr interface{}) (ok bool)
100129

101130
// ReceiveAsyncWithMoreFlag is the same as ReceiveAsync, with an extra return to indicate if there could be
102-
// more value from the Channel. more is false when Channel is closed.
131+
// more values from the Channel in the future.
132+
// `more` is false only when Channel is closed and the read failed (empty).
103133
//
104-
// This is equivalent to:
134+
// This is technically equivalent to:
105135
// select {
106-
// case v, more := <- aChannel: ok = true
107-
// default: ok = false
136+
// case received, ok := <- aChannel:
137+
// if ok {
138+
// *valuePtr = received
139+
// }
140+
// more = ok
141+
// default:
142+
// // no value was read
143+
// ok = false
144+
// // but the read would have blocked, so the channel is not closed
145+
// more = true
146+
// }
147+
//
148+
// But if your output values are zero values, this is equivalent to a simpler form:
149+
// select {
150+
// case value, ok := <- aChannel:
151+
// more = ok
152+
// default:
153+
// // no value was read
154+
// ok = false
155+
// // but the read would have blocked, so the channel is not closed
156+
// more = true
108157
// }
109158
//
110159
// Decoding or assigning failures are handled like Receive.
111160
ReceiveAsyncWithMoreFlag(valuePtr interface{}) (ok bool, more bool)
112161

113162
// Send blocks until the data is sent.
114163
//
115-
// This is equivalent to `aChannel <- v`
164+
// This is equivalent to `aChannel <- v`.
116165
Send(ctx Context, v interface{})
117166

118167
// SendAsync will try to send without blocking.
@@ -137,20 +186,16 @@ type (
137186
// The interface is intended to simulate Go's select statement, and any Go select can be fairly trivially rewritten
138187
// for a Selector with effectively identical behavior.
139188
//
140-
// For example, normal Go code like below:
189+
// For example, normal Go code like below (which will receive values forever, until idle for an hour):
141190
// chA := make(chan int)
142191
// chB := make(chan int)
143192
// counter := 0
144193
// for {
145194
// select {
146-
// case i, more := <- chA:
147-
// if more {
148-
// counter += i
149-
// }
150-
// case i, more := <- chB:
151-
// if more {
152-
// counter += i
153-
// }
195+
// case x := <- chA:
196+
// counter += i
197+
// case y := <- chB:
198+
// counter += i
154199
// case <- time.After(time.Hour):
155200
// break
156201
// }
@@ -163,18 +208,14 @@ type (
163208
// timedout := false
164209
// s := workflow.NewSelector(ctx)
165210
// s.AddReceive(chA, func(c workflow.Channel, more bool) {
166-
// if more {
167-
// var i int
168-
// c.Receive(ctx, &i)
169-
// counter += i
170-
// }
211+
// var x int
212+
// c.Receive(ctx, &x)
213+
// counter += i
171214
// })
172215
// s.AddReceive(chB, func(c workflow.Channel, more bool) {
173-
// if more {
174-
// var i int
175-
// c.Receive(ctx, &i)
176-
// counter += i
177-
// }
216+
// var y int
217+
// c.Receive(ctx, &y)
218+
// counter += i
178219
// })
179220
// s.AddFuture(workflow.NewTimer(ctx, time.Hour), func(f workflow.Future) {
180221
// timedout = true
@@ -195,25 +236,29 @@ type (
195236
// Context used to construct the Selector, or the Context used to Select, will not (directly) unblock a Select call.
196237
// Read Select for more details.
197238
Selector interface {
198-
// AddReceive waits to until a value can be received from a channel.
239+
// AddReceive waits until a value can be received from a channel.
199240
// f is invoked when the channel has data or is closed.
200241
//
201-
// This is equivalent to `case v, more := <- aChannel`, and `more` will only
202-
// be false when the channel is both closed and no data was received.
242+
// This is equivalent to `case v, ok := <- aChannel`, and `ok` will only be false when
243+
// the channel is both closed and no data was received.
203244
//
204245
// When f is invoked, the data (or closed state) remains untouched in the channel, so
205246
// you need to `c.Receive(ctx, &out)` (or `c.ReceiveAsync(&out)`) to remove and decode the value.
206247
// Failure to do this is not an error - the value will simply remain in the channel until a future
207248
// Receive retrieves it.
208-
AddReceive(c Channel, f func(c Channel, more bool)) Selector
249+
//
250+
// The `ok` argument will match what a call to c.Receive would return (on a successful read), so it
251+
// may be used to check for closed + empty channels without needing to try to read from the channel.
252+
// See Channel.Receive for additional details about reading from channels.
253+
AddReceive(c Channel, f func(c Channel, ok bool)) Selector
209254
// AddSend waits to send a value to a channel.
210255
// f is invoked when the value was successfully sent to the channel.
211256
//
212257
// This is equivalent to `case aChannel <- value`.
213258
//
214259
// Unlike AddReceive, the value has already been sent on the channel when f is invoked.
215260
AddSend(c Channel, v interface{}, f func()) Selector
216-
// AddFuture invokes f after a Future is ready.
261+
// AddFuture waits until a Future is ready, and then invokes f only once.
217262
// If the Future is ready before Select is called, it is eligible to be invoked immediately.
218263
//
219264
// There is no direct equivalent in a native Go select statement.

0 commit comments

Comments
 (0)