-
Couldn't load subscription status.
- Fork 1.1k
Description
Description
LWWDictionary throws an ArgumentNullException with message "Delta operation can not be null" when the Replicator attempts to access the Delta property on a dictionary where the underlying ORDictionary.Delta is null.
This occurs during normal DData operations and results in ModifyFailure messages being delivered as dead letters.
Versions & Operating System
- Akka.NET version: 1.5.53
- OS: Windows
- .NET Runtime: .NET 9.0
Expected behavior
The LWWDictionary.Delta property should handle the case where ORDictionary.Delta returns null (which is valid according to the ORDictionary implementation after initialization or ResetDelta()).
Actual behavior
An ArgumentNullException is thrown, causing the update operation to fail with a ModifyFailure message:
Message [ModifyFailure] from [akka://DrawTogether/user/ddataReplicatorSupervisor/ddataReplicator#729325827] to [akka://DrawTogether/user/all-drawings-publisher#860097492] was unhandled.
Message content: ModifyFailure(all-drawings-index, System.ArgumentNullException: Delta operation can not be null (Parameter 'underlying')
at Akka.DistributedData.LWWDictionary`2.LWWDictionaryDelta..ctor(IDeltaOperation underlying)
at Akka.DistributedData.LWWDictionary`2.get_Delta()
at Akka.DistributedData.LWWDictionary`2.Akka.DistributedData.IDeltaReplicatedData.get_Delta()
at Akka.DistributedData.Replicator.ReceiveUpdate(IKey key, Func`2 modify, IWriteConsistency consistency, Object request))
Reproduction
public sealed class AllDrawingsPublisherActor : UntypedActor
{
private readonly IActorRef _replicator = DistributedData.Get(Context.System).Replicator;
private readonly IWriteConsistency _writeConsistency = new WriteMajority(TimeSpan.FromSeconds(3));
private readonly Cluster _cluster = Cluster.Get(Context.System);
protected override void OnReceive(object message)
{
switch (message)
{
case DrawingActivityUpdate update:
{
var key = ClusterConstants.AllDrawingsIndexKey;
var updateOp = new Update(key, LWWDictionary<string, DrawingActivityUpdate>.Empty, _writeConsistency,
dictionary =>
{
var dict = (LWWDictionary<string, DrawingActivityUpdate>)dictionary;
dict = dict.SetItem(_cluster, update.DrawingSessionId.SessionId, update);
return dict;
});
_replicator.Tell(updateOp);
break;
}
}
}
}The error occurs when the Replicator internally accesses the Delta property on the modified dictionary.
Root Cause
In LWWDictionary.cs, the Delta property is implemented as:
public IDeltaOperation Delta => new LWWDictionaryDelta(Underlying.Delta);However, ORDictionary.Delta can legitimately return null in two cases:
- After initial construction when no modifications have been made
- After
ResetDelta()is called
The LWWDictionaryDelta constructor throws when passed null:
public LWWDictionaryDelta(ORDictionary<TKey, LWWRegister<TValue>>.IDeltaOperation underlying)
{
Underlying = underlying ?? throw new ArgumentNullException(
nameof(underlying), "Delta operation can not be null");
// ...
}Proposed Fix
The LWWDictionary.Delta property should handle the null case:
public IDeltaOperation Delta => Underlying.Delta == null ? null : new LWWDictionaryDelta(Underlying.Delta);Or alternatively, the interface contract for IDeltaReplicatedData.Delta should clearly specify whether null is an acceptable return value, and all implementations should be consistent.