Reputation: 33
First let me introduce the application scene:
I have a service application which is spying the status of something, while also have multiple applications waiting for the status to change. Once the status is changed, each application will read the status value (via a named FileMap object) and execute corresponding actions, and then wait for the status to be changed again.
So I used a named Event object to do the synchronization work. All applications are waiting for this event to be signaled, and the service application will set this event to be signaled when that status is changed.
I need to guarantee that when the status is changed, each waiting application will be released and is released only once!
I have tried with these 2 methods
Both methods seem work well during the test. But I think neither of them is reliable because:
For ## Method 1 ##, maybe some of the waiting threads won't get chance to be executed before the ResetEvent function is called.
For ## Method 2 ##, Microsoft has claimed PulseEvent is unreliable and should not be used.
Is there any workable solution for this case? Any advice is welcome.
Upvotes: 3
Views: 644
Reputation: 4782
Named pipes is you choice in this scenario!
If you need to just send data to multiple processes, it’s better to use named pipes, not the events. Unlike auto-reset events, you don't need own pipe for each of the processes. Each named pipe has an associated server process and one or more associated client processes. When there are many clients, many instances of the same named pipe are automatically created by the operating system for each of the clients. All instances of a named pipe share the same pipe name, but each instance has its own buffers and handles, and provides a separate conduit for client/server communication. The use of instances enables multiple pipe clients to use the same named pipe simultaneously. Any process can act as both a server for one pipe and a client for another pipe, and vice versa, making peer-to-peer communication possible.
If you will use a named pipe, there would be no need in the events at all in your scenario, and the data will have guaranteed delivery no matter what happens with the processes – each of the processes may get long delays (e.g. by a swap) but the data will be finally delivered ASAP without your special involvement.
If you are still interested in the events, here we go. ☺
As you have correctly pointed out, PulseEvent should not be used at all. This function exists from the very first 32-bit version of Windows NT - 3.1. Maybe it was reliable then, but not now.
Please consider using the named auto-reset events. Just to reiterate about the auto-reset events and how to use them: the CreateEvent function has the bManualReset argument (if this parameter is TRUE, the function creates a manual-reset event object, which requires the use of the ResetEvent function to set the event state to non-signaled -- this is not what you need). If this parameter is FALSE, the function creates an auto-reset event object, and system automatically resets the event state to non-signaled after a single waiting thread has been released, i.e. has exited from a function like WaitForMultipleObjects or WaitForSigleObject. With auto-reset event, you would not ever call ResetEvent (since it automatically reset) or PulseEvent (since it is not reliable and deprecated).
As you have also correctly pointed out even Microsoft has admitted that PulseEvent should not be used -- see https://msdn.microsoft.com/en-us/library/windows/desktop/ms684914(v=vs.85).aspx -- only those threads will be notified that are in the "wait" state at the moment PulseEvent is called. If they are in any other state, they will not be notified, and you may never know for sure what the thread state is. A thread waiting on a synchronization object can be momentarily removed from the wait state by a kernel-mode Asynchronous Procedure Call, and then returned to the wait state after the APC is complete. If the call to PulseEvent occurs during the time when the thread has been removed from the wait state, the thread will not be released because PulseEvent releases only those threads that are waiting at the moment it is called. You can find out more about the kernel-mode Asynchronous Procedure Calls (APC) at the following links:
We have never used PulseEvent in our applications – we manage to do everything with just auto-reset event - we are using them since Windows NT 3.51 and they work very well, we are very happy with them.
You have multiple threads in multiple processes that need to be notified. They are waiting for an event, and you have to make sure that all the threads did in fact receive the notification, and receive only once. There is no other reliable way to do that other than (surprise!) use auto-reset events. Just create own named event for each waiting application. So, you will need to have as many events as are the waiting applications. Besides that, you will need to keep a list of registered consumers, where each consumer has an associated event name. So, to notify all the consumers, you will have to do SetEvent in a loop for all the consumer events. This is a very fast, reliable and cheap way. Since you are using cross-process communication, the waiting application will have to register and de-register its events via other means of inter-process communication, like SendMessage, or a more complex way, as your FileMap object. Let me, for the sake of simplicity, give an example of using SendMessage for that purpose. When a waiting application registers itself at your main notifier process, it sends SendMessage to your process to request a unique event name. You just increment the counter and return something like YourAppNameEvent1, YourAppNameEvent2, etc. Before you return this name, call CreateEvent to actually and create the auto-reset named events with that name, so the waiting applications will call OpenEvent with that name to open that existing event and get the event handle of this event object. When the waiting application de-registers – it closes the event handle that it opened, and sends another SendMessage, to let you know that you should CloseHandle too on your side to finally release this event object (it is closed on last CloseHandle - the event object is destroyed when its last handle has been closed). If the consumer process crashes, you will end up with a dummy event, since you will not know that you should do CloseHandle, but this should not be a problem - the events are very fast and very cheap, and there is virtually no limit on the kernel objects - the per-process limit on kernel handles is 2^24. This may be as a temporary solution – for you to make sure that everything works, but then you should change the program logic so that the clients create the events but you open them before calling SetEvent and closing afterwards. If they won’t open – then the client has crashed and you just remove it from the list. This is a little bit slower method if you do multiple notifications per second, but you can close events not each time, but with only after a certain time have passed since it last time have been closed – you decide. If you notify rarely, you can open and close each time.
Upvotes: 0
Reputation: 16761
There is no way this can be safely implemented with O(1) synchronization primitives.
To inform N applications about new status change use N events. Service should set them all, and every application should reset its corresponding event upon handling the current status change.
To wait until all applications handle new status change service can use another set of N events and WaitForMultipleObjects with bWaitAll==TRUE. Each application should set its corresponding event.
So, service does a loop: observe status change, write to shared memory, set all A events, wait on all B events, reset all B events, continue loop. Every application does a loop: wait on its A(i) event, reset A(i), handle status change, set B(i), continue loop.
Both A and B events can be of auto-resetting type. Then you don't have to reset anything.
If you feel green and don't want to waste resources you can use some sort of reversed semaphore instead of B set of events. This can be implemented with one shared counter synchronized by mutex and one event (B') to notify the service. Instead of waiting on whole B set the service waits on B' and when wait is over set the counter to N. Instead of setting its B(i) event each application should decrement the counter, and if counter drops to zero that last application should set only B'.
You can't bypass the A set of events. The problem is not in setting the A event but in resetting. By resetting its A(i) event the application will not miss another status change. And it is not helpful to use semaphore here.
Note that this solution does not take into account possible crash of an application. If that happens the service will wait forever for non-existing application to respond.
Upvotes: 1
Reputation: 340178
One problem with method #1 (even if it's unlikely, timing-wise) is that you can't make the guarantee that an application "is released only once" - it would be possible for a thread that's waiting on the event to be released when you call SetEvent()
,do it's work, then try to wait on the event again before your thread gets around to calling ResetEvent()
. I ResetEvent() hasn't occurred yet, the thread will not block (essentially being released more than once).
You may need a second event object that's in the non-signaled state for threads to wait on during the SetEvent()/ResetEvent() sequence that occurs on your 'real' event object. Essentially, each consumer thread needs to wait for both events sequentially, with only one of the 2 events signaled at any moment. Normally only one of the events would be in the reset state where threads would block, but there will be a brief period during the SetEvent()/ResetEvent() sequence of the 'main' event object where both events would be a reset state.
However, one important thing or you to consider is the requirement that "each waiting application will be released". How will your overall application deal with a thread that was just about to block on the status changed event, but hadn't quite gotten there (so it's not actually an application waiting to be released yet)? How is that different than if SetEvent()
doesn't guarantee that all threads blocked on the event will be run when the event is signaled? You have a race condition of sorts either way.
Upvotes: 1