Compare commits

..

3 Commits

Author SHA1 Message Date
Joseph Doherty 62f488dd12 wip: item handle reuse probe (#166)
ci / nightly-windev (push) Has been skipped
ci / windows-x86 (push) Successful in 1m29s
ci / java (push) Successful in 2m55s
ci / portable (push) Failing after 3m13s
2026-10-20 07:11:37 -04:00
Joseph Doherty f13950e453 fix(worker): forget write-completion entries for dropped ack item handles
Add MxAccessWriteCompletionCache.Remove and RemoveServer and call them
from AlarmAckWriter when it drops an item (dirty re-add, LRU eviction,
failed AddItem/Advise/Write) and when it tears down its registration, so
the cache no longer keeps one entry per dropped handle for the life of
the worker.

Fixes #165

Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-20 07:11:37 -04:00
Joseph Doherty 219611c821 fix(worker): narrow AlarmAckWriter ArgumentException interop filter
Resolve the operator user, password and comment before the interop try
blocks, and match ArgumentException as an MXAccess failure only when it
carries E_INVALIDARG and is not ArgumentNullException or
ArgumentOutOfRangeException. A programming error inside the try blocks
now propagates instead of being reported as an authentication or write
failure.

Fixes #164

Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-20 07:11:37 -04:00
@@ -195,7 +195,7 @@ public sealed class AlarmAckWriterTests
RecordingAckServer server = new RecordingAckServer
{
AuthenticateResult = 5,
AuthenticateException = new ArgumentException("bad credentials") { HResult = unchecked((int)0x80070057) },
AuthenticateException = new HResultArgumentException("bad credentials", unchecked((int)0x80070057)),
};
using AlarmAckWriter writer = new AlarmAckWriter(server, new MxAccessWriteCompletionCache(), () => { }, TimeSpan.FromMilliseconds(10), "MxGateway");
@@ -225,11 +225,11 @@ public sealed class AlarmAckWriterTests
RecordingAckServer server = new RecordingAckServer
{
AuthenticateResult = 5,
AuthenticateException = new ArgumentException("not interop") { HResult = unchecked((int)0x80131500) },
AuthenticateException = new HResultArgumentException("not interop", unchecked((int)0x80131500)),
};
using AlarmAckWriter writer = new AlarmAckWriter(server, new MxAccessWriteCompletionCache(), () => { }, TimeSpan.FromMilliseconds(10), "MxGateway");
Assert.Throws<ArgumentException>(() => writer.Acknowledge("T.A.AckMsg", "one", Alice()));
Assert.Throws<HResultArgumentException>(() => writer.Acknowledge("T.A.AckMsg", "one", Alice()));
}
[Fact]
@@ -424,6 +424,16 @@ public sealed class AlarmAckWriterTests
}
}
/// <summary>ArgumentException with an explicit HRESULT (the setter is protected on net48).</summary>
internal sealed class HResultArgumentException : ArgumentException
{
public HResultArgumentException(string message, int hresult)
: base(message)
{
HResult = hresult;
}
}
internal sealed class RecordingAckServer : IMxAccessServer
{
private int nextItemHandle = 100;