@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,784 @@
1
+ # Grain API Reference
2
+
3
+ ## Contents
4
+
5
+ - Grain Identity
6
+ - Grain Interface Rules
7
+ - Grain Lifecycle
8
+ - Placement Strategies
9
+ - Request Scheduling and Reentrancy
10
+ - Code Generation
11
+ - Cancellation Tokens
12
+ - Timers
13
+ - Reminders
14
+ - Interceptors
15
+ - POCO Grains
16
+ - Grain References
17
+ - Grain Extensions
18
+ - Stateless Worker Grains
19
+ - One-Way Requests
20
+ - External Tasks and Grains
21
+ - Request Context
22
+ - GrainServices
23
+ - Grain Versioning
24
+ - Activation Collection
25
+ - Error Handling
26
+
27
+ Detailed grain API patterns extracted from official Orleans documentation. Use when you need exact API shapes, not just link navigation.
28
+
29
+ ## Grain Identity
30
+
31
+ Identity structure: `type/key` (e.g., `shoppingcart/bob65`).
32
+
33
+ Grain type name derived from class name by removing "Grain" suffix and lowercasing. Customize with `[GrainType("cart")]`.
34
+
35
+ ### Key Types
36
+
37
+ | Interface | Key Type | Access | Factory |
38
+ |---|---|---|---|
39
+ | `IGrainWithGuidKey` | `Guid` | `this.GetPrimaryKey()` | `GetGrain<T>(guid)` |
40
+ | `IGrainWithIntegerKey` | `long` | `this.GetPrimaryKeyLong()` | `GetGrain<T>(longId)` |
41
+ | `IGrainWithStringKey` | `string` | `this.GetPrimaryKeyString()` | `GetGrain<T>(stringId)` |
42
+ | `IGrainWithGuidCompoundKey` | `Guid+string` | `this.GetPrimaryKey(out string ext)` | `GetGrain<T>(guid, "ext", null)` |
43
+ | `IGrainWithIntegerCompoundKey` | `long+string` | `this.GetPrimaryKeyLong(out string ext)` | `GetGrain<T>(0, "ext", null)` |
44
+
45
+ Singleton pattern: use a well-known fixed key like `"default"` or `0`.
46
+
47
+ ## Grain Interface Rules
48
+
49
+ ```csharp
50
+ public interface IHello : IGrainWithIntegerKey
51
+ {
52
+ ValueTask<string> SayHello(string greeting);
53
+ Task DoWork();
54
+ IAsyncEnumerable<int> StreamData(int count, [EnumeratorCancellation] CancellationToken ct = default);
55
+ }
56
+
57
+ [ResponseTimeout("00:00:05")]
58
+ Task<Result> TimeSensitiveCall();
59
+ ```
60
+
61
+ - All methods must return `Task`, `Task<T>`, `ValueTask<T>`, or `IAsyncEnumerable<T>`
62
+ - `CancellationToken` as last parameter, optional with default
63
+ - `[ResponseTimeout]` on interface methods only (not implementations)
64
+ - Default response timeout: 30 seconds
65
+
66
+ ## Grain Lifecycle
67
+
68
+ ```mermaid
69
+ flowchart LR
70
+ S1["First<br/>(int.MinValue)"] --> S2["SetupState<br/>(1000)<br/>loads persistent state"]
71
+ S2 --> S3["Activate<br/>(2000)<br/>OnActivateAsync"]
72
+ S3 --> S4["Active<br/>serving requests"]
73
+ S4 --> S5["Deactivate<br/>OnDeactivateAsync"]
74
+ S5 --> S6["Last<br/>(int.MaxValue)"]
75
+ ```
76
+
77
+ ```csharp
78
+ public override Task OnActivateAsync(CancellationToken ct)
79
+ {
80
+ // Called during activation (stage 2000)
81
+ return base.OnActivateAsync(ct);
82
+ }
83
+
84
+ public override Task OnDeactivateAsync(DeactivationReason reason, CancellationToken ct)
85
+ {
86
+ // Best-effort, not guaranteed on crash
87
+ return base.OnDeactivateAsync(reason, ct);
88
+ }
89
+ ```
90
+
91
+ ### Memory-Based Activation Shedding (Orleans 9+)
92
+
93
+ Auto-deactivates grains under memory pressure.
94
+
95
+ ```csharp
96
+ // Configure via GrainCollectionOptions
97
+ services.Configure<GrainCollectionOptions>(options =>
98
+ {
99
+ options.EnableActivationSheddingOnMemoryPressure = true;
100
+ options.MemoryUsageLimitPercentage = 80; // default
101
+ options.MemoryUsageTargetPercentage = 75; // default
102
+ options.CollectionAge = TimeSpan.FromMinutes(15); // default
103
+ });
104
+ ```
105
+
106
+ ### Grain Migration (Orleans 8+)
107
+
108
+ Move activations between silos preserving in-memory state.
109
+
110
+ ```csharp
111
+ // Implement IGrainMigrationParticipant
112
+ public class MyGrain : Grain, IMyGrain, IGrainMigrationParticipant
113
+ {
114
+ public void OnDehydrate(IDehydrationContext context)
115
+ {
116
+ context.TryAddValue("key", _myValue);
117
+ }
118
+
119
+ public void OnRehydrate(IRehydrationContext context)
120
+ {
121
+ context.TryGetValue("key", out _myValue);
122
+ }
123
+ }
124
+
125
+ // Trigger migration
126
+ this.MigrateOnIdle();
127
+
128
+ // Prevent migration
129
+ [Immovable]
130
+ public class PinnedGrain : Grain, IPinnedGrain { }
131
+ ```
132
+
133
+ ## Placement Strategies
134
+
135
+ **Cratis qualification:** Resource-optimized placement is the default from Orleans 9.2; earlier versions used random placement.
136
+
137
+ | Strategy | Attribute | Behavior |
138
+ |---|---|---|
139
+ | Resource-Optimized (default 9.2+) | `[ResourceOptimizedPlacement]` | CPU + memory + activation count weighted scoring |
140
+ | Random | `[RandomPlacement]` | Random compatible server |
141
+ | Prefer Local | `[PreferLocalPlacement]` | Local if compatible, else random |
142
+ | Hash-Based | `[HashBasedPlacement]` | Hash grain ID mod server count |
143
+ | Activation-Count-Based | `[ActivationCountBasedPlacement]` | Power of Two Choices algorithm |
144
+ | Stateless Worker | `[StatelessWorker]` | Multiple activations per server |
145
+ | Silo-Role-Based | `[SiloRoleBasedPlacement]` | Deterministic on silos with role |
146
+
147
+ ### Placement Hints in Orleans 10.3.1
148
+
149
+ Built-in placement directors can honor a placement hint for a new activation and for activation migration. Choose a target only from the current active membership snapshot and let Orleans apply grain-version and placement compatibility filtering.
150
+
151
+ The hint is not a command to relocate an activation that already exists. For an existing activation, invoke `MigrateOnIdle` or pass a migration-specific request-context dictionary to `IGrainContext.Migrate`. Because request context flows across grain calls, restore or remove the caller's placement-hint value after the targeted call so a receiving grain does not accidentally propagate it to unrelated outgoing calls.
152
+
153
+ Source: [Orleans 10.3.1 placement-hint guidance](https://github.com/dotnet/orleans/blob/v10.3.1/docs/site/src/content/docs/grains/grain-placement.md).
154
+
155
+ ### Custom Placement
156
+
157
+ ```csharp
158
+ // 1. Define strategy
159
+ public class MyPlacementStrategy : PlacementStrategy { }
160
+
161
+ // 2. Define attribute
162
+ [AttributeUsage(AttributeTargets.Class)]
163
+ public class MyPlacementAttribute : PlacementAttribute
164
+ {
165
+ public MyPlacementAttribute() : base(new MyPlacementStrategy()) { }
166
+ }
167
+
168
+ // 3. Implement director
169
+ public class MyPlacementDirector : IPlacementDirector
170
+ {
171
+ public Task<SiloAddress> OnAddActivation(
172
+ PlacementStrategy strategy, PlacementTarget target,
173
+ IPlacementContext context) { /* ... */ }
174
+ }
175
+
176
+ // 4. Register
177
+ services.AddPlacementDirector<MyPlacementStrategy, MyPlacementDirector>();
178
+ ```
179
+
180
+ ## Request Scheduling and Reentrancy
181
+
182
+ Default: **single-threaded, non-reentrant**. Each request runs to completion before the next starts. Safe but can deadlock with circular call patterns (A → B → A).
183
+
184
+ ### Reentrancy Mechanisms
185
+
186
+ | Mechanism | Scope | Effect |
187
+ |---|---|---|
188
+ | `[Reentrant]` | Grain class | All methods interleave freely |
189
+ | `[AlwaysInterleave]` | Interface method | Method always interleaves even on non-reentrant grains |
190
+ | `[ReadOnly]` | Interface method | Concurrent with other `[ReadOnly]` methods |
191
+ | `[MayInterleave(nameof(P))]` | Grain class | Per-call predicate decides interleaving |
192
+ | `AllowCallChainReentrancy()` | Scoped | Reentrancy for current call chain only |
193
+ | `SuppressCallChainReentrancy()` | Scoped | Disables call chain reentrancy |
194
+
195
+ ### Deadlock Prevention
196
+
197
+ ```csharp
198
+ // Problem: A calls B, B calls back to A → deadlock (A is blocked waiting for B)
199
+
200
+ // Solution 1: Mark entire grain as reentrant
201
+ [Reentrant]
202
+ public class GrainA : Grain, IGrainA { }
203
+
204
+ // Solution 2: Mark specific callback method
205
+ public interface IGrainA : IGrainWithIntegerKey
206
+ {
207
+ Task DoWork();
208
+ [AlwaysInterleave] Task Callback(); // always interleaves
209
+ }
210
+
211
+ // Solution 3: Scoped reentrancy (best — minimal surface)
212
+ public async Task DoWork()
213
+ {
214
+ using var _ = RequestContext.AllowCallChainReentrancy();
215
+ await otherGrain.MethodThatMightCallBack();
216
+ }
217
+ ```
218
+
219
+ ### ReadOnly Methods
220
+
221
+ ```csharp
222
+ public interface ICounterGrain : IGrainWithIntegerKey
223
+ {
224
+ [ReadOnly] Task<int> GetCount(); // concurrent with other ReadOnly
225
+ Task Increment(); // exclusive access
226
+ }
227
+ ```
228
+
229
+ ### MayInterleave Predicate
230
+
231
+ ```csharp
232
+ [MayInterleave(nameof(ArgHasInterleaveFlag))]
233
+ public class MyGrain : Grain, IMyGrain
234
+ {
235
+ private static bool ArgHasInterleaveFlag(IInvokable req)
236
+ {
237
+ return req.GetArgument<MyRequest>(0)?.AllowInterleave == true;
238
+ }
239
+ }
240
+ ```
241
+
242
+ ### Tradeoff
243
+
244
+ | Approach | Liveness | Safety |
245
+ |---|---|---|
246
+ | Non-reentrant (default) | Risk of deadlocks | No concurrent state mutation |
247
+ | `[Reentrant]` | No deadlocks | Must handle concurrent state access |
248
+ | `[AlwaysInterleave]` on method | Targeted | Only that method interleaves |
249
+ | `[ReadOnly]` | Concurrent reads | Only safe for read-only operations |
250
+ | `AllowCallChainReentrancy` | Targeted | Only the originating call chain reenters |
251
+
252
+ ## Code Generation
253
+
254
+ Orleans 7+ uses **C# source generators** at build time. No runtime code generation.
255
+
256
+ ### NuGet Packages
257
+
258
+ | Package | Use |
259
+ |---|---|
260
+ | `Microsoft.Orleans.Sdk` | Shared — grain interfaces, state types, serialization |
261
+ | `Microsoft.Orleans.Server` | Silo — includes Sdk |
262
+ | `Microsoft.Orleans.Client` | External client — includes Sdk |
263
+
264
+ ### Key Attributes
265
+
266
+ ```csharp
267
+ // Required on all serialized types (state, messages, events)
268
+ [GenerateSerializer]
269
+ public class MyState
270
+ {
271
+ [Id(0)] public string Name { get; set; } = "";
272
+ [Id(1)] public int Count { get; set; }
273
+ }
274
+ ```
275
+
276
+ Source generators create:
277
+ - Grain reference proxies (method invokers)
278
+ - Serializers and copiers for `[GenerateSerializer]` types
279
+ - Method metadata for interceptors and profiling
280
+
281
+ ### F# and VB.NET
282
+
283
+ ```fsharp
284
+ [<assembly: Orleans.GenerateCodeForDeclaringAssembly(typeof<IMyGrainInterface>)>]
285
+ do ()
286
+ ```
287
+
288
+ ### Build-Time Only
289
+
290
+ No runtime IL emission or reflection-based generation. All code generated as part of compilation. Analyzers (`Microsoft.Orleans.Analyzers`) provide warnings for missing `[GenerateSerializer]`, `[Id]`, etc.
291
+
292
+ ## Cancellation Tokens
293
+
294
+ Native `CancellationToken` support in grain interface methods (Orleans 7+). Cooperative cancellation.
295
+
296
+ ```csharp
297
+ // Interface — CancellationToken as last parameter, optional with default
298
+ public interface IProcessGrain : IGrainWithStringKey
299
+ {
300
+ Task<Result> Process(RequestData data, CancellationToken ct = default);
301
+ IAsyncEnumerable<Item> StreamItems(int count,
302
+ [EnumeratorCancellation] CancellationToken ct = default);
303
+ }
304
+
305
+ // Grain implementation
306
+ public class ProcessGrain : Grain, IProcessGrain
307
+ {
308
+ public async Task<Result> Process(RequestData data, CancellationToken ct)
309
+ {
310
+ ct.ThrowIfCancellationRequested();
311
+ var step1 = await DoStep1(ct);
312
+ ct.ThrowIfCancellationRequested();
313
+ return await DoStep2(step1, ct);
314
+ }
315
+
316
+ public async IAsyncEnumerable<Item> StreamItems(int count,
317
+ [EnumeratorCancellation] CancellationToken ct = default)
318
+ {
319
+ for (int i = 0; i < count && !ct.IsCancellationRequested; i++)
320
+ yield return await FetchItem(i);
321
+ }
322
+ }
323
+
324
+ // Client usage with timeout
325
+ using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
326
+ var result = await grain.Process(data, cts.Token);
327
+
328
+ // IAsyncEnumerable with cancellation
329
+ await foreach (var item in grain.StreamItems(100).WithCancellation(cts.Token))
330
+ Process(item);
331
+ ```
332
+
333
+ ### Cancellation Behavior
334
+
335
+ | Scenario | Behavior |
336
+ |---|---|
337
+ | Token cancelled before call | Throws `OperationCanceledException` immediately |
338
+ | Token cancelled during enqueued (not started) request | Request cancelled |
339
+ | Token cancelled during active call | Signal propagated, grain cooperatively cancels |
340
+ | Adding/removing `CancellationToken` parameter | Backward compatible, doesn't break existing callers |
341
+ | Multiple `CancellationToken` params | Compiler error `ORLEANS0109` |
342
+
343
+ ### Configuration
344
+
345
+ ```csharp
346
+ // SiloMessagingOptions / ClientMessagingOptions
347
+ options.CancelRequestOnTimeout = true; // default: auto-cancel on timeout
348
+ options.WaitForCancellationAcknowledgement = false; // default: don't wait for ack
349
+ ```
350
+
351
+ Metric: `orleans-app-requests-canceled`.
352
+
353
+ Legacy `GrainCancellationToken` / `GrainCancellationTokenSource` still available but deprecated.
354
+
355
+ ## Timers
356
+
357
+ Non-durable, activation-local periodic work. Stops on deactivation or silo crash.
358
+
359
+ ```csharp
360
+ // Modern API (Orleans 8+)
361
+ IGrainTimer timer = this.RegisterGrainTimer<MyState>(
362
+ async (state, ct) => { /* callback with CancellationToken */ },
363
+ myState,
364
+ new GrainTimerCreationOptions
365
+ {
366
+ DueTime = TimeSpan.FromSeconds(5),
367
+ Period = TimeSpan.FromMinutes(1),
368
+ Interleave = false, // default — respects single-threaded execution
369
+ KeepAlive = false // default — doesn't prevent deactivation
370
+ });
371
+
372
+ // Update timer at runtime
373
+ timer.Change(newDueTime, newPeriod);
374
+
375
+ // Dispose to stop
376
+ timer.Dispose();
377
+ ```
378
+
379
+ ### Timer Properties
380
+
381
+ - Period measured from callback **completion** to next invocation (not fixed interval)
382
+ - `Interleave = false` (default): callback waits for turn like normal grain calls
383
+ - `Interleave = true`: callback interleaves with other grain calls (old `RegisterTimer` behavior)
384
+ - `KeepAlive = true`: prevents grain deactivation while timer is active
385
+ - Callback receives `CancellationToken` that is cancelled when timer is disposed or grain deactivates
386
+
387
+ ### Migration from Legacy RegisterTimer
388
+
389
+ | Legacy `RegisterTimer` | Modern `RegisterGrainTimer` |
390
+ |---|---|
391
+ | Returns `IDisposable` | Returns `IGrainTimer` |
392
+ | Default interleave: `true` | Default interleave: `false` |
393
+ | No cancellation token | Callback receives `CancellationToken` |
394
+ | No `KeepAlive` option | `KeepAlive` available |
395
+
396
+ ### POCO Grains — Timer via DI
397
+
398
+ ```csharp
399
+ public class MyPocoGrain : IMyGrain
400
+ {
401
+ private readonly ITimerRegistry _timers;
402
+ private readonly IGrainContext _context;
403
+ public MyPocoGrain(ITimerRegistry timers, IGrainContext context)
404
+ {
405
+ _timers = timers;
406
+ _context = context;
407
+ }
408
+ public Task Start()
409
+ {
410
+ _timers.RegisterGrainTimer(_context, async (state, ct) => { },
411
+ new object(), new GrainTimerCreationOptions { Period = TimeSpan.FromMinutes(1) });
412
+ return Task.CompletedTask;
413
+ }
414
+ }
415
+ ```
416
+
417
+ ## Reminders
418
+
419
+ Durable, persistent periodic wakeups. Survive activation/deactivation and cluster restarts. Minimum granularity: minutes/hours/days (not for high-frequency).
420
+
421
+ ```csharp
422
+ public class MyGrain : Grain, IMyGrain, IRemindable
423
+ {
424
+ public async Task StartReminder()
425
+ {
426
+ // Register or update (idempotent)
427
+ IGrainReminder reminder = await RegisterOrUpdateReminder(
428
+ "daily-check",
429
+ dueTime: TimeSpan.FromHours(1),
430
+ period: TimeSpan.FromHours(24));
431
+ }
432
+
433
+ public async Task StopReminder()
434
+ {
435
+ // Lookup by name
436
+ IGrainReminder? reminder = await GetReminder("daily-check");
437
+ if (reminder is not null)
438
+ await UnregisterReminder(reminder);
439
+ }
440
+
441
+ public Task ReceiveReminder(string reminderName, TickStatus status)
442
+ {
443
+ // Reactivates idle grains when reminder ticks
444
+ return reminderName switch
445
+ {
446
+ "daily-check" => DoCheck(),
447
+ _ => Task.CompletedTask
448
+ };
449
+ }
450
+ }
451
+ ```
452
+
453
+ ### Reminder Operations
454
+
455
+ | Operation | Method |
456
+ |---|---|
457
+ | Register/update | `RegisterOrUpdateReminder(name, dueTime, period)` → `IGrainReminder` |
458
+ | Cancel | `UnregisterReminder(reminder)` |
459
+ | Lookup by name | `GetReminder(name)` → `IGrainReminder?` |
460
+ | List all | `GetReminders()` → `List<IGrainReminder>` |
461
+
462
+ ### Reminder Storage Providers
463
+
464
+ | Provider | Registration Method |
465
+ |---|---|
466
+ | Azure Table Storage | `UseAzureTableReminderService(options)` |
467
+ | Redis | `UseRedisReminderService(options)` |
468
+ | Cosmos DB | `UseCosmosReminderService(options)` |
469
+ | ADO.NET | `UseAdoNetReminderService(options)` |
470
+ | In-Memory (dev only) | `UseInMemoryReminderService()` |
471
+
472
+ ### Aspire Integration
473
+
474
+ ```csharp
475
+ // AppHost
476
+ var redis = builder.AddRedis("redis");
477
+ var orleans = builder.AddOrleans("cluster")
478
+ .WithReminders(redis);
479
+ // or .WithMemoryReminders() for dev
480
+ ```
481
+
482
+ ### POCO Grains — Reminder via DI
483
+
484
+ Inject `IReminderRegistry` instead of using `Grain` base class methods.
485
+
486
+ ### Timers vs Reminders vs Durable Jobs Decision
487
+
488
+ | Need | Use |
489
+ |---|---|
490
+ | High-frequency ticks (seconds) | Timer |
491
+ | Recurring schedule must survive deactivation/restart | Reminder |
492
+ | One-time future execution must survive deactivation/restart | Durable Job (experimental on Orleans 10.3) |
493
+ | Activation-local periodic work | Timer |
494
+ | Durable low-frequency wakeups | Reminder |
495
+ | Should prevent deactivation | Timer with `KeepAlive = true` |
496
+ | Must replay every occurrence missed during cluster downtime | Neither a plain reminder nor timer; persist due work or use an appropriate scheduler/workflow |
497
+
498
+ Reminder definitions are durable, but individual missed ticks are not replayed. Durable Jobs execute at least once, so handlers must be idempotent. See [scheduling-and-services.md](scheduling-and-services.md) for the full ownership and failure model.
499
+
500
+ ## Interceptors
501
+
502
+ ```csharp
503
+ // Silo-wide incoming filter
504
+ public class AuthFilter : IIncomingGrainCallFilter
505
+ {
506
+ public async Task Invoke(IIncomingGrainCallContext context)
507
+ {
508
+ // context.Grain, context.InterfaceMethod, context.Arguments, context.Result
509
+ await context.Invoke(); // call next filter or grain method
510
+ }
511
+ }
512
+ siloBuilder.AddIncomingGrainCallFilter<AuthFilter>();
513
+
514
+ // Per-grain filter: grain implements IIncomingGrainCallFilter
515
+ // Outgoing filter: IOutgoingGrainCallFilter on silo or client
516
+
517
+ // Execution order: DI-registered → grain-level → grain method
518
+ ```
519
+
520
+ ## POCO Grains
521
+
522
+ For grains that don't inherit from `Grain`:
523
+
524
+ ```csharp
525
+ public class MyPocoGrain : IMyGrain
526
+ {
527
+ private readonly ITimerRegistry _timers;
528
+ private readonly IReminderRegistry _reminders;
529
+ private readonly IGrainContext _context;
530
+
531
+ public MyPocoGrain(ITimerRegistry timers, IReminderRegistry reminders, IGrainContext context)
532
+ {
533
+ _timers = timers;
534
+ _reminders = reminders;
535
+ _context = context;
536
+ }
537
+ }
538
+ ```
539
+
540
+ ## Grain References
541
+
542
+ Proxy objects that encapsulate logical identity (type + key). Location-independent, survive restarts, serializable.
543
+
544
+ ```csharp
545
+ // From grain code
546
+ var grain = GrainFactory.GetGrain<IMyGrain>("key");
547
+
548
+ // From client code
549
+ var grain = client.GetGrain<IMyGrain>("key");
550
+
551
+ // Disambiguation when multiple implementations exist
552
+ var grain = GrainFactory.GetGrain<ICounterGrain>("key", grainClassNamePrefix: "Up");
553
+
554
+ // Or via explicit GrainId
555
+ var grain = GrainFactory.GetGrain<ICounterGrain>(GrainId.Create("up-counter", "key"));
556
+
557
+ // Or via DefaultGrainType attribute on interface
558
+ [DefaultGrainType("up-counter")]
559
+ public interface ICounterGrain : IGrainWithStringKey { }
560
+
561
+ // Or via unique marker interfaces
562
+ public interface IUpCounterGrain : ICounterGrain, IGrainWithStringKey { }
563
+ ```
564
+
565
+ ## Grain Extensions
566
+
567
+ Add behavior to grains without modifying the grain class via `IGrainExtension`.
568
+
569
+ ```csharp
570
+ // Define extension interface
571
+ public interface IDeactivateExtension : IGrainExtension
572
+ {
573
+ Task Deactivate(string msg);
574
+ }
575
+
576
+ // Implement
577
+ public sealed class DeactivateExtension : IDeactivateExtension
578
+ {
579
+ private readonly IGrainContext _context;
580
+ public DeactivateExtension(IGrainContext context) => _context = context;
581
+ public Task Deactivate(string msg)
582
+ {
583
+ _context.Deactivate(new DeactivationReason(
584
+ DeactivationReasonCode.ApplicationRequested, msg));
585
+ return Task.CompletedTask;
586
+ }
587
+ }
588
+
589
+ // Register globally
590
+ siloBuilder.AddGrainExtension<IDeactivateExtension, DeactivateExtension>();
591
+
592
+ // Use from anywhere
593
+ var ext = grain.AsReference<IDeactivateExtension>();
594
+ await ext.Deactivate("cleanup");
595
+ ```
596
+
597
+ Per-grain registration: call `GrainContext.SetComponent<T>(instance)` in `OnActivateAsync`.
598
+
599
+ ## Stateless Worker Grains
600
+
601
+ Multiple activations per silo, requests dispatched locally, auto-scaling.
602
+
603
+ ```csharp
604
+ [StatelessWorker] // scales up to CPU core count per silo
605
+ public class ProcessorGrain : Grain, IProcessorGrain { }
606
+
607
+ [StatelessWorker(4)] // max 4 activations per silo
608
+ public class LimitedWorker : Grain, ILimitedWorker { }
609
+
610
+ // Typically called with fixed key
611
+ var worker = GrainFactory.GetGrain<IProcessorGrain>(0);
612
+ ```
613
+
614
+ - Pool expands when all activations busy (up to limit), shrinks via idle deactivation
615
+ - Not individually addressable — two requests may hit different activations
616
+ - Useful for: CPU-bound stateless ops, hot cache items, reduce-style pre-aggregation
617
+ - Limitations: versioning does not apply, not supported in heterogeneous mode
618
+
619
+ ## One-Way Requests
620
+
621
+ Fire-and-forget: returns immediately, no completion signal, no error propagation.
622
+
623
+ ```csharp
624
+ public interface INotifyGrain : IGrainWithGuidKey
625
+ {
626
+ [OneWay]
627
+ Task Notify(MyData data); // must return non-generic Task or ValueTask
628
+ }
629
+ ```
630
+
631
+ Advanced feature — prefer regular bidirectional requests by default.
632
+
633
+ ## External Tasks and Grains
634
+
635
+ Orleans grain scheduler is single-threaded. Understanding which APIs stay on it is critical.
636
+
637
+ | API | Scheduler |
638
+ |---|---|
639
+ | `await`, `Task.Factory.StartNew`, `ContinueWith`, `WhenAny`, `WhenAll`, `Task.Delay` | Stays on grain scheduler |
640
+ | `Task.Run`, `TaskFactory.FromAsync` endMethod | Escapes to thread pool |
641
+ | `ConfigureAwait(false)` | **NEVER** use in grain code |
642
+ | `async void` | **NEVER** use in grain code |
643
+
644
+ ```csharp
645
+ // WRONG — unwrapped async delegate
646
+ var bad = Task.Factory.StartNew(SomeDelegateAsync);
647
+
648
+ // CORRECT
649
+ var good = Task.Factory.StartNew(SomeDelegateAsync).Unwrap();
650
+ ```
651
+
652
+ **Never** use `Task.Wait()`, `.Result`, `WaitAny`, `WaitAll`, or `GetAwaiter().GetResult()` in grain code.
653
+
654
+ ## Request Context
655
+
656
+ Ambient metadata flowing with requests (client → grain, grain → grain). Does NOT flow back with responses.
657
+
658
+ ```csharp
659
+ // Set on client or calling grain
660
+ RequestContext.Set("TraceId", Guid.NewGuid().ToString());
661
+
662
+ // Read in target grain
663
+ var traceId = RequestContext.Get("TraceId") as string;
664
+ ```
665
+
666
+ Values must be serializable. Prefer simple types (string, Guid, numeric) to minimize overhead.
667
+
668
+ ## GrainServices
669
+
670
+ Special grains running on every silo from startup to shutdown. Not individually addressable, not collected when idle. Used for distributed per-silo services.
671
+
672
+ ```csharp
673
+ // 1. Service interface
674
+ public interface IDataService : IGrainService { Task MyMethod(); }
675
+
676
+ // 2. Implementation
677
+ [Reentrant]
678
+ public class DataService : GrainService, IDataService
679
+ {
680
+ private readonly IGrainFactory _grainFactory;
681
+ public DataService(IServiceProvider services, GrainId id, Silo silo,
682
+ ILoggerFactory loggerFactory, IGrainFactory grainFactory)
683
+ : base(id, silo, loggerFactory) => _grainFactory = grainFactory;
684
+
685
+ public override Task Init(IServiceProvider serviceProvider) => base.Init(serviceProvider);
686
+ public override Task Start() => base.Start();
687
+ public override Task Stop() => base.Stop();
688
+ public Task MyMethod() => Task.CompletedTask;
689
+ }
690
+
691
+ // 3. Client interface
692
+ public interface IDataServiceClient : IGrainServiceClient<IDataService>, IDataService { }
693
+
694
+ // 4. Client implementation
695
+ public class DataServiceClient : GrainServiceClient<IDataService>, IDataServiceClient
696
+ {
697
+ public DataServiceClient(IServiceProvider sp) : base(sp) { }
698
+ private IDataService GrainService => GetGrainService(CurrentGrainReference.GrainId);
699
+ public Task MyMethod() => GrainService.MyMethod();
700
+ }
701
+
702
+ // 5. Register
703
+ siloBuilder.AddGrainService<DataService>();
704
+ builder.Services.AddSingleton<IDataServiceClient, DataServiceClient>();
705
+ ```
706
+
707
+ ## Grain Versioning
708
+
709
+ Different silos can support different versions of a grain interface.
710
+
711
+ ```csharp
712
+ [Version(2)]
713
+ public interface IMyGrain : IGrainWithIntegerKey
714
+ {
715
+ Task OriginalMethod(int arg); // from V1
716
+ Task NewMethod(int arg, obj o); // added in V2
717
+ }
718
+ ```
719
+
720
+ ### Compatibility Strategies
721
+
722
+ | Strategy | Behavior |
723
+ |---|---|
724
+ | `BackwardCompatible` (default) | V2 handles V1 requests; V1 cannot handle V2 |
725
+ | `FullyCompatible` | Bidirectional if no new methods added |
726
+
727
+ ### Version Selector Strategies
728
+
729
+ | Strategy | Behavior |
730
+ |---|---|
731
+ | `AllCompatibleVersions` (default) | Random selection proportional to silo count |
732
+ | `LatestVersion` | Always newest compatible version |
733
+ | `MinimumVersion` | Always minimum compatible version |
734
+
735
+ ```csharp
736
+ siloBuilder.Configure<GrainVersioningOptions>(options =>
737
+ {
738
+ options.DefaultCompatibilityStrategy = nameof(BackwardCompatible);
739
+ options.DefaultVersionSelectorStrategy = nameof(AllCompatibleVersions);
740
+ });
741
+ ```
742
+
743
+ ### Backward Compatibility Rules
744
+
745
+ - **Never** change signatures of existing methods
746
+ - **Never** rename parameters (position-based serialization)
747
+ - Add new methods in new versions instead of modifying existing
748
+ - Use two-step deprecation: mark `[Obsolete]` in V2, remove in V3 after V1 decommissioned
749
+ - Limitations: no versioning on stateless workers
750
+
751
+ ### Deployment Strategies
752
+
753
+ - **Rolling upgrade**: deploy newer silos directly, use `BackwardCompatible` + `AllCompatibleVersions`
754
+ - **Staging environment**: deploy V2 in staging slot joining same cluster, use `BackwardCompatible` + `MinimumVersion`, VIP swap when validated
755
+
756
+ ## Activation Collection
757
+
758
+ Automatic removal of idle grain activations. Default collection age: 15 minutes (Orleans 7+).
759
+
760
+ ```csharp
761
+ siloBuilder.Configure<GrainCollectionOptions>(options =>
762
+ {
763
+ options.CollectionAge = TimeSpan.FromMinutes(10);
764
+ options.ClassSpecificCollectionAge[typeof(MyGrain).FullName!] =
765
+ TimeSpan.FromMinutes(5);
766
+ });
767
+
768
+ // In grain code
769
+ this.DelayDeactivation(TimeSpan.FromHours(1)); // delay collection
770
+ this.DeactivateOnIdle(); // deactivate ASAP
771
+
772
+ // Prevent collection for a grain type
773
+ [KeepAlive]
774
+ public class PermanentGrain : Grain, IPermanentGrain { }
775
+ ```
776
+
777
+ What counts as active: receiving a method call, reminder, or streaming event. NOT: outbound calls, timer events (unless the timer was created with `GrainTimerCreationOptions.KeepAlive = true`), arbitrary I/O.
778
+
779
+ ## Error Handling
780
+
781
+ - Exceptions propagate across hosts with async/distributed try/catch semantics
782
+ - `InconsistentStateException` causes grain deactivation; other exceptions do not
783
+ - Read failures during activation fail the activation
784
+ - Write failures fault the `WriteStateAsync` Task