@@ -14,14 +14,13 @@ public class EffectResults
14
14
{
15
15
private readonly StoredId _storedId ;
16
16
private readonly FlowId _flowId ;
17
- private readonly Dictionary < EffectId , StoredEffect > _effectResults = new ( ) ;
18
17
private readonly Lock _sync = new ( ) ;
19
18
private readonly Lazy < Task < IReadOnlyList < StoredEffect > > > _lazyExistingEffects ;
20
19
private readonly IEffectsStore _effectsStore ;
21
20
private readonly ISerializer _serializer ;
22
21
private volatile bool _initialized ;
23
22
24
- private readonly Dictionary < StoredEffectId , PendingChange > _pendingChanges = new ( ) ;
23
+ private readonly Dictionary < EffectId , PendingEffectChange > _effectResults = new ( ) ;
25
24
26
25
public EffectResults (
27
26
FlowId flowId ,
@@ -49,7 +48,13 @@ private async Task InitializeIfRequired()
49
48
return ;
50
49
51
50
foreach ( var existingEffect in existingEffects )
52
- _effectResults [ existingEffect . EffectId ] = existingEffect ;
51
+ _effectResults [ existingEffect . EffectId ] =
52
+ new PendingEffectChange (
53
+ existingEffect . StoredEffectId ,
54
+ existingEffect ,
55
+ Operation : null ,
56
+ Existing : true
57
+ ) ;
53
58
54
59
_initialized = true ;
55
60
}
@@ -66,7 +71,7 @@ public async Task<bool> Contains(EffectId effectId)
66
71
{
67
72
await InitializeIfRequired ( ) ;
68
73
lock ( _sync )
69
- return _effectResults . GetValueOrDefault ( effectId ) ;
74
+ return _effectResults . GetValueOrDefault ( effectId ) ? . StoredEffect ;
70
75
}
71
76
72
77
public async Task Set ( StoredEffect storedEffect , bool flush )
@@ -76,7 +81,8 @@ await FlushOrAddToPending(
76
81
storedEffect . EffectId ,
77
82
storedEffect . StoredEffectId ,
78
83
storedEffect ,
79
- flush
84
+ flush ,
85
+ delete : false
80
86
) ;
81
87
}
82
88
@@ -85,19 +91,20 @@ public async Task<T> CreateOrGet<T>(EffectId effectId, T value, bool flush)
85
91
await InitializeIfRequired ( ) ;
86
92
lock ( _sync )
87
93
{
88
- if ( _effectResults . TryGetValue ( effectId , out var existing ) && existing . WorkStatus == WorkStatus . Completed )
89
- return _serializer . Deserialize < T > ( existing . Result ! ) ;
94
+ if ( _effectResults . TryGetValue ( effectId , out var existing ) && existing . StoredEffect ? . WorkStatus == WorkStatus . Completed )
95
+ return _serializer . Deserialize < T > ( existing . StoredEffect . Result ! ) ;
90
96
91
- if ( existing ? . StoredException != null )
92
- throw _serializer . DeserializeException ( _flowId , existing . StoredException ! ) ;
97
+ if ( existing ? . StoredEffect ? . StoredException != null )
98
+ throw _serializer . DeserializeException ( _flowId , existing . StoredEffect . StoredException ! ) ;
93
99
}
94
100
95
101
var storedEffect = StoredEffect . CreateCompleted ( effectId , _serializer . Serialize ( value ) ) ;
96
102
await FlushOrAddToPending (
97
103
storedEffect . EffectId ,
98
104
storedEffect . StoredEffectId ,
99
105
storedEffect ,
100
- flush
106
+ flush ,
107
+ delete : false
101
108
) ;
102
109
103
110
return value ;
@@ -112,7 +119,8 @@ await FlushOrAddToPending(
112
119
storedEffect . EffectId ,
113
120
storedEffect . StoredEffectId ,
114
121
storedEffect ,
115
- flush
122
+ flush ,
123
+ delete : false
116
124
) ;
117
125
}
118
126
@@ -122,15 +130,16 @@ public async Task<Option<T>> TryGet<T>(EffectId effectId)
122
130
123
131
lock ( _sync )
124
132
{
125
- if ( _effectResults . TryGetValue ( effectId , out var storedEffect ) )
133
+ if ( _effectResults . TryGetValue ( effectId , out var change ) )
126
134
{
127
- if ( storedEffect . WorkStatus == WorkStatus . Completed )
135
+ var storedEffect = change . StoredEffect ;
136
+ if ( storedEffect ? . WorkStatus == WorkStatus . Completed )
128
137
{
129
138
var value = _serializer . Deserialize < T > ( storedEffect . Result ! ) ! ;
130
139
return Option . Create ( value ) ;
131
140
}
132
141
133
- if ( storedEffect . StoredException != null )
142
+ if ( storedEffect ? . StoredException != null )
134
143
throw _serializer . DeserializeException ( _flowId , storedEffect . StoredException ! ) ;
135
144
}
136
145
}
@@ -147,21 +156,20 @@ public async Task InnerCapture(string id, EffectType effectType, Func<Task> work
147
156
148
157
lock ( _sync )
149
158
{
150
- var success = _effectResults . TryGetValue ( effectId , out var storedEffect ) ;
151
- if ( success && storedEffect ! . WorkStatus == WorkStatus . Completed )
159
+ var success = _effectResults . TryGetValue ( effectId , out var pendingChange ) ;
160
+ var storedEffect = pendingChange ? . StoredEffect ;
161
+ if ( success && storedEffect ? . WorkStatus == WorkStatus . Completed )
152
162
return ;
153
- if ( success && storedEffect ! . WorkStatus == WorkStatus . Failed )
163
+ if ( success && storedEffect ? . WorkStatus == WorkStatus . Failed )
154
164
throw _serializer . DeserializeException ( _flowId , storedEffect . StoredException ! ) ;
155
165
if ( success && resiliency == ResiliencyLevel . AtMostOnce )
156
166
throw new InvalidOperationException ( $ "Effect '{ id } ' started but did not complete previously") ;
157
167
}
158
168
159
169
if ( resiliency == ResiliencyLevel . AtMostOnce )
160
170
{
161
- var storedEffect = StoredEffect . CreateStarted ( effectId ) ;
162
- await _effectsStore . SetEffectResult ( _storedId , storedEffect ) ;
163
- lock ( _sync )
164
- _effectResults [ effectId ] = storedEffect ;
171
+ var storedEffect = StoredEffect . CreateStarted ( effectId ) ;
172
+ await FlushOrAddToPending ( effectId , storedEffect . StoredEffectId , storedEffect , flush : true , delete : false ) ;
165
173
}
166
174
167
175
try
@@ -184,7 +192,8 @@ await FlushOrAddToPending(
184
192
storedEffect . EffectId ,
185
193
storedEffect . StoredEffectId ,
186
194
storedEffect ,
187
- flush : true
195
+ flush : true ,
196
+ delete : false
188
197
) ;
189
198
190
199
exception . FlowId = _flowId ;
@@ -199,7 +208,8 @@ await FlushOrAddToPending(
199
208
storedEffect . EffectId ,
200
209
storedEffect . StoredEffectId ,
201
210
storedEffect ,
202
- flush : true
211
+ flush : true ,
212
+ delete : false
203
213
) ;
204
214
205
215
throw fatalWorkflowException ;
@@ -211,7 +221,8 @@ await FlushOrAddToPending(
211
221
storedEffect . EffectId ,
212
222
storedEffect . StoredEffectId ,
213
223
storedEffect ,
214
- flush : resiliency != ResiliencyLevel . AtLeastOnceDelayFlush
224
+ flush : resiliency != ResiliencyLevel . AtLeastOnceDelayFlush ,
225
+ delete : false
215
226
) ;
216
227
}
217
228
}
@@ -226,20 +237,24 @@ public async Task<T> InnerCapture<T>(string id, EffectType effectType, Func<Task
226
237
lock ( _sync )
227
238
{
228
239
var success = _effectResults . TryGetValue ( effectId , out var storedEffect ) ;
229
- if ( success && storedEffect ! . WorkStatus == WorkStatus . Completed )
230
- return ( storedEffect . Result == null ? default : _serializer . Deserialize < T > ( storedEffect . Result ) ) ! ;
231
- if ( success && storedEffect ! . WorkStatus == WorkStatus . Failed )
232
- throw FatalWorkflowException . Create ( _flowId , storedEffect . StoredException ! ) ;
240
+ if ( success && storedEffect ! . StoredEffect ? . WorkStatus == WorkStatus . Completed )
241
+ return ( storedEffect . StoredEffect ? . Result == null ? default : _serializer . Deserialize < T > ( storedEffect . StoredEffect ? . Result ! ) ) ! ;
242
+ if ( success && storedEffect ! . StoredEffect ? . WorkStatus == WorkStatus . Failed )
243
+ throw FatalWorkflowException . Create ( _flowId , storedEffect . StoredEffect ? . StoredException ! ) ;
233
244
if ( success && resiliency == ResiliencyLevel . AtMostOnce )
234
245
throw new InvalidOperationException ( $ "Effect '{ id } ' started but did not complete previously") ;
235
246
}
236
247
237
248
if ( resiliency == ResiliencyLevel . AtMostOnce )
238
249
{
239
250
var storedEffect = StoredEffect . CreateStarted ( effectId ) ;
240
- await _effectsStore . SetEffectResult ( _storedId , storedEffect ) ;
241
- lock ( _sync )
242
- _effectResults [ effectId ] = storedEffect ;
251
+ await FlushOrAddToPending (
252
+ effectId ,
253
+ storedEffect . StoredEffectId ,
254
+ storedEffect ,
255
+ flush : true ,
256
+ delete : false
257
+ ) ;
243
258
}
244
259
245
260
T result ;
@@ -263,7 +278,8 @@ await FlushOrAddToPending(
263
278
storedEffect . EffectId ,
264
279
storedEffect . StoredEffectId ,
265
280
storedEffect ,
266
- flush : true
281
+ flush : true ,
282
+ delete : false
267
283
) ;
268
284
269
285
exception . FlowId = _flowId ;
@@ -279,7 +295,8 @@ await FlushOrAddToPending(
279
295
storedEffect . EffectId ,
280
296
storedEffect . StoredEffectId ,
281
297
storedEffect ,
282
- flush : true
298
+ flush : true ,
299
+ delete : false
283
300
) ;
284
301
throw fatalWorkflowException ;
285
302
}
@@ -290,7 +307,8 @@ await FlushOrAddToPending(
290
307
storedEffect . EffectId ,
291
308
storedEffect . StoredEffectId ,
292
309
storedEffect ,
293
- flush : resiliency != ResiliencyLevel . AtLeastOnceDelayFlush
310
+ flush : resiliency != ResiliencyLevel . AtLeastOnceDelayFlush ,
311
+ delete : false
294
312
) ;
295
313
296
314
return result ;
@@ -309,64 +327,79 @@ await FlushOrAddToPending(
309
327
effectId ,
310
328
effectId . ToStoredEffectId ( ) ,
311
329
storedEffect : null ,
312
- flush
330
+ flush ,
331
+ delete : true
313
332
) ;
314
333
}
315
334
316
- private async Task FlushOrAddToPending ( EffectId effectId , StoredEffectId storedEffectId , StoredEffect ? storedEffect , bool flush )
335
+ private async Task FlushOrAddToPending ( EffectId effectId , StoredEffectId storedEffectId , StoredEffect ? storedEffect , bool flush , bool delete )
317
336
{
318
- if ( flush )
319
- await Flush ( storedEffectId , storedEffect ) ;
320
- else
321
- lock ( _sync )
322
- _pendingChanges [ storedEffectId ] = new PendingChange ( storedEffectId , storedEffect ) ;
323
-
324
337
lock ( _sync )
325
- if ( storedEffect == null )
326
- _effectResults . Remove ( effectId ) ;
338
+ if ( _effectResults . ContainsKey ( effectId ) )
339
+ {
340
+ var existing = _effectResults [ effectId ] ;
341
+ _effectResults [ effectId ] = existing with
342
+ {
343
+ StoredEffect = storedEffect ,
344
+ Operation = delete
345
+ ? CrudOperation . Delete
346
+ : ( existing . Existing ? CrudOperation . Update : CrudOperation . Insert )
347
+ } ;
348
+ }
327
349
else
328
- _effectResults [ effectId ] = storedEffect ;
350
+ {
351
+ _effectResults [ effectId ] = new PendingEffectChange (
352
+ storedEffectId ,
353
+ storedEffect ,
354
+ CrudOperation . Insert ,
355
+ Existing : false
356
+ ) ;
357
+ }
358
+
359
+ if ( flush )
360
+ await Flush ( ) ;
329
361
}
330
362
331
363
private readonly SemaphoreSlim _flushSync = new ( initialCount : 1 , maxCount : 1 ) ;
332
- private async Task Flush ( StoredEffectId changedStoredId , StoredEffect ? change )
364
+ private async Task Flush ( )
333
365
{
334
366
await _flushSync . WaitAsync ( ) ;
335
367
336
368
try
337
369
{
338
- IReadOnlyList < PendingChange > pendingChanges ;
370
+ IReadOnlyList < PendingEffectChange > pendingChanges ;
339
371
lock ( _sync )
340
- {
341
- if ( _pendingChanges . Count == 0 )
342
- pendingChanges = [ ] ;
343
- else
344
- {
345
- _pendingChanges [ changedStoredId ] = new PendingChange ( changedStoredId , change ) ;
346
- pendingChanges = _pendingChanges . Values . ToList ( ) ;
347
- _pendingChanges . Clear ( ) ;
348
- }
349
- }
372
+ pendingChanges = _effectResults . Values . Where ( r => r . Operation != null ) . ToList ( ) ;
350
373
351
374
if ( pendingChanges . Count == 0 )
352
- {
353
- if ( change == null )
354
- await _effectsStore . DeleteEffectResult ( _storedId , changedStoredId ) ;
355
- else
356
- await _effectsStore . SetEffectResult ( _storedId , change ) ;
357
-
358
375
return ;
359
- }
360
376
361
377
var changes = pendingChanges
362
- . Select ( pc => new StoredEffectChange (
363
- _storedId ,
364
- pc . Id ,
365
- pc . StoredEffect == null ? CrudOperation . Delete : CrudOperation . Upsert ,
366
- pc . StoredEffect )
378
+ . Select ( pc =>
379
+ new StoredEffectChange (
380
+ _storedId ,
381
+ pc . Id ,
382
+ pc . Operation ! . Value ,
383
+ pc . StoredEffect
384
+ )
367
385
) . ToList ( ) ;
386
+
368
387
await _effectsStore . SetEffectResults ( _storedId , changes ) ;
388
+
389
+ lock ( _sync )
390
+ foreach ( var ( key , value ) in _effectResults . ToList ( ) )
391
+ {
392
+ if ( value . Operation == CrudOperation . Delete )
393
+ _effectResults . Remove ( key ) ;
394
+ else
395
+ _effectResults [ key ] = value with
396
+ {
397
+ Existing = true ,
398
+ Operation = null
399
+ } ;
400
+ }
369
401
}
402
+
370
403
finally
371
404
{
372
405
_flushSync . Release ( ) ;
0 commit comments