Unblocking if someone closes a file descriptor

Updated: May 06, 2022

Suppose the following sequence occurs:

If your resource manager handles the disconnection without considering the read(), then the client's read() is indefinitely blocked, even after the fd has been detached. This could happen for other blocking operations, such as calls to write() and devctl().

To avoid this situation, your resource manager needs to maintain a list of blocked clients. Whenever it blocks a client that's waiting for an operation to complete, you must add this client to the list—and you need to remove a client from the list when you unblock it.

The resource manager framework provides an io_close_dup() callout (see the Resource Managers chapter of Getting Started with QNX Neutrino) that gets called whenever close() is called on a file descriptor or a client otherwise disconnects. In this callout, you must traverse the list and use the server connection ID (scoid) and connection ID (coid) to determine which client threads are blocked on that file descriptor, and reply to them (via MsgError() or otherwise) to unblock them. For example:

int io_close_dup (resmgr_context_t *ctp, io_close_t *msg, RESMGR_OCB_T *ocb)
{
    // unblock any clients blocked on the file descriptor being closed
    blocked_client_t *client, *prev;
    prev = NULL;
    
    iofunc_lock_ocb_default(ctp, NULL, ocb);
    client = blocked_clients;
    while ( client != NULL )
    {
        if ( (client->coid == ctp->info.coid) && (client->scoid == ctp->info.scoid) )
        {
            MsgError( client -> rcvid, EBADF );
            if (prev != NULL) // anywhere but the head of the list
            {
                prev->next = client->next;
                free(client);
                client = prev->next;
            }
            else // head of the list case
            {
                blocked_clients = client->next; 
                free(client); 
                client = blocked_clients; 
            }
        }
        else // no match, move to the next entry and track previous entry
        { 
            prev = client;
            client = client->next;
        }
    }
    iofunc_unlock_ocb_default(ctp, NULL, ocb);
    
    // do rest of the regular close handling
    return iofunc_close_dup_default(ctp, msg, ocb );
}
CAUTION:

Unlike with most other I/O handlers, when the io_close_dup() handler runs, the attribute structure in the OCB (ocb->attr) is not locked by default. In your implementation, you may need to modify some fields in this structure. In this case, you must explicitly lock it to prevent server-side race conditions, which can cause bugs that are difficult to detect and fix.