Repository navigation
Expand file tree
/
Copy pathAtomicCommandCommit.cs
More file actions
194 lines (175 loc) · 13 KB
/
Copy pathAtomicCommandCommit.cs
File metadata and controls
194 lines (175 loc) · 13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
using System.Text.Json;
using KeyLoad.Core.BackupRestore.Execution;
using KeyLoad.Core.CommandOutcomes.Contracts;
using KeyLoad.Core.CommandOutcomes.Execution;
using KeyLoad.Core.CommandOutcomes.Identity;
using KeyLoad.Storage;
using ManagedCode.Communication;
using ManagedCode.Communication.Commands;
namespace KeyLoad.Core;
/// <summary>Executes AtomicCommandCommit operations against the caller's database context.</summary>
public static class AtomicCommandCommit
{
internal const int AtomicCommandCommitInitialSequence = 0;
internal const string PreviousCommandIncarnationMessage = "This command belongs to a previous database incarnation.";
internal const string CommandContentConflictMessage = "The command ID was already used with different content.";
internal const string CommittedClockAheadMessage = "The operation time is behind the committed storage time.";
internal const string InvalidCommandJsonMessage = "The operation contains invalid protocol JSON.";
internal const string MissingOutcomePartitionMessage = "A partition-scoped command outcome has no partition identity.";
/// <summary>Prepares one authorized operation once before sending identical bytes to storage copies.</summary>
/// <param name = "engine">The shared database execution context.</param>
/// <param name = "operation">The operation.</param>
/// <param name = "cancellationToken">The cancellation Token.</param>
public static Command<DatabaseCommand> PrepareStorageOperation(this DatabaseEngine engine, Command<DatabaseCommand> operation, CancellationToken cancellationToken)
=> engine.PrepareStorageOperation(operation, null, cancellationToken);
/// <summary>Prepares one operation at a business time no earlier than the committed clock or <paramref name="notBefore"/>.</summary>
internal static Command<DatabaseCommand> PrepareStorageOperation(this DatabaseEngine engine, Command<DatabaseCommand> operation, DateTimeOffset? notBefore, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(engine) /* KLD0041: The BCL null guard accepts this existing reference through its object parameter without copying its contents. */;
cancellationToken.ThrowIfCancellationRequested();
return engine.PrepareOwnedStorageOperation(engine.OwnCommand(operation), notBefore, cancellationToken);
}
/// <summary>Prepares the already owned operation under the committed clock.</summary>
internal static Command<DatabaseCommand> PrepareOwnedStorageOperation(this DatabaseEngine engine,
Command<DatabaseCommand> owned, DateTimeOffset? notBefore, CancellationToken cancellationToken)
{
var prepared = engine.Store.Read(view =>
{
cancellationToken.ThrowIfCancellationRequested();
var now = engine.EvaluationClock.GetUtcNow();
if (view.ReadOwnedValue(StorageCommandKeys.Clock(StorageCommandKeys.PartitionOf(owned))) is { } encoded)
{
var committed = OrleansSerialization.Deserialize<DateTimeOffset>(encoded);
now = now < committed ? committed : now;
}
if (notBefore is { } floor && now < floor)
{
now = floor;
}
owned.Timestamp = now.UtcDateTime;
return engine.PrepareQueueRetryInView(view, owned);
});
return prepared;
}
internal static StorageOperationResult ApplyCore(this DatabaseEngine engine, Command<DatabaseCommand> operation, long? canonicalSequence, bool selectEmbeddedClock, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(operation) /* KLD0041: The BCL null guard accepts this existing reference through its object parameter without copying its contents. */;
operation = engine.OwnCommand(operation);
var siloLocal = engine.IsOwnedSiloLocalOperation(operation);
cancellationToken.ThrowIfCancellationRequested();
var committed = engine.Store.Commit((transaction, position) =>
engine.CompileCommand(transaction, position, operation, canonicalSequence, selectEmbeddedClock, siloLocal, cancellationToken));
return committed;
}
internal static Task<StorageOperationResult> ApplyCoreAsync(this DatabaseEngine engine, Command<DatabaseCommand> operation,
long? canonicalSequence, bool selectEmbeddedClock, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(operation) /* KLD0041: The BCL null guard accepts this existing reference through its object parameter without copying its contents. */;
operation = engine.OwnCommand(operation);
var siloLocal = engine.IsOwnedSiloLocalOperation(operation);
return engine.Store.CommitAsync((transaction, position) =>
engine.CompileCommand(transaction, position, operation, canonicalSequence, selectEmbeddedClock, siloLocal, cancellationToken),
cancellationToken);
}
private static StorageOperationResult CompileCommand(this DatabaseEngine engine, IAtomicTransaction transaction,
long position, Command<DatabaseCommand> operation, long? canonicalSequence, bool selectEmbeddedClock, bool siloLocal,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var admitted = selectEmbeddedClock ? engine.EvaluateCommand(operation, engine.EvaluationClock.GetUtcNow()) : operation;
if (selectEmbeddedClock)
{
admitted = engine.PrepareQueueRetryInView(transaction, admitted);
}
var current = StorageOperationApply.ReadCanonicalSequence(transaction, StorageCommandKeys.PartitionOf(admitted));
var sequence = siloLocal ? StorageOperationApply.InitialCanonicalSequence
: canonicalSequence ?? StorageOperationApply.SelectNextCanonicalSequence(current);
var result = engine.ApplyCommittedCommand(transaction, admitted, position, current, sequence, siloLocal, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
return result.Result;
}
internal static (StorageOperationResult Result, bool Persisted) ApplyCommittedCommand(this DatabaseEngine engine, IAtomicTransaction transaction, Command<DatabaseCommand> operation, long position, long currentSequence, long replicationIndex, bool siloLocal, CancellationToken cancellationToken,
ISet<PartitionRef>? readPartitions = null)
{
var partitionScope = CommandOutcomePartitionIdentity.Resolve(operation);
var rosterTransaction = new AtomicPartitionRosterTransaction(transaction, position, replicationIndex,
currentSequence, partitionScope.Partition, readPartitions);
var outcome = engine.ExecuteAndBuildOutcome(rosterTransaction, operation, position, currentSequence, replicationIndex, partitionScope, siloLocal, out var replayed, out var selection, out var persistOutcome, cancellationToken);
if (replayed || !persistOutcome)
{
return (new StorageOperationResult(outcome.CanonicalSequence, outcome.Result /* KLD0041: The existing native stored-outcome schema carries different operation payload types; this step preserves that exact persisted result. */) /* KLD0041: This constructor preserves the original native mixed-operation result in its existing persisted storage contract. */, false);
}
if (!siloLocal)
{
StorageOperationApply.RequireNextCanonicalSequence(currentSequence, replicationIndex);
}
outcome = outcome with
{
CanonicalSequence = replicationIndex
};
CommandOutcomeApplication.PersistCommandOutcome(rosterTransaction, operation, selection.Key, outcome, replicationIndex, persistOutcome);
rosterTransaction.PersistCandidates();
return (new StorageOperationResult(outcome.CanonicalSequence, outcome.Result /* KLD0041: The existing native stored-outcome schema carries different operation payload types; this step preserves that exact persisted result. */) /* KLD0041: This constructor preserves the original native mixed-operation result in its existing persisted storage contract. */, true);
}
internal static StoredOutcome ExecuteAndBuildOutcome(this DatabaseEngine engine, AtomicPartitionRosterTransaction transaction, Command<DatabaseCommand> operation, long position, long currentSequence, long replicationIndex, CommandOutcomePartitionScope partitionScope, bool siloLocal, out bool replayed, out CommandOutcomeSelection selection, out bool persistOutcome, CancellationToken cancellationToken)
{
replayed = false;
selection = CommandOutcomeKeyResolver.ForNew(operation.UserId!, operation.CommandId, partitionScope);
persistOutcome = false;
var authorizationPending = false;
long policyEpoch = AtomicCommandCommitInitialSequence;
var onlineTextAuthority = (OnlineTextPublicationCommands.CaptureOnlineTextOutcomeAuthority(operation)).Value;
global::KeyLoad.Core.Messaging.RemoteTransferAcceptFailureAuthority? transferFailure = null;
Result<object> /* KLD0041: The existing native stored-outcome schema carries different operation payload types; this step preserves that exact persisted result. */ result;
global::KeyLoad.Core.DatabaseComposition.CompositionOutcomeAuthority? compositionAuthority = null;
try
{
var principal = engine.Principal(transaction, operation.UserId!, engine.Clock.GetUtcNow());
authorizationPending = true;
policyEpoch = principal.PolicyEpoch;
var placement = (engine.AuthorizeOperation(transaction, principal, operation)).Value;
authorizationPending = false;
selection = CommandOutcomeKeyResolver.Select(transaction, operation.UserId!, operation.CommandId, partitionScope);
persistOutcome = selection.Outcome is null;
if (selection.Outcome is { } previous)
{
replayed = true;
return engine.ReplayCommand(transaction, operation, previous, selection, siloLocal, principal, currentSequence);
}
if (!siloLocal)
{
StorageOperationApply.RequireNextCanonicalSequence(currentSequence, replicationIndex);
}
engine.RequireOriginalEffectAdmission(transaction, principal, operation);
ValidateCommandClock(transaction, StorageCommandKeys.PartitionOf(operation), operation.Timestamp);
engine.ValidateQueueRetryDecisions(transaction, principal, operation);
transferFailure = (engine.CaptureRemoteTransferFailureAuthority(transaction, principal, operation)).Value;
result = (engine.Execute(transaction, principal, operation, replicationIndex > AtomicCommandCommitInitialSequence ? replicationIndex : position, placement, cancellationToken) /* KLD0041: The dispatcher places the typed business outcome in the existing native mixed-operation stored-result schema. */);
compositionAuthority = (engine.CaptureCompositionOutcome(transaction, principal, operation, result) /* KLD0041: The existing native stored-outcome schema carries different operation payload types; this step preserves that exact persisted result. */).Value;
}
catch (KeyLoadException exception) when (exception.Code is not (ErrorCode.Corruption or ErrorCode.FormatUnsupported or ErrorCode.RecoveryRequired or ErrorCode.UnknownWriteOutcome))
{
if (authorizationPending)
{
persistOutcome = CommandOutcomeKeyResolver.IsUnstored(transaction, operation.UserId!, operation.CommandId, partitionScope);
}
transaction.Reset();
replayed = false;
result = (ManagedCode.Communication.Result<object>.Fail(Errors.Problem(exception.Code, exception.Message)) /* KLD0041: The existing native stored-outcome schema carries different operation payload types; this step preserves that exact persisted result. */);
}
catch (JsonException)
{
transaction.Reset();
replayed = false;
result = (ManagedCode.Communication.Result<object>.Fail(Errors.Problem(ErrorCode.Validation, InvalidCommandJsonMessage)) /* KLD0041: The existing native stored-outcome schema carries different operation payload types; this step preserves that exact persisted result. */);
}
return engine.BuildStoredOutcome(operation, policyEpoch, result, compositionAuthority, partitionScope, onlineTextAuthority, transferFailure, selection.Outcome?.CanonicalSequence ?? (persistOutcome ? replicationIndex : currentSequence)) /* KLD0041: The existing native stored-outcome schema carries different operation payload types; this step preserves that exact persisted result. */;
}
internal static void ValidateCommandClock(AtomicPartitionRosterTransaction view, PartitionRef? partition, DateTimeOffset evaluatedAt)
{
if (view.ReadOwnedValue(StorageCommandKeys.Clock(partition)) is { } clock && evaluatedAt < OrleansSerialization.Deserialize<DateTimeOffset>(clock))
{
throw Errors.Fail(ErrorCode.ClockUncertain, CommittedClockAheadMessage);
}
}
}