@cratis/pi 7.102.0 → 7.103.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/package/corpus/harnesses/copilot/instructions/profiles.instructions.md +10 -1
  2. package/package/corpus/harnesses/copilot/instructions/pull-requests.instructions.md +2 -0
  3. package/package/corpus/harnesses/copilot/instructions/secrets.instructions.md +4 -0
  4. package/package/corpus/hooks/README.md +11 -1
  5. package/package/corpus/hooks/scripts/cratis-check-pr.mjs +12 -1
  6. package/package/corpus/hooks/scripts/cratis-guard-secrets.sh +79 -23
  7. package/package/corpus/hooks/scripts/cratis-guard-writes.sh +145 -40
  8. package/package/corpus/rules/profiles.md +10 -1
  9. package/package/corpus/rules/pull-requests.md +2 -0
  10. package/package/corpus/rules/secrets.md +4 -0
  11. package/package/corpus/skills/analyzing-dotnet-performance/LICENSE +23 -0
  12. package/package/corpus/skills/analyzing-dotnet-performance/NOTICE +36 -0
  13. package/package/corpus/skills/analyzing-dotnet-performance/SKILL.md +214 -0
  14. package/package/corpus/skills/analyzing-dotnet-performance/references/async-patterns.md +122 -0
  15. package/package/corpus/skills/analyzing-dotnet-performance/references/collections-and-linq.md +220 -0
  16. package/package/corpus/skills/analyzing-dotnet-performance/references/critical-patterns.md +291 -0
  17. package/package/corpus/skills/analyzing-dotnet-performance/references/io-and-serialization.md +135 -0
  18. package/package/corpus/skills/analyzing-dotnet-performance/references/memory-and-strings.md +219 -0
  19. package/package/corpus/skills/analyzing-dotnet-performance/references/regex-patterns.md +95 -0
  20. package/package/corpus/skills/analyzing-dotnet-performance/references/structural-patterns.md +38 -0
  21. package/package/corpus/skills/binlog-generation/LICENSE +23 -0
  22. package/package/corpus/skills/binlog-generation/NOTICE +22 -0
  23. package/package/corpus/skills/binlog-generation/SKILL.md +129 -0
  24. package/package/corpus/skills/check-bin-obj-clash/LICENSE +23 -0
  25. package/package/corpus/skills/check-bin-obj-clash/NOTICE +27 -0
  26. package/package/corpus/skills/check-bin-obj-clash/SKILL.md +127 -0
  27. package/package/corpus/skills/check-bin-obj-clash/references/common-causes-and-fixes.md +206 -0
  28. package/package/corpus/skills/coverage-analysis/LICENSE +23 -0
  29. package/package/corpus/skills/coverage-analysis/NOTICE +32 -0
  30. package/package/corpus/skills/coverage-analysis/SKILL.md +198 -0
  31. package/package/corpus/skills/coverage-analysis/references/guidelines.md +68 -0
  32. package/package/corpus/skills/coverage-analysis/references/output-format.md +91 -0
  33. package/package/corpus/skills/coverage-analysis/references/report-generation.md +58 -0
  34. package/package/corpus/skills/coverage-analysis/references/setup-discovery.md +307 -0
  35. package/package/corpus/skills/coverage-analysis/scripts/Compute-CrapScores.ps1 +80 -0
  36. package/package/corpus/skills/coverage-analysis/scripts/CoverageData.ps1 +272 -0
  37. package/package/corpus/skills/coverage-analysis/scripts/Extract-MethodCoverage.ps1 +65 -0
  38. package/package/corpus/skills/cratis-screenplay-command-surface/SKILL.md +26 -1
  39. package/package/corpus/skills/cratis-screenplay-projections/SKILL.md +17 -0
  40. package/package/corpus/skills/cratis-screenplay-render-and-gap-fill/SKILL.md +17 -0
  41. package/package/corpus/skills/cratis-screenplay-specifications/SKILL.md +8 -0
  42. package/package/corpus/skills/cratis-screenplay-toolchain/SKILL.md +1 -1
  43. package/package/corpus/skills/cratis-screenplay-toolchain/references/cheat-sheet-example.md +1 -0
  44. package/package/corpus/skills/cratis-screenplay-toolchain/references/fast-path.md +7 -0
  45. package/package/corpus/skills/cratis-screenplay-toolchain/references/traps.md +1 -0
  46. package/package/corpus/skills/cratis-screenplay-ui-composition/SKILL.md +40 -0
  47. package/package/corpus/skills/cratis-screenplay-ui-composition/references/response-continuations.md +1 -0
  48. package/package/corpus/skills/cratis-screenplay-ui-composition/references/worked-flow.md +1 -0
  49. package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/admission.md +3 -2
  50. package/package/corpus/skills/csharp-refactoring/LICENSE +23 -0
  51. package/package/corpus/skills/csharp-refactoring/NOTICE +22 -0
  52. package/package/corpus/skills/csharp-refactoring/SKILL.md +175 -0
  53. package/package/corpus/skills/csharp-refactoring/references/operation-catalog.md +37 -0
  54. package/package/corpus/skills/directory-build-organization/LICENSE +23 -0
  55. package/package/corpus/skills/directory-build-organization/NOTICE +21 -0
  56. package/package/corpus/skills/directory-build-organization/SKILL.md +205 -0
  57. package/package/corpus/skills/directory-build-organization/references/common-patterns.md +56 -0
  58. package/package/corpus/skills/directory-build-organization/references/multi-level-examples.md +167 -0
  59. package/package/corpus/skills/directory-build-organization/references/targetframework-props-pitfall.md +54 -0
  60. package/package/corpus/skills/dotnet-aot-compat/LICENSE +23 -0
  61. package/package/corpus/skills/dotnet-aot-compat/NOTICE +18 -0
  62. package/package/corpus/skills/dotnet-aot-compat/SKILL.md +265 -0
  63. package/package/corpus/skills/dotnet-aot-compat/references/polyfills.md +43 -0
  64. package/package/corpus/skills/dotnet-trace-collect/LICENSE +23 -0
  65. package/package/corpus/skills/dotnet-trace-collect/NOTICE +65 -0
  66. package/package/corpus/skills/dotnet-trace-collect/SKILL.md +116 -0
  67. package/package/corpus/skills/dotnet-trace-collect/references/dotnet-monitor.md +97 -0
  68. package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect-linux.md +84 -0
  69. package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect.md +91 -0
  70. package/package/corpus/skills/dotnet-trace-collect/references/perfcollect.md +200 -0
  71. package/package/corpus/skills/dotnet-trace-collect/references/perfview.md +141 -0
  72. package/package/corpus/skills/dotnet-trace-collect/references/tool-selection.md +160 -0
  73. package/package/corpus/skills/dump-collect/LICENSE +23 -0
  74. package/package/corpus/skills/dump-collect/NOTICE +40 -0
  75. package/package/corpus/skills/dump-collect/SKILL.md +97 -0
  76. package/package/corpus/skills/dump-collect/references/container-dumps.md +412 -0
  77. package/package/corpus/skills/dump-collect/references/coreclr-dumps.md +108 -0
  78. package/package/corpus/skills/dump-collect/references/nativeaot-dumps.md +280 -0
  79. package/package/corpus/skills/eliminate-redundant-restores/LICENSE +23 -0
  80. package/package/corpus/skills/eliminate-redundant-restores/NOTICE +19 -0
  81. package/package/corpus/skills/eliminate-redundant-restores/SKILL.md +112 -0
  82. package/package/corpus/skills/extension-points/LICENSE +23 -0
  83. package/package/corpus/skills/extension-points/NOTICE +18 -0
  84. package/package/corpus/skills/extension-points/SKILL.md +248 -0
  85. package/package/corpus/skills/filter-syntax/LICENSE +23 -0
  86. package/package/corpus/skills/filter-syntax/NOTICE +17 -0
  87. package/package/corpus/skills/filter-syntax/SKILL.md +179 -0
  88. package/package/corpus/skills/including-generated-files/LICENSE +23 -0
  89. package/package/corpus/skills/including-generated-files/NOTICE +17 -0
  90. package/package/corpus/skills/including-generated-files/SKILL.md +139 -0
  91. package/package/corpus/skills/microbenchmarking/LICENSE +23 -0
  92. package/package/corpus/skills/microbenchmarking/NOTICE +36 -0
  93. package/package/corpus/skills/microbenchmarking/SKILL.md +131 -0
  94. package/package/corpus/skills/microbenchmarking/references/bdn-internals-and-tuning.md +143 -0
  95. package/package/corpus/skills/microbenchmarking/references/comparison-strategies.md +200 -0
  96. package/package/corpus/skills/microbenchmarking/references/diagnosers-and-exporters.md +150 -0
  97. package/package/corpus/skills/microbenchmarking/references/project-setup-and-running.md +189 -0
  98. package/package/corpus/skills/microbenchmarking/references/writing-benchmarks.md +310 -0
  99. package/package/corpus/skills/msbuild/LICENSE +23 -0
  100. package/package/corpus/skills/msbuild/NOTICE +55 -0
  101. package/package/corpus/skills/msbuild/SKILL.md +117 -0
  102. package/package/corpus/skills/msbuild/references/antipatterns/additional-antipatterns.md +291 -0
  103. package/package/corpus/skills/msbuild/references/antipatterns/incremental-build-inputs-outputs.md +57 -0
  104. package/package/corpus/skills/msbuild/references/antipatterns/private-assets.md +29 -0
  105. package/package/corpus/skills/msbuild/references/binlog-failure-analysis.md +85 -0
  106. package/package/corpus/skills/msbuild/references/binlog-generation.md +69 -0
  107. package/package/corpus/skills/msbuild/references/build-perf-baseline.md +493 -0
  108. package/package/corpus/skills/msbuild/references/build-perf-diagnostics.md +268 -0
  109. package/package/corpus/skills/msbuild/references/copy-to-output-directory.md +183 -0
  110. package/package/corpus/skills/msbuild/references/eval-performance.md +190 -0
  111. package/package/corpus/skills/msbuild/references/extension-points.md +281 -0
  112. package/package/corpus/skills/msbuild/references/incremental-build.md +365 -0
  113. package/package/corpus/skills/msbuild/references/msbuild-antipatterns.md +401 -0
  114. package/package/corpus/skills/orleans/LICENSE +21 -0
  115. package/package/corpus/skills/orleans/NOTICE +64 -0
  116. package/package/corpus/skills/orleans/SKILL.md +140 -0
  117. package/package/corpus/skills/orleans/references/anti-patterns.md +660 -0
  118. package/package/corpus/skills/orleans/references/configuration-api.md +933 -0
  119. package/package/corpus/skills/orleans/references/examples.md +66 -0
  120. package/package/corpus/skills/orleans/references/grain-api.md +784 -0
  121. package/package/corpus/skills/orleans/references/grains.md +75 -0
  122. package/package/corpus/skills/orleans/references/hosting.md +71 -0
  123. package/package/corpus/skills/orleans/references/implementation.md +591 -0
  124. package/package/corpus/skills/orleans/references/official-docs-index.md +250 -0
  125. package/package/corpus/skills/orleans/references/patterns.md +811 -0
  126. package/package/corpus/skills/orleans/references/persistence-api.md +495 -0
  127. package/package/corpus/skills/orleans/references/primitive-selection.md +35 -0
  128. package/package/corpus/skills/orleans/references/scheduling-and-services.md +295 -0
  129. package/package/corpus/skills/orleans/references/serialization-api.md +299 -0
  130. package/package/corpus/skills/orleans/references/streaming-api.md +396 -0
  131. package/package/corpus/skills/orleans/references/testing-patterns.md +210 -0
  132. package/package/corpus/skills/platform-detection/LICENSE +23 -0
  133. package/package/corpus/skills/platform-detection/NOTICE +17 -0
  134. package/package/corpus/skills/platform-detection/SKILL.md +230 -0
  135. package/package/corpus/skills/platform-detection/references/command-mode.md +27 -0
  136. package/package/corpus/skills/profiling/LICENSE +21 -0
  137. package/package/corpus/skills/profiling/NOTICE +50 -0
  138. package/package/corpus/skills/profiling/SKILL.md +144 -0
  139. package/package/corpus/skills/profiling/references/commands.md +439 -0
  140. package/package/corpus/skills/profiling/references/patterns.md +586 -0
  141. package/package/corpus/skills/profiling/references/profiling.md +190 -0
  142. package/package/corpus/skills/run-tests/LICENSE +23 -0
  143. package/package/corpus/skills/run-tests/NOTICE +19 -0
  144. package/package/corpus/skills/run-tests/SKILL.md +305 -0
  145. package/package/corpus/skills/target-authoring/LICENSE +23 -0
  146. package/package/corpus/skills/target-authoring/NOTICE +17 -0
  147. package/package/corpus/skills/target-authoring/SKILL.md +165 -0
  148. package/package/corpus/skills/vectorization/LICENSE +23 -0
  149. package/package/corpus/skills/vectorization/NOTICE +17 -0
  150. package/package/corpus/skills/vectorization/SKILL.md +189 -0
  151. package/package/profile-catalog.json +85 -1
  152. package/package.json +1 -1
@@ -0,0 +1,660 @@
1
+ # Orleans Anti-Patterns
2
+
3
+ ## Contents
4
+
5
+ - Blocking Calls
6
+ - Large Grain State
7
+ - Chatty Grain Communication
8
+ - Single Bottleneck Grain
9
+ - Improper Grain Activation
10
+ - Ignoring Reentrancy
11
+ - Misusing Timers and Reminders
12
+ - Incorrect State Serialization
13
+ - Exception Handling
14
+ - Cluster Configuration
15
+ - Summary: Quick Reference
16
+
17
+ Common mistakes when building Orleans applications and how to avoid them.
18
+
19
+ ---
20
+
21
+ ## Blocking Calls
22
+
23
+ ### Anti-Pattern: Synchronous Blocking
24
+
25
+ ```csharp
26
+ // BAD: Blocking call in async context
27
+ public class BadGrain : Grain, IBadGrain
28
+ {
29
+ public Task<string> GetData()
30
+ {
31
+ var client = new HttpClient();
32
+ var result = client.GetStringAsync("https://api.example.com/data").Result; // BLOCKS!
33
+ return Task.FromResult(result);
34
+ }
35
+ }
36
+ ```
37
+
38
+ **Why it's bad:**
39
+ - Blocks the grain's single-threaded scheduler
40
+ - Can cause deadlocks with Orleans runtime
41
+ - Prevents other grain calls from executing
42
+ - Severely degrades cluster throughput
43
+
44
+ ### Correct Approach
45
+
46
+ ```csharp
47
+ // GOOD: Fully async
48
+ public class GoodGrain : Grain, IGoodGrain
49
+ {
50
+ private readonly HttpClient _client;
51
+
52
+ public GoodGrain(HttpClient client)
53
+ {
54
+ _client = client;
55
+ }
56
+
57
+ public async Task<string> GetData()
58
+ {
59
+ return await _client.GetStringAsync("https://api.example.com/data");
60
+ }
61
+ }
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Large Grain State
67
+
68
+ ### Anti-Pattern: Unbounded State Growth
69
+
70
+ ```csharp
71
+ // BAD: State grows without limit
72
+ [GenerateSerializer]
73
+ public class ChatRoomState
74
+ {
75
+ [Id(0)] public List<ChatMessage> AllMessages { get; set; } = []; // Grows forever!
76
+ }
77
+
78
+ public class ChatRoomGrain : Grain, IChatRoomGrain
79
+ {
80
+ private readonly IPersistentState<ChatRoomState> _state;
81
+
82
+ public async Task SendMessage(ChatMessage message)
83
+ {
84
+ _state.State.AllMessages.Add(message); // Unbounded growth
85
+ await _state.WriteStateAsync(); // Gets slower over time
86
+ }
87
+ }
88
+ ```
89
+
90
+ **Why it's bad:**
91
+ - Serialization time increases linearly
92
+ - Memory usage grows unbounded
93
+ - Activation time becomes very slow
94
+ - Storage costs increase
95
+
96
+ ### Correct Approach
97
+
98
+ ```csharp
99
+ // GOOD: Bounded state with external storage for history
100
+ [GenerateSerializer]
101
+ public class ChatRoomState
102
+ {
103
+ [Id(0)] public List<ChatMessage> RecentMessages { get; set; } = [];
104
+ [Id(1)] public int TotalMessageCount { get; set; }
105
+ private const int MaxRecentMessages = 100;
106
+
107
+ public void AddMessage(ChatMessage message)
108
+ {
109
+ RecentMessages.Add(message);
110
+ TotalMessageCount++;
111
+
112
+ if (RecentMessages.Count > MaxRecentMessages)
113
+ {
114
+ RecentMessages.RemoveAt(0);
115
+ }
116
+ }
117
+ }
118
+
119
+ public class ChatRoomGrain : Grain, IChatRoomGrain
120
+ {
121
+ private readonly IPersistentState<ChatRoomState> _state;
122
+ private readonly IMessageArchive _archive; // External storage for old messages
123
+
124
+ public async Task SendMessage(ChatMessage message)
125
+ {
126
+ _state.State.AddMessage(message);
127
+ await _state.WriteStateAsync();
128
+
129
+ // Archive to external storage asynchronously
130
+ await _archive.StoreAsync(this.GetPrimaryKeyString(), message);
131
+ }
132
+
133
+ public async Task<List<ChatMessage>> GetHistory(int page, int pageSize)
134
+ {
135
+ return await _archive.GetPageAsync(this.GetPrimaryKeyString(), page, pageSize);
136
+ }
137
+ }
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Chatty Grain Communication
143
+
144
+ ### Anti-Pattern: Many Small Calls
145
+
146
+ ```csharp
147
+ // BAD: Multiple round-trips per operation
148
+ public class OrderGrain : Grain, IOrderGrain
149
+ {
150
+ public async Task<OrderSummary> GetOrderSummary()
151
+ {
152
+ var customer = GrainFactory.GetGrain<ICustomerGrain>(_customerId);
153
+ var product = GrainFactory.GetGrain<IProductGrain>(_productId);
154
+ var shipping = GrainFactory.GetGrain<IShippingGrain>(_shippingId);
155
+
156
+ // Sequential calls - very slow!
157
+ var customerName = await customer.GetName();
158
+ var customerEmail = await customer.GetEmail();
159
+ var customerAddress = await customer.GetAddress();
160
+ var productName = await product.GetName();
161
+ var productPrice = await product.GetPrice();
162
+ var shippingStatus = await shipping.GetStatus();
163
+ var shippingEta = await shipping.GetEta();
164
+
165
+ return new OrderSummary { /* ... */ };
166
+ }
167
+ }
168
+ ```
169
+
170
+ **Why it's bad:**
171
+ - Each call incurs network latency
172
+ - Sequential execution multiplies delay
173
+ - High overhead for small payloads
174
+ - Poor cluster resource utilization
175
+
176
+ ### Correct Approach
177
+
178
+ ```csharp
179
+ // GOOD: Batch operations and parallel calls
180
+ public class OrderGrain : Grain, IOrderGrain
181
+ {
182
+ public async Task<OrderSummary> GetOrderSummary()
183
+ {
184
+ var customer = GrainFactory.GetGrain<ICustomerGrain>(_customerId);
185
+ var product = GrainFactory.GetGrain<IProductGrain>(_productId);
186
+ var shipping = GrainFactory.GetGrain<IShippingGrain>(_shippingId);
187
+
188
+ // Parallel calls with batched data retrieval
189
+ var customerTask = customer.GetDetails(); // Returns all customer info
190
+ var productTask = product.GetDetails(); // Returns all product info
191
+ var shippingTask = shipping.GetStatus(); // Returns full status
192
+
193
+ await Task.WhenAll(customerTask, productTask, shippingTask);
194
+
195
+ return new OrderSummary
196
+ {
197
+ Customer = customerTask.Result,
198
+ Product = productTask.Result,
199
+ Shipping = shippingTask.Result
200
+ };
201
+ }
202
+ }
203
+ ```
204
+
205
+ ---
206
+
207
+ ## Single Bottleneck Grain
208
+
209
+ ### Anti-Pattern: Hot Grain
210
+
211
+ ```csharp
212
+ // BAD: All operations go through one grain
213
+ public interface IGlobalCounterGrain : IGrainWithIntegerKey
214
+ {
215
+ Task<long> IncrementAndGet();
216
+ }
217
+
218
+ // Usage everywhere:
219
+ var counter = grainFactory.GetGrain<IGlobalCounterGrain>(0);
220
+ await counter.IncrementAndGet(); // ALL requests hit this single grain
221
+ ```
222
+
223
+ **Why it's bad:**
224
+ - Single grain handles all load
225
+ - No horizontal scaling possible
226
+ - Becomes the bottleneck for entire system
227
+ - Single point of failure
228
+
229
+ ### Correct Approach
230
+
231
+ ```csharp
232
+ // GOOD: Partitioned counters with aggregation
233
+ public interface IPartitionedCounterGrain : IGrainWithIntegerKey
234
+ {
235
+ Task Increment();
236
+ Task<long> GetLocalCount();
237
+ }
238
+
239
+ public interface ICounterAggregatorGrain : IGrainWithIntegerKey
240
+ {
241
+ Task<long> GetTotalCount();
242
+ }
243
+
244
+ public class PartitionedCounterGrain : Grain, IPartitionedCounterGrain
245
+ {
246
+ private long _count;
247
+
248
+ public Task Increment()
249
+ {
250
+ _count++;
251
+ return Task.CompletedTask;
252
+ }
253
+
254
+ public Task<long> GetLocalCount() => Task.FromResult(_count);
255
+ }
256
+
257
+ public class CounterAggregatorGrain : Grain, ICounterAggregatorGrain
258
+ {
259
+ private const int PartitionCount = 100;
260
+
261
+ public async Task<long> GetTotalCount()
262
+ {
263
+ var tasks = Enumerable.Range(0, PartitionCount)
264
+ .Select(i => GrainFactory
265
+ .GetGrain<IPartitionedCounterGrain>(i)
266
+ .GetLocalCount());
267
+
268
+ var counts = await Task.WhenAll(tasks);
269
+ return counts.Sum();
270
+ }
271
+ }
272
+
273
+ // Usage: Distribute load across partitions
274
+ var partitionId = HashCode(userId) % PartitionCount;
275
+ var counter = grainFactory.GetGrain<IPartitionedCounterGrain>(partitionId);
276
+ await counter.Increment();
277
+ ```
278
+
279
+ ---
280
+
281
+ ## Improper Grain Activation
282
+
283
+ ### Anti-Pattern: Short-Lived Grains
284
+
285
+ ```csharp
286
+ // BAD: Creating unique grains for each request
287
+ public class ApiController
288
+ {
289
+ public async Task<IActionResult> ProcessRequest(RequestData data)
290
+ {
291
+ // New unique grain for each request!
292
+ var processor = _grainFactory.GetGrain<IProcessorGrain>(Guid.NewGuid());
293
+ var result = await processor.Process(data);
294
+ return Ok(result);
295
+ }
296
+ }
297
+ ```
298
+
299
+ **Why it's bad:**
300
+ - Activation overhead on every request
301
+ - Grains never benefit from cached state
302
+ - Memory churn in silo
303
+ - Completely defeats Orleans' actor model benefits
304
+
305
+ ### Correct Approach
306
+
307
+ ```csharp
308
+ // GOOD: Reuse grains based on business identity
309
+ public class ApiController
310
+ {
311
+ public async Task<IActionResult> ProcessRequest(RequestData data)
312
+ {
313
+ // Grain identity based on logical entity
314
+ var processor = _grainFactory.GetGrain<IProcessorGrain>(data.CustomerId);
315
+ var result = await processor.Process(data);
316
+ return Ok(result);
317
+ }
318
+ }
319
+
320
+ // Or use StatelessWorker for truly stateless operations
321
+ [StatelessWorker]
322
+ public class ProcessorGrain : Grain, IProcessorGrain
323
+ {
324
+ public Task<Result> Process(RequestData data)
325
+ {
326
+ // Stateless processing, Orleans manages pooling
327
+ return Task.FromResult(DoProcess(data));
328
+ }
329
+ }
330
+ ```
331
+
332
+ ---
333
+
334
+ ## Ignoring Reentrancy
335
+
336
+ ### Anti-Pattern: Deadlock-Prone Calls
337
+
338
+ ```csharp
339
+ // BAD: Can deadlock if A calls B and B calls A
340
+ public class GrainA : Grain, IGrainA
341
+ {
342
+ public async Task DoSomething()
343
+ {
344
+ var grainB = GrainFactory.GetGrain<IGrainB>(0);
345
+ await grainB.DoOther(); // GrainB might call back to GrainA!
346
+ }
347
+
348
+ public Task Callback()
349
+ {
350
+ // This will deadlock if called while DoSomething is waiting
351
+ return Task.CompletedTask;
352
+ }
353
+ }
354
+ ```
355
+
356
+ **Why it's bad:**
357
+ - Circular calls cause deadlock
358
+ - Grain waits for itself
359
+ - Hard to debug
360
+ - System appears hung
361
+
362
+ ### Correct Approach
363
+
364
+ ```csharp
365
+ // GOOD: Allow reentrancy for callbacks
366
+ [Reentrant] // Allows interleaved calls
367
+ public class GrainA : Grain, IGrainA
368
+ {
369
+ public async Task DoSomething()
370
+ {
371
+ var grainB = GrainFactory.GetGrain<IGrainB>(0);
372
+ await grainB.DoOther();
373
+ }
374
+
375
+ public Task Callback()
376
+ {
377
+ return Task.CompletedTask;
378
+ }
379
+ }
380
+
381
+ // Or use [AlwaysInterleave] for specific methods
382
+ public class GrainA : Grain, IGrainA
383
+ {
384
+ public async Task DoSomething()
385
+ {
386
+ var grainB = GrainFactory.GetGrain<IGrainB>(0);
387
+ await grainB.DoOther();
388
+ }
389
+
390
+ [AlwaysInterleave] // This method can always execute
391
+ public Task Callback()
392
+ {
393
+ return Task.CompletedTask;
394
+ }
395
+ }
396
+ ```
397
+
398
+ ---
399
+
400
+ ## Misusing Timers and Reminders
401
+
402
+ ### Anti-Pattern: Timer for Persistence
403
+
404
+ ```csharp
405
+ // BAD: Using timer for critical persistence
406
+ public class BadGrain : Grain, IBadGrain
407
+ {
408
+ private int _importantData;
409
+
410
+ public override Task OnActivateAsync(CancellationToken ct)
411
+ {
412
+ // Timer is NOT persistent - data loss on silo crash!
413
+ RegisterGrainTimer(
414
+ SaveData,
415
+ default,
416
+ TimeSpan.FromMinutes(5),
417
+ TimeSpan.FromMinutes(5));
418
+
419
+ return base.OnActivateAsync(ct);
420
+ }
421
+
422
+ public Task UpdateData(int value)
423
+ {
424
+ _importantData = value;
425
+ return Task.CompletedTask; // Not persisted until timer fires!
426
+ }
427
+ }
428
+ ```
429
+
430
+ **Why it's bad:**
431
+ - Timers don't survive grain deactivation
432
+ - Data lost if silo crashes
433
+ - No guarantee timer will fire
434
+ - Not suitable for critical operations
435
+
436
+ ### Correct Approach
437
+
438
+ ```csharp
439
+ // GOOD: Persist immediately for critical data, use reminders for scheduled work
440
+ public class GoodGrain : Grain, IGoodGrain, IRemindable
441
+ {
442
+ private readonly IPersistentState<GrainState> _state;
443
+
444
+ public async Task UpdateData(int value)
445
+ {
446
+ _state.State.ImportantData = value;
447
+ await _state.WriteStateAsync(); // Immediate persistence
448
+ }
449
+
450
+ // Use reminder for scheduled work that must survive failures
451
+ public async Task ScheduleDailyReport()
452
+ {
453
+ await this.RegisterOrUpdateReminder(
454
+ "daily-report",
455
+ TimeSpan.FromHours(24),
456
+ TimeSpan.FromHours(24));
457
+ }
458
+
459
+ public Task ReceiveReminder(string reminderName, TickStatus status)
460
+ {
461
+ if (reminderName == "daily-report")
462
+ {
463
+ return GenerateReport();
464
+ }
465
+ return Task.CompletedTask;
466
+ }
467
+ }
468
+ ```
469
+
470
+ ---
471
+
472
+ ## Incorrect State Serialization
473
+
474
+ ### Anti-Pattern: Non-Serializable State
475
+
476
+ ```csharp
477
+ // BAD: Missing serialization attributes
478
+ public class PlayerState
479
+ {
480
+ public int Score { get; set; }
481
+ public HttpClient Client { get; set; } // Can't serialize!
482
+ public Action OnScoreChanged { get; set; } // Can't serialize!
483
+ }
484
+ ```
485
+
486
+ **Why it's bad:**
487
+ - Serialization fails at runtime
488
+ - State cannot be persisted
489
+ - Grain crashes on activation
490
+
491
+ ### Correct Approach
492
+
493
+ ```csharp
494
+ // GOOD: Proper serialization with Orleans attributes
495
+ [GenerateSerializer]
496
+ public class PlayerState
497
+ {
498
+ [Id(0)] public int Score { get; set; }
499
+ [Id(1)] public DateTime LastPlayed { get; set; }
500
+ [Id(2)] public List<string> Achievements { get; set; } = [];
501
+
502
+ // Non-serializable fields marked appropriately
503
+ [NonSerialized]
504
+ private HttpClient? _client;
505
+
506
+ [NonSerialized]
507
+ private Action? _onScoreChanged;
508
+ }
509
+
510
+ // Inject dependencies instead of storing them
511
+ public class PlayerGrain : Grain, IPlayerGrain
512
+ {
513
+ private readonly IPersistentState<PlayerState> _state;
514
+ private readonly HttpClient _client; // Injected, not in state
515
+
516
+ public PlayerGrain(
517
+ [PersistentState("player")] IPersistentState<PlayerState> state,
518
+ HttpClient client)
519
+ {
520
+ _state = state;
521
+ _client = client;
522
+ }
523
+ }
524
+ ```
525
+
526
+ ---
527
+
528
+ ## Exception Handling
529
+
530
+ ### Anti-Pattern: Swallowing Exceptions
531
+
532
+ ```csharp
533
+ // BAD: Silent failures
534
+ public class BadGrain : Grain, IBadGrain
535
+ {
536
+ public async Task ProcessOrder(Order order)
537
+ {
538
+ try
539
+ {
540
+ await _paymentService.Charge(order.Amount);
541
+ await _inventoryService.Reserve(order.Items);
542
+ }
543
+ catch (Exception)
544
+ {
545
+ // Silently swallow - order appears successful but isn't!
546
+ }
547
+ }
548
+ }
549
+ ```
550
+
551
+ **Why it's bad:**
552
+ - Failures are hidden
553
+ - System enters inconsistent state
554
+ - Very hard to debug
555
+ - Breaks caller's error handling
556
+
557
+ ### Correct Approach
558
+
559
+ ```csharp
560
+ // GOOD: Proper exception handling and propagation
561
+ public class GoodGrain : Grain, IGoodGrain
562
+ {
563
+ private readonly ILogger<GoodGrain> _logger;
564
+
565
+ public async Task ProcessOrder(Order order)
566
+ {
567
+ try
568
+ {
569
+ await _paymentService.Charge(order.Amount);
570
+ }
571
+ catch (PaymentException ex)
572
+ {
573
+ _logger.LogError(ex, "Payment failed for order {OrderId}", order.Id);
574
+ throw new OrderProcessingException("Payment failed", ex);
575
+ }
576
+
577
+ try
578
+ {
579
+ await _inventoryService.Reserve(order.Items);
580
+ }
581
+ catch (InventoryException ex)
582
+ {
583
+ _logger.LogError(ex, "Inventory reservation failed for order {OrderId}", order.Id);
584
+
585
+ // Compensate for partial success
586
+ await _paymentService.Refund(order.Amount);
587
+
588
+ throw new OrderProcessingException("Inventory unavailable", ex);
589
+ }
590
+ }
591
+ }
592
+ ```
593
+
594
+ ---
595
+
596
+ ## Cluster Configuration
597
+
598
+ ### Anti-Pattern: Dev Config in Production
599
+
600
+ ```csharp
601
+ // BAD: Localhost clustering in production
602
+ builder.UseOrleans(silo =>
603
+ {
604
+ silo.UseLocalhostClustering(); // Single-node only!
605
+ silo.AddMemoryGrainStorage("Default"); // No persistence!
606
+ });
607
+ ```
608
+
609
+ **Why it's bad:**
610
+ - Cannot scale beyond one silo
611
+ - Data lost on restart
612
+ - No fault tolerance
613
+ - Not suitable for production
614
+
615
+ ### Correct Approach
616
+
617
+ ```csharp
618
+ // GOOD: Environment-appropriate configuration
619
+ builder.UseOrleans((context, silo) =>
620
+ {
621
+ if (context.HostingEnvironment.IsDevelopment())
622
+ {
623
+ silo.UseLocalhostClustering();
624
+ silo.AddMemoryGrainStorage("Default");
625
+ }
626
+ else
627
+ {
628
+ silo.UseAzureStorageClustering(options =>
629
+ options.ConfigureTableServiceClient(
630
+ context.Configuration.GetConnectionString("Orleans")));
631
+
632
+ silo.AddAzureTableGrainStorage("Default", options =>
633
+ options.ConfigureTableServiceClient(
634
+ context.Configuration.GetConnectionString("Orleans")));
635
+
636
+ silo.Configure<ClusterOptions>(options =>
637
+ {
638
+ options.ClusterId = context.Configuration["Orleans:ClusterId"];
639
+ options.ServiceId = context.Configuration["Orleans:ServiceId"];
640
+ });
641
+ }
642
+ });
643
+ ```
644
+
645
+ ---
646
+
647
+ ## Summary: Quick Reference
648
+
649
+ | Anti-Pattern | Problem | Solution |
650
+ |--------------|---------|----------|
651
+ | `.Result` / `.Wait()` | Deadlocks, blocks scheduler | Use `async/await` throughout |
652
+ | Unbounded state | Slow activation, memory bloat | Bound state, use external storage |
653
+ | Many small calls | High latency | Batch operations, parallel calls |
654
+ | Hot single grain | Bottleneck, no scaling | Partition across grains |
655
+ | Unique grain per request | Activation overhead | Reuse grains, use StatelessWorker |
656
+ | Circular calls | Deadlocks | Use [Reentrant] or redesign |
657
+ | Timer for persistence | Data loss | Use immediate persist + reminders |
658
+ | Missing [GenerateSerializer] | Runtime failures | Add proper serialization |
659
+ | Swallowing exceptions | Hidden failures | Log and propagate errors |
660
+ | Dev config in prod | No persistence/scaling | Environment-specific config |