Part of a ModuleManager class that handles enabling a module. It stages a module startup by creating a registration scope and cancellation token, invokes the module StartAsync, waits up to a deadline for the task to complete using a polling loop, then commits registrations on success or stops and rolls back on failure.
namespace SboxSuperuser.Engine.Modules;
internal sealed partial class ModuleManager
{
private async Task<CommandResult> EnableAsync( ModuleEntry entry, Func<bool> authorized, CancellationToken cancellation )
{
Refresh( entry );
if ( cancellation.IsCancellationRequested || _disposed || !_available() || !authorized() )
{
return Result( CommandStatus.Cancelled, "Module startup authority or lifetime ended." );
}
if ( entry.State == ModuleState.Enabled )
{
return Result( CommandStatus.Success, "Module is already enabled." );
}
if ( entry.State is ModuleState.Quarantined or ModuleState.Starting or ModuleState.Stopping )
{
return Result( CommandStatus.Conflict, "The previous module generation has not completed cleanup." );
}
entry.Scope = new CommandRegistrationScope( _registry, entry.Descriptor.Id, _discover, managed: true );
entry.Cancellation = new CancellationTokenSource();
entry.Cleanup = null;
entry.CleanupFailed = false;
entry.State = ModuleState.Starting;
entry.Message = "Staging contributions.";
using var cancelled = cancellation.Register( () => Cancel( entry ) );
try
{
entry.Startup = entry.Module.StartAsync( new ModuleRegistration( entry.Scope ), entry.Cancellation.Token )
?? Task.FromException( new InvalidOperationException( "Module startup returned no task." ) );
}
catch ( Exception error )
{
entry.Startup = Task.FromException( error );
}
var finished = await WithinDeadlineAsync( entry.Startup, cancellation );
if ( finished && entry.Startup.IsCompletedSuccessfully && !_disposed && _available() && authorized()
&& !cancellation.IsCancellationRequested && !entry.Cancellation.IsCancellationRequested )
{
try
{
entry.Scope.Commit();
entry.State = ModuleState.Enabled;
entry.Message = "Enabled.";
return Result( CommandStatus.Success, "Module enabled." );
}
catch ( Exception error )
{
_report( error );
}
}
await StopEntryAsync( entry, ModuleState.Faulted, cancellation );
return Result( entry.State == ModuleState.Quarantined ? CommandStatus.Unknown : CommandStatus.Failed,
$"Module '{entry.Descriptor.Id}' did not start; registrations were rolled back. {entry.Message}" );
}
private async Task<bool> WithinDeadlineAsync( Task work, CancellationToken cancellation )
{
// Task.WhenAny overloads are blocked by the engine whitelist. Yield in bounded timer slices.
var timer = System.Diagnostics.Stopwatch.StartNew();
while ( !work.IsCompleted && !cancellation.IsCancellationRequested && timer.Elapsed < _deadline )
{
await Task.Delay( 10 );
}
return work.IsCompleted;
}
}