Handling client unblocking due to signals or timeouts

Another convenient service that the resource manager library does for us is unblocking.

When a client issues a request (e.g., read()), this translates (via the client's C library) into a MsgSend() to our resource manager. The MsgSend() is a blocking call. If the client receives a signal during the time that the MsgSend() is outstanding, our resource manager needs to have some indication of this so that it can abort the request.

Because the library set the _NTO_CHF_UNBLOCK flag when it called ChannelCreate(), we'll receive a pulse whenever the client tries to unblock from a MsgSend() that we have MsgReceive()'d.

As an aside, recall that in the QNX Neutrino messaging model, the client can be in one of two states as a result of calling MsgSend(). If the server hasn't yet received the message (via the server's MsgReceive()), the client is in a SEND-blocked state—the client is waiting for the server to receive the message. When the server has actually received the message, the client transits to a REPLY-blocked state—the client is now waiting for the server to reply to the message (via MsgReply()).

When this happens and the pulse is generated, the resource manager library handles the pulse message and synthesizes an _IO_UNBLOCK message.

Looking through the resmgr_io_funcs_t and the resmgr_connect_funcs_t structures (see the QNX Neutrino C Library Reference), you'll notice that there are actually two unblock message handlers: one in the I/O functions structure and one in the connect functions structure.

Why two? Because we may get an abort in one of two places. We can get the abort pulse right after the client has sent the _IO_OPEN message (but before we've replied to it), or we can get the abort during an I/O message.

Once we've performed the handling of the _IO_CONNECT message, the I/O functions' unblock member will be used to service an unblock pulse. Therefore, if you're supplying your own io_open handler, be sure to set up all relevant fields in the OCB before you call resmgr_open_bind(); otherwise, your I/O functions' version of the unblock handler may get called with invalid data in the OCB. (Note that this issue of abort pulses “during” message processing arises only if there are multiple threads running in your resource manager. If there's only one thread, then the messages will be serialized by the library's MsgReceive() function.)

The effect of this is that if the client is SEND-blocked, the server doesn't need to know that the client is aborting the request, because the server hasn't yet received it.

Only in the case where the server has received the request and is performing processing on that request does the server need to know that the client now wishes to abort.

For more information on these states and their interactions, see the MsgSend(), MsgReceive(), MsgReply(), and ChannelCreate() functions in the QNX Neutrino C Library Reference; see also the chapter on Interprocess Communication in the System Architecture book.

If you're overriding the default unblock handler, iofunc_unblock_default(), you should always call the default handler to process any generic unblocking cases first (which it does by calling iofunc_unblock()). For example:

if((status = iofunc_unblock_default(...)) != _RESMGR_DEFAULT) {
    return status;
}

This ensures that any client waiting on a resource manager list (such as an advisory lock list) will be unblocked if possible.

You need some way to walk your table of blocked client rcvids, look for a match, and unblock it. The unblocking is done by replying to the client; you aren't replying to the unblock request as such, but to the original client call. Your resource manager could do this by returning a result indicating partial success (a short result from read(), for example); otherwise, you would typically use MsgError(rcvid,-1) to return an EINTR or ETIMEDOUT error as appropriate.

The routine should confirm the unblock is still pending (for example, to avoid the race condition where the resource manager unblocks a client thread before seeing its UNBLOCK pulse, and then the same thread sends another message), by calling MsgInfo() and then checking for the _NTO_MI_UNBLOCK_REQ flag. If you can't find a matching client, you can ignore the unblock request by returning _RESMGR_NOREPLY:

/* Check if rcvid is still valid and still has an unblock
   request pending. */
if (MsgInfo(ctp->rcvid, &info) == -1 ||
    !(info.flags & _NTO_MI_UNBLOCK_REQ)) {
	return _RESMGR_NOREPLY;
}

If you don't provide an unblock handler, having your client thread left REPLY-blocked on the server is expected behavior; the server has to be given a chance to clean up client data structures when a client terminates.