@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.
- 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/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-screenplay-command-surface/SKILL.md +26 -1
- package/package/corpus/skills/cratis-screenplay-projections/SKILL.md +17 -0
- package/package/corpus/skills/cratis-screenplay-render-and-gap-fill/SKILL.md +17 -0
- package/package/corpus/skills/cratis-screenplay-specifications/SKILL.md +8 -0
- package/package/corpus/skills/cratis-screenplay-toolchain/SKILL.md +1 -1
- package/package/corpus/skills/cratis-screenplay-toolchain/references/cheat-sheet-example.md +1 -0
- package/package/corpus/skills/cratis-screenplay-toolchain/references/fast-path.md +7 -0
- package/package/corpus/skills/cratis-screenplay-toolchain/references/traps.md +1 -0
- package/package/corpus/skills/cratis-screenplay-ui-composition/SKILL.md +40 -0
- package/package/corpus/skills/cratis-screenplay-ui-composition/references/response-continuations.md +1 -0
- package/package/corpus/skills/cratis-screenplay-ui-composition/references/worked-flow.md +1 -0
- package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/admission.md +3 -2
- 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 +85 -1
- package/package.json +1 -1
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
# Implementation Details, Runtime Internals, and Testing
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- Implementation Overview
|
|
6
|
+
- Grain Directory Internals
|
|
7
|
+
- Orleans Lifecycle
|
|
8
|
+
- Messaging Delivery Guarantees
|
|
9
|
+
- Scheduler
|
|
10
|
+
- Cluster Management
|
|
11
|
+
- Streams Implementation
|
|
12
|
+
- Load Balancing
|
|
13
|
+
- Unit Testing
|
|
14
|
+
- Tutorials, Samples, and Resource Pages
|
|
15
|
+
- API Reference and Source Entry Points
|
|
16
|
+
- Usage Guidance
|
|
17
|
+
|
|
18
|
+
Use this reference when architecture decisions depend on Orleans internals, scheduler rules, delivery guarantees, stream internals, or test-cluster behavior.
|
|
19
|
+
|
|
20
|
+
## Implementation Overview
|
|
21
|
+
|
|
22
|
+
Orleans runtime is built on a few core subsystems:
|
|
23
|
+
|
|
24
|
+
```mermaid
|
|
25
|
+
flowchart TD
|
|
26
|
+
C["Client / Gateway"] --> M["Messaging Layer"]
|
|
27
|
+
M --> S["Scheduler"]
|
|
28
|
+
S --> A["Activation Catalog"]
|
|
29
|
+
A --> G["Grain Directory"]
|
|
30
|
+
G --> P["Placement Director"]
|
|
31
|
+
M --> ST["Streaming Runtime"]
|
|
32
|
+
A --> PS["Persistence / State"]
|
|
33
|
+
CLM["Cluster Management"] --> G
|
|
34
|
+
CLM --> LB["Load Balancer"]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Key principle: grains are virtual — they always exist logically, and the runtime activates/deactivates physical instances as needed. The grain directory maps identities to activations, the scheduler enforces single-threaded execution per grain, and the messaging layer routes calls across silos.
|
|
38
|
+
|
|
39
|
+
## Grain Directory Internals
|
|
40
|
+
|
|
41
|
+
The grain directory maintains a mapping: `GrainId → (SiloAddress, ActivationId)`.
|
|
42
|
+
|
|
43
|
+
### How Activation Works
|
|
44
|
+
|
|
45
|
+
1. Client or grain makes a call to `GrainId`
|
|
46
|
+
2. Runtime checks local cache for existing activation
|
|
47
|
+
3. On cache miss, queries the grain directory
|
|
48
|
+
4. If no activation exists, directory picks a silo (via placement) and creates one
|
|
49
|
+
5. Directory registers the new activation
|
|
50
|
+
6. Future calls route directly to the registered silo
|
|
51
|
+
|
|
52
|
+
### Directory Partitioning
|
|
53
|
+
|
|
54
|
+
The default distributed directory uses a DHT (Distributed Hash Table):
|
|
55
|
+
- Each silo owns a range of the grain ID hash space
|
|
56
|
+
- Directory lookups are single-hop: hash the grain ID → find owning silo → query that silo
|
|
57
|
+
- On silo failure, its directory partition is rebuilt from surviving silos
|
|
58
|
+
|
|
59
|
+
### Consistency
|
|
60
|
+
|
|
61
|
+
- **Default (eventually consistent)**: may allow brief duplicate activations during cluster instability. The duplicate is detected and one is deactivated.
|
|
62
|
+
- **Strong consistency (Orleans 10 preview)**: uses versioned range locks, 30 virtual nodes per silo. Prevents duplicate activations entirely. Enable with `builder.AddDistributedGrainDirectory()`.
|
|
63
|
+
|
|
64
|
+
### Per-Grain-Type Directory
|
|
65
|
+
|
|
66
|
+
```csharp
|
|
67
|
+
[GrainDirectory(GrainDirectoryName = "my-directory")]
|
|
68
|
+
public class MyGrain : Grain, IMyGrain { }
|
|
69
|
+
|
|
70
|
+
// Register external directory
|
|
71
|
+
siloBuilder.AddRedisGrainDirectory("my-directory", options => { });
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Available backends: In-Cluster (default), ADO.NET, Azure Table, Redis, Cosmos DB.
|
|
75
|
+
|
|
76
|
+
## Orleans Lifecycle
|
|
77
|
+
|
|
78
|
+
Orleans uses an observable lifecycle pattern for ordered startup and shutdown of components.
|
|
79
|
+
|
|
80
|
+
### Silo Lifecycle Stages
|
|
81
|
+
|
|
82
|
+
```mermaid
|
|
83
|
+
flowchart LR
|
|
84
|
+
S1["First<br/>int.MinValue"] --> S2["RuntimeInitialize<br/>2000"]
|
|
85
|
+
S2 --> S3["RuntimeServices<br/>4000"]
|
|
86
|
+
S3 --> S4["RuntimeStorageServices<br/>6000"]
|
|
87
|
+
S4 --> S5["RuntimeGrainServices<br/>8000"]
|
|
88
|
+
S5 --> S6["ApplicationServices<br/>10000"]
|
|
89
|
+
S6 --> S7["BecomeActive<br/>Active-1"]
|
|
90
|
+
S7 --> S8["Active<br/>20000"]
|
|
91
|
+
S8 --> S9["Last<br/>int.MaxValue"]
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
| Stage | Value | What Happens |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| `First` | `int.MinValue` | Earliest possible stage |
|
|
97
|
+
| `RuntimeInitialize` | 2000 | Threading initialization |
|
|
98
|
+
| `RuntimeServices` | 4000 | Networking, messaging, agents started |
|
|
99
|
+
| `RuntimeStorageServices` | 6000 | Storage providers initialized |
|
|
100
|
+
| `RuntimeGrainServices` | 8000 | Grain type management, membership joined, grain directory started |
|
|
101
|
+
| `ApplicationServices` | 10000 | Application-layer services initialized |
|
|
102
|
+
| `ValidateInitialConnectivity` | before `BecomeActive` | Orleans 10.2 validates initial peer connectivity while the silo is still `Joining` |
|
|
103
|
+
| `BecomeActive` | `Active - 1` | Silo joins the cluster |
|
|
104
|
+
| `Active` | 20000 | Ready for workload — grains can be activated |
|
|
105
|
+
| `Last` | `int.MaxValue` | Latest possible stage |
|
|
106
|
+
|
|
107
|
+
Shutdown reverses the order.
|
|
108
|
+
|
|
109
|
+
### Participation API
|
|
110
|
+
|
|
111
|
+
Components participate via `ILifecycleParticipant<ISiloLifecycle>`:
|
|
112
|
+
|
|
113
|
+
```csharp
|
|
114
|
+
public class MyComponent : ILifecycleParticipant<ISiloLifecycle>
|
|
115
|
+
{
|
|
116
|
+
public void Participate(ISiloLifecycle lifecycle)
|
|
117
|
+
{
|
|
118
|
+
lifecycle.Subscribe<MyComponent>(
|
|
119
|
+
ServiceLifecycleStage.ApplicationServices,
|
|
120
|
+
onStart: async ct =>
|
|
121
|
+
{
|
|
122
|
+
// Initialization logic
|
|
123
|
+
},
|
|
124
|
+
onStop: async ct =>
|
|
125
|
+
{
|
|
126
|
+
// Cleanup logic
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Register in DI
|
|
132
|
+
services.AddSingleton<ILifecycleParticipant<ISiloLifecycle>, MyComponent>();
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Grain Lifecycle Stages
|
|
136
|
+
|
|
137
|
+
Grain-level lifecycle (distinct from silo lifecycle):
|
|
138
|
+
|
|
139
|
+
| Stage | Value | What Happens |
|
|
140
|
+
|---|---|---|
|
|
141
|
+
| `First` | `int.MinValue` | Earliest |
|
|
142
|
+
| `SetupState` | 1000 | Loads persistent state from storage |
|
|
143
|
+
| `Activate` | 2000 | Calls `OnActivateAsync` / `OnDeactivateAsync` |
|
|
144
|
+
| `Last` | `int.MaxValue` | Latest |
|
|
145
|
+
|
|
146
|
+
Override `Grain.Participate(IGrainLifecycle)` to hook into grain-level lifecycle.
|
|
147
|
+
|
|
148
|
+
### Logging
|
|
149
|
+
|
|
150
|
+
At `Information` level on `Orleans.Runtime.SiloLifecycleSubject`, logs which components participate at each stage and timing.
|
|
151
|
+
|
|
152
|
+
## Messaging Delivery Guarantees
|
|
153
|
+
|
|
154
|
+
### Default: At-Most-Once
|
|
155
|
+
|
|
156
|
+
Orleans delivers messages **at most once** by default. A message is either delivered exactly once or not at all — never duplicated.
|
|
157
|
+
|
|
158
|
+
- Every message has an automatic configurable timeout
|
|
159
|
+
- On timeout, the caller's `Task` is faulted with a timeout exception
|
|
160
|
+
- **No automatic retries** by default
|
|
161
|
+
|
|
162
|
+
### With Retries: At-Least-Once
|
|
163
|
+
|
|
164
|
+
If the application implements retry logic (e.g., via Polly), delivery becomes at-least-once: the message may arrive multiple times. Orleans does **not** deduplicate messages.
|
|
165
|
+
|
|
166
|
+
### Timeout Behavior
|
|
167
|
+
|
|
168
|
+
```csharp
|
|
169
|
+
// Global timeout
|
|
170
|
+
siloBuilder.Configure<SiloMessagingOptions>(options =>
|
|
171
|
+
{
|
|
172
|
+
options.ResponseTimeout = TimeSpan.FromSeconds(30); // default
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Per-method timeout
|
|
176
|
+
[ResponseTimeout("00:00:05")]
|
|
177
|
+
Task<Result> TimeSensitiveCall();
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Failure Scenarios
|
|
181
|
+
|
|
182
|
+
| Scenario | What Happens |
|
|
183
|
+
|---|---|
|
|
184
|
+
| Target silo alive | Message delivered, response returned |
|
|
185
|
+
| Target silo dead (detected) | Grain reactivated on another silo, message re-routed |
|
|
186
|
+
| Target silo dead (not yet detected) | Timeout → exception → caller retries → grain activates elsewhere |
|
|
187
|
+
| Network partition | Timeout → exception to caller |
|
|
188
|
+
| Grain method throws | Exception propagated back to caller |
|
|
189
|
+
| Grain `OnActivateAsync` throws | Activation fails, exception to caller |
|
|
190
|
+
|
|
191
|
+
### Key Guarantee
|
|
192
|
+
|
|
193
|
+
With infinite retries, eventual delivery is guaranteed because grains never enter a permanent failure state — failed grains reactivate on another silo automatically.
|
|
194
|
+
|
|
195
|
+
## Scheduler
|
|
196
|
+
|
|
197
|
+
Orleans uses a **cooperative, single-threaded-per-grain scheduler** built on the .NET Thread Pool.
|
|
198
|
+
|
|
199
|
+
### Core Rules
|
|
200
|
+
|
|
201
|
+
1. **Single-threaded execution**: a grain never executes on more than one thread simultaneously (unless `[Reentrant]`)
|
|
202
|
+
2. **Turn-based**: each "turn" runs to the next `await` or completion. State only changes between turns.
|
|
203
|
+
3. **No preemption**: long-running synchronous code blocks the grain's scheduler slot
|
|
204
|
+
4. **Cooperative multitasking**: grains yield at `await` points
|
|
205
|
+
|
|
206
|
+
### Task Scheduling Behavior Within Grain Code
|
|
207
|
+
|
|
208
|
+
| API | Where It Runs |
|
|
209
|
+
|---|---|
|
|
210
|
+
| `await task` | Resumes on grain scheduler |
|
|
211
|
+
| `Task.Factory.StartNew(delegate)` | Runs on grain scheduler |
|
|
212
|
+
| `ContinueWith(delegate)` | Runs on grain scheduler |
|
|
213
|
+
| `Task.WhenAll`, `Task.WhenAny` | Continuation on grain scheduler |
|
|
214
|
+
| `Task.Delay` | Continuation on grain scheduler |
|
|
215
|
+
| `Task.Run(delegate)` | Delegate on thread pool; `await` resumes on grain scheduler |
|
|
216
|
+
| `ConfigureAwait(false)` | **NEVER use** — escapes grain scheduler |
|
|
217
|
+
| `async void` | **NEVER use** in grain code |
|
|
218
|
+
|
|
219
|
+
### Thread Pool Usage
|
|
220
|
+
|
|
221
|
+
Orleans runs grain turns on the .NET Thread Pool with cooperative scheduling. With proper async code, can achieve **90%+ CPU utilization** with stability.
|
|
222
|
+
|
|
223
|
+
```csharp
|
|
224
|
+
// Background work on thread pool (OK)
|
|
225
|
+
var result = await Task.Run(() => CpuBoundWork(data));
|
|
226
|
+
// Resumes here on grain scheduler
|
|
227
|
+
|
|
228
|
+
// WRONG — deadlock risk
|
|
229
|
+
var bad = task.Result; // NEVER block in grain code
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Scheduling Options
|
|
233
|
+
|
|
234
|
+
```csharp
|
|
235
|
+
siloBuilder.Configure<SchedulingOptions>(options =>
|
|
236
|
+
{
|
|
237
|
+
options.AllowCallChainReentrancy = false; // default
|
|
238
|
+
options.PerformDeadlockDetection = true; // default (dev)
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Reentrancy and Interleaving
|
|
243
|
+
|
|
244
|
+
Default non-reentrant: each request runs to completion. With `[Reentrant]`, multiple requests can interleave at `await` points. See grain-api.md `## Request Scheduling and Reentrancy` for full details.
|
|
245
|
+
|
|
246
|
+
## Cluster Management
|
|
247
|
+
|
|
248
|
+
Fully distributed peer-to-peer membership protocol with no central coordinator.
|
|
249
|
+
|
|
250
|
+
### Protocol Overview
|
|
251
|
+
|
|
252
|
+
```mermaid
|
|
253
|
+
flowchart TD
|
|
254
|
+
S1["Silo 1"] -->|"Probe every 10s"| S2["Silo 2"]
|
|
255
|
+
S1 -->|"Probe every 10s"| S3["Silo 3"]
|
|
256
|
+
S2 -->|"Probe every 10s"| S1
|
|
257
|
+
S2 -->|"Probe every 10s"| S3
|
|
258
|
+
S3 -->|"Probe every 10s"| S1
|
|
259
|
+
S3 -->|"Probe every 10s"| S2
|
|
260
|
+
S1 & S2 & S3 -->|"IAmAlive every 30s"| MT["IMembershipTable<br/>(Azure Table / Redis / SQL / ...)"]
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Configuration
|
|
264
|
+
|
|
265
|
+
```csharp
|
|
266
|
+
siloBuilder.Configure<ClusterMembershipOptions>(options =>
|
|
267
|
+
{
|
|
268
|
+
options.NumProbedSilos = 10; // how many silos monitor each silo (default 10 in 9.x+, was 3)
|
|
269
|
+
options.NumVotesForDeathDeclaration = 2; // votes needed to declare dead
|
|
270
|
+
options.DeathVoteExpirationTimeout = TimeSpan.FromSeconds(180); // vote TTL
|
|
271
|
+
options.ProbeTimeout = TimeSpan.FromSeconds(10); // probe interval
|
|
272
|
+
options.NumMissedProbesLimit = 3; // missed probes before suspicion
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Failure Detection Timeline
|
|
277
|
+
|
|
278
|
+
Typical: **~15 seconds** (Orleans 9+) from silo crash to detection.
|
|
279
|
+
|
|
280
|
+
1. Monitoring silos send probes every 10 seconds
|
|
281
|
+
2. After 3 missed probes (30s), silo is suspected
|
|
282
|
+
3. 2 independent suspicions trigger death declaration
|
|
283
|
+
4. Dead silo evicted from cluster, its activations destroyed
|
|
284
|
+
5. Grains reactivate on other silos on next call
|
|
285
|
+
|
|
286
|
+
### Protocol Properties
|
|
287
|
+
|
|
288
|
+
- Handles any number of simultaneous failures (f ≤ n), including full cluster restart
|
|
289
|
+
- Light table traffic: probes go direct silo-to-silo, not through `IMembershipTable`
|
|
290
|
+
- Self-monitoring with Lifeguard-inspired health scoring (unhealthy silos get increased probe timeouts)
|
|
291
|
+
- Indirect probing for accuracy improvement
|
|
292
|
+
- Table unavailability **never** causes false death declarations
|
|
293
|
+
- `IAmAlive` writes every 30 seconds for diagnostics and disaster recovery
|
|
294
|
+
- Ordered membership views with guaranteed connectivity on join
|
|
295
|
+
- Dead silos forced to terminate and restart as new processes
|
|
296
|
+
|
|
297
|
+
### IMembershipTable Implementations
|
|
298
|
+
|
|
299
|
+
| Provider | Package |
|
|
300
|
+
|---|---|
|
|
301
|
+
| Azure Table Storage | `Microsoft.Orleans.Clustering.AzureStorage` |
|
|
302
|
+
| Redis | `Microsoft.Orleans.Clustering.Redis` |
|
|
303
|
+
| ADO.NET (SQL/PostgreSQL/MySQL/Oracle) | `Microsoft.Orleans.Clustering.AdoNet` |
|
|
304
|
+
| Cosmos DB | `Microsoft.Orleans.Clustering.Cosmos` |
|
|
305
|
+
| DynamoDB | `Microsoft.Orleans.Clustering.DynamoDB` |
|
|
306
|
+
| Consul | `Microsoft.Orleans.Clustering.Consul` |
|
|
307
|
+
| ZooKeeper | `Microsoft.Orleans.Clustering.ZooKeeper` |
|
|
308
|
+
| In-Memory (dev only) | built-in |
|
|
309
|
+
|
|
310
|
+
## Streams Implementation
|
|
311
|
+
|
|
312
|
+
### Architecture
|
|
313
|
+
|
|
314
|
+
The streaming runtime uses a **pulling model** with agents inside silos:
|
|
315
|
+
|
|
316
|
+
```mermaid
|
|
317
|
+
flowchart LR
|
|
318
|
+
P["Producers"] -->|"OnNextAsync"| Q["Queue<br/>(Event Hubs / Azure Queue / Memory)"]
|
|
319
|
+
Q -->|"Pull"| PA["Pulling Agent<br/>(per silo)"]
|
|
320
|
+
PA -->|"Deliver"| G["Consumer Grains"]
|
|
321
|
+
PA -->|"Checkpoint"| CP["Checkpoint Store"]
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
- **Pulling agents** run inside each silo, one per queue partition
|
|
325
|
+
- Agents pull batches of events from the underlying queue
|
|
326
|
+
- Events are dispatched to consumer grains via `IAsyncObserver<T>`
|
|
327
|
+
- Checkpoint position tracked in a separate storage provider
|
|
328
|
+
|
|
329
|
+
### Pub-Sub System
|
|
330
|
+
|
|
331
|
+
Stream subscriptions are managed by `PubSubRendezvousGrain`:
|
|
332
|
+
- One rendezvous grain per `StreamId`
|
|
333
|
+
- Stores list of subscribers
|
|
334
|
+
- Persisted via the `"PubSubStore"` storage provider
|
|
335
|
+
- Implicit subscriptions (`[ImplicitStreamSubscription]`) registered automatically
|
|
336
|
+
|
|
337
|
+
### Azure Queue Streams Implementation
|
|
338
|
+
|
|
339
|
+
NuGet: `Microsoft.Orleans.Streaming.AzureStorage`
|
|
340
|
+
|
|
341
|
+
```csharp
|
|
342
|
+
siloBuilder.AddAzureQueueStreams("AQProvider", optionsBuilder =>
|
|
343
|
+
optionsBuilder.ConfigureAzureQueue(options =>
|
|
344
|
+
options.Configure(opt =>
|
|
345
|
+
{
|
|
346
|
+
opt.QueueServiceClient = new QueueServiceClient(endpoint, credential);
|
|
347
|
+
opt.QueueNames = new List<string> { "queue1", "queue2" }; // optional
|
|
348
|
+
})));
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Behavior:
|
|
352
|
+
- Uses Azure Storage Queues as the backing store
|
|
353
|
+
- Pulling agents poll queues at configurable intervals
|
|
354
|
+
- **Not rewindable** — cannot replay from arbitrary position
|
|
355
|
+
- Does **not** guarantee FIFO on failures (poison messages re-queued)
|
|
356
|
+
- Multiple queues for parallelism
|
|
357
|
+
- Automatic queue assignment to silo agents via consistent hashing
|
|
358
|
+
|
|
359
|
+
### Event Hubs vs Azure Queue
|
|
360
|
+
|
|
361
|
+
| Feature | Event Hubs | Azure Queue |
|
|
362
|
+
|---|---|---|
|
|
363
|
+
| Rewindable | Yes (replay from token) | No |
|
|
364
|
+
| FIFO guarantee | Per partition | Not on failures |
|
|
365
|
+
| Throughput | High (millions/sec) | Moderate |
|
|
366
|
+
| Cost | Higher | Lower |
|
|
367
|
+
| Checkpointing | Yes (via checkpoint store) | Via queue dequeue |
|
|
368
|
+
| Use case | High-volume event streaming | Simple message queuing |
|
|
369
|
+
|
|
370
|
+
## Load Balancing
|
|
371
|
+
|
|
372
|
+
Orleans uses multiple mechanisms to distribute load across the cluster:
|
|
373
|
+
|
|
374
|
+
### Placement-Based Balancing
|
|
375
|
+
|
|
376
|
+
**Cratis qualification:** Resource-optimized placement is the default from Orleans 9.2; earlier versions used random placement.
|
|
377
|
+
|
|
378
|
+
The placement strategy determines where new activations are created:
|
|
379
|
+
|
|
380
|
+
| Strategy | Load Distribution |
|
|
381
|
+
|---|---|
|
|
382
|
+
| `ResourceOptimizedPlacement` (default 9.2+) | Weighted scoring: CPU (40), memory (20), available memory (20), max memory (5), activation count (15) |
|
|
383
|
+
| `ActivationCountBasedPlacement` | Power of Two Choices — pick two random silos, place on the one with fewer activations |
|
|
384
|
+
| `RandomPlacement` | Uniform random across compatible silos |
|
|
385
|
+
| `PreferLocalPlacement` | Local first, then random |
|
|
386
|
+
|
|
387
|
+
### Activation Repartitioning (Experimental)
|
|
388
|
+
|
|
389
|
+
Monitors grain-to-grain communication patterns and migrates grains closer to frequent communication partners.
|
|
390
|
+
|
|
391
|
+
```csharp
|
|
392
|
+
#pragma warning disable ORLEANSEXP001
|
|
393
|
+
siloBuilder.AddActivationRepartitioner();
|
|
394
|
+
#pragma warning restore ORLEANSEXP001
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Uses probabilistic tracking (sampling) and anchoring filters to limit migration churn.
|
|
398
|
+
|
|
399
|
+
### Activation Rebalancing (Experimental, Orleans 10)
|
|
400
|
+
|
|
401
|
+
Cluster-wide redistribution for memory and activation count balance.
|
|
402
|
+
|
|
403
|
+
```csharp
|
|
404
|
+
#pragma warning disable ORLEANSEXP002
|
|
405
|
+
siloBuilder.AddActivationRebalancer();
|
|
406
|
+
#pragma warning restore ORLEANSEXP002
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Uses entropy calculations to detect imbalance and session-based execution to coordinate rebalancing.
|
|
410
|
+
|
|
411
|
+
### Memory-Based Activation Shedding (Orleans 9+)
|
|
412
|
+
|
|
413
|
+
Auto-deactivates least-recently-used grains when memory exceeds threshold:
|
|
414
|
+
|
|
415
|
+
```csharp
|
|
416
|
+
services.Configure<GrainCollectionOptions>(options =>
|
|
417
|
+
{
|
|
418
|
+
options.EnableActivationSheddingOnMemoryPressure = true;
|
|
419
|
+
options.MemoryUsageLimitPercentage = 80; // start shedding
|
|
420
|
+
options.MemoryUsageTargetPercentage = 75; // stop shedding
|
|
421
|
+
options.MemoryUsagePollingPeriod = TimeSpan.FromSeconds(5);
|
|
422
|
+
});
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## Unit Testing
|
|
426
|
+
|
|
427
|
+
### InProcessTestCluster (Orleans 9+, Recommended)
|
|
428
|
+
|
|
429
|
+
```csharp
|
|
430
|
+
// Setup
|
|
431
|
+
var builder = new InProcessTestClusterBuilder();
|
|
432
|
+
builder.ConfigureSilo((options, siloBuilder) =>
|
|
433
|
+
{
|
|
434
|
+
siloBuilder.AddMemoryGrainStorage("Default");
|
|
435
|
+
siloBuilder.AddMemoryGrainStorage("PubSubStore");
|
|
436
|
+
siloBuilder.UseInMemoryReminderService();
|
|
437
|
+
});
|
|
438
|
+
var cluster = builder.Build();
|
|
439
|
+
await cluster.DeployAsync();
|
|
440
|
+
|
|
441
|
+
// Test
|
|
442
|
+
var grain = cluster.Client.GetGrain<IPlayerGrain>("player-1");
|
|
443
|
+
await grain.UpdateScore(100);
|
|
444
|
+
var state = await grain.GetState();
|
|
445
|
+
Assert.Equal(100, state.Score);
|
|
446
|
+
|
|
447
|
+
// Cleanup
|
|
448
|
+
await cluster.DisposeAsync();
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Options: `InitialSilosCount` (default 1), `InitializeClientOnDeploy` (default true), `ConfigureFileLogging` (default true), `GatewayPerSilo` (default true).
|
|
452
|
+
|
|
453
|
+
### Dynamic Silo Management
|
|
454
|
+
|
|
455
|
+
```csharp
|
|
456
|
+
// Add silo to running cluster
|
|
457
|
+
var newSilo = await cluster.StartSiloAsync();
|
|
458
|
+
|
|
459
|
+
// Stop specific silo (simulate failure)
|
|
460
|
+
await cluster.StopSiloAsync(newSilo);
|
|
461
|
+
|
|
462
|
+
// Restart entire cluster
|
|
463
|
+
await cluster.RestartAsync();
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### TestCluster (Legacy, Still Supported)
|
|
467
|
+
|
|
468
|
+
```csharp
|
|
469
|
+
var builder = new TestClusterBuilder();
|
|
470
|
+
builder.AddSiloBuilderConfigurator<TestSiloConfigurator>();
|
|
471
|
+
var cluster = builder.Build();
|
|
472
|
+
cluster.Deploy(); // synchronous
|
|
473
|
+
|
|
474
|
+
public class TestSiloConfigurator : ISiloConfigurator
|
|
475
|
+
{
|
|
476
|
+
public void Configure(ISiloBuilder siloBuilder)
|
|
477
|
+
{
|
|
478
|
+
siloBuilder.AddMemoryGrainStorage("Default");
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Multi-Silo Testing
|
|
484
|
+
|
|
485
|
+
```csharp
|
|
486
|
+
var builder = new InProcessTestClusterBuilder();
|
|
487
|
+
builder.InitialSilosCount = 3; // start with 3 silos
|
|
488
|
+
|
|
489
|
+
// Test placement, failover, reminders, etc.
|
|
490
|
+
var grain = cluster.Client.GetGrain<IMyGrain>("key");
|
|
491
|
+
await grain.DoWork();
|
|
492
|
+
|
|
493
|
+
// Kill a silo and verify grain reactivates
|
|
494
|
+
var siloToKill = cluster.Silos[1];
|
|
495
|
+
await cluster.StopSiloAsync(siloToKill);
|
|
496
|
+
|
|
497
|
+
// Grain auto-reactivates on next call
|
|
498
|
+
var result = await grain.DoWork(); // succeeds on different silo
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### xUnit Fixture Sharing
|
|
502
|
+
|
|
503
|
+
```csharp
|
|
504
|
+
public class ClusterFixture : IAsyncLifetime
|
|
505
|
+
{
|
|
506
|
+
public InProcessTestCluster Cluster { get; private set; } = null!;
|
|
507
|
+
|
|
508
|
+
public async Task InitializeAsync()
|
|
509
|
+
{
|
|
510
|
+
var builder = new InProcessTestClusterBuilder();
|
|
511
|
+
builder.ConfigureSilo((_, silo) => silo.AddMemoryGrainStorage("Default"));
|
|
512
|
+
Cluster = builder.Build();
|
|
513
|
+
await Cluster.DeployAsync();
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
public async Task DisposeAsync() => await Cluster.DisposeAsync();
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
[CollectionDefinition("Orleans")]
|
|
520
|
+
public class ClusterCollection : ICollectionFixture<ClusterFixture> { }
|
|
521
|
+
|
|
522
|
+
[Collection("Orleans")]
|
|
523
|
+
public class MyGrainTests
|
|
524
|
+
{
|
|
525
|
+
private readonly InProcessTestCluster _cluster;
|
|
526
|
+
public MyGrainTests(ClusterFixture fixture) => _cluster = fixture.Cluster;
|
|
527
|
+
|
|
528
|
+
[Fact]
|
|
529
|
+
public async Task TestGrainBehavior()
|
|
530
|
+
{
|
|
531
|
+
var grain = _cluster.Client.GetGrain<IMyGrain>("test");
|
|
532
|
+
var result = await grain.DoWork();
|
|
533
|
+
Assert.NotNull(result);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### Mocking Approach
|
|
539
|
+
|
|
540
|
+
```csharp
|
|
541
|
+
// Override GrainFactory for mocking
|
|
542
|
+
public class TestableOrderGrain : OrderGrain
|
|
543
|
+
{
|
|
544
|
+
public new virtual IGrainFactory GrainFactory { get; set; }
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// With Moq
|
|
548
|
+
var mockInventory = new Mock<IInventoryGrain>();
|
|
549
|
+
mockInventory.Setup(i => i.Reserve(It.IsAny<int>())).Returns(Task.CompletedTask);
|
|
550
|
+
|
|
551
|
+
var mockFactory = new Mock<IGrainFactory>();
|
|
552
|
+
mockFactory.Setup(f => f.GetGrain<IInventoryGrain>(It.IsAny<string>(), null))
|
|
553
|
+
.Returns(mockInventory.Object);
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
Alternative: `OrleansTestKit` from OrleansContrib provides unit-test-friendly grain activation with less ceremony.
|
|
557
|
+
|
|
558
|
+
## Tutorials, Samples, and Resource Pages
|
|
559
|
+
|
|
560
|
+
| Need | Official Source |
|
|
561
|
+
|---|---|
|
|
562
|
+
| Browse tutorials and samples | [Code samples overview](https://learn.microsoft.com/dotnet/orleans/tutorials-and-samples/) |
|
|
563
|
+
| Hello World tutorial | [Hello World](https://learn.microsoft.com/dotnet/orleans/tutorials-and-samples/overview-helloworld) |
|
|
564
|
+
| Orleans basics tutorial | [Tutorial 1](https://learn.microsoft.com/dotnet/orleans/tutorials-and-samples/tutorial-1) |
|
|
565
|
+
| Adventure game sample | [Adventure](https://learn.microsoft.com/dotnet/orleans/tutorials-and-samples/adventure) |
|
|
566
|
+
| Custom grain storage | [Custom storage sample](https://learn.microsoft.com/dotnet/orleans/tutorials-and-samples/custom-grain-storage) |
|
|
567
|
+
| Design principles | [Architecture principles](https://learn.microsoft.com/dotnet/orleans/resources/orleans-architecture-principles-and-approach) |
|
|
568
|
+
| When Orleans fits | [Applicability](https://learn.microsoft.com/dotnet/orleans/resources/orleans-thinking-big-and-small) |
|
|
569
|
+
| NuGet package map | [NuGet packages](https://learn.microsoft.com/dotnet/orleans/resources/nuget-packages) |
|
|
570
|
+
| Best practices | [Best practices](https://learn.microsoft.com/dotnet/orleans/resources/best-practices) |
|
|
571
|
+
| FAQ | [FAQ](https://learn.microsoft.com/dotnet/orleans/resources/frequently-asked-questions) |
|
|
572
|
+
| External links | [Links](https://learn.microsoft.com/dotnet/orleans/resources/links) |
|
|
573
|
+
|
|
574
|
+
## API Reference and Source Entry Points
|
|
575
|
+
|
|
576
|
+
| Need | Official Source |
|
|
577
|
+
|---|---|
|
|
578
|
+
| Core API | [Orleans.Core](https://learn.microsoft.com/dotnet/api/orleans.core) |
|
|
579
|
+
| Runtime API | [Orleans.Runtime](https://learn.microsoft.com/dotnet/api/orleans.runtime) |
|
|
580
|
+
| Streams API | [Orleans.Streams](https://learn.microsoft.com/dotnet/api/orleans.streams) |
|
|
581
|
+
| Source repo | [dotnet/orleans](https://github.com/dotnet/orleans) |
|
|
582
|
+
| Official samples | [dotnet/samples Orleans](https://github.com/dotnet/samples/tree/main/orleans) |
|
|
583
|
+
| Repo samples README | [Samples README](https://github.com/dotnet/orleans/blob/main/samples/README.md) |
|
|
584
|
+
|
|
585
|
+
## Usage Guidance
|
|
586
|
+
|
|
587
|
+
- Start here when the problem depends on scheduler rules, runtime delivery guarantees, stream internals, cluster management, or test-cluster behavior.
|
|
588
|
+
- Use grain-api.md for grain-level API details (reentrancy, timers, placement).
|
|
589
|
+
- Use configuration-api.md for operational setup (deployment, observability, providers).
|
|
590
|
+
- Use examples.md for example-first navigation.
|
|
591
|
+
- Use official-docs-index.md for the full documentation tree.
|