I was just reading WWN and saw this discussion. Here's a wacky idea that might or might not work.
The basic idea is to grant ownership of each mutex to one Win32 process at a time. Let any thread grab the mutex quickly if it belongs to the process that owns the mutex. If its process does not own the mutex, then it must communicate with the wineserver and the wineserver must communicate with the owning process to transfer ownership of the mutex from the current owner to the requesting thread.
Here's how to implement it. For each Win32 process, for each mutex that's been mapped into it using DuplicateHandle or whatever, allocate a standard shared-memory-based critical section, along with a weOwnIt flag that indicates whether we own the mutex or not. Processes that don't own the mutex leave their critical sections locked. Then implement the operations as follows:
AcquireMutex { retry: TryToEnterCriticalSection(); // nonblocking if (failed) { if (!weOwnIt) { wineserver->GiveUsOwnership(); // blocking goto retry; } else { EnterCriticalSection(); // blocking } } }
ReleaseMutex { LeaveCriticalSection(); }
Then on some other thread in the same address space that serves wineserver requests:
WineserverGrantsUsOwnership { weOwnIt = true; LeaveCriticalSection(); }
WineserverWantsToTakeAwayOwnership { EnterCriticalSection(); weOwnIt = false; wineserver->WeHaveGivenUpOwnership(); }
Because the ownership test in AcquireMutex can race, the server needs to ignore GiveUsOwnership requests for processes that already own the mutex.
It seems like this will be efficient (a lot more efficient than a kernel call actually) unless the mutex is pingponging between processes, in which case you'll be eating lots of context switches anyway.
In terms of safety, a correctly functioning process will never complete AcquireMutex unless the wineserver has granted it the mutex. A misbehaving process can hold the mutex indefinitely or incorrectly think it has the mutex, but of course that's true under any scheme. When the wineserver terminates a process, it can reassign mutex ownership. (So if a process is asked to give up the mutex but doesn't, the wineserver can time out, kill the process and grant the mutex to someone else).
Rob
On Thu, 22 Feb 2001, Robert O'Callahan wrote:
Then on some other thread in the same address space that serves wineserver requests:
There's no such thread, and will never be such a thread (having the wineserver calling into client threads is an inherently unstable design).
Ove Kaaven wrote:
On Thu, 22 Feb 2001, Robert O'Callahan wrote:
Then on some other thread in the same address space that serves wineserver requests:
There's no such thread, and will never be such a thread (having the wineserver calling into client threads is an inherently unstable design).
The wineserver need not depend on the client thread responding to its (asynchronous) messages. In this case, if the client ignores the messages, you get the same effect as if it never releases its mutex, which is not a new failure mode.
Actually there is another design that doesn't need a per-client thread. Instead it uses a per-client memory block shared between the client and the wineserver. This memory block holds the lock associated with each mutex, and the wineserver itself does the actions that I previously ascribed to the client thread. (It's a bit tricky because the wineserver never wants to block on acquiring the lock in shared memory; you have to set some flag in the lock word, wait for the client process to send you a message that it's released its mutex lock, and then try again.) As long as the wineserver maintains its own record of who owns each mutex, it need not actually trust any of the contents of the shared memory blocks. Other than not requiring separate client threads, this design also requires fewer process context switches. In the non-contended case, acquiring a mutex previously held by another process would require only a switch to the wineserver and back.
Sorry I'm not familiar with the Wine code and I don't have time to dig into it, but I just want to urge you not to give up on finding a safe, very efficient solution.
Rob
On Sat, 24 Feb 2001, Robert O'Callahan wrote:
Ove Kaaven wrote:
On Thu, 22 Feb 2001, Robert O'Callahan wrote:
Then on some other thread in the same address space that serves wineserver requests:
There's no such thread, and will never be such a thread (having the wineserver calling into client threads is an inherently unstable design).
The wineserver need not depend on the client thread responding to its (asynchronous) messages. In this case, if the client ignores the messages, you get the same effect as if it never releases its mutex, which is not a new failure mode.
Sure it is, if the process isn't holding the mutex. Your design lets a process own the mutex without holding it. So if a process acquires it, then releases it, does something else for 10 minutes and then hangs, and some other process decides it should grab the mutex, then it should be allowed to do so, since the original process isn't holding it. But it can't...
Actually there is another design that doesn't need a per-client thread. Instead it uses a per-client memory block shared between the client and the wineserver.
Alexandre already shot down the shared-memory solution, especially disliking solutions that force the wineserver out of its single-threaded model, like making it do locking and atomic operations for itself.
Ove Kaaven wrote:
On Sat, 24 Feb 2001, Robert O'Callahan wrote:
Ove Kaaven wrote:
On Thu, 22 Feb 2001, Robert O'Callahan wrote:
Then on some other thread in the same address space that serves wineserver requests:
There's no such thread, and will never be such a thread (having the wineserver calling into client threads is an inherently unstable design).
The wineserver need not depend on the client thread responding to its (asynchronous) messages. In this case, if the client ignores the messages, you get the same effect as if it never releases its mutex, which is not a new failure mode.
Sure it is, if the process isn't holding the mutex. Your design lets a process own the mutex without holding it. So if a process acquires it, then releases it, does something else for 10 minutes and then hangs, and some other process decides it should grab the mutex, then it should be allowed to do so, since the original process isn't holding it. But it can't...
I call that "not a new failure mode" because it's the same behavior as if the faulty client process had a bug that caused it to fail to release the mutex in the first place. If you want a more restricted fault model, you can limit the vulnerability window by having the client periodically poll for owned-but-unlocked mutexes and release them to the wineserver.
Actually there is another design that doesn't need a per-client thread. Instead it uses a per-client memory block shared between the client and the wineserver.
Alexandre already shot down the shared-memory solution, especially disliking solutions that force the wineserver out of its single-threaded model, like making it do locking and atomic operations for itself.
You can do it all with non-blocking operations. The shared memory solution previously proposed had the disastrous property that a misbehaving client process could interfere with mutexes owned by other processes. It doesn't have to be that way if you use a separate shared memory block per client.
This could be extended to other shared resources too. But I could believe that it is too complicated to be worth doing.
Rob
Ove Kaaven wrote:
On Sat, 24 Feb 2001, Robert O'Callahan wrote:
Ove Kaaven wrote:
On Thu, 22 Feb 2001, Robert O'Callahan wrote:
Then on some other thread in the same address space that serves wineserver requests:
There's no such thread, and will never be such a thread (having the wineserver calling into client threads is an inherently unstable design).
The wineserver need not depend on the client thread responding to its (asynchronous) messages. In this case, if the client ignores the messages, you get the same effect as if it never releases its mutex, which is not a new failure mode.
Sure it is, if the process isn't holding the mutex. Your design lets a process own the mutex without holding it. So if a process acquires it, then releases it, does something else for 10 minutes and then hangs, and some other process decides it should grab the mutex, then it should be allowed to do so, since the original process isn't holding it. But it can't...
Rather than have a dedicated thread to maintain any such communication back and forth with the server, why not simply use a signal to the first thread in the process. The signal should execute regardless of whether or not the thread is hung, and if the mutex is 'owned' by the process, but not currently 'held', the signal handler releases the mutex, and marks it as no longer owned.
Even without that approach though, I don't think that the new failure mode is really likely to be that big a deal. If process A owns a mutex that process B wants, they quite likely have some functional interdependance which would be disrupted as much by any generic hang in process A as by a hang followed by a failure to release the mutex. And besides, once the user kills the hung process, the server should be able to reclaim the shared mutex object anyway.
-Gav
Ove,
On Sat, Feb 24, 2001 at 12:06:07PM +0100, Ove Kaaven wrote:
There's no such thread, and will never be such a thread (having the wineserver calling into client threads is an inherently unstable design).
I think I've read this before, when the topic was FindWindow. IIRC that thread never came to any conlusion/solution. Will the same happen to this topic too? If it looks like these problems could be solved by some sort of server calling into the client then why not do this until someone finds a better solution? On the other hand, if this mechanism doesn't work on a technical basis, that is something I can live with. "inherently unsable design" could be both: People simply not liking the idea of this and the corresponding design implications or it could be just another variant of "it won't work any better than the solution proposed so far". Which one is it?
Ciao Jörg