Skip to content

feat(runtime): add deterministic dissemination broadcast and repair - #10236

Open
ReubenBond wants to merge 50 commits into
dotnet:mainfrom
ReubenBond:feature/efficient-broadcast-draft
Open

ReubenBond wants to merge 50 commits into
dotnet:mainfrom
ReubenBond:feature/efficient-broadcast-draft

Conversation

@ReubenBond

@ReubenBond ReubenBond commented Jun 18, 2026 •

Copy link
Copy Markdown
Member

Problem

Deployment-load publication sends each silo's sample to every other silo: N*(N-1) requests per period. Membership dissemination also needs a small ordinary-update path while retaining reliable convergence after missed updates, topology changes, and partitions.

Solution

Add opt-in deterministic dissemination for membership and deployment load, with bounded broadcast queues and anti-entropy repair. The subsystem and both namespace enablement flags default to false; existing direct paths cover bootstrap, mixed-version operation, and failed admission.

Membership broadcasts are sparse deltas; anti-entropy repairs are full snapshots. The broadcast forest includes Joining, Active, ShuttingDown, and Stopping silos, with membership updates ahead of load updates. Each outbound peer/key retains one immutable comparison snapshot captured by an accepted, exactly acknowledged broadcast. After send admission, its next delta compares that snapshot with current owner state, carrying changed entries and removed identities. This preserves coalesced updates and reversions without a generic history or repair-chain protocol.

Receivers apply a delta at its base view, or merge a replay at the target view while preserving canonical fields and maximum heartbeat timestamps. Same-version pruning removes Dead rows only; all other statuses retain their canonical membership. Coalesced deltas across versions can span an intervening Dead transition and cleanup. Missing baselines become eligible for the next full anti-entropy repair. A peer without a comparison snapshot receives a small empty current-view probe; bootstrap and repair establish missing state. Relays compare their accepted current view with each child's baseline, including when the relay is ahead of an incoming update. Initial independent heartbeat and retained-Dead differences remain repair work.

Full repair uses the view version plus a liveness/inventory fingerprint. Same-version repair merges maximum actual IAmAliveTime independently of StartTime, and reconciles pruned Dead rows while preserving non-Dead entries. Application confirms requested effects against the resulting owner snapshot before producing an exact compact acknowledgment. The cached sender comparison snapshot comes from construction time, preserving updates which arrive while an acknowledgment is in flight.

Load publication uses receipt-synchronized cohorts. Non-root silos send one fresh sample to a deterministic root, which contributes locally. A cohort seals when every expected Active incarnation contributes or the publication period expires (one second by default). Held ingress RPCs return the remaining delay to the next cohort boundary, aligning actual sampling timers. Distribution uses fanout eight and forwards admitted sets immediately. Independent ingress admission keeps membership's send slots available.

Item, payload, pending-key, and concurrency limits bound work. Queue notifications retain identities; membership comparison snapshots are shared references and are pruned with peer/key knowledge. A slow set of peers can retain different snapshots, so worst-case comparison memory scales with outbound peers times membership size. Namespace payload caches are constant-size. Local cancellation releases waits while admitted state retains its owner. Shutdown seals cohorts before draining held admissions and then peer queues within the caller's budget.

Scope and rationale

Broadcast creation is separate from full repair construction. Public options focus on enablement, topology, cadence, and resource limits. The independent prerequisites #11294 and #11296 have been merged by humans; this branch is rebased onto their finalized implementations. Provider-owned fatal rollback handling and canonical snapshot merging come from main. Superseded manager reset special-casing and restart-as-recovery tests are removed. Dissemination follows ordinary monotonic membership views.

Broadcast, repair, and cohort integration remain one working convergence feature. Manual performance tooling is separate in #11272. At one sample and one fitting cohort per second, healthy load traffic is approximately 2*(N-1) RPCs/s: 18 at 10 silos, 198 at 100, and 3,998 at 2,000, plus repair and replies. These are projections; root ingress remains concentrated and value delivery remains quadratic. Earlier fixed-window measurements cover a different implementation. Receipt-driven process measurements remain pending separately authorized tooling execution.

See runtime dissemination architecture for invariants, timing, limits, and failure semantics. Old/new-binary compatibility covers rolling upgrade, rollback, partition recovery, isolated full-snapshot/heartbeat repair, cancellation, and bounded shutdown with exact state comparisons.

@ReubenBond ReubenBond changed the title Add deterministic dissemination broadcast and repair feat(runtime): add deterministic dissemination broadcast and repair Jun 19, 2026
Comment thread src/Orleans.Core/Configuration/Options/DisseminationOptions.cs Outdated
Comment thread src/Orleans.Core/Configuration/Options/DisseminationOptions.cs Outdated
Comment thread src/Orleans.Core/Configuration/Options/DisseminationOptions.cs Outdated
Comment thread src/Orleans.Core/Configuration/Options/DisseminationOptions.cs Outdated
Comment thread src/Orleans.Core/Configuration/Options/DisseminationOptions.cs
Comment thread src/Orleans.Core/Configuration/Options/DisseminationOptions.cs
@ReubenBond
ReubenBond force-pushed the feature/efficient-broadcast-draft branch 2 times, most recently from 611d5dd to 72bfd57 Compare July 7, 2026 00:16
@ReubenBond
ReubenBond requested a lite review from Copilot July 9, 2026 20:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new internal dissemination substrate for monotonically versioned runtime state, using deterministic fixed-tree broadcast for the fast path and periodic anti-entropy repair for convergence. It integrates the substrate into deployment load statistics and membership gossip, and improves manifest convergence by reusing content-addressed manifest hashes/caching and peer-assisted fills.

Changes:

  • Added dissemination system-target contracts, runtime implementation (broadcast queue, protocol, membership snapshotting, metrics/events), and configuration options/validators.
  • Wired dissemination into deployment load publishing and membership gossip with opt-in options and legacy fallbacks.
  • Added manifest hash-based fetch/caching and peer-based fill to reduce manifest convergence request fanout; expanded SiloAddress parsing APIs and added new dissemination-focused tests/docs.
Show a summary per file
File Description
test/TestInfrastructure/TestExtensions/Diagnostics/PlacementDiagnosticObserver.cs Uses Equals for SiloAddress comparisons in placement diagnostics helper.
test/Orleans.Runtime.Internal.Tests/Orleans.Runtime.Internal.Tests.csproj Adds test dependencies (Accordant/net10-only, NSubstitute + analyzers).
test/Orleans.Runtime.Internal.Tests/Dissemination/WakeTimerTests.cs Adds unit tests for the new wakeable one-shot timer.
test/Orleans.Runtime.Internal.Tests/Dissemination/DisseminationMembershipSnapshotTests.cs Adds CsCheck property tests for dissemination membership snapshot invariants.
test/Orleans.Core.Tests/General/Identifiertests.cs Adds tests for new SiloAddress parsing interfaces (string + UTF-8).
src/Orleans.Runtime/Scheduler/SchedulerExtensions.cs Adds RunOrQueueTask<TResult> helper for scheduling result-returning tasks.
src/Orleans.Runtime/Placement/Rebalancing/ActivationRebalancerMonitor.cs Uses value equality for SiloAddress.Zero comparisons.
src/Orleans.Runtime/Placement/DeploymentLoadPublisher.cs Publishes runtime stats via dissemination when enabled; adds fallback direct publish path; exposes apply/obsolete helpers.
src/Orleans.Runtime/Networking/SiloConnectionMaintainer.cs Uses value equality for silo address comparisons on status changes.
src/Orleans.Runtime/MembershipService/MembershipGossiper.cs Attempts dissemination publish for membership snapshots, falls back to legacy gossip.
src/Orleans.Runtime/Manifest/ClusterManifestProvider.cs Adds manifest hash cache, peer fill optimization, and hash-based fetch fallback logic.
src/Orleans.Runtime/Hosting/EndpointOptions.cs Uses Equals for IPAddress comparisons; trims trailing whitespace in docs.
src/Orleans.Runtime/Hosting/DefaultSiloServices.cs Registers dissemination services/namespaces and validators; adds formatter for DisseminationOptions.
src/Orleans.Runtime/GrainTypeManager/ClusterManifestSystemTarget.cs Adds manifest-hash APIs (hash summary, fetch-by-hash) and caches local manifest hash.
src/Orleans.Runtime/Dissemination/WakeTimer.cs Adds a thread-safe wakeable timer used for coalescing/flush scheduling.
src/Orleans.Runtime/Dissemination/MembershipDisseminationNamespace.cs Implements membership snapshot dissemination with diff-based repair and bounded history.
src/Orleans.Runtime/Dissemination/ManifestHashCalculator.cs Adds canonical manifest hashing utility for CAS reuse/validation.
src/Orleans.Runtime/Dissemination/IDisseminationService.cs Defines internal publish service abstraction.
src/Orleans.Runtime/Dissemination/IDisseminationNamespace.cs Defines dissemination namespace abstraction (digests, repair materialization, apply).
src/Orleans.Runtime/Dissemination/DisseminationSystemTarget.cs Implements system target endpoint and anti-entropy loop lifecycle.
src/Orleans.Runtime/Dissemination/DisseminationProtocol.cs Implements fixed-tree broadcast routing, anti-entropy exchanges, and apply/forward logic.
src/Orleans.Runtime/Dissemination/DisseminationNamespaceNames.cs Defines namespace IDs for load + membership dissemination.
src/Orleans.Runtime/Dissemination/DisseminationMembershipSnapshot.cs Computes deterministic forwarding targets and anti-entropy peer selection helpers.
src/Orleans.Runtime/Dissemination/DisseminationMembership.cs Builds dissemination membership snapshots from membership manager, ordered by status/age/address.
src/Orleans.Runtime/Dissemination/DisseminationInstruments.cs Adds low-cardinality metrics for dissemination traffic/results.
src/Orleans.Runtime/Dissemination/DisseminationEvents.cs Adds DiagnosticListener events for apply/drop events.
src/Orleans.Runtime/Dissemination/DisseminationBroadcastQueue.cs Adds per-peer coalescing/flush queue for broadcast batches and bounded sending.
src/Orleans.Runtime/Dissemination/DisseminationApplyResult.cs Defines apply result enum for value application semantics.
src/Orleans.Runtime/Dissemination/DeploymentLoadStatisticsDisseminationNamespace.cs Implements dissemination namespace for per-silo runtime load statistics.
src/Orleans.Runtime/Diagnostics/DeploymentLoadPublisherEvents.cs Uses value equality for self-filtering in diagnostics events.
src/Orleans.Runtime/Configuration/Options/DisseminationOptionsValidator.cs Adds options validation for dissemination global/namespace options and related owners.
src/Orleans.Runtime/Configuration/Options/DeploymentLoadPublisherOptions.cs Adds per-namespace dissemination options to deployment load publisher options.
src/Orleans.Core/SystemTargetInterfaces/IDisseminationSystemTarget.cs Adds wire contracts for dissemination broadcast + anti-entropy and related DTOs.
src/Orleans.Core/Runtime/Constants.cs Adds dissemination system target grain type constant and singleton name mapping.
src/Orleans.Core/Placement/Repartitioning/IActivationRepartitionerSystemTarget.cs Updates EdgeVertex.Equals to use SiloAddress.Equals.
src/Orleans.Core/Networking/Shared/SocketConnectionListener.cs Uses value equality for IPv6Any check and adds cancellation to AcceptAsync.
src/Orleans.Core/Manifest/IClusterManifestSystemTarget.cs Adds manifest hash/CAS APIs and DTOs (ManifestHash, ClusterManifestHashSummary).
src/Orleans.Core/Configuration/Options/DisseminationOptions.cs Adds public configuration options for dissemination subsystem + overlay + namespaces.
src/Orleans.Core/Configuration/Options/ClusterMembershipOptions.cs Adds per-namespace dissemination options for membership updates.
src/Orleans.Core.Abstractions/IDs/SiloAddress.cs Adds IParsable/IUtf8SpanParsable support and non-throwing TryParse implementations.
src/api/Orleans.Runtime/Orleans.Runtime.cs Updates public API baseline for DeploymentLoadPublisherOptions dissemination property.
src/api/Orleans.Core/Orleans.Core.cs Updates public API baseline for dissemination options and membership options property.
src/api/Orleans.Core.Abstractions/Orleans.Core.Abstractions.cs Updates public API baseline for SiloAddress parsing interfaces/methods.
efficient-broadcast.md Adds design/branch documentation for the efficient broadcast + repair approach.
dissemination.md Adds detailed design documentation for topic-based dissemination and testing/rollout.
Directory.Packages.props Adds Microsoft.Accordant package version.
agency.toml Adds MCP/agent configuration (appears unrelated to Orleans runtime).

Copilot's findings

  • Files reviewed: 48/48 changed files
  • Comments generated: 4

Comment thread src/Orleans.Core/Placement/Repartitioning/IActivationRepartitionerSystemTarget.cs Outdated
Comment thread src/Orleans.Runtime/Manifest/ClusterManifestProvider.cs
Comment thread src/Orleans.Runtime/Dissemination/ManifestHashCalculator.cs Outdated
Comment thread agency.toml Outdated
@ReubenBond
ReubenBond force-pushed the feature/efficient-broadcast-draft branch 2 times, most recently from a94b017 to 7e8cb58 Compare July 15, 2026 23:09
@ReubenBond
ReubenBond force-pushed the feature/efficient-broadcast-draft branch 3 times, most recently from 6017675 to 43d72cf Compare August 10, 2026 23:46
Copilot AI review requested due to automatic review settings August 18, 2026 09:24
@ReubenBond
ReubenBond force-pushed the feature/efficient-broadcast-draft branch from 43d72cf to 34c76fa Compare August 18, 2026 09:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Suppressed comments (2)

src/Orleans.Runtime/Manifest/ClusterManifestProvider.cs:416

  • _manifestCache is a plain Dictionary, but UpdateManifest fetches missing manifests via multiple concurrent tasks (Task.WhenAll) which all call GetSiloManifest(). That results in concurrent reads/writes to _manifestCache (TryGetValue, indexer assignment), which is not thread-safe and can corrupt the dictionary or throw at runtime. Consider switching _manifestCache to ConcurrentDictionary or guarding all accesses (including PruneManifestCache/FillFromCachedHashes) with a dedicated lock.
                var remoteManifestProvider = _grainFactory!.GetSystemTarget<IClusterManifestSystemTarget>(Constants.ManifestProviderType, siloAddress);
                var hash = await remoteManifestProvider.GetSiloManifestHash().AsTask().WaitAsync(_shutdownCts.Token);
                if (_manifestCache.TryGetValue(hash, out var cached))
                {
                    return cached;

src/Orleans.Runtime/MembershipService/MembershipGossiper.cs:34

  • TryGossipViaDissemination's gossipPartners parameter is unused. Removing it keeps the method contract accurate and avoids unnecessary plumbing.
    private async Task<bool> TryGossipViaDissemination(List<SiloAddress> gossipPartners, MembershipTableSnapshot snapshot)
  • Files reviewed: 40/41 changed files
  • Comments generated: 3
  • Review effort level: Lite

Comment thread src/Orleans.Runtime/MembershipService/MembershipGossiper.cs Outdated
Comment thread src/Orleans.Core/SystemTargetInterfaces/IDisseminationSystemTarget.cs Outdated
Comment thread src/api/Orleans.Core/Orleans.Core.cs Outdated
Copilot AI review requested due to automatic review settings August 18, 2026 11:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/Orleans.Core/Networking/Shared/SocketConnectionListener.cs:93

  • AcceptAsync now passes the provided cancellationToken to Socket.AcceptAsync. When that token is canceled, AcceptAsync will typically throw OperationCanceledException, but this method does not handle it and will propagate the exception instead of returning null (which is how the other IConnectionListener implementations here behave). This can break graceful shutdown/cancellation paths.
                try
                {
                    var acceptSocket = await _listenSocket!.AcceptAsync(cancellationToken);
                    acceptSocket.NoDelay = _options.NoDelay;
                    if (_options.KeepAlive)
  • Files reviewed: 40/41 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread src/Orleans.Core/Manifest/IClusterManifestSystemTarget.cs
Copilot AI review requested due to automatic review settings August 18, 2026 12:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Suppressed comments (1)

src/Orleans.Runtime/Manifest/ClusterManifestProvider.cs:416

  • GetSiloManifest() is called concurrently from UpdateManifest() via Task.WhenAll, but it reads/writes _manifestCache (a Dictionary) without synchronization. Concurrent Dictionary access can throw (e.g., during resize) or corrupt internal state. Protect cache access (or switch to ConcurrentDictionary) so multiple GetSiloManifest calls can run safely in parallel.
                var remoteManifestProvider = _grainFactory!.GetSystemTarget<IClusterManifestSystemTarget>(Constants.ManifestProviderType, siloAddress);
                var hash = await remoteManifestProvider.GetSiloManifestHash().AsTask().WaitAsync(_shutdownCts.Token);
                if (_manifestCache.TryGetValue(hash, out var cached))
                {
                    return cached;
  • Files reviewed: 40/41 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@ReubenBond
ReubenBond marked this pull request as ready for review August 21, 2026 22:26
Copilot AI review requested due to automatic review settings August 22, 2026 02:18
@ReubenBond
ReubenBond force-pushed the feature/efficient-broadcast-draft branch from 66aeda2 to b247a09 Compare August 22, 2026 02:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The distributed-state and cross-version changes span many failure-sensitive paths, and membership-provider cleanup semantics remain inconsistent.

Review effort: Balanced
Findings: 1 High severity · 3 Medium severity

Open (4)
Previously missed (1)

In code that hasn't changed since last review

Low severity Documented cleanup guarantee is not implemented

src/​Orleans.Core/​SystemTargetInterfaces/​IMembershipTable.cs:75

This new contract does not match existing providers. For example, src/Orleans.Runtime/MembershipService/InMemoryMembershipTable.cs:91 and src/Google/Orleans.Clustering.Firestore/FirestoreMembershipTable.cs:79 delete every old non-Active row, including Joining, ShuttingDown, and Stopping entries. The same-version merge logic added by this change relies on only Dead rows being pruned, so those providers can still produce snapshots which violate that invariant. Align the provider cleanup implementations and conformance tests before documenting this guarantee, or weaken the guarantee and update the merge logic accordingly.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The documented constant-size payload-cache model does not match the per-active-silo cache implemented by the load namespace.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity · 4 Medium severity

Open (5)

Serializer serializer) : IDisseminationNamespace
{
private readonly object _cacheLock = new();
private readonly Dictionary<SiloAddress, DisseminationValue> _cachedValues = [];

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +505 to +525
foreach (var (namespaceName, namespaceState) in _statesByNamespace)
{
if (!membershipSnapshots.GetSnapshot(namespaceState.Namespace.MembershipScope).ContainsMember(Peer))
{
var removedDirtyCount = namespaceState.Keys.Values.Count(static key => key.Dirty);
droppedDirtyCount += removedDirtyCount;
DirtyCount -= removedDirtyCount;
_statesByNamespace.Remove(namespaceName);
continue;
}

activeKeys.TryGetValue(namespaceName, out var namespaceKeys);
foreach (var (key, keyState) in namespaceState.Keys)
{
if (!keyState.Dirty
&& !keyState.InFlight
&& (namespaceKeys is null || !namespaceKeys.Contains(key)))
{
namespaceState.Keys.Remove(key);
}
}

public void PruneKnownVersions(HashSet<DisseminationKey>? activeKeys)
{
foreach (var key in KnownVersions.Keys)

lock (_valueUpdateLock)
{
foreach (var key in _lastValueUpdates.Keys)
}

membership = _membership.CurrentSnapshots.AllMembers;
foreach (var peer in _confirmedPeerNamespaces.Keys)
Comment on lines +618 to +621
if (pending.Node.List is not null)
{
_ready.Remove(pending.Node);
}

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants