Repository navigation
Handle three documented limitations: same-thread two instances, auto layout offsets, Global\ names - #6
Merged
Conversation
…tance Locks are reentrant only through the instance that took them. A second instance of the same region on the same thread (two components that each open the region) waited for the whole timeout for a write lock that its own thread holds, for ever with an infinite timeout. The write lock records its owner by process, thread and PID namespace, so StructuredMemory and SharedArray now check it before they wait and throw InvalidOperationException with an explanation. A read lock held through another instance cannot be recognised (read locks have no owner), and the raw MemoryRegion.TryAcquire* methods keep returning false.
The fingerprint of a generic struct (ValueTuple, KeyValuePair, ...) was built from field names, types and explicit FieldOffset values, so for LayoutKind.Auto it said nothing about where the runtime put each field. Two processes on runtimes that lay a type out differently (net8.0 and net10.0 now both ship) would have agreed on the fingerprint and read each other's fields from the wrong places. The offsets are measured with a dynamic method (Marshal.OffsetOf rejects these types) and are part of the descriptor. Types the marshaller accepts keep their 3.0.0 fingerprint, and the generic fallback has never been released, so no existing region is affected. Fingerprints are cached per type now, since measuring is not free.
Without a prefix a Windows section lives in the namespace of the session of the process that creates it, so a service (session 0) and a desktop process could not share a region, and any backslash was rejected. A name may now start with Global\ or Local\ (in any case); what follows must still be flat. Windows gets the full name; Linux has no sessions and drops the prefix from the file name, so Global\x, Local\x and x are one region there and one name works on both platforms. Remove uses the same file name. The two SharedArray tests that used a second instance on the test thread as a stand-in for "another holder" ask from another thread now, since the same thread is reported at once (previous commit).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three things that 4.0.0 only documented. Each is its own commit, with tests.
72c6a2b). Locks are reentrant only through the instance that took them, so a thread that asked for a lock through a second instance waited for the whole timeout (for ever with an infinite one) for a write lock that it holds itself. The write lock records its owner by process, thread and PID namespace, soStructuredMemory<T>andSharedArray<T>now check it before they wait and throwInvalidOperationExceptionwith an explanation. A read lock held through another instance cannot be recognised (read locks have no owner) and still waits; the rawMemoryRegion.TryAcquire*methods are unchanged. Two existingSharedArraytests used a second instance on the test thread as a stand-in for another holder; they ask from another thread now.52243e6). The fingerprint of a generic struct (ValueTuple,KeyValuePair, ...) did not say where the runtime put each field of aLayoutKind.Autotype. Withnet8.0andnet10.0both shipping (Target net10.0 as well as net8.0 #5) two processes could agree on the fingerprint and read each other's fields from the wrong places. The offsets are measured with a dynamic method (Marshal.OffsetOfrejects these types) and are part of the descriptor. Types the marshaller accepts keep their 3.0.0 fingerprint (pinned by a test), and the generic fallback has never been released. Fingerprints are cached per type.Global\andLocal\in names (86a7c83). A name may start withGlobal\orLocal\(any case); what follows must be flat. Windows gets the full name, so a service and a desktop process can shareGlobal\name. Linux has no sessions and drops the prefix from the file name, soGlobal\x,Local\xandxare one region there and one name works on both platforms;MemoryRegion.Removeuses the same file name.README and CHANGELOG (a 4.1.0 section) describe all three, including what stays unsupported: creating a
Global\section needsSeCreateGlobalPrivilege, the section keeps the default access control of its creator, so other accounts may be refused (run both sides under one account for now).Verification
Local (Linux, NUnit shim): 581 passed, 2 failed (the two known shim-only ones), 6 skipped (Windows-only and explicit). Run on a Windows runner by CI:
Local\andGlobal\sections (theGlobal\test is ignored when the runner lacks the privilege). Run against the previous code the new behaviour tests fail (a second instance waits for its timeout;Global\xis rejected).Stacking: independent of #4 and #5; all three touch the top of
CHANGELOG.md, so the ones that merge later need a trivial conflict resolution (this one adds the 4.1.0 section, #5 adds an entry to the same section).Generated by Claude Code