clinuxgccpthreads

pthread_rwlock_wrlock after pthread_rwlock_rdlock does not return EDEADLK


In the documentation at Open Group, it states that:

NAME
pthread_rwlock_wrlock, pthread_rwlock_trywrlock - lock a read-write lock object for writing

...

The pthread_rwlock_wrlock() and pthread_rwlock_trywrlock() functions may fail if:

...

[EDEADLK]
The current thread already owns the read-write lock for writing or reading.

However, when I run the following code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>

int main()
{
    int error; 

    pthread_rwlock_t *mutex = (pthread_rwlock_t *)malloc(sizeof(pthread_rwlock_t));
    if (mutex == NULL) {
        perror("malloc");
        abort();
    }

    pthread_rwlockattr_t attr;

    error = pthread_rwlockattr_init(&attr);
    if (error != 0)
    {
        fprintf(stderr, "%s:%d: pthread_rwlockattr_init failed: %s", __FILE__, __LINE__, strerror(error));
        abort();
    }

    error = pthread_rwlock_init(mutex, &attr);
    if (error != 0)
    {
        fprintf(stderr, "%s:%d: pthread_rwlock_init failed: %s", __FILE__, __LINE__, strerror(error));
        abort();
    }
    
    error = pthread_rwlock_wrlock(mutex);
    if (error != 0)
    {
        fprintf(stderr, "%s:%d: pthread_rwlock_wrlock failed: %s", __FILE__, __LINE__, strerror(error));
        abort();
    }
    
    error = pthread_rwlock_rdlock(mutex);
    if (error != 0)
    {
        fprintf(stderr, "%s:%d: pthread_rwlock_rdlock failed: %s", __FILE__, __LINE__, strerror(error));
        abort();
    }

    return 0;
}

The output is:

main.c:53: pthread_rwlock_rdlock failed: Resource deadlock avoided

But when I run the following code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>

int main()
{
    int error; 

    pthread_rwlock_t *mutex = (pthread_rwlock_t *)malloc(sizeof(pthread_rwlock_t));
    if (mutex == NULL) {
        perror("malloc");
        abort();
    }

    pthread_rwlockattr_t attr;

    error = pthread_rwlockattr_init(&attr);
    if (error != 0)
    {
        fprintf(stderr, "%s:%d: pthread_rwlockattr_init failed: %s", __FILE__, __LINE__, strerror(error));
        abort();
    }

    error = pthread_rwlock_init(mutex, &attr);
    if (error != 0)
    {
        fprintf(stderr, "%s:%d: pthread_rwlock_init failed: %s", __FILE__, __LINE__, strerror(error));
        abort();
    }
    
    error = pthread_rwlock_rdlock(mutex);
    if (error != 0)
    {
        fprintf(stderr, "%s:%d: pthread_rwlock_rdlock failed: %s", __FILE__, __LINE__, strerror(error));
        abort();
    }
    
    error = pthread_rwlock_wrlock(mutex);
    if (error != 0)
    {
        fprintf(stderr, "%s:%d: pthread_rwlock_wrlock failed: %s", __FILE__, __LINE__, strerror(error));
        abort();
    }
        
    return 0;
}

In this case, the pthread_rwlock_wrlock call does not return EDEADLK and instead blocks.

Is the Open Group explanation incorrect? It seems more reasonable for EDEADLK to be returned. What could be the issue here?

I have tested this on Linux using gcc, on my computer with WSL, and also on OnlineGDB, and it does not work in any of these environments. This seems to be a common implementation issue rather than a problem specific to my setup. Is this behavior intentional?


Solution

  • You have been over-selective in what part of the doc you quoted. The "Description" is among the normative sections, and it includes this:

    Results are undefined if the calling thread holds the read-write lock (whether a read or write lock) at the time the call is made.

    (Emphasis added)

    That is somewhat inconsistent with the part you did quote. One reasonable reconciliation that gives non-trivial meaning to both parts would be that the implementation is permitted to manifest any behavior whatever, but that explicit among its alternatives is returning the error code EDEADLK, and that that is the significance of that error code if an implementation does return it.

    With that said, the docs to which you linked are obsolete. There have been at least three subsequent revisions of POSIX.1. The latest version of POSIX resolves the inconsistency with a different version of the above-quoted text:

    If a deadlock condition occurs or the calling thread already owns the read-write lock for writing or reading, the call shall either deadlock or return [EDEADLK].

    ... and by changing EDEADLK from a "shall fail" to a "may fail".

    Note that although the implementation is now afforded alternatives, the behavior is no longer undefined, and no part of the doc any longer promises EDEADLK. That is consistent with the behavior you observe in your test.

    The explicit affirmation that an implementation may deadlock under the circumstances you describe goes back all the way to the 2004 edition of POSIX.1, though the language there is a bit different from both of the above.