CultLib/src/GameCult.Caching/CultManagedDocument.cs
Meta Vens 456234018f Carry order as data: per-cache change Sequence, no delivery scheduler
bcae483 made the cache a delivery scheduler (tickets, _deliveryGate turn
waits, per-thread delivery queues). It deadlocked inside CultLib: a load's
delivery reaching a CultNetDatabaseSubscriptionServer observer that needs
_lifecycleGate, while a subscribe's demand handler holding that gate writes
the cache and waits for the delivery's turn.

The cache now owns order as data. Every admitted change takes the next value
of a per-cache in-memory sequence under the gate, published as
CultCacheDocumentChange<T>.Sequence. Delivery is the 544c087/b653f68 rule:
each call publishes its own changes on its own thread after releasing the
gate; nothing waits on another call's delivery. Cross-thread delivery order is
not guaranteed.

Deleted: ticket counter, _delivered, _deliveryGate, Monitor.Wait turns,
per-thread delivery queues, per-ticket catch-all, and the save-order contract
text with its observer-must-not-block rule.

Consumer: a cache-backed CultMesh document handle hands its observed and
reactive mirrors each snapshot's Sequence; ApplyCanonicalSnapshot drops one
not newer than the last applied.

Tests: SequenceIncreasesInAdmissionOrder,
ObserverWritingTheSameCacheDoesNotWaitOnOtherDeliveries,
SubscriptionDemandHandlerWritingCacheDuringDeliveryDoesNotDeadlock,
WatchRecordIgnoresStaleSequence. The last three time out on bcae483.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 20:43:11 +02:00

46 lines
1 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using R3;
namespace GameCult.Caching
{
public enum CultCacheDocumentChangeKind
{
Added,
Updated,
Removed
}
public sealed class CultCacheDocumentChange<T> where T : class
{
public CultCacheDocumentChange(
CultCacheDocumentChangeKind kind,
CultRecordKey key,
T? document,
T? previousDocument,
long sequence = 0)
{
Kind = kind;
Key = key;
Document = document;
PreviousDocument = previousDocument;
Sequence = sequence;
}
// Per-cache, in memory, assigned at admission. A consumer keeping a latest value ignores a lower one.
public long Sequence { get; }
public CultCacheDocumentChangeKind Kind { get; }
public CultRecordKey Key { get; }
public T? Document { get; }
public T? PreviousDocument { get; }
}
}