@@ -265,65 +265,87 @@ func GetSignalChannel(ctx Context, signalName string) Channel {
265
265
return internal .GetSignalChannel (ctx , signalName )
266
266
}
267
267
268
- // SideEffect executes the provided function once, records its result into the workflow history. The recorded result on
269
- // history will be returned without executing the provided function during replay. This guarantees the deterministic
270
- // requirement for workflow as the exact same result will be returned in replay.
271
- // Common use case is to run some short non-deterministic code in workflow, like getting random number or new UUID.
272
- // The only way to fail SideEffect is to panic which causes decision task failure. The decision task after timeout is
273
- // rescheduled and re-executed giving SideEffect another chance to succeed.
274
- //
275
- // Caution: do not use SideEffect to modify closures. Always retrieve result from SideEffect's encoded return value.
276
- // For example this code is BROKEN:
268
+ // SideEffect executes the provided callback, and records its result into the workflow history.
269
+ // During replay the recorded result will be returned instead, so the callback can do non-deterministic
270
+ // things (such as reading files or getting random numbers) without breaking determinism during replay.
271
+ //
272
+ // As there is no error return, the callback's code is assumed to be reliable.
273
+ // If you cannot retrieve the value for some reason, panicking and failing the decision task
274
+ // will cause it to be retried, possibly succeeding on another machine.
275
+ // For better error handling, use ExecuteLocalActivity instead.
276
+ //
277
+ // Caution: the callback MUST NOT call any blocking or history-creating APIs,
278
+ // e.g. workflow.Sleep or ExecuteActivity or calling .Get on any future.
279
+ // This will (potentially) work during the initial recording of history, but will
280
+ // fail when replaying because the data is not available when the call occurs
281
+ // (it becomes available later).
282
+ //
283
+ // In other words, in general: use this *only* for code that does not need the workflow.Context.
284
+ // Incorrect context-using calls are not currently prevented in tests or during execution,
285
+ // but they should be eventually.
286
+ //
287
+ // // Bad example: this will work until a replay occurs,
288
+ // // but then the workflow will fail to replay with a non-deterministic error.
289
+ // var out string
290
+ // err := workflow.SideEffect(func(ctx workflow.Context) interface{} {
291
+ // var signal data
292
+ // err := workflow.GetSignalChannel(ctx, "signal").Receive(ctx, &signal)
293
+ // _ = err // ignore err for the example
294
+ // return signal
295
+ // }).Get(&out)
296
+ // workflow.GetLogger(ctx).Info(out)
297
+ //
298
+ // Caution: do not use SideEffect to modify values outside the callback.
299
+ // Always retrieve result from SideEffect's encoded return value.
300
+ // For example this code will break during replay:
277
301
//
278
302
// // Bad example:
279
303
// var random int
280
304
// workflow.SideEffect(func(ctx workflow.Context) interface{} {
281
- // random = rand.Intn(100)
282
- // return nil
305
+ // random = rand.Intn(100) // this only occurs when recording history, not during replay
306
+ // return nil
283
307
// })
284
308
// // random will always be 0 in replay, thus this code is non-deterministic
285
309
// if random < 50 {
286
- // ....
310
+ // ....
287
311
// } else {
288
- // ....
312
+ // ....
289
313
// }
290
314
//
291
- // On replay the provided function is not executed, the random will always be 0, and the workflow could takes a
292
- // different path breaking the determinism.
315
+ // On replay the provided function is not executed, the ` random` var will always be 0,
316
+ // and the workflow could take a different path and break determinism.
293
317
//
294
- // Here is the correct way to use SideEffect:
318
+ // The only safe way to use SideEffect is to read from the returned encoded.Value :
295
319
//
296
320
// // Good example:
297
321
// encodedRandom := SideEffect(func(ctx workflow.Context) interface{} {
298
- // return rand.Intn(100)
322
+ // return rand.Intn(100)
299
323
// })
300
324
// var random int
301
325
// encodedRandom.Get(&random)
302
326
// if random < 50 {
303
- // ....
327
+ // ....
304
328
// } else {
305
- // ....
329
+ // ....
306
330
// }
307
331
func SideEffect (ctx Context , f func (ctx Context ) interface {}) encoded.Value {
308
332
return internal .SideEffect (ctx , f )
309
333
}
310
334
311
- // MutableSideEffect executes the provided function once, then it looks up the history for the value with the given id.
312
- // If there is no existing value, then it records the function result as a value with the given id on history;
313
- // otherwise, it compares whether the existing value from history has changed from the new function result by calling the
314
- // provided equals function. If they are equal, it returns the value without recording a new one in history;
315
- //
316
- // otherwise, it records the new value with the same id on history.
335
+ // MutableSideEffect is similar to SideEffect, but it only records changed values per ID instead of every call.
336
+ // Changes are detected by calling the provided "equals" func - return true to mark a result
337
+ // as the same as before, and not record the new value. The first call per ID is always "changed" and will always be recorded.
317
338
//
318
- // Caution: do not use MutableSideEffect to modify closures. Always retrieve result from MutableSideEffect's encoded
319
- // return value.
339
+ // This is intended for things you want to *check* frequently, but which *change* only occasionally, as non-changing
340
+ // results will not add anything to your history. This helps keep those workflows under its size/count limits, and
341
+ // can help keep replays more efficient than when using SideEffect or ExecuteLocalActivity (due to not storing duplicated data).
320
342
//
321
- // The difference between MutableSideEffect() and SideEffect() is that every new SideEffect() call in non-replay will
322
- // result in a new marker being recorded on history. However, MutableSideEffect() only records a new marker if the value
323
- // changed. During replay, MutableSideEffect() will not execute the function again, but it will return the exact same
324
- // value as it was returning during the non-replay run.
343
+ // Caution: the callback MUST NOT block or call any history-modifying events, or replays will be broken.
344
+ // See SideEffect docs for more details.
325
345
//
326
- // One good use case of MutableSideEffect() is to access dynamically changing config without breaking determinism.
346
+ // Caution: do not use MutableSideEffect to modify closures.
347
+ // Always retrieve result from MutableSideEffect's encoded return value.
348
+ // See SideEffect docs for more details.
327
349
func MutableSideEffect (ctx Context , id string , f func (ctx Context ) interface {}, equals func (a , b interface {}) bool ) encoded.Value {
328
350
return internal .MutableSideEffect (ctx , id , f , equals )
329
351
}
0 commit comments