private static async ValueTask <long> SlowEnqueueAsync(FasterLog @this, IReadOnlySpanBatch readOnlySpanBatch, CancellationToken token) { long logicalAddress; while (true) { var task = @this.CommitTask; if (@this.TryEnqueue(readOnlySpanBatch, out logicalAddress)) { break; } if (@this.NeedToWait(@this.CommittedUntilAddress, @this.TailAddress)) { // Wait for *some* commit - failure can be ignored except if the token was signaled (which the caller should handle correctly) try { await task.WithCancellationAsync(token); } catch when(!token.IsCancellationRequested) { } } } return(logicalAddress); }
/// <summary> /// Append batch of entries to log (async) - completes after batch is committed to storage. /// Does NOT itself issue flush! /// </summary> /// <param name="readOnlySpanBatch"></param> /// <param name="token">Cancellation token</param> /// <returns></returns> public async ValueTask <long> EnqueueAndWaitForCommitAsync(IReadOnlySpanBatch readOnlySpanBatch, CancellationToken token = default) { token.ThrowIfCancellationRequested(); long logicalAddress; Task <LinkedCommitInfo> task; // Phase 1: wait for commit to memory while (true) { task = CommitTask; if (TryEnqueue(readOnlySpanBatch, out logicalAddress)) { break; } if (NeedToWait(CommittedUntilAddress, TailAddress)) { // Wait for *some* commit - failure can be ignored except if the token was signaled (which the caller should handle correctly) try { await task.WithCancellationAsync(token); } catch when(!token.IsCancellationRequested) { } } } // since the task object was read before enqueueing, there is no need for the CommittedUntilAddress >= logicalAddress check like in WaitForCommit // Phase 2: wait for commit/flush to storage while (true) { LinkedCommitInfo linkedCommitInfo; try { linkedCommitInfo = await task.WithCancellationAsync(token); } catch (CommitFailureException e) { linkedCommitInfo = e.LinkedCommitInfo; if (logicalAddress >= linkedCommitInfo.CommitInfo.FromAddress && logicalAddress < linkedCommitInfo.CommitInfo.UntilAddress) { throw; } } if (linkedCommitInfo.CommitInfo.UntilAddress < logicalAddress + 1) { task = linkedCommitInfo.NextTask; } else { break; } } return(logicalAddress); }
/// <summary> /// Enqueue batch of entries to log in memory (async) - completes after entry is /// appended to memory, NOT committed to storage. /// </summary> /// <param name="readOnlySpanBatch">Batch to enqueue</param> /// <param name="token">Cancellation token</param> /// <returns></returns> public ValueTask <long> EnqueueAsync(IReadOnlySpanBatch readOnlySpanBatch, CancellationToken token = default) { token.ThrowIfCancellationRequested(); if (TryEnqueue(readOnlySpanBatch, out long address)) { return(new ValueTask <long>(address)); } return(SlowEnqueueAsync(this, readOnlySpanBatch, token)); }
/// <summary> /// Enqueue batch of entries to log (in memory) - no guarantee of flush/commit /// </summary> /// <param name="readOnlySpanBatch">Batch of entries to be enqueued to log</param> /// <returns>Logical address of added entry</returns> public long Enqueue(IReadOnlySpanBatch readOnlySpanBatch) { long logicalAddress; while (!TryEnqueue(readOnlySpanBatch, out logicalAddress)) { ; } return(logicalAddress); }
/// <summary> /// Append batch of entries to log (async) - completes after batch is committed to storage. /// Does NOT itself issue flush! /// </summary> /// <param name="readOnlySpanBatch"></param> /// <returns></returns> public async ValueTask <long> EnqueueAndWaitForCommitAsync(IReadOnlySpanBatch readOnlySpanBatch) { long logicalAddress; Task <LinkedCommitInfo> task; // Phase 1: wait for commit to memory while (true) { task = CommitTask; if (TryEnqueue(readOnlySpanBatch, out logicalAddress)) { break; } if (NeedToWait(CommittedUntilAddress, TailAddress)) { // Wait for *some* commit - failure can be ignored try { await task; } catch { } } } // Phase 2: wait for commit/flush to storage while (true) { LinkedCommitInfo linkedCommitInfo; try { linkedCommitInfo = await task; } catch (CommitFailureException e) { linkedCommitInfo = e.LinkedCommitInfo; if (logicalAddress >= linkedCommitInfo.CommitInfo.FromAddress && logicalAddress < linkedCommitInfo.CommitInfo.UntilAddress) { throw e; } } if (linkedCommitInfo.CommitInfo.UntilAddress < logicalAddress + 1) { task = linkedCommitInfo.NextTask; } else { break; } } return(logicalAddress); }
/// <summary> /// Append batch of entries to log - spin-waits until entry is committed to storage. /// Does NOT itself issue flush! /// </summary> /// <param name="readOnlySpanBatch"></param> /// <returns></returns> public long EnqueueAndWaitForCommit(IReadOnlySpanBatch readOnlySpanBatch) { long logicalAddress; while (!TryEnqueue(readOnlySpanBatch, out logicalAddress)) { ; } while (CommittedUntilAddress < logicalAddress + 1) { ; } return(logicalAddress); }
/// <summary> /// Enqueue batch of entries to log in memory (async) - completes after entry is /// appended to memory, NOT committed to storage. /// </summary> /// <param name="readOnlySpanBatch"></param> /// <returns></returns> public async ValueTask <long> EnqueueAsync(IReadOnlySpanBatch readOnlySpanBatch) { long logicalAddress; while (true) { var task = CommitTask; if (TryEnqueue(readOnlySpanBatch, out logicalAddress)) { break; } if (NeedToWait(CommittedUntilAddress, TailAddress)) { // Wait for *some* commit - failure can be ignored try { await task; } catch { } } } return(logicalAddress); }
/// <summary> /// Try to enqueue batch of entries as a single atomic unit (to memory). Entire /// batch needs to fit on one log page. /// </summary> /// <param name="readOnlySpanBatch">Batch to be appended to log</param> /// <param name="logicalAddress">Logical address of first added entry</param> /// <returns>Whether the append succeeded</returns> public bool TryEnqueue(IReadOnlySpanBatch readOnlySpanBatch, out long logicalAddress) { return(TryAppend(readOnlySpanBatch, out logicalAddress, out _)); }