6cd6876229
The T21 press pass 1 test host died at exit with "A callback was made on a garbage collected delegate of type ...plctag+callback_func_ex::Invoke". The cause is in libplctag.NET 1.5.2. When a tag's create fails, for example with NotFound, InitializeAsync throws with the callback still registered. Dispose() destroys a tag only if it is initialised, so the native tag outlives its wrapper. The GC then frees the delegate, and libplctag's atexit plc_tag_shutdown raises DESTROYED into it. Reproduced against ab_server: the production LibplctagTagRuntime with a NotFound tag exits 134 with that exact message, and exits 0 with this fix. A timed-out create unregisters its callback but never destroys the handle either, so that native tag keeps reconnecting for the life of the process. The fix adds LibplctagOrphanRelease (AbCip and AbLegacy). After a failed initialise/read/write that leaves the tag uninitialised, it reads the handle through the pinned wrapper's private fields, unregisters the callback and destroys the tag. If those fields cannot be read, it roots the tag for the life of the process instead. It is applied in both tag runtimes, the symbol-listing reader and the template reader. GetStatus keeps the status the tag held before it was destroyed. Pins: offline refused-connection tests in AbCip.Tests and AbLegacy.Tests, a live NotFound test on ab_server, and a reflection-shape pin. All go red with the release disabled. The differential harness also asserts that no orphan had to be rooted. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe