/// <summary> /// Triggers truncation of the transaction log (everything up to and including the current snapshot that has been /// superseded by the state persisted by the successful checkpoint). /// </summary> /// <param name="transaction">The transaction to use to update the underlying tables.</param> /// <returns> /// Task completing when metadata has been updated to indicate the intent to truncate; the returned object is then used /// to trigger a background GC using <see cref="ReclaimResource.ReclaimAsync"/>. /// </returns> /// <remarks> /// If a failure happens after a successful checkpoint but before calling (and completing) the lose reference operation, /// subsequent recovery will cause coalescing of the not-yet-discarded snapshotted-but-checkpointed log entries with any /// newer entries that were added. We're hardended against these operations that will cause "create existing" and "delete /// non-existing" resources, but it's not ideal. /// /// When/if we supersede the IStateWriter approach to checkpoints and fully commit to using the key/value store passed to /// the engine's constructor, we can have a transaction that spans both updates, and not have to worry about this edge case. /// /// The reason for having the stores in two places is historical when early versions of Reaqtor managed the transaction log /// externally to an engine using specialized transaction log facilities (akin to CLFS) that were not integrated with the /// key/value store used (i.e. Service Fabric KVS). Over time, we moved away from this model, but we had to keep the /// IStateWriter and IStateReader that's used in other environments as well. /// /// It's worth considering an alternative engine implementation (alongside the current one for starters) that's "active" /// and doesn't have to be told when to checkpoint either. It simply uses the key/value store passed to its constructor, /// supports `RecoverAsync()` to recover from the given store (or has a static factory to do so, rather than a separate /// construction step), and performs checkpointing when it needs to, akin to a GC deciding it's time to perform a GC. It /// can do this by measuring the amount of dirty state and maybe have a configure maximum delay in between checkpoints to /// ensure recovery time is bounded (and replay of events is limited). It can furthermore have probes on ingress pieces /// (reachable through some IIngressProbe interface implemented by operators, so it can hunt for these using visitors over /// the subscriptions and subjects) to compute metrics and be self-tuning. There can still be a `CheckpointAsync` which /// is similar to `GC.Collect()` where an external party can trigger a checkpoint, e.g. right before unloading the engine /// or when a known burst of events has passed through, so it's worth evacuating state to disk. /// </remarks> public async Task <ReclaimResource> LoseReferenceAsync(IKeyValueStoreTransaction transaction = null) { _parent.AssertInvariants(); var createdNewTransaction = false; if (transaction == null) { createdNewTransaction = true; transaction = _parent._keyValueStore.CreateTransaction(); } // Very important to make the active count = 1 at the very least. Garbage collection is secondary (but important too). using (await _parent._lock.EnterAsync().ConfigureAwait(false)) { var tx = _parent._metadataTable.Enter(transaction); try { tx.Update(ActiveCountKey, "1"); await transaction.CommitAsync().ConfigureAwait(false); } finally { if (createdNewTransaction) { transaction.Dispose(); } } _parent._activeCount = 1; } Tracing.Transaction_Log_Lost_Reference(null, _parent._engineId, _parent._latest, _parent._activeCount, _parent._heldCount); _parent.AssertInvariants(); return(new ReclaimResource(_parent)); }
public TransactedKeyValueTable(string prefix, IKeyValueStoreTransaction transaction) { _prefix = prefix; _transaction = transaction; }
public ITransactedKeyValueTable <string, byte[]> Enter(IKeyValueStoreTransaction transaction) { return(new TransactedKeyValueTable(_prefix, transaction)); }
public ScopedTransactionLog(ITransactionLog transactionLog, IKeyValueStoreTransaction transaction) { _transactionLog = transactionLog; _transaction = transaction; }
/// <summary> /// Enters a transaction scope (by entering transactions on the underlying tables in the key/value store). /// </summary> /// <param name="transaction">The transaction object to apply edits on (in the form of table, key, value triplets).</param> /// <returns>Handle to the transaction scope; enables abandoning changes by clearing the transaction.</returns> public IScopedTransactionLog Scope(IKeyValueStoreTransaction transaction) => new ScopedTransactionLog(this, transaction);
public Impl(IKeyValueStoreTransaction transaction, string name) => (_transaction, _name) = (transaction, name);
/// <summary> /// Enters the table into an open transaction. This means any operations on the table will /// be done on the transaction. /// </summary> /// <param name="transaction">The transaction with which to scope the table.</param> /// <returns>The transacted key value table.</returns> public ITransactedKeyValueTable <string, TValue> Enter(IKeyValueStoreTransaction transaction) { return(new Impl(_table.Enter(transaction), _serialize, _deserialize)); }