Kit.Core/LibExternal/System.Reactive/Concurrency/CatchScheduler.cs

169 lines
6.2 KiB
C#

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT License.
// See the LICENSE file in the project root for more information.
using System.Reactive.Disposables;
using System.Runtime.CompilerServices;
namespace System.Reactive.Concurrency
{
internal sealed class CatchScheduler<TException> : SchedulerWrapper
where TException : Exception
{
private readonly Func<TException, bool> _handler;
public CatchScheduler(IScheduler scheduler, Func<TException, bool> handler)
: base(scheduler)
{
_handler = handler;
}
protected override Func<IScheduler, TState, IDisposable> Wrap<TState>(Func<IScheduler, TState, IDisposable> action)
{
return (self, state) =>
{
try
{
return action(GetRecursiveWrapper(self), state);
}
catch (TException exception) when (_handler(exception))
{
return Disposable.Empty;
}
};
}
public CatchScheduler(IScheduler scheduler, Func<TException, bool> handler, ConditionalWeakTable<IScheduler, IScheduler> cache)
: base(scheduler, cache)
{
_handler = handler;
}
protected override SchedulerWrapper Clone(IScheduler scheduler, ConditionalWeakTable<IScheduler, IScheduler> cache)
{
return new CatchScheduler<TException>(scheduler, _handler, cache);
}
protected override bool TryGetService(IServiceProvider provider, Type serviceType, out object? service)
{
service = provider.GetService(serviceType);
if (service != null)
{
if (serviceType == typeof(ISchedulerLongRunning))
{
service = new CatchSchedulerLongRunning((ISchedulerLongRunning)service, _handler);
}
else if (serviceType == typeof(ISchedulerPeriodic))
{
service = new CatchSchedulerPeriodic((ISchedulerPeriodic)service, _handler);
}
}
return true;
}
private class CatchSchedulerLongRunning : ISchedulerLongRunning
{
private readonly ISchedulerLongRunning _scheduler;
private readonly Func<TException, bool> _handler;
public CatchSchedulerLongRunning(ISchedulerLongRunning scheduler, Func<TException, bool> handler)
{
_scheduler = scheduler;
_handler = handler;
}
public IDisposable ScheduleLongRunning<TState>(TState state, Action<TState, ICancelable> action)
{
// Note that avoiding closure allocation here would introduce infinite generic recursion over the TState argument
return _scheduler.ScheduleLongRunning(
state,
(state1, cancel) =>
{
try
{
action(state1, cancel);
}
catch (TException exception) when (_handler(exception))
{
}
});
}
}
private sealed class CatchSchedulerPeriodic : ISchedulerPeriodic
{
private sealed class PeriodicallyScheduledWorkItem<TState> : IDisposable
{
private SingleAssignmentDisposableValue _cancel;
private bool _failed;
private readonly Func<TState, TState> _action;
private readonly CatchSchedulerPeriodic _catchScheduler;
public PeriodicallyScheduledWorkItem(CatchSchedulerPeriodic scheduler, TState state, TimeSpan period, Func<TState, TState> action)
{
_catchScheduler = scheduler;
_action = action;
// Note that avoiding closure allocation here would introduce infinite generic recursion over the TState argument
_cancel.Disposable = scheduler._scheduler.SchedulePeriodic(state, period, state1 => Tick(state1).state);
}
public void Dispose()
{
_cancel.Dispose();
}
private (PeriodicallyScheduledWorkItem<TState> @this, TState state) Tick(TState state)
{
//
// Cancellation may not be granted immediately; prevent from running user
// code in that case. Periodic schedulers are assumed to introduce some
// degree of concurrency, so we should return from the SchedulePeriodic
// call eventually, allowing the d.Dispose() call in the catch block to
// take effect.
//
if (_failed)
{
return default;
}
try
{
return (this, _action(state));
}
catch (TException exception)
{
_failed = true;
if (!_catchScheduler._handler(exception))
{
throw;
}
_cancel.Dispose();
return default;
}
}
}
private readonly ISchedulerPeriodic _scheduler;
private readonly Func<TException, bool> _handler;
public CatchSchedulerPeriodic(ISchedulerPeriodic scheduler, Func<TException, bool> handler)
{
_scheduler = scheduler;
_handler = handler;
}
public IDisposable SchedulePeriodic<TState>(TState state, TimeSpan period, Func<TState, TState> action)
{
return new PeriodicallyScheduledWorkItem<TState>(this, state, period, action);
}
}
}
}