std::shared_mutex::unlock_shared() blocks even though there are no active exclusive locks on Windows
Asked Answered
A

5

31

My team has encountered a deadlock that I suspect is a bug in the Windows implementation of SRW locks. The code below is a distilled version of real code. Here's the summary:

  1. Main thread acquires exclusive lock
  2. Main thread creates N children threads
  3. Each child thread
    1. Acquires a shared lock
    2. Spins until all children have acquired a shared lock
    3. Releases the shared lock
  4. Main thread releases exclusive lock

Yes this could be done with std::latch in C++20. That's not the point.

This code works most of the time. However roughly 1 in 5000 loops it deadlocks. When it deadlocks exactly 1 child successfully acquires a shared lock and N-1 children are stuck in lock_shared(). On Windows this function calls into RtlAcquireSRWLockShared and blocks in NtWaitForAlertByThreadId.

The behavior is observed when used std::shared_mutex directly, std::shared_lock/std::unique_lock, or simply calling SRW functions directly.

A 2017 Raymond Chen post asks about this exact behavior, but user error is blamed.

This looks like an SRW bug to me. It's maybe worth noting that if a child doesn't attempt to latch and calls unlock_shared that this will wake its blocked siblings. There is nothing in the documentation for std::shared_lock or *SRW* that suggests is allowed to block even when there is not an active exclusive lock.

This deadlock has not been observed on non-Windows platforms.

Example code:

#include <atomic>
#include <cstdint>
#include <iostream>
#include <memory>
#include <shared_mutex>
#include <thread>
#include <vector>

struct ThreadTestData {
    int32_t numThreads = 0;
    std::shared_mutex sharedMutex = {};
    std::atomic<int32_t> readCounter;
};

int DoStuff(ThreadTestData* data) {
    // Acquire reader lock
    data->sharedMutex.lock_shared();

    // wait until all read threads have acquired their shared lock
    data->readCounter.fetch_add(1);
    while (data->readCounter.load() != data->numThreads) {
        std::this_thread::yield();
    }

    // Release reader lock
    data->sharedMutex.unlock_shared();

    return 0;
}

int main() {
    int count = 0;
    while (true) {
        ThreadTestData data = {};
        data.numThreads = 5;

        // Acquire write lock
        data.sharedMutex.lock();

        // Create N threads
        std::vector<std::unique_ptr<std::thread>> readerThreads;
        readerThreads.reserve(data.numThreads);
        for (int i = 0; i < data.numThreads; ++i) {
            readerThreads.emplace_back(std::make_unique<std::thread>(DoStuff, &data));
        }

        // Release write lock
        data.sharedMutex.unlock();

        // Wait for all readers to succeed
        for (auto& thread : readerThreads) {
            thread->join();
        }

        // Cleanup
        readerThreads.clear();

        // Spew so we can tell when it's deadlocked
        count += 1;
        std::cout << count << std::endl;
    }

    return 0;
}

Here's a picture of the parallel stacks. You can see the main thread is correctly blocking on thread::join. One reader thread acquired the lock and is in a yield loop. Four reader threads are blocked within lock_shared.

enter image description here

Avow answered 1/3 at 23:9 Comment(16)
Runs clean with AddressSanitizer. There is no ThreadSanitizer on Windows. Clang + mingw does not exhibit this behavior because it has a wholly separate pthread implementation that doesn't use SRW. github.com/mingw-w64/mingw-w64/blob/master/mingw-w64-libraries/… I appreciate that the odds of a Windows implementation bug and non-user error are vanishingly small. The code is quite small and simple, only 64 lines. If there's a bug or the spec for std::shared_mutex / SRW allows this behavior please point it out.Avow
pure winapi implementation is gist.github.com/rbmm/678abaa008f12aaf1851e5df6e670515 - can you catch bug in this code ? 5000 test not simply todo. if you can catch - will be interesting look under debugger what is happensPrivileged
@Privileged exact same issue with your code. One reader thread is spinning on ntYieldExecution and all other readers are blocked on AcquireSRWLockShared. I added a screenshot of the Parallel Stacks view to the root post. It's effectively identical as my original code. It still deadlocks roughly once per few thousand.Avow
It just loops forever creating a new ThreadTestData object on the stack. Here's my full program paste based on RbMm's "pure win api implementation" code. Both this and my original code should trivially compile and run. pastebin.com/DD9RwVZj I just let it run until it deadlocks then I click pause in the debugger. I've noticed in the debugger that when it succeeds the value of SRWLock (or the std::shared_lock inner handle) is a low integer. Values between 0x01 and 0x50 are quite common. Numbers can be re-used.Avow
However when it deadlocks the handle value is... not garbage, but not a small value. Examples: 0x7263aff6f3, 0xedf26ffb63, 0xb5606ff843, 0x1e6e8ff753, 0xa811bffae3. Always ends with a 3. Very curious!! I think it always sets 40 bits, never the full 64. They also all include the same ff. So it definitely doesn't seem random. The docs don't include any details on what the state means that I can tell. learn.microsoft.com/en-us/windows/win32/sync/…Avow
All you need is the code in my root post or comment paste. But here's a VS2022 solution if it's helpful. dropbox.com/scl/fi/f7f4dugzhox23wgvil7tu/… It really is just one simple main.cpp. As bare bones as it gets.Avow
sorry, can you test this code github.com/rbmm/SRW_ALT ? the github.com/rbmm/SRW_ALT/blob/main/SrwTest.cpp exactly same code wich i paste before and you already test, but with replaced winapi to my custom implementation. in files pushlock.cpp / pushlock.h . if you can build and test it, will be interesting ( i until not paste complete vs solution, but can do this if you want)Privileged
i replace SRWLOCK to my class CPushLock - #define SRWLOCK CPushLock and all 4 api calls - #define AcquireSRWLockShared(p) (p)->AcquireShared() and so on. are my implementation also have bug )). now i need to go, but some late try full research bug wich you foundPrivileged
I get earlier deadlocks if I replace std::this_thread::yield(); with Sleep(10). Once I get it even after 1 is dumped, i.e. on the second iteration of the main loop. Interesting fact, readCounter is always 1, i.e. 1 thread is waked up, other 4 threads are not.Tayib
@273K - no, you make some bug in self src code. must no be any deadlock with my code. i build full solution - github.com/rbmm/SRW_ALT . here exist 2 exe files - srw.exe which use system implementation of SRW locks and i always catch deadlock with 2000 loops ( srw.exe *2000 ) and my implementation - push.exe which have no deadlocks ( push.exe *2000). look src code in SrwTest.cpp. some late i write full research on this.Privileged
@Privileged I didn't talk about your code.Tayib
@273K - yes, sorry, I didn’t immediately understand what you wrote and the main thing is that it was not written by the author of the question - I confused you with him. I updated the code on Github - there is now both source and binary for tests, and if you want, you can try it. and yes - this is a 100% windows error, wonderPrivileged
I don't know if this solves your problem, but I think you have your logic somewhat backwards. You have in your list of steps that the main thread acquires the exclusive lock at the beginning, then the children threads acquires shared locks and releases them, then the main threads releases it's exclusives lock at the end. However when the main thread acquires the exclusive lock, it prevents the children threads from acquiring a shared lock at all. When the main thread releases it's exclusive lock, then the children threads are able to acquire a shared lock. Hope that helps.Fylfot
reddit.com/r/ProgrammerHumor/comments/wy6nsw/…Vibraharp
Rust (since 1.78) has moved away from SRW locks due to this: lwn.net/Articles/972246 github.com/rust-lang/rust/pull/121956Motherwort
On the other hand, the MSVC STL may or may not attempt a fix, or attempt to change the C++ standard: github.com/microsoft/STL/issues/4448Motherwort
A
23

This is a confirmed bug in the OS SlimReaderWriter API.

I posted a thread in r/cpp on Reddit because I knew Reddit user u/STL works on Microsoft's STL implementation and is an active user.

u/STL posted a comment declaring it an SRW bug. He filed OS bug report" OS-49268777 "SRWLOCK can deadlock after an exclusive owner has released ownership and several reader threads are attempting to acquire shared ownership together". Unfortunately this a Microsoft internal bug tracker so we can't follow it.

Thanks to commenters in this thread (RbMm in particular) for helping fully explain and understand the observed behavior.

RbMm posted a secondary answer which appears to show that "AcquireSRWLockShared some time can really acquires a slim SRW lock in exclusive mode". Read his response for details. I think almost everyone would be surprised by this behavior!

Avow answered 3/3 at 6:53 Comment(2)
This is not exactly deadlock, because threads not hung for ever. If your thread [25108] leave the lock, all another threads can enter. Why you totally ignore my expectations what is exactly happens, and that thread in lock hold it exclusive (this just confirmed by values of lock Ptr which you post in comments). If you ask for exclusive access and system give you shared - this is 100% bug. But if you ask shared and got exclusive - are this is bug ? ( Because exclusive include shared you got what you ask). Problem was only because you wait inside lock, what is wrong designPrivileged
Is there any way of knowing when this gets fixed?Jacquiline
M
10

At the time I updated SRW to be unfair (now SRW might not have existed at that time and so it might only be the kernel code) the idea of stealing the lock was really important. We had a number of locks that passed ownership. An unlock didn't really unlock but select a thread/threads that would be the new owner. This could lead to convoys as the lock hold time can get expanded by the context swap time if any thread selected needed to be context swapped in. The solution is to make the locks unfair so that any threads that didn't need to context swap could take the lock and hopefully release it even before any other thread could get context swapped in. So unlock becomes a two phase thing where we unconditionally release the lock then find threads to wake. Between these two phases any acquire (shared or exclusive) can take the lock but they can do so only one at a time. There isn't enough state in the single pointer to record anything else. It was really important for us to have a small lock so we could put fine grained locking anywhere. I would like to understand in the c++ standard requires a different behavior. Obviously, the code was likely written before the standard in question. I expect you can easily fix this if you want to support some kind of rendezvous under a shared lock. I had kind of ruled that out given that one shared acquire is not always compatible with another if you have an intervening exclusive acquire. So, my rule of thumb was 'you can never fix a deadlock by changing a lock from exclusive to shared except in a system with no exclusive locks'. Shared aquires can instead of stealing just queue the wait block in the contended case and let the waker sort everything out. Losing the unfairness is worrying though. Could well be so rare as to not matter.

Milk answered 4/3 at 19:2 Comment(3)
Neill, are you the original author on the MSFT's SRW code?Eject
Yes. I wrote the original code and did the unfair update. There have been some updates since I left. The bug is mine though and I did this on purpose. I never considered people using the locks for rendezvous.Milk
I would like to understand in the c++ standard requires a different behavior. - c++ standard NOT require different behavior. nowhere claimed that acquire lock in shared mode can be guaranteed not blocked under some conditions. standart alow multiple threads, which ask for shared access, enter to lock at once. but not guaranteed that shared request will be not block if already shared owners inside lock. even indirect claimed - that behavior is undefined if the calling thread try reqursive asquire lock. error in OP code - it based on wrong assumption.Privileged
P
5

i try post yet another answer, for describe details from some another side ( not mean that previous answer is wrong, but already overloaded )

AcquireSRWLockShared some time can really acquires a slim SRW lock in exclusive mode !

steps to reproduce the situation - https://github.com/rbmm/SRW-2/tree/main

  • main thread start N (> 1) worker threads
  • the N-1 threads is started normal and we give them time to call AcquireSRWLockShared by using Sleep(1000) and one thread is started in suspended state
  • then we call ReleaseSRWLockExclusive from the main thread

and with the help of VEX we catch the moment when ReleaseSRWLockExclusive set the K bit in SRW here we suspend the main thread and resume the last worker. and give it time (again Sleep(1000)) to enter to the lock. and it enter in - AcquireSRWLockShared works successfully - since the Lock bit has already been removed then, we continue executing the main thread in ReleaseSRWLockExclusive

and here we have after this: 1 worker thread inside SRW shows a message box and another N-1 worker threads is waiting inside AcquireSRWLockShared

this is repro in crystal clear form - we have a thread that requested shared and received exclusive access to SRW

But ! as soon as we close the messagebox and release SRW, N-1 messageboxes will immediately appear then. nothing stuck. no deadlock.

and if the code was written correctly, such behavior would not cause any problems. we got MORE than we asked for. but so what ? did we ask for shared access? we got it. then we must work with the data that this SRW protect and exit. and everything will be ok. no one will even notice anything. visa versa - if we asked for exclusive and got shared - this is a critical error and can lead to data corruption


here i use N=4. if you run exe (without debugger !!), you first view single message box. and if you look to call stacks of another threads ( you can attach debugger after message box ) - you view that all N-1 threads wait in RtlAcquireSRWLockShared. the thread by fact have exclusive access to lock, despite code ask for shared. close message box. and just 3 new message box popup. this is last N-1 worker enter to the SRW already as shared

void DoStuff()
{
    AcquireSRWLockShared(SRWLock);

    Message(__FUNCTIONW__, MB_ICONINFORMATION);

    ReleaseSRWLockShared(SRWLock);

    EndThread();
}

Any critical sections are not intended to be waited in. they are designed to synchronize data access. and not to wait for something inside them. if the stream is waiting for something inside, it’s already an error. design error. if a worker thread waits for other worker threads, that's another design error. The worker thread should do its own thing and not care about the state of other worker threads. did they also enter some section or did not enter. worker thread can wait for data and that's normal. but it should not wait on the state of other threads.

Privileged answered 3/3 at 13:54 Comment(4)
"AcquireSRWLockShared some time can really acquires a slim SRW lock in exclusive mode !" Wow! That is crazy. Definitely not something I would expect without documentation. RbMm, your Windows debugging skills impress the heck out of me.Avow
@Avow - are you try my second repo - github.com/rbmm/SRW-2/tree/main - -i hope you can build it, i even copy ntdllp.lib here. try binaru and look src. for fun how minimumPrivileged
@Avow - Windows debugging skills impress - unfortunatelly for me personally this not help got any job in last 10 years ))Privileged
Shoot me an e-mail. Username at gmail.Avow
L
4

The SRWLOCK is behaving as designed. There is a shared -> exclusive ownership upgrade path which was added as a deliberate feature, designed to help prevent convoys, when the lock was under contention and in the middle of being released / other waiters woken.

Lock convoys are a real and serious performance problem, well known in academic literature. The seminal paper describing them, written by a group including Jim Gray, dates from 1977 and can be read here:

https://dl.acm.org/doi/pdf/10.1145/850657.850659

Unfortunately, the SRWLOCK documentation never covered this anti-convoy behaviour, but as a result of discussions arising from this issue it has been updated in https://github.com/MicrosoftDocs/sdk-api/pull/1785 to indicate the possibility of this shared -> exclusive ownership upgrade path being used.

As per the discussion on https://github.com/microsoft/STL/issues/4448, the MSFT C++ STL team believe that this shared -> exclusive ownership upgrade path in the SRWLOCK then causes the behaviour of their std::shared_mutex implementation to not conform to [thread.sharedmutex.requirements.general]/2, which states:

The maximum number of execution agents which can share a shared lock on a single shared mutex type is unspecified, but is at least 10000. If more than the maximum number of execution agents attempt to obtain a shared lock, the excess execution agents block until the number of shared locks are reduced below the maximum amount by other execution agents releasing their shared lock.

To get the behaviour they desire, the MSFT C++ STL team will have to agree a change SRWLOCK behaviour (either optional or unilateral) with the Windows team, and then a change will need to be made to the MSFT C++ STL after this. The MSFT C++ STL issue (https://github.com/microsoft/STL/issues/4448) should be used to track progress.

However, the above explanation doesn't provide an immediate solution to the OP's problem. To prevent hitting this, I suggest it would be best to simply roll your own latch/barrier using condition_variable_any and shared_mutex. The following code (using condition_variable_any and shared_mutex) demonstrates this approach, is C++17-compatible and is not subject to the deadlock seen with the original code:

#include <cstdint>
#include <iostream>
#include <memory>
#include <shared_mutex>
#include <thread>
#include <vector>

struct ThreadTestData {
    int32_t numThreads = 0;
    std::shared_mutex sharedMutex = {};
    std::condition_variable_any allStarted;
    int32_t readCounter;
};

int DoStuff(ThreadTestData* data) {
    // Register that this thread has started
    data->sharedMutex.lock();
    data->readCounter += 1;
    data->sharedMutex.unlock();

    data->allStarted.notify_all();

    // Acquire reader lock
    std::shared_lock lock(data->sharedMutex);

    // Wait until all reader threads have started, re-acquire reader lock
    data->allStarted.wait(lock,
                          [&]{ return data->readCounter == data->numThreads; });

    // Reader lock released on scope exit (~shared_lock)

    return 0;
}

int main() {
    int count = 0;
    while (true) {
        ThreadTestData data = {};
        data.numThreads = 5;

        // Acquire write lock
        data.sharedMutex.lock();

        // Create N threads
        std::vector<std::unique_ptr<std::thread>> readerThreads;
        readerThreads.reserve(data.numThreads);
        for (int i = 0; i < data.numThreads; ++i) {
            readerThreads.emplace_back(std::make_unique<std::thread>(DoStuff, &data));
        }

        // Release write lock
        data.sharedMutex.unlock();

        // Wait for all readers to succeed
        for (auto& thread : readerThreads) {
            thread->join();
        }

        // Cleanup
        readerThreads.clear();

        // Spew so we can tell when it's deadlocked
        count += 1;
        std::cout << count << std::endl;
    }

    return 0;
}

(I ran it locally until it hit 1,000,000 iterations with no sign of deadlock)

Latvina answered 10/3 at 23:14 Comment(0)
P
0

really error was in your code logic. your are wait inside SRW lock. but locks not designed to wait, it designed for fast operations. thread must leave lock fast as possible, after he enter to it.

what and why happens with your concrete code ?

  1. when thread (A) release lock from exclusive access ( ReleaseSRWLockExclusive )
  2. and at "same time" another thread (B) try acquire lock shared ( AcquireSRWLockShared )
  3. and another threads (how minimum yet one) wait already for lock

possible situation when thread B acquire lock with exclusive access by fact, despite he ask for shared only. as result another shared waiters and new shared requests, will be wait, until thread B not leave srw lock. but in your case it not leave, until another threads not enter to section, but they can not enter, until B leave.. deadlock.

however if you leave srw with thread B another threads wake up.

how you can modify self code for test this ?

save time before enter

while (data->readCounter.load() != data->numThreads)

loop. and check time in loop. really, after SRW lock released from exclusive access, all shared waiters must "very fast" enter to lock. if during some time this not happens ( let be 1 second - really this is HUGE time in this case) - let thread which in SRW now - exit from loop and release lock. and deadlock will is gone.

try next code

int DoStuff(ThreadTestData* data) {
    // Acquire reader lock
    data->sharedMutex.lock_shared();

    ULONG64 time = GetTickCount64() + 1000;
    // wait until all read threads have acquired their shared lock
    // but no more 1000 ms !!
    data->readCounter.fetch_add(1);
    while (data->readCounter.load() != data->numThreads && GetTickCount64() < time) {
        std::this_thread::yield();
    }

    // Release reader lock
    data->sharedMutex.unlock_shared();

    return 0;
}

however i prefer pure winapi and for better visual effect, next code:

struct ThreadTestData 
{
    HANDLE hEvent;
    SRWLOCK SRWLock = {};
    LONG numThreads = 1;
    LONG readCounter = 0;
    LONG done = 0;

    void EndThread()
    {
        if (!InterlockedDecrementNoFence(&numThreads))
        {
            if (!SetEvent(hEvent)) __debugbreak();
        }
    }

    void DoStuff()
    {
        AcquireSRWLockShared(&SRWLock);

        InterlockedDecrementNoFence(&readCounter);

        ULONG64 time = GetTickCount64() + 1000;

        while (readCounter)
        {
            if (GetTickCount64() > time)
            {
                if (InterlockedExchangeNoFence(&done, TRUE))
                {
                    MessageBoxW(0, 0, 0, MB_ICONHAND);
                    break;
                }
            }

            SwitchToThread();
        }

        ReleaseSRWLockShared(&SRWLock);

        EndThread();
    }

    static ULONG WINAPI _S_DoStuff(PVOID data)
    {
        reinterpret_cast<ThreadTestData*>(data)->DoStuff();
        return 0;
    }

    BOOL Test(ULONG n)
    {
        if (hEvent = CreateEventW(0, 0, 0, 0))
        {
            AcquireSRWLockExclusive(&SRWLock);

            do 
            {
                numThreads++;
                readCounter++;

                if (HANDLE hThread = CreateThread(0, 0, _S_DoStuff, this, 0, 0))
                {
                    CloseHandle(hThread);
                }
                else
                {
                    readCounter--;
                    numThreads--;
                }

            } while (--n);

            ReleaseSRWLockExclusive(&SRWLock);

            EndThread();

            if (WAIT_OBJECT_0 != WaitForSingleObject(hEvent, INFINITE))
            {
                __debugbreak();
            }

            CloseHandle(hEvent);
        }

        return done;
    }
};

BOOL DoSrwTest(ULONG nThreads)
{
    ThreadTestData data;
    return data.Test(nThreads);
}

ULONG DoSrwTest(ULONG nLoops, ULONG nThreads)
{
    while (!DoSrwTest(nThreads) && --nLoops);

    return nLoops;
}

the full project is here


steps to reproduce - we need only 2 working threads - A and B. and main thread M.

  • thread A call AcquireSRWLockShared and begin wait
  • thread M call ReleaseSRWLockExclusive, when he enter RtlpWakeSRWLock we need suspend M at this point ( in real case sheduler can do it ). at this point section already unlocked !!
  • thread B call AcquireSRWLockShared and enter lock, because SRW is unlocked
  • thread M continue execution RtlpWakeSRWLock but now NOT wake thread A !!
  • when (and if in case your code) thread B call ReleaseSRWLockShared, RtlpWakeSRWLock will be called again and now thread A will be waked

or by another words - ReleaseSRWLockExclusive first remove Lock bit and then, if Waiters present, walk by Wait Blocks for wake waiters ( RtlpWakeSRWLock ) but this 2 operations not atomic. in between, just after Lock bit removed, another thread can acquire the lock. and in this case acquire always will be exclusive by fact, even if thread ask for shared access only

Privileged answered 2/3 at 19:36 Comment(14)
but locks not designed to wait Please add a reference to a manual. This one does not show such limitations. B acquire lock with exclusive access by fact Please add a reference to a manual when a shared lock may be an exclusive lock by fact. It looks like a conditional variable, that may not be waited w/o releasing a lock.Tayib
@273K - what is "wait" this is relative thing. we of course can wait for something inside lock. but need try spend as little time there as possible. manual - read about K bit - The K bit arbitrates who is allowed to... they behave like exclusive releasers... the SRW locks have almost the same implementation as PushLock. note also 0x7263aff6f3, 0xedf26ffb63.. from LordCecil comments and compare it with structure of a push lock. we have P and W/L bits set .Privileged
@273K in concrete case the single thread in SRW lock became exclusive owner by fact. if you want more details - when he call AcquireSRWLockShared he view that lock is owned exclusively and insert self wait block to list, and then set K bit for optimize wait chain. if at this point main thread call ReleaseSRWLockShared he view K bit and not release waiters. this task will be done by shared thread but only after he leave lockPrivileged
@273K It looks like a conditional variable, that may not be waited w/o releasing a lock. it looks like you doubt the accuracy of my answer :)Privileged
really error was in your code logic - This answer doesn't prove that. Your point is that Window's AcquireSRWLockShared sometimes taking an exclusive lock is working as Microsoft intended. The question is about std::shared_mutex: its behaviour is defined by the C++ standard. Looking at thread.sharedmutex.requirements.general.5, my interpretation (not a lawyer) is that lock_shared must stop blocking whenever shared ownership can be obtained. Microsoft's stdlib might not be standard compliant.Kimono
@VincentSaulue-Laborde - i not touch c++ at all here. but describe only SRW locks. however in windows c++ implementation of mutex based on SRW (the OP also confirm this). i research how all this work internally. i explain in details what happen. i paste on github more relevant demo code. you if want can check and binary and src. i describe how reproduce situation in single loop with only 2 threads, if of course you can use debugger, and suspend/resume threads here. so with what you not agree ? you can test and my fix to OP code and ensure that deadlock is gone here.Privileged
@VincentSaulue-Laborde so really - i do fantastic work really here. very few peoples can research such case. and still not ok ? :)) you can do better ? i not say about any documentation. i say how is really all is going. inot guess this. i reproduce this under debugger. you can ? i can. the fact that after thread (25108 in OP picture) which in SRW lock, after exit from lock (try my code or fix) wake another threads from AcquireSRWLockShared prove that this thread really hold lock exclusive !Privileged
@VincentSaulue-Laborde really error was in your code logic - This answer doesn't prove that. - and this i not prove. this i explain ! this is wrong behavior wait something in "critical section". it not disabled, it not fail, but this is by design error. and i show - that no really deadlock - just thread from SRW lock ( 25108 in picture) exit from lock (look my fix) - all is other threads will wake. so only OP logic hold another threadsPrivileged
Can't run the project because it's using a mysterious ntdllp.lib to make direct ntdll.dll calls. I think RbMm has correctly assessed and we are all in agreement as to how SRW and, by extension, std::shared_mutex behave. The question, that none of us here can answer, is this a bug? I assert yes. If a user calls shr_mtx.lock_shared() and there are no exclusive locks and the function blocks that is a bug. SRW should document its behavior if its not a bug. std::shared_mutex should not use SRW if the SRW behavior is intended. Need MSFT to chime in I think.Avow
@Avow - mysterious ntdllp.lib - this is from WDK - you have it installed ? if not and not want - simply resolve api with GetProcAddress as you already do. you can also check my binary. you can also use fix in your code, which i describe here. you can also under debugger, after you catch deadlock - simply change ip of thread 25108 (on your screen) for leave while loop and you view that no more deadlock after this !Privileged
@Privileged I'm not contesting your explanation of how the deadlock happened. I'm not contesting that your revised code doesn't deadlock. What I want to discuss is: should OP's C++ code, compiled with a fully C++-standard conforming compiler, running on a fully compliant standard library, has a risk of deadlock. Again, I'm not a language lawyer. But my understanding of the C++ standard is that it shouldn't deadlock. If that's the case, the "bug" isn't in OP's code, but in Microsoft's standard library implementation that shouldn't rely on Microsoft's SRW.Kimono
@Avow - Spins until all children have acquired a shared lock - this is key point. in real code you must not do this. how i say - this is error by design, wait in lock. lock by design for very fast operations and small time. only becase you wait (ok spin) inside lock - was hung. in real code - you enter lock, do your task and exit lock. in this case will be okPrivileged
@VincentSaulue-Laborde - i dont know about c++ standarts. i not say here yes or no. i try explain what and why happens. then already question of interpretation - from which side is error. but i also have next opinion - wait (or spin) inside any lock is error by design. not because this is impossible or formal disabled, but because this is wrong design. and you can, if want try c++ code with my fix, and compare resultPrivileged
@Avow -ntdllp.lib for x64 you can use it or install ms WDK, if it yet not installed, or chat me in private of any question about windows (but not c++ general) developmentPrivileged

© 2022 - 2024 — McMap. All rights reserved.