Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
38a72b0
feat(messaging): add durable messaging contracts
ReubenBond Oct 12, 2026
b26f9ef
refactor(messaging): rename InboxOptions to DurableMessagingOptions
ReubenBond Oct 12, 2026
9c89257
feat(messaging): create message types from the serializer service
ReubenBond Oct 12, 2026
2205305
docs(messaging): add Microsoft.Orleans.Messaging to the package inven…
ReubenBond Oct 12, 2026
2f0ab1b
chore(messaging): add generated API surface
ReubenBond Oct 12, 2026
588e15d
fix(messaging)!: reserve zero as an invalid delivery status
ReubenBond Oct 13, 2026
60f85a7
fix(messaging): check cancellation before decoding typed inbox bodies
ReubenBond Oct 13, 2026
be3ca67
build(messaging): set the package readme file
ReubenBond Oct 13, 2026
e126492
feat(messaging): add Envelope.DeliverAt
ReubenBond Oct 13, 2026
51d3162
docs(messaging): align capacity and pending-intent contracts with Del…
ReubenBond Oct 13, 2026
b0fd78e
test(messaging): cover DeliverAt wire round trips and HierarchicalKey…
ReubenBond Oct 13, 2026
6296161
feat(messaging): add outbox message source and inbox message sink con…
ReubenBond Oct 13, 2026
17f09a6
feat(messaging): send source-provided messages by ID and let sources …
ReubenBond Oct 13, 2026
b3f25b4
feat(messaging): identify messages with MessageId wrapping Hierarchic…
ReubenBond Oct 13, 2026
4f7d5c7
feat(messaging): dead-letter outbox messages whose source fails
ReubenBond Oct 13, 2026
998ba42
feat(messaging): offer scheduled messages to inbox sinks and let sink…
ReubenBond Oct 13, 2026
fde50b1
refactor(messaging)!: decode message types from envelopes only
ReubenBond Oct 13, 2026
39422b6
refactor(messaging): rename IInboxExtension to IMessagingExtension
ReubenBond Oct 13, 2026
b015f03
feat(messaging): run outbox delivery attempts through a Polly resilie…
ReubenBond Oct 13, 2026
4dcff4f
feat(messaging)!: demote failed deliveries to later batches and retry…
ReubenBond Oct 14, 2026
a70e19c
refactor(messaging)!: track one outbox retry period and drop MaxHandl…
ReubenBond Oct 14, 2026
7b8414a
refactor(messaging)!: align outbox retry terminology with Temporal
ReubenBond Oct 14, 2026
1076e0e
fix(messaging): reject ill-formed UTF-16 in identities and header names
ReubenBond Oct 18, 2026
1623c5b
feat(messaging)!: make inbox handler selection read-only
ReubenBond Oct 18, 2026
3fc61ea
docs(messaging): describe DeliverAt acceptance and capacity in the RE…
ReubenBond Oct 18, 2026
76d98df
build(messaging): enable trim, AOT, and single-file analyzers
ReubenBond Oct 18, 2026
704536d
feat(journaling): add journaled state hooks and write preparation
ReubenBond Oct 12, 2026
54c6d60
feat(messaging): process inbox messages
ReubenBond Oct 12, 2026
0298911
refactor(journaling): remove operation hooks
ReubenBond Oct 12, 2026
6e8a285
fix(messaging): start the pump loop asynchronously
ReubenBond Oct 12, 2026
d27a5c2
test(messaging): remove handler races and simplify timeout reporting
ReubenBond Oct 13, 2026
ecc09bc
test(messaging): capture pump shutdown token before deactivation
ReubenBond Oct 13, 2026
5a54d14
test(messaging): hold the active handler through deactivation
ReubenBond Oct 13, 2026
49e470a
fix(messaging): address inbox review feedback
ReubenBond Oct 13, 2026
4021665
fix(messaging): discard wake-ups overtaken by a completed job run
ReubenBond Oct 13, 2026
d2b49bd
feat(messaging): process inbox messages at DeliverAt
ReubenBond Oct 13, 2026
58c45b6
test(messaging): fail once per message across reactivations
ReubenBond Oct 13, 2026
488d439
test(messaging): compare staged test outbox messages by command equiv…
ReubenBond Oct 13, 2026
22c975a
fix(messaging): bind IInbox to the delivered inbox
ReubenBond Oct 13, 2026
0dfaa1b
feat(messaging): accept messages into application state through inbox…
ReubenBond Oct 13, 2026
d6d217d
test(messaging): implement the source-provided outbox send in the tes…
ReubenBond Oct 13, 2026
26754a2
feat(messaging): identify messages with MessageId wrapping Hierarchic…
ReubenBond Oct 13, 2026
ac44fad
feat(messaging): offer scheduled messages to inbox sinks and let sink…
ReubenBond Oct 13, 2026
d010819
refactor(messaging): rename IInboxExtension to IMessagingExtension
ReubenBond Oct 13, 2026
651045f
feat(messaging)!: demote failed deliveries to later batches and retry…
ReubenBond Oct 14, 2026
94db692
refactor(messaging)!: track one outbox retry period and drop MaxHandl…
ReubenBond Oct 14, 2026
f20a8b7
refactor(messaging)!: align outbox retry terminology with Temporal
ReubenBond Oct 14, 2026
b30adde
feat(messaging): keep activations alive while the pump has inbox work
ReubenBond Oct 14, 2026
bb3072c
feat(messaging)!: bound inbox handler retries by time instead of atte…
ReubenBond Oct 15, 2026
ba6d29d
feat(messaging): wake grains for work held by registered inbox work s…
ReubenBond Oct 15, 2026
a63f744
fix(messaging): acknowledge duplicates only after their completion is…
ReubenBond Oct 18, 2026
4eb66a4
fix(messaging): count sink dead-letters as received dead_lettered
ReubenBond Oct 18, 2026
731a3e6
fix(messaging): resume pending messages when a handler registers inst…
ReubenBond Oct 18, 2026
688d606
fix(messaging): schedule inbox retries on the Durable Jobs clock
ReubenBond Oct 18, 2026
3496b8e
fix(messaging): schedule an earlier wake-up for work due before the c…
ReubenBond Oct 18, 2026
db74943
feat(messaging)!: make inbox handler selection read-only
ReubenBond Oct 18, 2026
f169b5b
fix(messaging): report null delivery pipeline through options validation
ReubenBond Oct 18, 2026
90b4fa6
chore(messaging): remove unused ObjectPool package reference
ReubenBond Oct 18, 2026
386c759
test(messaging): fix test helper edge cases
ReubenBond Oct 18, 2026
e2c48a9
test(messaging): build the messaging test cluster during fixture init…
ReubenBond Oct 18, 2026
79d25cf
fix(messaging): normalize non-positive work-source delays to a prompt…
ReubenBond Oct 18, 2026
3c3237f
fix(messaging): answer duplicates of sink-rejected messages with thei…
ReubenBond Oct 18, 2026
a70ec98
feat(messaging): deliver outbox messages
ReubenBond Oct 12, 2026
f04a3fc
fix(messaging): schedule outbox retries on the durable jobs clock
ReubenBond Oct 13, 2026
f5e8c06
fix(messaging): validate recovered outbox dead-letter keys
ReubenBond Oct 13, 2026
79e42e6
feat(messaging): deliver scheduled outbox messages immediately
ReubenBond Oct 13, 2026
f458484
docs(messaging): document terminal outbox delivery outcomes
ReubenBond Oct 13, 2026
861c6b5
fix(messaging): bind IOutbox to the delivered outbox
ReubenBond Oct 13, 2026
ae7ee28
feat(messaging): stage self-addressed messages directly in the inbox
ReubenBond Oct 13, 2026
8baa934
feat(messaging): journal outbox messages by reference to application-…
ReubenBond Oct 13, 2026
8a3cf2b
feat(messaging): identify messages with MessageId wrapping Hierarchic…
ReubenBond Oct 13, 2026
b5943a9
feat(messaging): dead-letter outbox messages whose source fails
ReubenBond Oct 13, 2026
d6623d3
feat(messaging): offer scheduled messages to inbox sinks and let sink…
ReubenBond Oct 13, 2026
3fbd064
refactor(messaging): rename IInboxExtension to IMessagingExtension
ReubenBond Oct 13, 2026
0eb22b1
feat(messaging): run outbox delivery attempts through a Polly resilie…
ReubenBond Oct 13, 2026
0d25a95
feat(messaging)!: demote failed deliveries to later batches and retry…
ReubenBond Oct 14, 2026
cf6b2ad
refactor(messaging)!: track one outbox retry period and drop MaxHandl…
ReubenBond Oct 14, 2026
16ebae6
refactor(messaging)!: align outbox retry terminology with Temporal
ReubenBond Oct 14, 2026
8a96e76
feat(messaging): keep activations alive while outbox deliveries are i…
ReubenBond Oct 14, 2026
92ff609
feat(messaging)!: describe outbox retirements with a status and the d…
ReubenBond Oct 15, 2026
9e80017
feat(messaging): carry an application-defined code with sink rejections
ReubenBond Oct 15, 2026
b322993
fix(messaging)!: retry deliveries whose inbox sink throws
ReubenBond Oct 15, 2026
d58697a
test(messaging): add reusable delivery fault injection seams
ReubenBond Oct 15, 2026
8417071
test(messaging): inject faults at each boundary of the delivery chain
ReubenBond Oct 15, 2026
c187301
feat(messaging): report inbox and outbox transitions through diagnost…
ReubenBond Oct 15, 2026
fb8bced
fix(messaging): make diagnostic event emission trim-safe
ReubenBond Oct 15, 2026
6b16000
fix(messaging): resume pending messages when a handler registers inst…
ReubenBond Oct 18, 2026
f784037
fix(messaging): schedule an earlier wake-up for work due before the c…
ReubenBond Oct 18, 2026
843117a
feat(messaging)!: make inbox handler selection read-only
ReubenBond Oct 18, 2026
c7455bd
fix(messaging): copy source-owned messages into outbox dead letters
ReubenBond Oct 18, 2026
e6e13eb
fix(messaging): count unavailable outbox references toward the batch …
ReubenBond Oct 18, 2026
917490b
test(messaging): cover unrelated cancellation, outbox depth, and dupl…
ReubenBond Oct 18, 2026
91fd1dd
fix(messaging): answer duplicates of sink-rejected messages with thei…
ReubenBond Oct 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Orleans.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
<Project Path="src/Orleans.CodeGenerator/Orleans.CodeGenerator.csproj" />
<Project Path="src/Orleans.Core.Abstractions/Orleans.Core.Abstractions.csproj" />
<Project Path="src/Orleans.Core/Orleans.Core.csproj" />
<Project Path="src/Orleans.Messaging/Orleans.Messaging.csproj" />
<Project Path="src/Orleans.EventSourcing/Orleans.EventSourcing.csproj" />
<Project Path="src/Orleans.Journaling/Orleans.Journaling.csproj" />
<Project Path="src/Orleans.Persistence.Memory/Orleans.Persistence.Memory.csproj" />
Expand Down Expand Up @@ -152,6 +153,7 @@
<Project Path="test/Orleans.GrainDirectory.Tests/Orleans.GrainDirectory.Tests.csproj" />
<Project Path="test/Orleans.Streaming.Tests/Orleans.Streaming.Tests.csproj" />
<Project Path="test/Orleans.BroadcastChannel.Tests/Orleans.BroadcastChannel.Tests.csproj" />
<Project Path="test/Orleans.Messaging.Tests/Orleans.Messaging.Tests.csproj" />
<Project Path="test/Orleans.EventSourcing.Tests/Orleans.EventSourcing.Tests.csproj" />
<Project Path="test/Orleans.DurableJobs.Tests/Orleans.DurableJobs.Tests.csproj" />
</Folder>
Expand Down
1 change: 1 addition & 0 deletions docs/site/src/content/docs/resources/nuget-packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ Memory persistence distributes records across cluster storage grains but isn't d
| [Microsoft.Orleans.Reminders.Redis](https://www.nuget.org/packages/Microsoft.Orleans.Reminders.Redis) | Redis reminders. |
| [Microsoft.Orleans.DurableJobs](https://www.nuget.org/packages/Microsoft.Orleans.DurableJobs) | Distributed scheduling for durable one-time jobs. |
| [Microsoft.Orleans.DurableJobs.AzureStorage](https://www.nuget.org/packages/Microsoft.Orleans.DurableJobs.AzureStorage) | Azure Blob Storage for durable jobs. |
| [Microsoft.Orleans.Messaging](https://www.nuget.org/packages/Microsoft.Orleans.Messaging) | Experimental grain-scoped inbox and outbox messaging. |

Use reminders for recurring durable callbacks and durable jobs for scheduled one-time work. Grain timers are activation-scoped and use the core runtime rather than a provider package.

Expand Down
1 change: 1 addition & 0 deletions docs/site/src/data/external-link-allowlist.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"https://docs.aws.amazon.com/sdk-for-net/v4/developer-guide/creds-assign.html": "AWS serves this public SDK credential-resolution page to browsers but rejects the bounded automated HEAD/GET probe with HTTP 403.",
"https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/Util/TLoggingConfig.html": "AWS serves this public .NET SDK logging configuration reference to browsers but rejects the bounded automated HEAD/GET probe with HTTP 403.",
"https://docs.aws.amazon.com/streams/latest/dev/introduction.html": "AWS serves this public Kinesis overview to browsers but rejects the bounded automated HEAD/GET probe with HTTP 403.",
"https://www.nuget.org/packages/Microsoft.Orleans.Messaging": "The new Messaging package is documented but not yet published; remove this entry and its unpublished API-package entry after publication.",
"https://en.wikipedia.org/wiki/Kalman_filter": "Wikipedia serves this public article to browsers but rejects the bounded automated HEAD/GET probe with HTTP 403.",
"https://twitter.com/intent/follow?screen_name=msftorleans": "Twitter serves this public Orleans follow intent to browsers and intermittently returns HTTP 403 to the bounded automated HEAD/GET probe; successful responses also occur.",
"https://www.f5.com/company/blog/nginx/nginx-power-of-two-choices-load-balancing-algorithm": "The F5 site serves this canonical NGINX article to browsers but rejects the bounded automated HEAD/GET probe with HTTP 403."
Expand Down
4 changes: 3 additions & 1 deletion docs/site/src/data/unpublished-api-packages.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{
"description": "Generated API assemblies which are not currently published as standalone NuGet packages.",
"packages": {}
"packages": {
"Microsoft.Orleans.Messaging": "The new package is awaiting its first NuGet publication."
}
}
1 change: 1 addition & 0 deletions src/Orleans.Core/Orleans.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
<InternalsVisibleTo Include="Orleans.Persistence.Firestore.Tests" />
<InternalsVisibleTo Include="Orleans.GrainDirectory.Tests" />
<InternalsVisibleTo Include="Orleans.Journaling" />
<InternalsVisibleTo Include="Orleans.Messaging" />
<InternalsVisibleTo Include="Orleans.NativeAotSmoke" />
<InternalsVisibleTo Include="Orleans.Placement.Tests" />
<InternalsVisibleTo Include="Orleans.Redis.Tests" />
Expand Down
3 changes: 3 additions & 0 deletions src/Orleans.Journaling/IJournaledStateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ public interface IJournaledStateManager : IAsyncDisposable
/// Stage mutations only after establishing that they are safe to commit. Pending changes are shared
/// by all callers using this manager. Storage acknowledgement establishes durability.
/// A failed journal operation fences the manager; recovery requires a new manager and state machine instances.
/// Before capture, the manager prepares each registered state through <see cref="IStateMachine.PrepareWriteAsync"/>.
/// A preparation failure reports <see cref="JournaledStatePreCommitException"/> and retains pending changes
/// for an explicit retry.
/// Cancellation stops the caller's wait; an already queued write continues to its storage outcome.
/// </remarks>
/// <param name="cancellationToken">The token used to cancel the caller's wait.</param>
Expand Down
19 changes: 19 additions & 0 deletions src/Orleans.Journaling/IStateMachine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace Orleans.Journaling;
/// </item>
/// <item>
/// When the application requests a write, the journaled state manager calls
/// <see cref="PrepareWriteAsync"/> to establish the state's persistence prerequisites, then
/// <see cref="WritePendingEntries"/> (and occasionally <see cref="WriteSnapshot"/>) to materialize
/// the pending changes, then flushes the journal to durable storage.
/// </item>
Expand Down Expand Up @@ -68,6 +69,24 @@ public interface IStateMachine
/// </remarks>
void OnRecoveryCompleted() { }

/// <summary>
/// Establishes the persistence prerequisites for this state's pending changes.
/// </summary>
/// <param name="cancellationToken">The token which cancels preparation when the journal owner stops.</param>
/// <returns>A completion representing readiness to capture this state.</returns>
/// <remarks>
/// The manager invokes preparation before capturing an append or snapshot.
/// Preparation is idempotent and prepares this state's own prerequisites. After asynchronous
/// preparation completes, the manager repeats preparation for all states to include changes
/// staged on interleaved turns. Prepared prerequisites complete synchronously on subsequent calls
/// and remain valid through capture. A complete synchronous pass flows directly into capture.
/// Preparation completes without awaiting another operation on its own journal manager,
/// since that operation is serialized behind the current write.
/// A failure reports <see cref="JournaledStatePreCommitException"/> and retains pending changes
/// for an explicit write retry.
/// </remarks>
ValueTask PrepareWriteAsync(CancellationToken cancellationToken = default) => ValueTask.CompletedTask;

/// <summary>
/// Writes pending state changes to the journal.
/// </summary>
Expand Down
52 changes: 52 additions & 0 deletions src/Orleans.Journaling/JournaledStateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,31 @@ private async Task WorkLoop()
"The journaled state operation was queued before deletion reset its state.");
}

if (workItem is AppendJournalWorkItem or WriteSnapshotWorkItem)
{
try
{
for (var prepared = false; !prepared;)
{
await PrepareStates(ref prepared, _shutdownCancellation.Token).ConfigureAwait(true);
}

_shutdownCancellation.Token.ThrowIfCancellationRequested();
}
catch (OperationCanceledException) when (_shutdownCancellation.IsCancellationRequested)
{
workItem.TrySetCanceled(_shutdownCancellation.Token);
return;
}
catch (Exception exception)
{
LogStatePreparationFailed(_shared.Logger, exception);
storageActivity?.SetStatus(ActivityStatusCode.Error, "Journal state preparation failed.");
workItem.SetException(new JournaledStatePreCommitException(exception));
continue;
}
}

// Note that the implementation of each command is inlined to avoid allocating unnecessary async states.
// We are ok sacrificing some code organization for performance in the inner loop.
switch (workItem)
Expand Down Expand Up @@ -596,6 +621,28 @@ private async Task WorkLoop()
}
}

// Completes when every state is synchronously ready; otherwise returns the first incomplete preparation.
private ValueTask PrepareStates(ref bool prepared, CancellationToken cancellationToken)
{
prepared = true;
lock (_lock)
{
foreach (var state in _states.Values)
{
var preparation = state.PrepareWriteAsync(cancellationToken);
if (!preparation.IsCompletedSuccessfully)
{
prepared = false;
return preparation;
}

preparation.GetAwaiter().GetResult();
}
}

return ValueTask.CompletedTask;
}

private void Fence(Exception exception)
{
lock (_lock)
Expand Down Expand Up @@ -1352,6 +1399,11 @@ void IStateMachine.WritePendingEntries(JournalStreamWriter writer) { }
Message = "Error processing work items.")]
private static partial void LogErrorProcessingWorkItems(ILogger logger, Exception exception);

[LoggerMessage(
Level = LogLevel.Error,
Message = "Journal state preparation failed; pending changes remain staged.")]
private static partial void LogStatePreparationFailed(ILogger logger, Exception exception);

[LoggerMessage(
Level = LogLevel.Information,
Message = "State \"{Name}\" was not found. I have substituted a placeholder for graceful time-based retirement.")]
Expand Down
21 changes: 21 additions & 0 deletions src/Orleans.Journaling/JournaledStatePreCommitException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Orleans.Journaling;

/// <summary>
/// Reports a state preparation failure which prevented a journal write.
/// </summary>
/// <remarks>
/// Pending changes remain staged and safe to commit. The caller can restore the prerequisite
/// and explicitly retry persistence, or retire its owner and recover from durable state.
/// </remarks>
[GenerateSerializer]
public sealed class JournaledStatePreCommitException : Exception
{
/// <summary>
/// Initializes a state preparation failure.
/// </summary>
/// <param name="innerException">The original preparation failure.</param>
public JournaledStatePreCommitException(Exception innerException)
: base("The journal write was prevented by a state preparation failure.", innerException)
{
}
}
14 changes: 14 additions & 0 deletions src/Orleans.Journaling/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,15 +207,29 @@ provider and requested state name.

- `Reset`: reset in-memory state and bind a `JournalStreamWriter`.
- `ReplayEntry`: reconstruct state from a recorded operation.
- `PrepareWriteAsync`: establish asynchronous persistence prerequisites for pending state.
- `WritePendingEntries`: emit pending operations into the supplied writer.
- `WriteSnapshot`: emit a reconstructible snapshot into the supplied writer.
- `OnRecoveryCompleted`: finish reconstruction before application use.
- `OnWriteCompleted`: publish effects that depend on storage acknowledgement.

Command-backed states can use `IDurableValueCommandCodec<TCommand>` to encode a
complete command before applying it locally. A snapshot is a command in the same
application-defined schema which reconstructs the whole state. Annotate command data
for Orleans serialization and supply System.Text.Json metadata when using the JSON codec.

Recovery resets state machines and replays durable entries. A failed initialization can be retried on the
same manager and registered states. `JournalReplayContext.ResolveStateMachine` routes entries to the
state machine for their stream.

Write preparation runs before append or snapshot capture.
Each state prepares its own prerequisites idempotently. An asynchronous completion
leaves prepared prerequisites synchronously ready; the manager then rechecks all states
to include mutations staged on interleaved turns. A complete synchronous pass flows
directly into capture. Preparation failures retain pending changes for an explicit
write retry and report `JournaledStatePreCommitException`. The owner's shutdown token
cancels preparation, and disposal drains its actual completion.

`IJournaledStateManager` is independent of the grain-facing `IDurableStateManager` and extends
`IAsyncDisposable`. Its owner API provides `RegisterStateMachine`, `TryGetStateMachine`,
`InitializeAsync`, `WriteStateAsync`, whole-journal `DeleteStateAsync`, and `PendingWriteByteCount`.
Expand Down
Loading
Loading