Multiple watches on the same socket
Submitted by Owen Taylor
Link to original bug (#338943)
Description
In the GLib API, it's legitimate to add multiple watches to the same IO channel, but that doesn't work currently on Windows. Each watch results in a separate GPollFD, and are passed individually. to MsgWaitForMultipleObjects(). That violates the docs for MsgWaitForMultipleObjects:
The array can contain handles of objects of different types. It may not contain multiple copies of the same handle.
But that isn't what is causing the problems; the problems are:
- The logic in g_io_win32_prepare() will cause the different channels to overwrite each other unpredictably for WSAEventSelect()
- As soon as MsgWaitForMultipleObjects() returns the fd as being ready for any event, all GPollFDs waiting on any condition will be marked as ready. (In GPoll)
I'll attach a test case that I think will demonstrate the problem, though I haven't tried compiling it or running it on Win32, just on Linux, where it works. Depending on the order sources prepared, I'd expect it to either not print anything at all, or hit the HUP condition on Win32.
A sketch of a possible fix:
-
Keep a single GPollFD for GIOChannel, not once for each watch. Make the events in that GPollFD be the union of all the active watches.
You could implement this by keeping a linked list of all watches for the channel on the channel and recomputing the union of the events on change.
An alternate approach would be to use a refcounted bitmask. There's an implementation I wrote somewhere in the GTK+ stack that's pretty nifty, that possibly could be moved into GLib. But I have no clue where :-(
[ Basic technique was keep a set of masks in a linked list, when setting look for an existing mask with that bit clear and set it, otherwise add a new mask. On clearing, clear the first mask with that bit set. Remove masks that are 0. ]
-
In g_poll(), before waiting, call WSAEventSelect() to reset the record for the socket and clear any pending events. I believe that the current code slightly racy ... in a sequence like:
- Input arrives, callback is called
- More input arrives, reseting the event flag in the socket record
- App reads all available input
- MsgWaitForMultipleObjects is called, reports the socket readable
- Callback is called, app expects data, gets none
Now, it's probably good to make your app robust against getting no data in a read callback, but it doesn't match the guarantees that we present on Unix (*)
There is no race condition with more data arriving before clearing pending events because the interesting events are level triggered - see the WSAEventSelect() docs.
-
Then after polling call WSAEnumNetworkEvents() to determine what events actually happened.
I'm not sure there is enough information currently to let g_poll() know if the fd it is passed is a winsock event object ... if not, you could probably stuff some magic flag into the GPollFD->events field to mark that.
An alternate approach would be to reset the events in prepare(), and then call WSAEnumNetworkEvents() in the first check() on any of the sources. (g_poll() would stick a value into the events field that represents "something is ready, not sure what")
There would be duplicate work if there are multiple sources for the same channel... you'd have to reset the events in every call to prepare() ... but it should be survivable.
What the above doesn't handle is multiple channels for the same underlying socket, but I don't think that's really that much worth worrying about.
Version: 2.10.x