@cratis/pi 7.101.0 → 7.103.0
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.
- package/package/corpus/harnesses/copilot/instructions/github-actions.instructions.md +17 -0
- package/package/corpus/harnesses/copilot/instructions/profiles.instructions.md +10 -1
- package/package/corpus/harnesses/copilot/instructions/pull-requests.instructions.md +2 -0
- package/package/corpus/harnesses/copilot/instructions/secrets.instructions.md +4 -0
- package/package/corpus/hooks/README.md +11 -1
- package/package/corpus/hooks/scripts/cratis-check-pr.mjs +12 -1
- package/package/corpus/hooks/scripts/cratis-guard-secrets.sh +79 -23
- package/package/corpus/hooks/scripts/cratis-guard-writes.sh +145 -40
- package/package/corpus/rules/github-actions.md +17 -0
- package/package/corpus/rules/profiles.md +10 -1
- package/package/corpus/rules/pull-requests.md +2 -0
- package/package/corpus/rules/secrets.md +4 -0
- package/package/corpus/skills/analyzing-dotnet-performance/LICENSE +23 -0
- package/package/corpus/skills/analyzing-dotnet-performance/NOTICE +36 -0
- package/package/corpus/skills/analyzing-dotnet-performance/SKILL.md +214 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/async-patterns.md +122 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/collections-and-linq.md +220 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/critical-patterns.md +291 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/io-and-serialization.md +135 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/memory-and-strings.md +219 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/regex-patterns.md +95 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/structural-patterns.md +38 -0
- package/package/corpus/skills/binlog-generation/LICENSE +23 -0
- package/package/corpus/skills/binlog-generation/NOTICE +22 -0
- package/package/corpus/skills/binlog-generation/SKILL.md +129 -0
- package/package/corpus/skills/check-bin-obj-clash/LICENSE +23 -0
- package/package/corpus/skills/check-bin-obj-clash/NOTICE +27 -0
- package/package/corpus/skills/check-bin-obj-clash/SKILL.md +127 -0
- package/package/corpus/skills/check-bin-obj-clash/references/common-causes-and-fixes.md +206 -0
- package/package/corpus/skills/coverage-analysis/LICENSE +23 -0
- package/package/corpus/skills/coverage-analysis/NOTICE +32 -0
- package/package/corpus/skills/coverage-analysis/SKILL.md +198 -0
- package/package/corpus/skills/coverage-analysis/references/guidelines.md +68 -0
- package/package/corpus/skills/coverage-analysis/references/output-format.md +91 -0
- package/package/corpus/skills/coverage-analysis/references/report-generation.md +58 -0
- package/package/corpus/skills/coverage-analysis/references/setup-discovery.md +307 -0
- package/package/corpus/skills/coverage-analysis/scripts/Compute-CrapScores.ps1 +80 -0
- package/package/corpus/skills/coverage-analysis/scripts/CoverageData.ps1 +272 -0
- package/package/corpus/skills/coverage-analysis/scripts/Extract-MethodCoverage.ps1 +65 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/LICENSE +2 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/SKILL.md +201 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/build-once-and-shard.md +158 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/caching.md +86 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/flaky-and-hung-jobs.md +70 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/publish-pipelines.md +67 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/required-checks-and-path-filters.md +117 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/runner-sizing-and-images.md +65 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/security-and-pinning.md +73 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/sources.md +33 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/references/test-setup-cost.md +82 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/scripts/ci-timings.sh +356 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/scripts/fixtures/jobs/1003.json +79 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/scripts/fixtures/jobs/1007.json +79 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/scripts/fixtures/jobs/1010.json +79 -0
- package/package/corpus/skills/cratis-ci-pipeline-performance/scripts/fixtures/runs.json +102 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/LICENSE +2 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/SKILL.md +244 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/docker-images.md +86 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/dotnet-nuget.md +87 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/go-jvm-python-elixir.md +83 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/npm-yarn.md +78 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/other-environments.md +60 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/worked-examples.md +97 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/scripts/cratis-local-packages.sh +778 -0
- package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/scripts/npm-restore-check.cjs +109 -0
- package/package/corpus/skills/csharp-refactoring/LICENSE +23 -0
- package/package/corpus/skills/csharp-refactoring/NOTICE +22 -0
- package/package/corpus/skills/csharp-refactoring/SKILL.md +175 -0
- package/package/corpus/skills/csharp-refactoring/references/operation-catalog.md +37 -0
- package/package/corpus/skills/directory-build-organization/LICENSE +23 -0
- package/package/corpus/skills/directory-build-organization/NOTICE +21 -0
- package/package/corpus/skills/directory-build-organization/SKILL.md +205 -0
- package/package/corpus/skills/directory-build-organization/references/common-patterns.md +56 -0
- package/package/corpus/skills/directory-build-organization/references/multi-level-examples.md +167 -0
- package/package/corpus/skills/directory-build-organization/references/targetframework-props-pitfall.md +54 -0
- package/package/corpus/skills/dotnet-aot-compat/LICENSE +23 -0
- package/package/corpus/skills/dotnet-aot-compat/NOTICE +18 -0
- package/package/corpus/skills/dotnet-aot-compat/SKILL.md +265 -0
- package/package/corpus/skills/dotnet-aot-compat/references/polyfills.md +43 -0
- package/package/corpus/skills/dotnet-trace-collect/LICENSE +23 -0
- package/package/corpus/skills/dotnet-trace-collect/NOTICE +65 -0
- package/package/corpus/skills/dotnet-trace-collect/SKILL.md +116 -0
- package/package/corpus/skills/dotnet-trace-collect/references/dotnet-monitor.md +97 -0
- package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect-linux.md +84 -0
- package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect.md +91 -0
- package/package/corpus/skills/dotnet-trace-collect/references/perfcollect.md +200 -0
- package/package/corpus/skills/dotnet-trace-collect/references/perfview.md +141 -0
- package/package/corpus/skills/dotnet-trace-collect/references/tool-selection.md +160 -0
- package/package/corpus/skills/dump-collect/LICENSE +23 -0
- package/package/corpus/skills/dump-collect/NOTICE +40 -0
- package/package/corpus/skills/dump-collect/SKILL.md +97 -0
- package/package/corpus/skills/dump-collect/references/container-dumps.md +412 -0
- package/package/corpus/skills/dump-collect/references/coreclr-dumps.md +108 -0
- package/package/corpus/skills/dump-collect/references/nativeaot-dumps.md +280 -0
- package/package/corpus/skills/eliminate-redundant-restores/LICENSE +23 -0
- package/package/corpus/skills/eliminate-redundant-restores/NOTICE +19 -0
- package/package/corpus/skills/eliminate-redundant-restores/SKILL.md +112 -0
- package/package/corpus/skills/extension-points/LICENSE +23 -0
- package/package/corpus/skills/extension-points/NOTICE +18 -0
- package/package/corpus/skills/extension-points/SKILL.md +248 -0
- package/package/corpus/skills/filter-syntax/LICENSE +23 -0
- package/package/corpus/skills/filter-syntax/NOTICE +17 -0
- package/package/corpus/skills/filter-syntax/SKILL.md +179 -0
- package/package/corpus/skills/including-generated-files/LICENSE +23 -0
- package/package/corpus/skills/including-generated-files/NOTICE +17 -0
- package/package/corpus/skills/including-generated-files/SKILL.md +139 -0
- package/package/corpus/skills/microbenchmarking/LICENSE +23 -0
- package/package/corpus/skills/microbenchmarking/NOTICE +36 -0
- package/package/corpus/skills/microbenchmarking/SKILL.md +131 -0
- package/package/corpus/skills/microbenchmarking/references/bdn-internals-and-tuning.md +143 -0
- package/package/corpus/skills/microbenchmarking/references/comparison-strategies.md +200 -0
- package/package/corpus/skills/microbenchmarking/references/diagnosers-and-exporters.md +150 -0
- package/package/corpus/skills/microbenchmarking/references/project-setup-and-running.md +189 -0
- package/package/corpus/skills/microbenchmarking/references/writing-benchmarks.md +310 -0
- package/package/corpus/skills/msbuild/LICENSE +23 -0
- package/package/corpus/skills/msbuild/NOTICE +55 -0
- package/package/corpus/skills/msbuild/SKILL.md +117 -0
- package/package/corpus/skills/msbuild/references/antipatterns/additional-antipatterns.md +291 -0
- package/package/corpus/skills/msbuild/references/antipatterns/incremental-build-inputs-outputs.md +57 -0
- package/package/corpus/skills/msbuild/references/antipatterns/private-assets.md +29 -0
- package/package/corpus/skills/msbuild/references/binlog-failure-analysis.md +85 -0
- package/package/corpus/skills/msbuild/references/binlog-generation.md +69 -0
- package/package/corpus/skills/msbuild/references/build-perf-baseline.md +493 -0
- package/package/corpus/skills/msbuild/references/build-perf-diagnostics.md +268 -0
- package/package/corpus/skills/msbuild/references/copy-to-output-directory.md +183 -0
- package/package/corpus/skills/msbuild/references/eval-performance.md +190 -0
- package/package/corpus/skills/msbuild/references/extension-points.md +281 -0
- package/package/corpus/skills/msbuild/references/incremental-build.md +365 -0
- package/package/corpus/skills/msbuild/references/msbuild-antipatterns.md +401 -0
- package/package/corpus/skills/orleans/LICENSE +21 -0
- package/package/corpus/skills/orleans/NOTICE +64 -0
- package/package/corpus/skills/orleans/SKILL.md +140 -0
- package/package/corpus/skills/orleans/references/anti-patterns.md +660 -0
- package/package/corpus/skills/orleans/references/configuration-api.md +933 -0
- package/package/corpus/skills/orleans/references/examples.md +66 -0
- package/package/corpus/skills/orleans/references/grain-api.md +784 -0
- package/package/corpus/skills/orleans/references/grains.md +75 -0
- package/package/corpus/skills/orleans/references/hosting.md +71 -0
- package/package/corpus/skills/orleans/references/implementation.md +591 -0
- package/package/corpus/skills/orleans/references/official-docs-index.md +250 -0
- package/package/corpus/skills/orleans/references/patterns.md +811 -0
- package/package/corpus/skills/orleans/references/persistence-api.md +495 -0
- package/package/corpus/skills/orleans/references/primitive-selection.md +35 -0
- package/package/corpus/skills/orleans/references/scheduling-and-services.md +295 -0
- package/package/corpus/skills/orleans/references/serialization-api.md +299 -0
- package/package/corpus/skills/orleans/references/streaming-api.md +396 -0
- package/package/corpus/skills/orleans/references/testing-patterns.md +210 -0
- package/package/corpus/skills/platform-detection/LICENSE +23 -0
- package/package/corpus/skills/platform-detection/NOTICE +17 -0
- package/package/corpus/skills/platform-detection/SKILL.md +230 -0
- package/package/corpus/skills/platform-detection/references/command-mode.md +27 -0
- package/package/corpus/skills/profiling/LICENSE +21 -0
- package/package/corpus/skills/profiling/NOTICE +50 -0
- package/package/corpus/skills/profiling/SKILL.md +144 -0
- package/package/corpus/skills/profiling/references/commands.md +439 -0
- package/package/corpus/skills/profiling/references/patterns.md +586 -0
- package/package/corpus/skills/profiling/references/profiling.md +190 -0
- package/package/corpus/skills/run-tests/LICENSE +23 -0
- package/package/corpus/skills/run-tests/NOTICE +19 -0
- package/package/corpus/skills/run-tests/SKILL.md +305 -0
- package/package/corpus/skills/target-authoring/LICENSE +23 -0
- package/package/corpus/skills/target-authoring/NOTICE +17 -0
- package/package/corpus/skills/target-authoring/SKILL.md +165 -0
- package/package/corpus/skills/vectorization/LICENSE +23 -0
- package/package/corpus/skills/vectorization/NOTICE +17 -0
- package/package/corpus/skills/vectorization/SKILL.md +189 -0
- package/package/profile-catalog.json +87 -1
- package/package.json +3 -2
|
@@ -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 |
|