@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.
Files changed (168) hide show
  1. package/package/corpus/harnesses/copilot/instructions/github-actions.instructions.md +17 -0
  2. package/package/corpus/harnesses/copilot/instructions/profiles.instructions.md +10 -1
  3. package/package/corpus/harnesses/copilot/instructions/pull-requests.instructions.md +2 -0
  4. package/package/corpus/harnesses/copilot/instructions/secrets.instructions.md +4 -0
  5. package/package/corpus/hooks/README.md +11 -1
  6. package/package/corpus/hooks/scripts/cratis-check-pr.mjs +12 -1
  7. package/package/corpus/hooks/scripts/cratis-guard-secrets.sh +79 -23
  8. package/package/corpus/hooks/scripts/cratis-guard-writes.sh +145 -40
  9. package/package/corpus/rules/github-actions.md +17 -0
  10. package/package/corpus/rules/profiles.md +10 -1
  11. package/package/corpus/rules/pull-requests.md +2 -0
  12. package/package/corpus/rules/secrets.md +4 -0
  13. package/package/corpus/skills/analyzing-dotnet-performance/LICENSE +23 -0
  14. package/package/corpus/skills/analyzing-dotnet-performance/NOTICE +36 -0
  15. package/package/corpus/skills/analyzing-dotnet-performance/SKILL.md +214 -0
  16. package/package/corpus/skills/analyzing-dotnet-performance/references/async-patterns.md +122 -0
  17. package/package/corpus/skills/analyzing-dotnet-performance/references/collections-and-linq.md +220 -0
  18. package/package/corpus/skills/analyzing-dotnet-performance/references/critical-patterns.md +291 -0
  19. package/package/corpus/skills/analyzing-dotnet-performance/references/io-and-serialization.md +135 -0
  20. package/package/corpus/skills/analyzing-dotnet-performance/references/memory-and-strings.md +219 -0
  21. package/package/corpus/skills/analyzing-dotnet-performance/references/regex-patterns.md +95 -0
  22. package/package/corpus/skills/analyzing-dotnet-performance/references/structural-patterns.md +38 -0
  23. package/package/corpus/skills/binlog-generation/LICENSE +23 -0
  24. package/package/corpus/skills/binlog-generation/NOTICE +22 -0
  25. package/package/corpus/skills/binlog-generation/SKILL.md +129 -0
  26. package/package/corpus/skills/check-bin-obj-clash/LICENSE +23 -0
  27. package/package/corpus/skills/check-bin-obj-clash/NOTICE +27 -0
  28. package/package/corpus/skills/check-bin-obj-clash/SKILL.md +127 -0
  29. package/package/corpus/skills/check-bin-obj-clash/references/common-causes-and-fixes.md +206 -0
  30. package/package/corpus/skills/coverage-analysis/LICENSE +23 -0
  31. package/package/corpus/skills/coverage-analysis/NOTICE +32 -0
  32. package/package/corpus/skills/coverage-analysis/SKILL.md +198 -0
  33. package/package/corpus/skills/coverage-analysis/references/guidelines.md +68 -0
  34. package/package/corpus/skills/coverage-analysis/references/output-format.md +91 -0
  35. package/package/corpus/skills/coverage-analysis/references/report-generation.md +58 -0
  36. package/package/corpus/skills/coverage-analysis/references/setup-discovery.md +307 -0
  37. package/package/corpus/skills/coverage-analysis/scripts/Compute-CrapScores.ps1 +80 -0
  38. package/package/corpus/skills/coverage-analysis/scripts/CoverageData.ps1 +272 -0
  39. package/package/corpus/skills/coverage-analysis/scripts/Extract-MethodCoverage.ps1 +65 -0
  40. package/package/corpus/skills/cratis-ci-pipeline-performance/LICENSE +2 -0
  41. package/package/corpus/skills/cratis-ci-pipeline-performance/SKILL.md +201 -0
  42. package/package/corpus/skills/cratis-ci-pipeline-performance/references/build-once-and-shard.md +158 -0
  43. package/package/corpus/skills/cratis-ci-pipeline-performance/references/caching.md +86 -0
  44. package/package/corpus/skills/cratis-ci-pipeline-performance/references/flaky-and-hung-jobs.md +70 -0
  45. package/package/corpus/skills/cratis-ci-pipeline-performance/references/publish-pipelines.md +67 -0
  46. package/package/corpus/skills/cratis-ci-pipeline-performance/references/required-checks-and-path-filters.md +117 -0
  47. package/package/corpus/skills/cratis-ci-pipeline-performance/references/runner-sizing-and-images.md +65 -0
  48. package/package/corpus/skills/cratis-ci-pipeline-performance/references/security-and-pinning.md +73 -0
  49. package/package/corpus/skills/cratis-ci-pipeline-performance/references/sources.md +33 -0
  50. package/package/corpus/skills/cratis-ci-pipeline-performance/references/test-setup-cost.md +82 -0
  51. package/package/corpus/skills/cratis-ci-pipeline-performance/scripts/ci-timings.sh +356 -0
  52. package/package/corpus/skills/cratis-ci-pipeline-performance/scripts/fixtures/jobs/1003.json +79 -0
  53. package/package/corpus/skills/cratis-ci-pipeline-performance/scripts/fixtures/jobs/1007.json +79 -0
  54. package/package/corpus/skills/cratis-ci-pipeline-performance/scripts/fixtures/jobs/1010.json +79 -0
  55. package/package/corpus/skills/cratis-ci-pipeline-performance/scripts/fixtures/runs.json +102 -0
  56. package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/LICENSE +2 -0
  57. package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/SKILL.md +244 -0
  58. package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/docker-images.md +86 -0
  59. package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/dotnet-nuget.md +87 -0
  60. package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/go-jvm-python-elixir.md +83 -0
  61. package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/npm-yarn.md +78 -0
  62. package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/other-environments.md +60 -0
  63. package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/references/worked-examples.md +97 -0
  64. package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/scripts/cratis-local-packages.sh +778 -0
  65. package/package/corpus/skills/cratis-engineering-local-cross-repo-verification/scripts/npm-restore-check.cjs +109 -0
  66. package/package/corpus/skills/csharp-refactoring/LICENSE +23 -0
  67. package/package/corpus/skills/csharp-refactoring/NOTICE +22 -0
  68. package/package/corpus/skills/csharp-refactoring/SKILL.md +175 -0
  69. package/package/corpus/skills/csharp-refactoring/references/operation-catalog.md +37 -0
  70. package/package/corpus/skills/directory-build-organization/LICENSE +23 -0
  71. package/package/corpus/skills/directory-build-organization/NOTICE +21 -0
  72. package/package/corpus/skills/directory-build-organization/SKILL.md +205 -0
  73. package/package/corpus/skills/directory-build-organization/references/common-patterns.md +56 -0
  74. package/package/corpus/skills/directory-build-organization/references/multi-level-examples.md +167 -0
  75. package/package/corpus/skills/directory-build-organization/references/targetframework-props-pitfall.md +54 -0
  76. package/package/corpus/skills/dotnet-aot-compat/LICENSE +23 -0
  77. package/package/corpus/skills/dotnet-aot-compat/NOTICE +18 -0
  78. package/package/corpus/skills/dotnet-aot-compat/SKILL.md +265 -0
  79. package/package/corpus/skills/dotnet-aot-compat/references/polyfills.md +43 -0
  80. package/package/corpus/skills/dotnet-trace-collect/LICENSE +23 -0
  81. package/package/corpus/skills/dotnet-trace-collect/NOTICE +65 -0
  82. package/package/corpus/skills/dotnet-trace-collect/SKILL.md +116 -0
  83. package/package/corpus/skills/dotnet-trace-collect/references/dotnet-monitor.md +97 -0
  84. package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect-linux.md +84 -0
  85. package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect.md +91 -0
  86. package/package/corpus/skills/dotnet-trace-collect/references/perfcollect.md +200 -0
  87. package/package/corpus/skills/dotnet-trace-collect/references/perfview.md +141 -0
  88. package/package/corpus/skills/dotnet-trace-collect/references/tool-selection.md +160 -0
  89. package/package/corpus/skills/dump-collect/LICENSE +23 -0
  90. package/package/corpus/skills/dump-collect/NOTICE +40 -0
  91. package/package/corpus/skills/dump-collect/SKILL.md +97 -0
  92. package/package/corpus/skills/dump-collect/references/container-dumps.md +412 -0
  93. package/package/corpus/skills/dump-collect/references/coreclr-dumps.md +108 -0
  94. package/package/corpus/skills/dump-collect/references/nativeaot-dumps.md +280 -0
  95. package/package/corpus/skills/eliminate-redundant-restores/LICENSE +23 -0
  96. package/package/corpus/skills/eliminate-redundant-restores/NOTICE +19 -0
  97. package/package/corpus/skills/eliminate-redundant-restores/SKILL.md +112 -0
  98. package/package/corpus/skills/extension-points/LICENSE +23 -0
  99. package/package/corpus/skills/extension-points/NOTICE +18 -0
  100. package/package/corpus/skills/extension-points/SKILL.md +248 -0
  101. package/package/corpus/skills/filter-syntax/LICENSE +23 -0
  102. package/package/corpus/skills/filter-syntax/NOTICE +17 -0
  103. package/package/corpus/skills/filter-syntax/SKILL.md +179 -0
  104. package/package/corpus/skills/including-generated-files/LICENSE +23 -0
  105. package/package/corpus/skills/including-generated-files/NOTICE +17 -0
  106. package/package/corpus/skills/including-generated-files/SKILL.md +139 -0
  107. package/package/corpus/skills/microbenchmarking/LICENSE +23 -0
  108. package/package/corpus/skills/microbenchmarking/NOTICE +36 -0
  109. package/package/corpus/skills/microbenchmarking/SKILL.md +131 -0
  110. package/package/corpus/skills/microbenchmarking/references/bdn-internals-and-tuning.md +143 -0
  111. package/package/corpus/skills/microbenchmarking/references/comparison-strategies.md +200 -0
  112. package/package/corpus/skills/microbenchmarking/references/diagnosers-and-exporters.md +150 -0
  113. package/package/corpus/skills/microbenchmarking/references/project-setup-and-running.md +189 -0
  114. package/package/corpus/skills/microbenchmarking/references/writing-benchmarks.md +310 -0
  115. package/package/corpus/skills/msbuild/LICENSE +23 -0
  116. package/package/corpus/skills/msbuild/NOTICE +55 -0
  117. package/package/corpus/skills/msbuild/SKILL.md +117 -0
  118. package/package/corpus/skills/msbuild/references/antipatterns/additional-antipatterns.md +291 -0
  119. package/package/corpus/skills/msbuild/references/antipatterns/incremental-build-inputs-outputs.md +57 -0
  120. package/package/corpus/skills/msbuild/references/antipatterns/private-assets.md +29 -0
  121. package/package/corpus/skills/msbuild/references/binlog-failure-analysis.md +85 -0
  122. package/package/corpus/skills/msbuild/references/binlog-generation.md +69 -0
  123. package/package/corpus/skills/msbuild/references/build-perf-baseline.md +493 -0
  124. package/package/corpus/skills/msbuild/references/build-perf-diagnostics.md +268 -0
  125. package/package/corpus/skills/msbuild/references/copy-to-output-directory.md +183 -0
  126. package/package/corpus/skills/msbuild/references/eval-performance.md +190 -0
  127. package/package/corpus/skills/msbuild/references/extension-points.md +281 -0
  128. package/package/corpus/skills/msbuild/references/incremental-build.md +365 -0
  129. package/package/corpus/skills/msbuild/references/msbuild-antipatterns.md +401 -0
  130. package/package/corpus/skills/orleans/LICENSE +21 -0
  131. package/package/corpus/skills/orleans/NOTICE +64 -0
  132. package/package/corpus/skills/orleans/SKILL.md +140 -0
  133. package/package/corpus/skills/orleans/references/anti-patterns.md +660 -0
  134. package/package/corpus/skills/orleans/references/configuration-api.md +933 -0
  135. package/package/corpus/skills/orleans/references/examples.md +66 -0
  136. package/package/corpus/skills/orleans/references/grain-api.md +784 -0
  137. package/package/corpus/skills/orleans/references/grains.md +75 -0
  138. package/package/corpus/skills/orleans/references/hosting.md +71 -0
  139. package/package/corpus/skills/orleans/references/implementation.md +591 -0
  140. package/package/corpus/skills/orleans/references/official-docs-index.md +250 -0
  141. package/package/corpus/skills/orleans/references/patterns.md +811 -0
  142. package/package/corpus/skills/orleans/references/persistence-api.md +495 -0
  143. package/package/corpus/skills/orleans/references/primitive-selection.md +35 -0
  144. package/package/corpus/skills/orleans/references/scheduling-and-services.md +295 -0
  145. package/package/corpus/skills/orleans/references/serialization-api.md +299 -0
  146. package/package/corpus/skills/orleans/references/streaming-api.md +396 -0
  147. package/package/corpus/skills/orleans/references/testing-patterns.md +210 -0
  148. package/package/corpus/skills/platform-detection/LICENSE +23 -0
  149. package/package/corpus/skills/platform-detection/NOTICE +17 -0
  150. package/package/corpus/skills/platform-detection/SKILL.md +230 -0
  151. package/package/corpus/skills/platform-detection/references/command-mode.md +27 -0
  152. package/package/corpus/skills/profiling/LICENSE +21 -0
  153. package/package/corpus/skills/profiling/NOTICE +50 -0
  154. package/package/corpus/skills/profiling/SKILL.md +144 -0
  155. package/package/corpus/skills/profiling/references/commands.md +439 -0
  156. package/package/corpus/skills/profiling/references/patterns.md +586 -0
  157. package/package/corpus/skills/profiling/references/profiling.md +190 -0
  158. package/package/corpus/skills/run-tests/LICENSE +23 -0
  159. package/package/corpus/skills/run-tests/NOTICE +19 -0
  160. package/package/corpus/skills/run-tests/SKILL.md +305 -0
  161. package/package/corpus/skills/target-authoring/LICENSE +23 -0
  162. package/package/corpus/skills/target-authoring/NOTICE +17 -0
  163. package/package/corpus/skills/target-authoring/SKILL.md +165 -0
  164. package/package/corpus/skills/vectorization/LICENSE +23 -0
  165. package/package/corpus/skills/vectorization/NOTICE +17 -0
  166. package/package/corpus/skills/vectorization/SKILL.md +189 -0
  167. package/package/profile-catalog.json +87 -1
  168. package/package.json +3 -2
@@ -0,0 +1,295 @@
1
+ # Scheduling and Runtime Services
2
+
3
+ ## Contents
4
+
5
+ - Version Gate
6
+ - Grain Timers
7
+ - Reminders
8
+ - Durable Jobs
9
+ - Stateless Worker Grains
10
+ - Background Services and Hosted Services
11
+ - Startup Tasks and Silo Lifecycle
12
+ - Grain Services
13
+ - Failure-Oriented Tests
14
+ - Official Sources
15
+
16
+ Use this reference after deciding whether work is activation-local, recurring, one-time, per-host, or per-silo. Do not select these APIs only because they all “run in the background.” Their ownership and failure models are different.
17
+
18
+ ## Version Gate
19
+
20
+ Check the target project's Orleans packages before copying an API. This reference targets the current Orleans 10.3 model:
21
+
22
+ - use `RegisterGrainTimer`, not the obsolete `RegisterTimer` API;
23
+ - use standard .NET `BackgroundService` or `IHostedService` for normal host-owned work;
24
+ - treat Orleans startup tasks as a lifecycle-specific compatibility surface;
25
+ - treat `Microsoft.Orleans.DurableJobs` and `Microsoft.Orleans.DurableJobs.AzureStorage` as experimental because the packages ship with a prerelease suffix;
26
+ - use `IJobRunContext`, not the older `IDurableJobContext` name found in stale examples.
27
+
28
+ ## Grain Timers
29
+
30
+ Purpose: run periodic or one-shot work within the current grain activation while preserving Orleans turn scheduling.
31
+
32
+ ```csharp
33
+ private IGrainTimer? _flushTimer;
34
+
35
+ public override Task OnActivateAsync(CancellationToken cancellationToken)
36
+ {
37
+ _flushTimer = this.RegisterGrainTimer(
38
+ static (grain, ct) => grain.FlushAsync(ct),
39
+ this,
40
+ new GrainTimerCreationOptions
41
+ {
42
+ DueTime = TimeSpan.FromSeconds(5),
43
+ Period = TimeSpan.FromSeconds(30),
44
+ Interleave = false,
45
+ KeepAlive = false
46
+ });
47
+
48
+ return base.OnActivateAsync(cancellationToken);
49
+ }
50
+ ```
51
+
52
+ Use a timer for cache refresh, batching, lease renewal, polling, or periodic flushes that matter only while the activation is alive. A timer:
53
+
54
+ - stops when the activation deactivates or its silo fails;
55
+ - does not make its work durable;
56
+ - invokes each callback on a separate grain turn;
57
+ - measures `Period` from callback completion, so callbacks do not overlap;
58
+ - does not keep an idle activation alive unless `KeepAlive` is `true`;
59
+ - does not interleave by default with the modern API;
60
+ - can be changed through `IGrainTimer.Change` and canceled by disposal.
61
+
62
+ Do not persist a timer handle. Recreate activation-local timers during activation. If a future occurrence must survive activation loss, select a reminder or Durable Job.
63
+
64
+ ## Reminders
65
+
66
+ Purpose: keep a recurring schedule definition for a grain identity and reactivate that grain when a tick is due.
67
+
68
+ ```csharp
69
+ public sealed class SubscriptionGrain : Grain, ISubscriptionGrain, IRemindable
70
+ {
71
+ public async Task StartRenewalChecksAsync()
72
+ {
73
+ await RegisterOrUpdateReminder(
74
+ "renewal-check",
75
+ dueTime: TimeSpan.FromMinutes(5),
76
+ period: TimeSpan.FromHours(12));
77
+ }
78
+
79
+ public Task ReceiveReminder(string reminderName, TickStatus status) =>
80
+ reminderName switch
81
+ {
82
+ "renewal-check" => CheckRenewalAsync(),
83
+ _ => Task.CompletedTask
84
+ };
85
+ }
86
+ ```
87
+
88
+ Use reminders for recurring low-frequency work such as subscription review, stale-state reconciliation, daily rollups, or periodic maintenance. A reminder:
89
+
90
+ - belongs to the logical grain, not one activation;
91
+ - persists its definition in a reminder provider;
92
+ - reactivates the grain when necessary;
93
+ - continues across partial/full cluster restarts unless unregistered;
94
+ - delivers through normal grain messaging and scheduling;
95
+ - is explicitly unregistered rather than disposed.
96
+
97
+ The reminder service does not persist every scheduled occurrence. If the cluster is unavailable when a tick is due, that tick is missed and only a later tick is delivered. Therefore, make the handler derive required work from durable timestamps/state when catch-up matters. Do not assume “one callback per calendar occurrence.”
98
+
99
+ Use periods measured in minutes, hours, or days. Use a grain timer for frequent activation-local ticks. Use a Durable Job for one future occurrence.
100
+
101
+ Configure a persistent reminder provider in production. In-memory reminders are for development/testing and disappear on restart.
102
+
103
+ ## Durable Jobs
104
+
105
+ Purpose: schedule persistent one-time work to a target grain for execution around a future due time.
106
+
107
+ Install the experimental packages on the same Orleans line as the rest of the app:
108
+
109
+ ```bash
110
+ dotnet add package Microsoft.Orleans.DurableJobs --version 10.3.1-alpha.1
111
+ dotnet add package Microsoft.Orleans.DurableJobs.AzureStorage --version 10.3.1-alpha.1
112
+ ```
113
+
114
+ Configure volatile in-memory jobs only for development:
115
+
116
+ ```csharp
117
+ builder.UseOrleans(siloBuilder =>
118
+ siloBuilder
119
+ .UseLocalhostClustering()
120
+ .UseInMemoryDurableJobs());
121
+ ```
122
+
123
+ Configure Azure Blob-backed jobs for durable storage:
124
+
125
+ ```csharp
126
+ builder.UseOrleans(siloBuilder =>
127
+ siloBuilder.UseAzureBlobDurableJobs(options =>
128
+ {
129
+ options.BlobServiceClient = blobServiceClient;
130
+ options.ContainerName = "durable-jobs";
131
+ }));
132
+ ```
133
+
134
+ Target a grain that implements `IDurableJobHandler`:
135
+
136
+ ```csharp
137
+ public sealed class OrderGrain(
138
+ ILocalDurableJobManager jobs) : Grain, IOrderGrain, IDurableJobHandler
139
+ {
140
+ public Task<DurableJob> ScheduleExpiryAsync(
141
+ DateTimeOffset dueTime,
142
+ CancellationToken cancellationToken) =>
143
+ jobs.ScheduleJobAsync(
144
+ new ScheduleJobRequest
145
+ {
146
+ Target = this.GetGrainId(),
147
+ JobName = "expire-order",
148
+ DueTime = dueTime,
149
+ Metadata = new Dictionary<string, string>
150
+ {
151
+ ["OrderId"] = this.GetPrimaryKeyString()
152
+ }
153
+ },
154
+ cancellationToken);
155
+
156
+ public Task ExecuteJobAsync(
157
+ IJobRunContext context,
158
+ CancellationToken cancellationToken) =>
159
+ context.Job.Name == "expire-order"
160
+ ? ExpireIfStillPendingAsync(context.Job.Id, cancellationToken)
161
+ : Task.CompletedTask;
162
+ }
163
+ ```
164
+
165
+ Use Durable Jobs for delayed notifications, expirations, appointment callbacks, and one-time workflow steps. The system partitions jobs into time-based shards, distributes ownership across silos, reassigns work after failure, and applies the configured retry policy.
166
+
167
+ Design for these semantics:
168
+
169
+ - execution is at-least-once, so a handler can run again;
170
+ - `DueTime` is scheduling intent, not a real-time deadline;
171
+ - keep metadata small and store identifiers rather than full domain objects;
172
+ - make effects idempotent using the job ID or a domain idempotency key;
173
+ - respect the cancellation token;
174
+ - cancellation can race with dispatch, so cancellation success/failure must be handled explicitly;
175
+ - configure `DurableJobsOptions.MaxConcurrentJobsPerSilo` to protect downstream resources;
176
+ - configure `ShouldRetry` to distinguish transient and permanent failures;
177
+ - monitor dispatch lag, dequeue count/retries, handler latency, shard ownership, and journal storage failures.
178
+
179
+ Current 10.3 Durable Jobs store/discover shards through `Orleans.Journaling`; custom job storage must support the journal catalog. Pin all Orleans package versions together and test upgrade/recovery before production adoption.
180
+
181
+ If alpha dependencies are unacceptable, use a stable external scheduler/workflow engine or model a bounded one-time workflow with durable grain state plus a stable trigger, making the tradeoff explicit.
182
+
183
+ ## Stateless Worker Grains
184
+
185
+ Purpose: expose a grain-shaped, auto-scaled pool for interchangeable stateless work.
186
+
187
+ ```csharp
188
+ [StatelessWorker(maxLocalWorkers: 4)]
189
+ public sealed class PayloadDecoderGrain : Grain, IPayloadDecoderGrain
190
+ {
191
+ public ValueTask<DecodedPayload> DecodeAsync(byte[] payload) =>
192
+ ValueTask.FromResult(Decode(payload));
193
+ }
194
+ ```
195
+
196
+ Use stateless workers for decompression, parsing, routing, CPU-bound transforms, local caches, or reduce-style pre-aggregation. Orleans prefers a local idle activation and creates more activations up to the per-silo limit.
197
+
198
+ Do not interpret “worker” as “job.” There is no durable queue, due time, retry record, or stable activation identity. Multiple activations can hold local state, but those copies are not coordinated. Never put one authoritative mutable value in a stateless worker.
199
+
200
+ ## Background Services and Hosted Services
201
+
202
+ Purpose: run standard .NET host-owned work for the lifetime of each process.
203
+
204
+ ```csharp
205
+ builder.UseOrleans(siloBuilder =>
206
+ {
207
+ // Configure the silo before the hosted service.
208
+ });
209
+ builder.Services.AddHostedService<ExternalFeedPump>();
210
+ ```
211
+
212
+ Use `BackgroundService` for a continuous loop such as consuming an external broker/feed and routing events into grains. Use `IHostedService` for simpler bounded startup/shutdown work. Register after `UseOrleans` when the service needs the local Orleans runtime to be started first.
213
+
214
+ Every application replica starts its own hosted service. Scaling from one to five silos creates five loops. Use one of these explicit models:
215
+
216
+ - duplicates are acceptable and work is naturally partitioned by the external source;
217
+ - the external broker uses consumer groups/leases;
218
+ - each hosted service owns only its local silo work;
219
+ - a well-known grain serializes one logical cluster-wide operation;
220
+ - an external leader/lease selects one process.
221
+
222
+ Never assume `AddHostedService` creates a cluster singleton.
223
+
224
+ ## Startup Tasks and Silo Lifecycle
225
+
226
+ Purpose: initialize or validate an Orleans component at a defined host/silo stage.
227
+
228
+ Prefer standard `BackgroundService` or `IHostedService` for application work. Use `AddStartupTask` only when the task genuinely belongs to Orleans silo lifecycle ordering:
229
+
230
+ ```csharp
231
+ siloBuilder.AddStartupTask(
232
+ async (services, cancellationToken) =>
233
+ {
234
+ var validator = services.GetRequiredService<RuntimeDependencyValidator>();
235
+ await validator.ValidateAsync(cancellationToken);
236
+ },
237
+ ServiceLifecycleStage.ApplicationServices);
238
+ ```
239
+
240
+ An exception from a startup task is logged and stops silo startup. Use that fail-fast behavior for deterministic configuration/provider validation, not transient remote work that should retry forever and keep the host unavailable.
241
+
242
+ Implement `ILifecycleParticipant<ISiloLifecycle>` when a provider or runtime component needs explicit ordered start/stop callbacks. Choose the narrowest lifecycle stage and avoid business data migrations on every silo unless the migration system itself provides safe distributed coordination.
243
+
244
+ ## Grain Services
245
+
246
+ Purpose: provide a remotely accessible service instance on every silo and partition responsibility for grains across those instances.
247
+
248
+ A `GrainService`:
249
+
250
+ - starts before the silo finishes initialization and lives until silo shutdown;
251
+ - has no stable domain identity;
252
+ - runs on every silo;
253
+ - can service a subset of grains through `GrainServiceClient<T>` routing;
254
+ - can call grains through `IGrainFactory`;
255
+ - is used by Orleans infrastructure such as reminders.
256
+
257
+ Choose a grain service only for cluster/runtime support that must be co-located with silos and partitioned across them. For ordinary domain behavior, use a normal grain. For a per-process external loop, use `BackgroundService`. For one future invocation, use a Durable Job. For a single cluster-wide owner, use a well-known normal grain or another leader mechanism.
258
+
259
+ Register both the service and its client:
260
+
261
+ ```csharp
262
+ builder.UseOrleans(siloBuilder =>
263
+ {
264
+ siloBuilder.Services
265
+ .AddGrainService<DataService>()
266
+ .AddSingleton<IDataServiceClient, DataServiceClient>();
267
+ });
268
+ ```
269
+
270
+ ## Failure-Oriented Tests
271
+
272
+ Test the guarantee selected by the architecture:
273
+
274
+ - timer: deactivate the grain and prove that the timer is not treated as durable;
275
+ - reminder: deactivate/reactivate, restart silos, and prove recurring behavior with a persistent provider;
276
+ - reminder catch-up: keep the cluster down across a due time and prove the application handles the missed tick;
277
+ - Durable Job: restart the owning silo, force handler failure/retry, and prove idempotent effects;
278
+ - cancellation: race cancellation with job dispatch and assert both outcomes are safe;
279
+ - stateless worker: saturate workers and prove scaling without relying on activation identity;
280
+ - hosted service: start multiple silos and prove duplicate loops are either prevented or safe;
281
+ - grain service: add/remove silos and prove responsibility is re-established;
282
+ - startup task: inject a deterministic failure and prove the silo remains unavailable with actionable logs.
283
+
284
+ Use in-memory providers for fast behavioral tests only. Use the real production provider for recovery, persistence, ordering, lease/ownership, and failover claims.
285
+
286
+ ## Official Sources
287
+
288
+ - [Timers and reminders](https://learn.microsoft.com/dotnet/orleans/grains/timers-and-reminders)
289
+ - [Stateless worker grains](https://learn.microsoft.com/dotnet/orleans/grains/stateless-worker-grains)
290
+ - [Background services and startup tasks](https://learn.microsoft.com/dotnet/orleans/host/configuration-guide/startup-tasks)
291
+ - [Silo lifecycle](https://learn.microsoft.com/dotnet/orleans/host/silo-lifecycle)
292
+ - [Grain services](https://learn.microsoft.com/dotnet/orleans/grains/grainservices)
293
+ - [Durable Jobs package README](https://github.com/dotnet/orleans/blob/main/src/Orleans.DurableJobs/README.md)
294
+ - [Azure Storage Durable Jobs README](https://github.com/dotnet/orleans/blob/main/src/Azure/Orleans.DurableJobs.AzureStorage/README.md)
295
+ - [Durable Jobs public API](https://github.com/dotnet/orleans/blob/main/src/api/Orleans.DurableJobs/Orleans.DurableJobs.cs)
@@ -0,0 +1,299 @@
1
+ # Serialization API Reference
2
+
3
+ ## Contents
4
+
5
+ - Core Attributes
6
+ - Versioning Rules
7
+ - Surrogates
8
+ - Copier
9
+ - Grain Storage Serialization
10
+ - Serialization of Immutable Types
11
+ - Configure Serialization
12
+ - Customize Serialization
13
+ - Common Serialization Mistakes
14
+
15
+ Detailed serialization patterns from official Orleans documentation. Orleans uses two kinds of serialization: grain call serialization (between grains/clients) and grain storage serialization (persistence).
16
+
17
+ ## Core Attributes
18
+
19
+ ### `[GenerateSerializer]`
20
+
21
+ Required on all types passed between grains, stored in state, or used in streams.
22
+
23
+ ```csharp
24
+ [GenerateSerializer]
25
+ public class PlayerState
26
+ {
27
+ [Id(0)] public string Name { get; set; } = "";
28
+ [Id(1)] public int Level { get; set; }
29
+ [Id(2)] public List<string> Achievements { get; set; } = [];
30
+ }
31
+ ```
32
+
33
+ ### `[Id(N)]`
34
+
35
+ Stable member identification. Rules:
36
+ - Each serialized member needs a unique `[Id(N)]`
37
+ - Adding new `[Id]` members is safe (backward compatible)
38
+ - Removing members is safe if the `[Id]` is not reused
39
+ - Changing member types is a **breaking change**
40
+ - IDs are per-type, not global
41
+
42
+ ### `[Alias("name")]`
43
+
44
+ Type aliases for safe renaming:
45
+
46
+ ```csharp
47
+ [GenerateSerializer]
48
+ [Alias("player-state-v1")]
49
+ public class PlayerState { }
50
+ ```
51
+
52
+ Allows type to be renamed without breaking deserialization.
53
+
54
+ ### `[Immutable]`
55
+
56
+ Skip copy overhead for immutable types:
57
+
58
+ ```csharp
59
+ [Immutable]
60
+ [GenerateSerializer]
61
+ public record SensorReading(
62
+ [property: Id(0)] string SensorId,
63
+ [property: Id(1)] double Value,
64
+ [property: Id(2)] DateTime Timestamp);
65
+ ```
66
+
67
+ Can also be applied per-parameter or per-property:
68
+
69
+ ```csharp
70
+ Task ProcessData([Immutable] LargePayload data);
71
+ ```
72
+
73
+ ## Versioning Rules
74
+
75
+ | Change | Safe? |
76
+ |---|---|
77
+ | Add new `[Id]` member | Yes |
78
+ | Remove member (don't reuse ID) | Yes |
79
+ | Rename member (keep same ID) | Yes |
80
+ | Change member type | **No** |
81
+ | Rename type (with `[Alias]`) | Yes |
82
+ | Rename type (without `[Alias]`) | **No** |
83
+
84
+ ## Surrogates
85
+
86
+ Serialize types you don't own:
87
+
88
+ ```csharp
89
+ // Surrogate for DateTimeOffset
90
+ [GenerateSerializer]
91
+ public struct DateTimeOffsetSurrogate
92
+ {
93
+ [Id(0)] public long Ticks;
94
+ [Id(1)] public short OffsetMinutes;
95
+ }
96
+
97
+ [RegisterConverter]
98
+ public sealed class DateTimeOffsetConverter :
99
+ IConverter<DateTimeOffset, DateTimeOffsetSurrogate>
100
+ {
101
+ public DateTimeOffset ConvertFromSurrogate(in DateTimeOffsetSurrogate s) =>
102
+ new(s.Ticks, TimeSpan.FromMinutes(s.OffsetMinutes));
103
+
104
+ public DateTimeOffsetSurrogate ConvertToSurrogate(in DateTimeOffset value) =>
105
+ new() { Ticks = value.Ticks, OffsetMinutes = (short)value.Offset.TotalMinutes };
106
+ }
107
+ ```
108
+
109
+ ## Copier
110
+
111
+ Orleans copies objects by default to prevent grain state corruption from accidental mutation.
112
+
113
+ ```csharp
114
+ // Custom copier
115
+ [RegisterCopier]
116
+ public sealed class MyTypeCopier : IDeepCopier<MyType>
117
+ {
118
+ public MyType DeepCopy(MyType input, CopyContext context) =>
119
+ new MyType { Value = input.Value };
120
+ }
121
+ ```
122
+
123
+ Use `[Immutable]` to skip copying entirely for types that are never mutated after creation.
124
+
125
+ ## Grain Storage Serialization
126
+
127
+ Configurable per provider. The default is provider-specific: most providers use `Newtonsoft.Json` for stored state; Redis uses the Orleans serializer.
128
+
129
+ ```csharp
130
+ // Use Orleans native format
131
+ siloBuilder.AddRedisGrainStorage("redis", options =>
132
+ {
133
+ options.GrainStorageSerializer = new OrleansGrainStorageSerializer(
134
+ serializerSessionPool);
135
+ });
136
+
137
+ // Custom serializer
138
+ public class MySerializer : IGrainStorageSerializer
139
+ {
140
+ public BinaryData Serialize<T>(T value) { }
141
+ public T Deserialize<T>(BinaryData data) { }
142
+ }
143
+ ```
144
+
145
+ ## Serialization of Immutable Types
146
+
147
+ Immutable types skip the copy step, improving performance for read-heavy grain communication.
148
+
149
+ ### Type-Level Immutability
150
+
151
+ ```csharp
152
+ [Immutable]
153
+ [GenerateSerializer]
154
+ public record SensorReading(
155
+ [property: Id(0)] string SensorId,
156
+ [property: Id(1)] double Value,
157
+ [property: Id(2)] DateTime Timestamp);
158
+ ```
159
+
160
+ ### Member-Level Immutability
161
+
162
+ ```csharp
163
+ [GenerateSerializer]
164
+ public class MyGrainState
165
+ {
166
+ [Id(0), Immutable] public IReadOnlyList<string> Tags { get; set; } = [];
167
+ [Id(1)] public int MutableCount { get; set; }
168
+ }
169
+ ```
170
+
171
+ ### Parameter-Level Immutability
172
+
173
+ ```csharp
174
+ public interface IMyGrain : IGrainWithStringKey
175
+ {
176
+ Task ProcessData([Immutable] LargePayload data);
177
+ }
178
+ ```
179
+
180
+ When `[Immutable]` is applied, Orleans trusts that the object will not be mutated after it's passed. Violating this contract can corrupt grain state.
181
+
182
+ ## Configure Serialization
183
+
184
+ ### Serializer Selection
185
+
186
+ Orleans uses its own high-performance serializer by default. Configuration options:
187
+
188
+ ```csharp
189
+ // Use System.Text.Json for specific types
190
+ siloBuilder.Services.AddSerializer(builder =>
191
+ {
192
+ builder.AddJsonSerializer(
193
+ isSupported: type => type.Namespace?.StartsWith("MyApp.Dto") == true);
194
+ });
195
+
196
+ // Use Newtonsoft.Json for specific types
197
+ siloBuilder.Services.AddSerializer(builder =>
198
+ {
199
+ builder.AddNewtonsoftJsonSerializer(
200
+ isSupported: type => type.GetCustomAttribute<JsonObjectAttribute>() != null);
201
+ });
202
+ ```
203
+
204
+ ### External Serializer Packages
205
+
206
+ | Package | Serializer |
207
+ |---|---|
208
+ | `Microsoft.Orleans.Serialization.SystemTextJson` | System.Text.Json |
209
+ | `Microsoft.Orleans.Serialization.NewtonsoftJson` | Newtonsoft.Json |
210
+ | `Microsoft.Orleans.Serialization.MessagePack` | MessagePack |
211
+ | `Microsoft.Orleans.Serialization.Protobuf` | Protobuf |
212
+ | `Microsoft.Orleans.Serialization.FSharp` | F# types |
213
+
214
+ ## Customize Serialization
215
+
216
+ ### Custom Serializer
217
+
218
+ ```csharp
219
+ [RegisterSerializer]
220
+ public sealed class MyTypeSerializer : IFieldCodec<MyType>
221
+ {
222
+ public void WriteField<TBufferWriter>(ref Writer<TBufferWriter> writer,
223
+ uint fieldIdDelta, Type expectedType, MyType value)
224
+ where TBufferWriter : IBufferWriter<byte>
225
+ {
226
+ StringCodec.WriteField(ref writer, fieldIdDelta, value.ToString());
227
+ }
228
+
229
+ public MyType ReadValue<TInput>(ref Reader<TInput> reader, Field field)
230
+ {
231
+ return MyType.Parse(StringCodec.ReadValue(ref reader, field));
232
+ }
233
+ }
234
+ ```
235
+
236
+ ### Custom Copier
237
+
238
+ ```csharp
239
+ [RegisterCopier]
240
+ public sealed class MyTypeCopier : IDeepCopier<MyType>
241
+ {
242
+ public MyType DeepCopy(MyType input, CopyContext context) =>
243
+ new MyType(input.Value); // create independent copy
244
+ }
245
+ ```
246
+
247
+ ### Surrogates for External Types
248
+
249
+ For types you don't own, create a surrogate with converter:
250
+
251
+ ```csharp
252
+ [GenerateSerializer]
253
+ public struct DateTimeOffsetSurrogate
254
+ {
255
+ [Id(0)] public long Ticks;
256
+ [Id(1)] public short OffsetMinutes;
257
+ }
258
+
259
+ [RegisterConverter]
260
+ public sealed class DateTimeOffsetConverter :
261
+ IConverter<DateTimeOffset, DateTimeOffsetSurrogate>
262
+ {
263
+ public DateTimeOffset ConvertFromSurrogate(in DateTimeOffsetSurrogate s) =>
264
+ new(s.Ticks, TimeSpan.FromMinutes(s.OffsetMinutes));
265
+
266
+ public DateTimeOffsetSurrogate ConvertToSurrogate(in DateTimeOffset value) =>
267
+ new() { Ticks = value.Ticks, OffsetMinutes = (short)value.Offset.TotalMinutes };
268
+ }
269
+ ```
270
+
271
+ ### Grain Storage Serializer Override
272
+
273
+ ```csharp
274
+ // Provider-specific default: Newtonsoft.Json for most providers; Orleans serializer for Redis
275
+ // Override per provider:
276
+ siloBuilder.AddRedisGrainStorage("redis", options =>
277
+ {
278
+ // Use Orleans native format instead of JSON
279
+ options.GrainStorageSerializer = new OrleansGrainStorageSerializer(
280
+ serializerSessionPool);
281
+ });
282
+
283
+ // Or implement custom
284
+ public class MyGrainStorageSerializer : IGrainStorageSerializer
285
+ {
286
+ public BinaryData Serialize<T>(T value) { /* ... */ }
287
+ public T Deserialize<T>(BinaryData data) { /* ... */ }
288
+ }
289
+ ```
290
+
291
+ ## Common Serialization Mistakes
292
+
293
+ | Mistake | Fix |
294
+ |---|---|
295
+ | Missing `[GenerateSerializer]` | Add to all grain state and message types |
296
+ | Missing `[Id(N)]` | Add unique IDs to all serialized members |
297
+ | Non-serializable fields in state | Mark with `[NonSerialized]` or inject via DI |
298
+ | Reusing `[Id]` after removal | Use a new unused ID number |
299
+ | Storing `HttpClient` / `Action` in state | Inject as service, not in state |