Skip to content

Commit

Permalink
Ensure the errors from AsyncLazyFailable<TResult>._activeTask are a…
Browse files Browse the repository at this point in the history
…lways consumed by at least one task (i.e. never bubble up to the worker). pt.2
  • Loading branch information
drasmart committed Oct 16, 2024
1 parent f1c1594 commit e2f50fd
Showing 1 changed file with 23 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public class AsyncLazyFailable<TResult>
private readonly Func<TResult> _mainFunc;
private readonly object _taskLock = new object();
private Task<TResult> _activeTask;
private Task _errorHandlingTask;
private TResult _result;

// volatile:
Expand Down Expand Up @@ -90,41 +91,49 @@ public async Task<TResult> GetValueAsync(CancellationToken token)

private Task<TResult> GetOrBuildActiveTask()
{
Task<TResult> createdTask;
lock (_taskLock)
{
if (_activeTask is object) // i.e. is not null
{
return _activeTask;
}
createdTask = _activeTask = Task.Run(TryGetNewValue);
_activeTask = Task.Run(_mainFunc);
_errorHandlingTask = ConsumeActiveTaskFailure(_activeTask);
return _activeTask;
}
_ = WatchForActiveTaskFailure(createdTask); // fire and forget
return createdTask;
}

private TResult TryGetNewValue()
{
_result = _mainFunc();
try
{
_result = _mainFunc();

// volatile write, can’t be reordered with prior operations
_hasResult = true;
// volatile write, can’t be reordered with prior operations
_hasResult = true;

return _result;
return _result;
}
catch
{
lock (_taskLock)
{
_activeTask = null;
_errorHandlingTask = null;
}
throw;
}
}

private async Task WatchForActiveTaskFailure(Task<TResult> mainTask)
private async Task ConsumeActiveTaskFailure(Task<TResult> mainTask)
{
try
{
await mainTask;
_ = await mainTask;
}
catch
{
lock (_taskLock)
{
_activeTask = null;
}
// nop -- just consume the failure
}
}
}
Expand Down

0 comments on commit e2f50fd

Please sign in to comment.