Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ namespace RedShirt.Example.JobWorker.Core.Services.ExecutionState;
/// </summary>
internal interface IJobLoaderStateReaderService
{
bool HasLoaderStarted();
/// <summary>
/// Thread-safe addition of callback actions invoked once the loader has both started and stopped.
/// If the loader is already finished, the callback is invoked immediately.
/// </summary>
/// <param name="callback"></param>
void AddOnFinishCallback(Action callback);

bool IsLoaderFinished();
}
Expand All @@ -24,44 +29,97 @@ internal interface IJobLoaderStateService : IJobLoaderStateReaderService
internal sealed class JobLoaderStateService : IJobLoaderStateService
{
/// <summary>
/// Multithreading protection.
/// Feels a little silly for a service that only sets booleans to true, but it makes automated audits happy.
/// Multithreading protection for start/stop flags and finish callbacks.
/// </summary>
private readonly Lock _lock = new();

private bool _isFinished;

private bool _isStarted;

private Action? _onFinishCallbacks;

private bool IsFinishedUnsafe()
{
return _isStarted && _isFinished;
}

/// <summary>
/// Detaches finish callbacks if the loader is finished.
/// Must be safe to call with or without the caller already holding <see cref="_lock" />
/// (<see cref="Lock" /> is reentrant).
/// </summary>
private Action? TakeCallbacksIfFinished()
{
lock (_lock)
{
if (!IsFinishedUnsafe())
{
return null;
}

return Interlocked.Exchange(ref _onFinishCallbacks, null);
}
}

private static void InvokeCallbacks(Action? callbacks)
{
if (callbacks is null)
{
return;
}

foreach (var invocation in callbacks.GetInvocationList())
{
((Action) invocation)();
}
}

public void ReportLoaderStart()
{
Action? callbacks;
lock (_lock)
{
_isStarted = true;
callbacks = TakeCallbacksIfFinished();
}

InvokeCallbacks(callbacks);
}

public void ReportLoaderStop()
{
Action? callbacks;
lock (_lock)
{
_isFinished = true;
callbacks = TakeCallbacksIfFinished();
}

InvokeCallbacks(callbacks);
}

public bool HasLoaderStarted()
public bool IsLoaderFinished()
{
lock (_lock)
{
return _isStarted;
return IsFinishedUnsafe();
}
}

public bool IsLoaderFinished()
public void AddOnFinishCallback(Action callback)
{
ArgumentNullException.ThrowIfNull(callback);

lock (_lock)
{
return _isStarted && _isFinished;
if (!IsFinishedUnsafe())
{
_onFinishCallbacks += callback;
return;
}
}

callback();
}
}
Loading
Loading