Skip to content

[BUG] LWWDictionary.Delta throws ArgumentNullException when underlying ORDictionary.Delta is null #7910

@Aaronontheweb

Description

@Aaronontheweb

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:

  1. After initial construction when no modifications have been made
  2. 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.

Links to source

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions