namespace AIStudio.Tools.Services.Indexing; /// /// The pause between two indexing steps, which spares an embedding server that many people share. /// /// /// Not to be confused with the pauses of the indexing as a whole, e.g., while a backup copies the /// indexes, cf. IndexingPauses.

/// /// The pause is a gap between the end of one step and the start of the next, whichever data source /// either of them belongs to: it is about the load on the embedding server, not about the order of /// the data sources. Only a step which worked on documents opens a gap. Its pause is drawn once the /// next step is about to start, from the bounds which apply then, and the time which passed in the /// meantime counts toward it. When no step comes, nothing is drawn. ///
internal sealed class PauseBetweenSteps { private readonly Lock stateLock = new(); /// /// When the last step which worked on documents ended. Null before the first one. /// private DateTimeOffset? lastStepEndedUtc; /// /// The pause drawn for the gap since the last step. Null until a step is about to start. /// private TimeSpan? drawnPause; /// /// Notes that a step which worked on documents ended, which opens a new gap. /// /// The current time, passed in so the pause can be tested. public void RecordStepEnded(DateTimeOffset nowUtc) { lock (this.stateLock) { this.lastStepEndedUtc = nowUtc; this.drawnPause = null; } } /// /// Works out how long the step which is about to start waits. /// /// /// The first step which asks after a gap opened draws its pause, with both bounds included. A step /// which stops while it waits, e.g., because its data source was deleted, leaves the same pause /// to the step after it, which waits only for the rest. /// /// The current time, passed in so the pause can be tested. /// The bounds which apply now. /// Draws the pause. /// The pause and what is left of it, or NONE when the step starts right away. public PauseBeforeStep GetPauseBeforeNextStep(DateTimeOffset nowUtc, PauseRange range, Random random) { lock (this.stateLock) { if (this.lastStepEndedUtc is not { } stepEndedUtc) return PauseBeforeStep.NONE; if (this.drawnPause is null) { // Nothing is drawn, so bounds which are set before the next step still apply to this gap: if (range.IsNone) return PauseBeforeStep.NONE; this.drawnPause = TimeSpan.FromSeconds(random.Next(range.MinimumSeconds, range.MaximumSeconds + 1)); } var remaining = stepEndedUtc + this.drawnPause.Value - nowUtc; return remaining > TimeSpan.Zero ? new(this.drawnPause.Value, remaining) : PauseBeforeStep.NONE; } } }