Ejemplo n.º 1
0
            /// <summary>
            /// Starts reclaiming transaction log entries that are no longer reachable. Typically called in a fire-and-forget fashion.
            /// </summary>
            /// <returns>Task to indicate when garbage collection is finished.</returns>
            public async Task ReclaimAsync()
            {
                _parent.AssertInvariants();

                if (_parent._heldCount > _parent._activeCount)
                {
                    using (await _parent._lock.EnterAsync().ConfigureAwait(false))
                    {
                        if (_parent._heldCount > _parent._activeCount)
                        {
                            Tracing.Transaction_Log_Garbage_Collection_Start(null, _parent._engineId, _parent._latest, _parent._activeCount, _parent._heldCount);

                            var difference = _parent._heldCount - _parent._activeCount;
                            using (var tx = _parent._keyValueStore.CreateTransaction())
                            {
                                foreach (var table in _parent._versionedLogs.Take((int)difference))
                                {
                                    table.Scope(tx).Clear();
                                }

                                var metadata = _parent._metadataTable.Enter(tx);
                                metadata.Update(HeldCountKey, _parent._activeCount.ToString(CultureInfo.InvariantCulture));

                                await tx.CommitAsync().ConfigureAwait(false);

                                Tracing.Transaction_Log_Garbage_Collection(null, _parent._engineId, difference, _parent._latest, _parent._activeCount);

                                for (var i = 0; i < difference; i++)
                                {
                                    _parent._versionedLogs.Dequeue();
                                }

                                _parent._heldCount = _parent._activeCount;
                            }

                            Tracing.Transaction_Log_Garbage_Collection_End(null, _parent._engineId, _parent._latest, _parent._activeCount, _parent._heldCount);
                        }
                    }
                }

                _parent.AssertInvariants();
            }
Ejemplo n.º 2
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));
            }