_NTO_CHF_UNBLOCK
Let's look at the _NTO_CHF_UNBLOCK flag; it has a few interesting wrinkles for both the client and the server.
Normally (i.e., where the server does not specify the _NTO_CHF_UNBLOCK flag) when a client wishes to unblock from a MsgSend() (and related MsgSendv(), MsgSendvs(), etc. family of functions), the client simply unblocks. The client could wish to unblock due to receiving a signal or a kernel timeout (see the TimerTimeout() function in the QNX OS C Library Reference. The unfortunate aspect to this is that the server has no idea that the client has unblocked and is no longer waiting for a reply. Note that it isn't possible to write a reliable server with this flag off, except in very special situations which require cooperation between the server and all its clients.
Let's assume that you have a server with multiple threads, all blocked on the server's MsgReceive() function. The client sends a message to the server, and one of the server's threads receives it. At this point, the client is blocked, and a thread in the server is actively processing the request. Now, before the server thread has a chance to reply to the client, the client unblocks from the MsgSend() (let's assume it was because of a signal).
Remember, a server thread is still processing the request on behalf of the client. But since the client is now unblocked (the client's MsgSend() would have returned with EINTR), the client is free to send another request to the server. Thanks to the architecture of QNX OS servers, another thread would receive another message from the client, with the exact same receive ID! The server has no way to tell these two requests apart! When the first thread completes and replies to the client, it's really replying to the second message that the client sent, not the first message (as the thread actually believes that it's doing). So, the server's first thread replies to the client's second message.
This is bad enough; but let's take this one step further. Now the server's second thread completes the request and tries to reply to the client. But since the server's first thread already replied to the client, the client is now unblocked and the server's second thread gets an error from its reply.
This problem is limited to multithreaded servers, because in a single-threaded server, the server thread would still be busy working on the client's first request. This means that even though the client is now unblocked and sends again to the server, the client would now go into the SEND-blocked state (instead of the REPLY-blocked state), allowing the server to finish the processing, reply to the client (which would result in an error, because the client isn't REPLY-blocked any more), and then the server would receive the second message from the client. The real problem here is that the server is performing useless processing on behalf of the client (the client's first request). The processing is useless because the client is no longer waiting for the results of that work.
The solution (in the multithreaded server case) is to have the server specify the _NTO_CHF_UNBLOCK
flag to its
ChannelCreate()
call.
This says to the kernel, Tell me when a client tries to unblock from me (by sending
me a pulse), but don't let the client unblock! I'll unblock the client myself.
The key thing to keep in mind is that this server flag changes the behavior of the client by not allowing the client to unblock until the server says it's okay to do so.
In a single-threaded server, the following happens:
Action | Client | Server |
---|---|---|
Client sends to server | Blocked | Processing |
Client gets hit with signal | Blocked | Processing |
Kernel sends pulse to server | Blocked | Processing (first message) |
Server completes the first request, replies to client | Unblocked with correct data | Processing (pulse) |
This didn't help the client unblock when it should have, but it did ensure that the server didn't get confused. In this kind of example, the server would most likely simply ignore the pulse that it got from the kernel. This is okay to do—the assumption being made here is that it's safe to let the client block until the server is ready with the data.
If you want the server to act on the pulse that the kernel sent, there are two ways to do this:
- Create another thread in the server that listens for messages (specifically, listening for the pulse from the kernel). This second thread would be responsible for canceling the operation that's under way in the first thread. One of the two threads would reply to the client.
- Don't do the client's work in the thread itself, but rather queue up the work. This is typically done in applications where the server is going to store the client's work on a queue and the server is event driven. Usually, one of the messages arriving at the server indicates that the client's work is now complete, and that the server should reply. In this case, when the kernel pulse arrives, the server cancels the work being performed on behalf of the client and replies.
Which method you choose depends on the type of work the server does. In the first case, the server is actively performing the work on behalf of the client, so you really don't have a choice—you'll have to have a second thread that listens for unblock-pulses from the kernel (or you could poll periodically within the thread to see if a pulse has arrived, but polling is generally discouraged).
In the second case, the server has something else doing the work—perhaps
a piece of hardware has been commanded to go and collect data.
In that case, the server's thread will be blocked on the MsgReceive() function
anyway, waiting for an indication from the hardware that the command has completed.
In either case, the server must reply to the client, otherwise the client will remain blocked.