diff --git a/src/DiffEngineViewer.Tests/AttachedViewerTests.cs b/src/DiffEngineViewer.Tests/AttachedViewerTests.cs index bb8f4314..d09181f6 100644 --- a/src/DiffEngineViewer.Tests/AttachedViewerTests.cs +++ b/src/DiffEngineViewer.Tests/AttachedViewerTests.cs @@ -151,6 +151,33 @@ public async Task ABusyOwnerDoesNotReadAsDead() _ = listening; } + /// + /// An owner that answers is an owner, whatever it answered. + /// + /// ViewerServer turns any exception in the listing handler into an error reply, and this side + /// read one of those as the owner having gone: the window closed with "The queue owner is no + /// longer running." over a single transient throw, taking the queue it was displaying with it. + /// + /// + [Test] + public async Task AnErrorReplyIsNotADeadOwner() + { + await Assert.That(ViewerServer.TryBind(0, out var bound)).IsTrue(); + using var server = bound!; + using var cancel = new CancelSource(); + var listening = server.Listen( + _ => ViewerResponse.Error("The listing could not be built"), + cancel.Token); + var host = new SessionHost(SessionState.Start(ViewerMode.Inline, Fixtures.Columns, Fixtures.Rows)); + + await Assert.That(new OwnerLink(host, server.Port).Pump()).IsTrue(); + + await Assert.That(host.State.Message).IsEqualTo("The listing could not be built"); + await Assert.That(host.State.Exit).IsFalse(); + await cancel.CancelAsync(); + _ = listening; + } + /// /// The owner has no window of its own, so raising, hiding and closing come back on a listing /// rather than being pushed at a port this process does not hold. diff --git a/src/DiffEngineViewer/Ipc/OwnerLink.cs b/src/DiffEngineViewer/Ipc/OwnerLink.cs index 1d800e2c..695466af 100644 --- a/src/DiffEngineViewer/Ipc/OwnerLink.cs +++ b/src/DiffEngineViewer/Ipc/OwnerLink.cs @@ -61,12 +61,24 @@ public bool Pump(out bool sent) message = Send(command); } - if (!ViewerClient.TrySend(new(ViewerVerb.ListFull), out var response, port, Wait) || - !response.Ok) + if (!ViewerClient.TrySend(new(ViewerVerb.ListFull), out var response, port, Wait)) { return false; } + if (!response.Ok) + { + // An owner that answers is an owner. ViewerServer turns any exception in the listing + // handler into an error reply, so reading one as death closed this window over a + // single transient throw and lost the queue it was displaying. Said instead, and asked + // again on the next pass. + host.Mutate(_ => _ with + { + Message = response.Message ?? "The queue owner refused the listing." + }); + return true; + } + var pending = InlineQueue.From(ViewerListing.Pending(response.Items)); var changes = ReadChanges(response); host.Mutate(_ => ViewerSession.Sync(_, pending, changes, message));