fix: dispatch controlled AutoCAD exit from idle

This commit is contained in:
codex
2026-08-03 16:49:57 +03:00
parent 239796aafe
commit 44de0cf787
2 changed files with 39 additions and 3 deletions
@@ -128,6 +128,7 @@ internal sealed class FirstLimitedApplyAutomationCoordinator
private int? before, after1, after2, afterUndo; private int? before, after1, after2, afterUndo;
private AutomationEntityCounts? baseline, counts1, counts2, countsUndo; private AutomationEntityCounts? baseline, counts1, counts2, countsUndo;
private readonly List<string> transitions = ["Created"]; private readonly List<string> transitions = ["Created"];
private bool exitQueued;
public void Start() public void Start()
{ {
@@ -164,8 +165,7 @@ internal sealed class FirstLimitedApplyAutomationCoordinator
{ {
transitions.Add("LifecycleProbeCompleted"); transitions.Add("LifecycleProbeCompleted");
WriteEvidence(true, null); WriteEvidence(true, null);
transitions.Add("ExitRequested"); RequestControlledExit();
AcadApplication.Quit();
} }
private void OnIdle(object? sender, EventArgs e) private void OnIdle(object? sender, EventArgs e)
@@ -256,12 +256,40 @@ internal sealed class FirstLimitedApplyAutomationCoordinator
AcadApplication.DocumentManager.DocumentActivated -= OnDocumentActivated; AcadApplication.DocumentManager.DocumentActivated -= OnDocumentActivated;
WriteEvidence(success, error); WriteEvidence(success, error);
if (document is not null) document.CloseAndDiscard(); if (document is not null) document.CloseAndDiscard();
AcadApplication.Quit(); RequestControlledExit();
} }
catch { } catch { }
finally { selectedTargetSnapshotId = null; pxpResults.Clear(); } finally { selectedTargetSnapshotId = null; pxpResults.Clear(); }
} }
// AutoCAD's direct Application.Quit can leave a startup-script host alive.
// Dispatching the native QUIT command from the next quiescent Idle cycle
// preserves normal command shutdown semantics.
private void RequestControlledExit()
{
if (exitQueued) return;
exitQueued = true;
transitions.Add("ExitQueued");
AcadApplication.Idle += OnExitIdle;
}
private void OnExitIdle(object? sender, EventArgs args)
{
AcadApplication.Idle -= OnExitIdle;
try
{
var active = AcadApplication.DocumentManager.MdiActiveDocument;
if (active is null) { AcadApplication.Quit(); return; }
transitions.Add("NativeQuitDispatched");
active.SendStringToExecute("_.QUIT _N ", true, false, false);
}
catch (System.Exception exception)
{
RuntimeDiagnostics.Write("AutomationControlledExitFailed", "ERROR", resultStatus: "AUTOMATION_CONTROLLED_EXIT_FAILED", exception: exception);
AcadApplication.Quit();
}
}
private void WriteEvidence(bool success, string? error) private void WriteEvidence(bool success, string? error)
{ {
if (config is null) return; if (config is null) return;
@@ -82,6 +82,14 @@ public sealed class FoundationTests
Assert.Contains("PostStartupReady", source); Assert.Contains("PostStartupReady", source);
} }
[Fact] public void AutomationControlledExit_UsesNativeQuitFromIdle()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
Assert.Contains("RequestControlledExit", source);
Assert.Contains("AcadApplication.Idle += OnExitIdle", source);
Assert.Contains("active.SendStringToExecute(\"_.QUIT _N \", true, false, false)", source);
}
[Fact] public void PrimaryCommand_IsRegistered() => Assert.Contains("[CommandMethod(\"PHP_REC_ANNOT\")]", File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs"))); [Fact] public void PrimaryCommand_IsRegistered() => Assert.Contains("[CommandMethod(\"PHP_REC_ANNOT\")]", File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs")));
[Fact] public void LegacyAliases_AreRegistered() => Assert.True(File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs")).Contains("PXP-AAV") && File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs")).Contains("PXP-OA")); [Fact] public void LegacyAliases_AreRegistered() => Assert.True(File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs")).Contains("PXP-AAV") && File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs")).Contains("PXP-OA"));
[Fact] public void RibbonCommand_ReferencesRegisteredCommand() => Assert.Contains("PHP_REC_ANNOT ", File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs"))); [Fact] public void RibbonCommand_ReferencesRegisteredCommand() => Assert.Contains("PHP_REC_ANNOT ", File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs")));