namespace AIStudio.Tools.Services; /// /// The pauses of the indexing, e.g., while a backup copies the indexes. /// /// /// Pauses may overlap, and the indexing goes on once the last one ended. Every run which a pause /// stopped is noted together with why it was asked for, so that it can be queued again for the same /// reason once the indexing goes on: a retry the user asked for still signs in to a mailbox again /// after its server refused a sign-in, cf. DataSourceRunQueue. /// internal sealed class IndexingPauses { private readonly Lock stateLock = new(); /// /// The runs which a pause stopped, each with why it was asked for. /// private readonly Dictionary stoppedRuns = new(StringComparer.OrdinalIgnoreCase); private int count; /// /// Completes once the last pause ended. Null while the indexing is not paused. /// private TaskCompletionSource? lastPauseEnded; /// /// Whether the indexing is paused right now. /// public bool IsPaused { get { lock (this.stateLock) return this.count > 0; } } /// /// Begins a pause. From now on, every run which is about to start waits. /// public void Begin() { lock (this.stateLock) { if (this.count++ == 0) this.lastPauseEnded = new(TaskCreationOptions.RunContinuationsAsynchronously); } } /// /// Notes a run which a pause stopped. /// /// The id of the data source. /// Why the run was asked for. public void RecordStoppedRun(string dataSourceId, DataSourceEmbeddingRefreshMode refreshMode) { lock (this.stateLock) this.stoppedRuns.TryAdd(dataSourceId, refreshMode); } /// /// Ends a pause. /// /// The runs which the pauses stopped, once the last pause ended, so the caller queues them again. Otherwise, none. public IReadOnlyList End() { lock (this.stateLock) { if (this.count == 0 || --this.count > 0) return []; this.lastPauseEnded?.TrySetResult(); this.lastPauseEnded = null; var runsToQueueAgain = this.stoppedRuns.Select(stoppedRun => new DataSourceRunRequest(stoppedRun.Key, stoppedRun.Value)).ToList(); this.stoppedRuns.Clear(); return runsToQueueAgain; } } /// /// Waits while the indexing is paused. /// /// Stops the waiting, e.g., because the run waiting here was stopped. public Task WaitWhilePausedAsync(CancellationToken token) { lock (this.stateLock) return this.lastPauseEnded?.Task.WaitAsync(token) ?? Task.CompletedTask; } }