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
33 changes: 33 additions & 0 deletions SW.Serverless.UnitTests/ResidentHostShutdownTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SW.Serverless.Resident;
using System;
using System.Threading.Tasks;

namespace SW.Serverless.UnitTests
{
/// <summary>
/// An adapter that exits while the host is stopping. The exit is handled on the process's own
/// callback, and the host can stop and dispose its token between that handler checking it is
/// still running and scheduling the restart. The restart then threw ObjectDisposedException on
/// a thread nothing catches, and that took the whole process down.
/// </summary>
[TestClass]
public class ResidentHostShutdownTests
{
[TestMethod]
public async Task A_restart_scheduled_after_the_host_stopped_is_dropped()
{
using var loggerFactory = LoggerFactory.Create(_ => { });
var host = new ResidentAdapterHost(new ResidentOptions(), null, null, null, loggerFactory);
await host.DisposeAsync();

var supervised = ResidentAdapterHost.Supervised.For("late-exit",
new AdapterSpec { AdapterId = "late.exit", InstanceKey = "late-exit" });

host.ScheduleRestart(supervised, TimeSpan.Zero);

Assert.IsNull(supervised.PendingRestart, "a stopped host must not schedule a restart");
}
}
}
9 changes: 7 additions & 2 deletions SW.Serverless/Resident/ResidentAdapterHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -311,9 +311,14 @@ static TimeSpan Backoff(Supervised supervised) =>
/// stopped or replaced by then. A failed relaunch is retried with backoff rather than logged
/// once and abandoned, which left an integration silently dead with nothing to notice it.
/// </summary>
void ScheduleRestart(Supervised supervised, TimeSpan delay)
internal void ScheduleRestart(Supervised supervised, TimeSpan delay)
{
var restart = CancellationTokenSource.CreateLinkedTokenSource(stopping.Token);
// The host can stop, and dispose this token, after an exit was seen but before its
// restart is scheduled. Throwing here, on the process's Exited callback, crashed the app.
CancellationTokenSource restart;
try { restart = CancellationTokenSource.CreateLinkedTokenSource(stopping.Token); }
catch (ObjectDisposedException) { return; }

Interlocked.Exchange(ref supervised.PendingRestart, restart)?.Cancel();

_ = Task.Run(async () =>
Expand Down
Loading