CultCache: [CultIndex] is unique at commit and load; [CultRole] names a selection role; replicas apply a commit's changes as one batch (#90)
Some checks failed
CultMesh QUIC native / Build win32-x64 (push) Has been skipped
CultMesh QUIC native / Build linux-x64 (push) Has been skipped
CultMesh QUIC native / Release native binaries (push) Has been skipped
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (push) Failing after 20m15s
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (push) Successful in 48s
Some checks failed
CultMesh QUIC native / Build win32-x64 (push) Has been skipped
CultMesh QUIC native / Build linux-x64 (push) Has been skipped
CultMesh QUIC native / Release native binaries (push) Has been skipped
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (push) Failing after 20m15s
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (push) Successful in 48s
This commit is contained in:
parent
de8a062ab9
commit
7760219b7c
32 changed files with 1686 additions and 349 deletions
|
|
@ -300,50 +300,76 @@ namespace GameCult.Networking
|
|||
SubscriptionKey key)
|
||||
{
|
||||
var legacyRequest = request.ToLegacyShape(subscriptionId);
|
||||
return _database.WatchAllChanges().Subscribe(change =>
|
||||
return _database.WatchCommits().Subscribe(commit =>
|
||||
{
|
||||
lock (_lifecycleGate)
|
||||
{
|
||||
if (_disposed || !_requests.ContainsKey(key) || !_projections.TryGetValue(key, out var projection))
|
||||
return;
|
||||
if (_authorizeRequest?.Invoke(legacyRequest, peer) == false)
|
||||
{
|
||||
Reconcile(key, request);
|
||||
return;
|
||||
}
|
||||
|
||||
// D6: cites/cited are set-dependent - a change to row B can change whether row A
|
||||
// matches - so a hop-bearing selection runs the full diff-against-delivered
|
||||
// reconcile on every change instead of the single-row fast path.
|
||||
if (request.Selection.HasHop)
|
||||
{
|
||||
Reconcile(key, request);
|
||||
return;
|
||||
}
|
||||
|
||||
var sourceRecordKey = ResolveChangeRecordKey(change);
|
||||
CultNetRawDocumentRecord? matched;
|
||||
// What this subscription sends for the commit is held and sent together, tagged with the commit, so the replica
|
||||
// applies it as one commit and judges the result.
|
||||
var outbox = _outbox = new CommitOutbox(commit.Id);
|
||||
try
|
||||
{
|
||||
matched = CreateMatchedRecord(change, request, peer);
|
||||
ForwardCommit(commit, request, key, projection, legacyRequest, peer, subscriptionId);
|
||||
}
|
||||
catch (NotSupportedException refusal)
|
||||
finally
|
||||
{
|
||||
// Q6: a variant is not a record CultNet can carry; the subscriber that would have received it is told, by key.
|
||||
peer.SendCultNet(CultNetErrorMessage.ForVariantUnsupported(refusal.Message));
|
||||
return;
|
||||
_outbox = null;
|
||||
outbox.Send(peer);
|
||||
}
|
||||
|
||||
ApplyProjectedChange(
|
||||
projection,
|
||||
sourceRecordKey,
|
||||
matched,
|
||||
peer,
|
||||
subscriptionId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void ForwardCommit(
|
||||
CultNetDatabaseCommit commit,
|
||||
SubscriptionRequest request,
|
||||
SubscriptionKey key,
|
||||
SubscriptionProjectionState projection,
|
||||
CultNetDatabaseSubscribeMessage legacyRequest,
|
||||
ICultNetSchemaServerPeer peer,
|
||||
string subscriptionId)
|
||||
{
|
||||
if (_authorizeRequest?.Invoke(legacyRequest, peer) == false)
|
||||
{
|
||||
Reconcile(key, request);
|
||||
return;
|
||||
}
|
||||
|
||||
// D6: cites/cited are set-dependent - a change to row B can change whether row A
|
||||
// matches - so a hop-bearing selection runs the full diff-against-delivered
|
||||
// reconcile once per commit instead of the single-row fast path.
|
||||
if (request.Selection.HasHop)
|
||||
{
|
||||
Reconcile(key, request);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var change in commit.Changes)
|
||||
{
|
||||
var sourceRecordKey = ResolveChangeRecordKey(change);
|
||||
CultNetRawDocumentRecord? matched;
|
||||
try
|
||||
{
|
||||
matched = CreateMatchedRecord(change, request, peer);
|
||||
}
|
||||
catch (NotSupportedException refusal)
|
||||
{
|
||||
// Q6: a variant is not a record CultNet can carry; the subscriber that would have received it is told, by key.
|
||||
peer.SendCultNet(CultNetErrorMessage.ForVariantUnsupported(refusal.Message));
|
||||
continue;
|
||||
}
|
||||
|
||||
ApplyProjectedChange(
|
||||
projection,
|
||||
sourceRecordKey,
|
||||
matched,
|
||||
peer,
|
||||
subscriptionId);
|
||||
}
|
||||
}
|
||||
|
||||
private void Reconcile(SubscriptionKey key, SubscriptionRequest request)
|
||||
{
|
||||
if (!_projections.TryGetValue(key, out var projection)) return;
|
||||
|
|
@ -487,12 +513,15 @@ namespace GameCult.Networking
|
|||
string.Equals(left.SourceAgentId, right.SourceAgentId, StringComparison.Ordinal) &&
|
||||
string.Equals(left.SourceRole, right.SourceRole, StringComparison.Ordinal);
|
||||
|
||||
private static void SendUpsert(
|
||||
// The commit being forwarded, under _lifecycleGate: the change messages it produces are held here, not sent one by one.
|
||||
private CommitOutbox? _outbox;
|
||||
|
||||
private void SendUpsert(
|
||||
ICultNetSchemaServerPeer peer,
|
||||
string subscriptionId,
|
||||
CultNetRawDocumentRecord document,
|
||||
bool added) =>
|
||||
peer.SendCultNet(new CultNetDatabaseChangeRawMessage
|
||||
Deliver(peer, new CultNetDatabaseChangeRawMessage
|
||||
{
|
||||
MessageId = Guid.NewGuid().ToString("N"),
|
||||
SubscriptionId = subscriptionId,
|
||||
|
|
@ -500,11 +529,11 @@ namespace GameCult.Networking
|
|||
Document = document
|
||||
});
|
||||
|
||||
private static void SendRemoval(
|
||||
private void SendRemoval(
|
||||
ICultNetSchemaServerPeer peer,
|
||||
string subscriptionId,
|
||||
CultNetRawDocumentRecord document) =>
|
||||
peer.SendCultNet(new CultNetDatabaseChangeRawMessage
|
||||
Deliver(peer, new CultNetDatabaseChangeRawMessage
|
||||
{
|
||||
MessageId = Guid.NewGuid().ToString("N"),
|
||||
SubscriptionId = subscriptionId,
|
||||
|
|
@ -513,6 +542,38 @@ namespace GameCult.Networking
|
|||
SchemaId = document.SchemaId
|
||||
});
|
||||
|
||||
private void Deliver(ICultNetSchemaServerPeer peer, CultNetDatabaseChangeRawMessage message)
|
||||
{
|
||||
if (_outbox is { } outbox)
|
||||
outbox.Messages.Add(message);
|
||||
else
|
||||
peer.SendCultNet(message);
|
||||
}
|
||||
|
||||
private sealed class CommitOutbox
|
||||
{
|
||||
private readonly string _commitId;
|
||||
|
||||
public CommitOutbox(string commitId) => _commitId = commitId;
|
||||
|
||||
public List<CultNetDatabaseChangeRawMessage> Messages { get; } = new();
|
||||
|
||||
public void Send(ICultNetSchemaServerPeer peer)
|
||||
{
|
||||
if (Messages.Count > 1)
|
||||
{
|
||||
foreach (var message in Messages)
|
||||
{
|
||||
message.CommitId = _commitId;
|
||||
message.CommitCount = Messages.Count;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var message in Messages)
|
||||
peer.SendCultNet(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void Withdraw(SubscriptionKey key, bool sendRemovals, bool forgetRequest)
|
||||
{
|
||||
if (_subscriptions.TryRemove(key, out var subscription))
|
||||
|
|
|
|||
Loading…
Reference in a new issue