Example #1
0
            /// <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));
            }
Example #2
0
 public TransactedKeyValueTable(string prefix, IKeyValueStoreTransaction transaction)
 {
     _prefix      = prefix;
     _transaction = transaction;
 }
Example #3
0
 public ITransactedKeyValueTable <string, byte[]> Enter(IKeyValueStoreTransaction transaction)
 {
     return(new TransactedKeyValueTable(_prefix, transaction));
 }
Example #4
0
 public ScopedTransactionLog(ITransactionLog transactionLog, IKeyValueStoreTransaction transaction)
 {
     _transactionLog = transactionLog;
     _transaction    = transaction;
 }
Example #5
0
 /// <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));
 }