{"id":112291,"date":"2026-05-01T07:00:00","date_gmt":"2026-05-01T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112291"},"modified":"2026-05-01T13:17:11","modified_gmt":"2026-05-01T20:17:11","slug":"20260501-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260501-00\/?p=112291","title":{"rendered":"Developing a cross-process reader\/writer lock with limited readers, part 4: Abandonment"},"content":{"rendered":"<p>We&#8217;ve been building a cross-process reader\/writer lock with a cap on the number of readers, we concluded our investigation last time by noting <a title=\"Developing a cross-process reader\/writer lock with limited readers, part 3: Fairness\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260430-00\/?p=112288\"> that there is a serious problem that needs to be fixed<\/a>.<\/p>\n<p>That serious problem is abandonment.<\/p>\n<p>Suppose a process crashes while it holds a shared or exclusive lock on our cross-process reader\/writer lock. <a title=\"Semaphores don't have owners\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20051123-14\/?p=33233\"> Semaphores don&#8217;t have owners<\/a>, so if a thread terminates while in possession of a semaphore token, that token is lost forever. For our cross-process reader\/writer lock, that means that the maximum number of shared acquirers goes down by one, and exclusive acquisitions will never succeed, since they will be waiting for that last token which will never be returned.<\/p>\n<p>A synchronization object that does have the concept of ownership is the mutex, so we can build our reader\/writer lock out of mutexes.<\/p>\n<p>The idea here is that instead of claiming semaphore tokens, we claim mutexes. This means that we need one mutex for each potential shared acquisition, plus one more to avoid the starvation problem.<\/p>\n<p>The outline is<\/p>\n<ul>\n<li>Shared acquisition: Claim any available token mutex.<\/li>\n<li>Shared release: Release the claimed token mutex.<\/li>\n<li>Exclusive acquisition: Claim all token mutexes.<\/li>\n<li>Exclusive release: Release all token mutexes.<\/li>\n<\/ul>\n<pre>HANDLE sharedMutex;\r\nHANDLE tokenMutexes[MAX_SHARED];\r\n\r\nstruct TimeoutTracker\r\n{\r\n    explicit TimeoutTracker(DWORD timeout)\r\n        : m_timeout(timeout) {}\r\n\r\n    DWORD m_start = GetTickCount();\r\n\r\n    <span style=\"border: solid 1px currentcolor;\">DWORD<\/span> Wait(HANDLE h)\r\n    {\r\n        DWORD elapsed = GetTickCount() - m_start;\r\n        if (elapsed &gt; m_timeout) return <span style=\"border: solid 1px currentcolor;\">WAIT_TIMEOUT<\/span>;\r\n        return <span style=\"border: solid 1px currentcolor;\">WaitForSingleObject(h, m_timeout - elapsed)<\/span>;\r\n    }\r\n\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">DWORD WaitMultiple(DWORD count, const HANDLE* handles, BOOL waitAll)            <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">{                                                                               <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    DWORD elapsed = GetTickCount() - m_start;                                   <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    if (elapsed &gt; m_timeout) return WAIT_TIMEOUT;                               <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    return WaitForMultipleObjects(count, handles, waitAll, m_timeout - elapsed);<\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">}                                                                               <\/span>\r\n};\r\n<\/pre>\n<p>We change the return value of the <code>Wait<\/code> method so it returns the wait result rather than a success\/failure. We also add a <code>Wait\u00adMultiple<\/code> method for wrapping <code>Wait\u00adFor\u00adMultiple\u00adObjects<\/code>.<\/p>\n<p>Next is a handy helper function.<\/p>\n<pre>int WaitResultToindex(DWORD result)\r\n{\r\n    auto index = result - WAIT_OBJECT_0;\r\n    if (index &lt; MAX_SHARED) return static_cast&lt;int&gt;(index);\r\n\r\n    index = result - WAIT_ABANDONED_0;\r\n    if (index &lt; MAX_SHARED) return static_cast&lt;int&gt;(index);\r\n\r\n    return -1;\r\n}\r\n<\/pre>\n<p>The <code>Wait\u00adResult\u00adTo\u00adIndex<\/code> function takes the wait result and returns the index of the acquired mutex, or <tt>-1<\/tt> if no mutex was acquired.<\/p>\n<p>Notice that this code treats the abandoned the state the same as the normal wait state. <!-- backref: What should I do if a wait call reports WAIT_ABANDONED? --> We are assuming that the code can recover from inconsistent data somehow. (For example, maybe the shared and exclusive accesses are to control access to a set of files, so the existing code already has to deal with file corruption.)<\/p>\n<p>All that&#8217;s left is to implement the outline.<\/p>\n<pre>int AcquireShared()\r\n{\r\n    WaitForSingleObject(sharedMutex, INFINITE);\r\n\r\n    auto result = WaitForMultipleObjects(MAX_SHARED, tokenMutexes, FALSE \/* bWaitAll *\/, INFINITE);\r\n\r\n    ReleaseMutex(sharedMutex);\r\n\r\n    return WaitResultToIndex(result);\r\n}\r\n\r\nvoid ReleaseShared(int index)\r\n{\r\n    ReleaseMutex(tokenMutexes[index]);\r\n}\r\n\r\nint AcquireSharedWithTimeout(DWORD timeout)\r\n{\r\n    TimeoutTracker tracker(timeout);\r\n    DWORD result = tracker.Wait(hSharedMutex);\r\n    if (result != WAIT_OBJECT_0) return -1;\r\n    result = tracker.WaitMultiple(MAX_SHARED, tokenMutexes, FALSE \/* waitAll *\/);\r\n    ReleaseMutex(sharedMutex);\r\n\r\n    return WaitResultToIndex(result);\r\n}\r\n\r\nvoid AcquireExclusive()\r\n{\r\n    WaitForSingleObject(sharedMutex, INFINITE);\r\n\r\n    auto result = WaitForMultipleObjects(MAX_SHARED, tokenMutexes, TRUE \/* bWaitAll *\/, INFINITE);\r\n\r\n    ReleaseMutex(sharedMutex);\r\n}\r\n\r\nvoid ReleaseExclusive()\r\n{\r\n    for (unsigned i = 0; i &lt; MAX_SHARED; i++) {\r\n        ReleaseMutex(tokenMutexes[i]);\r\n    }\r\n}\r\n\r\nbool AcquireExclusiveWithTimeout(DWORD timeout)\r\n{\r\n    TimeoutTracker tracker(timeout);\r\n    DWORD result = tracker.Wait(hSharedMutex);\r\n    if (result != WAIT_OBJECT_0) return -1;\r\n    result = tracker.WaitMultiple(MAX_SHARED, tokenMutexes, TRUE \/* waitAll *\/);\r\n    ReleaseMutex(sharedMutex);\r\n\r\n    return result != WAIT_TIMEOUT;\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Recovering from death of the owner.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-112291","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Recovering from death of the owner.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112291","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=112291"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112291\/revisions"}],"predecessor-version":[{"id":112292,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112291\/revisions\/112292"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=112291"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112291"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112291"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}