@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,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
|