Skip to content

Commit bcb31fc

Browse files
Merge branch 'dev' into fix/artery-shutdown-flush
2 parents 11a36ac + b86a6a1 commit bcb31fc

28 files changed

Lines changed: 3098 additions & 210 deletions

BREAKING_CHANGES_V1.6.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ of `Behavior`, `Wire`, `API` (combine with `+`).
4040

4141
| Status | PR / Branch | Component | Type | Change | Migration |
4242
|--------|-------------|-----------|------|--------|-----------|
43+
| Planned | [#8324](https://github.com/akkadotnet/akka.net/pull/8324) | `Akka.Routing` | Behavior | `ConsistentHash<T>` no longer retains the `SortedDictionary` passed to its public constructor — the ring is snapshotted into internal sorted arrays. Mutating that dictionary after construction no longer affects the instance (previously the aliasing was inconsistent: `IsEmpty` and `operator +`/`-` read it live, while `NodeFor` froze it after the first lookup). A `null` dictionary now throws `ArgumentNullException` from the constructor instead of surfacing later as a `NullReferenceException`. No public API removed; the ring built by `ConsistentHash.Create` is byte-identical. (#8293) | None for normal use — `ConsistentHash.Create` already builds the dictionary fully before constructing, so routers/receptionists are unaffected. If you call the `ConsistentHash(SortedDictionary, int)` constructor directly, populate the dictionary before passing it and don't rely on post-construction mutation being visible. |
44+
| Planned | `fix/gossip-removal-tombstones` | `Akka.Cluster` | Wire + Behavior | Cluster gossip now carries a removal tombstone for every member the leader removes (new `repeated Tombstone tombstones = 7` on the `Gossip` proto message, previously the one vacant field number). A member the leader removed can no longer be put back into the gossip by a peer that has not caught up, which previously blocked convergence permanently and needed a full cluster restart to clear. Tombstones expire after the new `akka.cluster.prune-gossip-tombstones-after` setting (default `24h`), pruned by the leader on a converged tick and reclaimed for good: gossip reception keeps the tombstones of whichever gossip wins the causal comparison, so a peer that has not pruned yet cannot hand a pruned tombstone back. Tombstones are only unioned across the two gossips when neither descends from the other -- equal clocks, or concurrent clocks, where each side may hold a removal the other has not heard about. The field is additive and proto3 ignores unknown fields in both directions, so rolling upgrades are safe; an older node parses the gossip fine but drops the tombstones when it re-emits it, and if that stripped gossip is causally newer than an upgraded node's, the upgraded node adopts it and loses those tombstones too -- so the protection only holds between upgraded nodes and the fix is not fully in effect until every node is upgraded. | No action required. Raise `akka.cluster.prune-gossip-tombstones-after` if the cluster must survive partitions longer than 24 hours, or lower it if a cluster with heavy join/leave churn needs the gossip message kept small -- each tombstone adds a full address plus a timestamp to every gossip message until it expires. |
4345
| Planned | `feature/default-bounded-shard-rebalancing` | `Akka.Cluster.Sharding` | Behavior | The default `rebalance-absolute-limit` is now `20`, selecting the bounded shard allocation strategy instead of the legacy threshold-based strategy. | To retain the legacy strategy temporarily, explicitly set `akka.cluster.sharding.least-shard-allocation-strategy.rebalance-absolute-limit = 0`. Review `rebalance-threshold` and `max-simultaneous-rebalance`, which do not apply while the bounded strategy is active. |
4446
| Planned | `fix/artery-shutdown-flush` | `Akka.Remote` (Artery) | Behavior | Graceful transport shutdown now flushes. `ArteryRemoting.Shutdown()` completes each association's outbound channels, waits up to the new `akka.remote.artery.advanced.flush-wait-on-shutdown` (default `2 s`, mirroring classic remoting's `akka.remote.flush-wait-on-shutdown`) for the outbound streams to finish writing what they had already accepted, and only then drains the remainder to `Dropped`. Previously it completed and drained in the same breath, so a message accepted microseconds before shutdown -- an ack, a graceful notice, a handshake reply -- was published as `Dropped` instead of reaching the socket. Two consequences: graceful shutdown may now take up to `flush-wait-on-shutdown` longer per transport (only when a backlog cannot reach its peer; an association with nothing left to write completes at once), and messages that previously surfaced as `Dropped` may now be delivered. Abrupt termination (materializer death without a graceful `Shutdown()`) is unchanged. | No action required. Set `akka.remote.artery.advanced.flush-wait-on-shutdown = 0` to restore the previous drain-immediately behavior, or lower it to cap how long shutdown may wait. Monitoring that counts shutdown-time `Dropped` events will see fewer of them. |
4547
| Planned | `fix/artery-inbound-quarantine-check` | `Akka.Remote` (Artery) | Behavior | Quarantine is now enforced on the INBOUND path too. Previously Artery only gated outbound sends -- an envelope arriving FROM a uid this system has quarantined was still delivered, and the quarantined peer was only notified once, proactively, at the moment `Quarantine()` was called. A new `InboundQuarantineCheckStage`, woven into the inbound pipeline right after handshake, now drops every inbound envelope (ordinary or control, including system messages) whose origin uid is quarantined, and reactively re-sends a `Quarantined` control notice to the origin for each drop (except for a heartbeat or the peer's own `Quarantined` notice, to avoid a reply storm). The existing one-shot proactive notice in `Quarantine()` is unchanged. Additionally, an ordinary/large outbound stream that terminates while its association is quarantined no longer wedges permanently: the materialize-once gate is released (timer-driven auto-reconnect stays suppressed), so a quarantine-piercing `ActorSelection` send -- or any send after a new incarnation's handshake lifts the quarantine -- re-materializes the stream on demand and can reach a restarted peer at the same address. | No action required -- this is a bug fix restoring the documented "no further communication" guarantee of quarantine and the documented new-incarnation piercing behavior; code that (incorrectly) depended on a quarantined peer's replies still arriving is unsupported. |

Directory.Build.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
4242
<FsCheckVersion>3.3.3</FsCheckVersion>
4343
<FsCheck3Version>3.3.3</FsCheck3Version>
44+
<CsCheckVersion>4.8.0</CsCheckVersion>
4445
<HoconVersion>2.0.3</HoconVersion>
4546
<ConfigurationManagerVersion>6.0.1</ConfigurationManagerVersion>
4647
<MicrosoftLibVersion>[6.0.*,)</MicrosoftLibVersion>

src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCluster.DotNet.verified.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ namespace Akka.Cluster
213213
public System.Collections.Immutable.ImmutableDictionary<string, int> MinNrOfMembersOfRole { get; }
214214
public int MonitoredByNrOfMembers { get; }
215215
public System.TimeSpan PeriodicTasksInitialDelay { get; }
216+
public System.TimeSpan PruneGossipTombstonesAfter { get; }
216217
public System.TimeSpan? PublishStatsInterval { get; }
217218
public int ReduceGossipDifferentViewProbability { get; }
218219
public System.TimeSpan? RetryUnsuccessfulJoinAfter { get; }
@@ -257,6 +258,7 @@ namespace Akka.Cluster
257258
public override string ToString() { }
258259
public static Akka.Cluster.Member HighestPriorityOf(Akka.Cluster.Member m1, Akka.Cluster.Member m2) { }
259260
public static System.Collections.Immutable.ImmutableHashSet<Akka.Cluster.Member> PickHighestPriority(System.Collections.Generic.IEnumerable<Akka.Cluster.Member> a, System.Collections.Generic.IEnumerable<Akka.Cluster.Member> b) { }
261+
public static System.Collections.Immutable.ImmutableHashSet<Akka.Cluster.Member> PickHighestPriority(System.Collections.Generic.IEnumerable<Akka.Cluster.Member> a, System.Collections.Generic.IEnumerable<Akka.Cluster.Member> b, System.Collections.Immutable.IImmutableSet<Akka.Cluster.UniqueAddress> tombstones) { }
260262
public static Akka.Cluster.Member PickNextTransition(Akka.Cluster.Member a, Akka.Cluster.Member b) { }
261263
public static System.Collections.Immutable.ImmutableSortedSet<Akka.Cluster.Member> PickNextTransition(System.Collections.Generic.IEnumerable<Akka.Cluster.Member> a, System.Collections.Generic.IEnumerable<Akka.Cluster.Member> b) { }
262264
}

src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCluster.Net.verified.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ namespace Akka.Cluster
213213
public System.Collections.Immutable.ImmutableDictionary<string, int> MinNrOfMembersOfRole { get; }
214214
public int MonitoredByNrOfMembers { get; }
215215
public System.TimeSpan PeriodicTasksInitialDelay { get; }
216+
public System.TimeSpan PruneGossipTombstonesAfter { get; }
216217
public System.TimeSpan? PublishStatsInterval { get; }
217218
public int ReduceGossipDifferentViewProbability { get; }
218219
public System.TimeSpan? RetryUnsuccessfulJoinAfter { get; }
@@ -257,6 +258,7 @@ namespace Akka.Cluster
257258
public override string ToString() { }
258259
public static Akka.Cluster.Member HighestPriorityOf(Akka.Cluster.Member m1, Akka.Cluster.Member m2) { }
259260
public static System.Collections.Immutable.ImmutableHashSet<Akka.Cluster.Member> PickHighestPriority(System.Collections.Generic.IEnumerable<Akka.Cluster.Member> a, System.Collections.Generic.IEnumerable<Akka.Cluster.Member> b) { }
261+
public static System.Collections.Immutable.ImmutableHashSet<Akka.Cluster.Member> PickHighestPriority(System.Collections.Generic.IEnumerable<Akka.Cluster.Member> a, System.Collections.Generic.IEnumerable<Akka.Cluster.Member> b, System.Collections.Immutable.IImmutableSet<Akka.Cluster.UniqueAddress> tombstones) { }
260262
public static Akka.Cluster.Member PickNextTransition(Akka.Cluster.Member a, Akka.Cluster.Member b) { }
261263
public static System.Collections.Immutable.ImmutableSortedSet<Akka.Cluster.Member> PickNextTransition(System.Collections.Generic.IEnumerable<Akka.Cluster.Member> a, System.Collections.Generic.IEnumerable<Akka.Cluster.Member> b) { }
262264
}

src/core/Akka.Cluster.Tests/Akka.Cluster.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<PackageReference Include="xunit.v3" Version="$(Xunit3Version)" />
1818
<PackageReference Include="xunit.runner.visualstudio" Version="$(Xunit3RunnerVersion)" />
1919
<PackageReference Include="FsCheck.Xunit.v3" Version="$(FsCheck3Version)" />
20+
<PackageReference Include="CsCheck" Version="$(CsCheckVersion)" />
2021
<PackageReference Include="FluentAssertions" Version="$(FluentAssertionsVersion)" />
2122
<PackageReference Include="Fsharp.Core" Version="$(FsharpVersion)" />
2223
</ItemGroup>

src/core/Akka.Cluster.Tests/ClusterConfigSpec.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public void Clustering_must_be_able_to_parse_generic_cluster_config_elements()
3737
settings.PeriodicTasksInitialDelay.Should().Be(1.Seconds());
3838
settings.GossipInterval.Should().Be(1.Seconds());
3939
settings.GossipTimeToLive.Should().Be(2.Seconds());
40+
settings.PruneGossipTombstonesAfter.Should().Be(TimeSpan.FromHours(24));
4041
settings.HeartbeatInterval.Should().Be(1.Seconds());
4142
settings.MonitoredByNrOfMembers.Should().Be(9);
4243
settings.HeartbeatExpectedResponseAfter.Should().Be(1.Seconds());

0 commit comments

Comments
 (0)