Trylock on an EOWNERDEAD state mutex

Viewed 398

I found unexpected behavior when i tried to lock mutex with 'trylock' when the previous owner is dead without unlock it.
The first process that uses 'trylock' gets EOWNERDEAD state as expected, so uses the 'unlock' function to relase the mutex:

lock_status = pthread_mutex_trylock(mutex);
if (lock_status == EOWNERDEAD)
{
    pthread_mutex_unlock(mutex);
    printf("P1 mutex status: EOWNERDEAD\n");
}
else if (lock_status == ENOTRECOVERABLE)
    {printf("P1 mutex status: ENOTRECOVERABLE\n");}
else if (lock_status == EBUSY)
    {printf("P1 mutex status: EBUSY\n");}
else {printf("P1 mutex status: %d\n", lock_status);}

The second executes the same code obtaining the ENOTRECOVERABLE state, as expected.
But when the third executes the same operations gets the EBUSY state and this is unexpected.
The state should be ENOTRECOVERABLE as reported here
I tried to use the 'lock' function instead 'trylock' and the correct state was returned.
Is that a bug? in my purpouse i have to check the mutex status before doing any operation, so i cannot use the 'lock' function or a deadlock may occur.
I'm thinking to destroy the mutex when the ENOTRECOVERABLE state has returned:

else if (lock_status == ENOTRECOVERABLE)
{
    pthread_mutex_destroy(mutex);
    printf("P1 mutex status: ENOTRECOVERABLE\n");
}

But maybe there is a better way than this drastic solution, could it be? Full code:

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>

main(void)
{
int lock_status, mutex_fdesc;
pid_t process_id;
pthread_mutex_t* mutex;
pthread_mutexattr_t m_att;

//Process 1 die with locked mutex
process_id = fork();
if (process_id < 0) {exit(0);}
if (process_id == 0)
{
    usleep(100000);
    mutex_fdesc = shm_open("/mutex", O_RDWR, S_IRWXU | S_IRWXG);
    mutex = (pthread_mutex_t*)mmap(NULL, sizeof(pthread_mutex_t), 
                PROT_READ | PROT_WRITE, MAP_SHARED, mutex_fdesc, 0);
    close(mutex_fdesc);
    pthread_mutex_lock(mutex);
    exit(0);
}
///Process 2 try to lock mutex and gets EOWNERDEAD then make an unlock
process_id = fork();
if (process_id < 0) {exit(0);}
if (process_id == 0)
{
    usleep(200000);
    mutex_fdesc = shm_open("/mutex", O_RDWR, S_IRWXU | S_IRWXG);
    mutex = (pthread_mutex_t*)mmap(NULL, sizeof(pthread_mutex_t), 
                PROT_READ | PROT_WRITE, MAP_SHARED, mutex_fdesc, 0);
    close(mutex_fdesc);
    lock_status = pthread_mutex_trylock(mutex);
    if (lock_status == EOWNERDEAD)
        {
        pthread_mutex_unlock(mutex);
        printf("P2 mutex status: EOWNERDEAD\n");}
    else if (lock_status == ENOTRECOVERABLE)
        {printf("P2 mutex status: ENOTRECOVERABLE\n");}
    else if (lock_status == EBUSY)
        {printf("P2 mutex status: EBUSY\n");}
    else {printf("P2 mutex status: %d\n", lock_status);}
    exit(0);
}
///Process 2 try to lock mutex and gets ENOTRECOVERABLE then do nothing
process_id = fork();
if (process_id < 0) {exit(0);}
if (process_id == 0)
{
    usleep(400000);
    mutex_fdesc = shm_open("/mutex", O_RDWR, S_IRWXU | S_IRWXG);
    mutex = (pthread_mutex_t*)mmap(NULL, sizeof(pthread_mutex_t), 
                PROT_READ | PROT_WRITE, MAP_SHARED, mutex_fdesc, 0);
    close(mutex_fdesc);
    lock_status = pthread_mutex_trylock(mutex);
    if (lock_status == EOWNERDEAD)
        {
        pthread_mutex_unlock(mutex);
        printf("P3 mutex status: EOWNERDEAD\n");}
    else if (lock_status == ENOTRECOVERABLE)
        {printf("P3 mutex status: ENOTRECOVERABLE\n");}
    else if (lock_status == EBUSY)
        {printf("P3 mutex status: EBUSY\n");}
    else {printf("P3 mutex status: %d\n", lock_status);}
    exit(0);
}

mutex_fdesc = shm_open("/mutex", O_RDWR | O_CREAT | O_EXCL, S_IRWXU | S_IRWXG);
ftruncate(mutex_fdesc, sizeof(pthread_mutex_t));
mutex = (pthread_mutex_t*)mmap(NULL, sizeof(pthread_mutex_t), 
            PROT_READ | PROT_WRITE, MAP_SHARED, mutex_fdesc, 0);
close(mutex_fdesc);
pthread_mutexattr_init(&m_att);
pthread_mutexattr_setpshared(&m_att, PTHREAD_PROCESS_SHARED);
pthread_mutexattr_setrobust(&m_att, PTHREAD_MUTEX_ROBUST);
pthread_mutex_init(mutex, &m_att);
pthread_mutexattr_destroy(&m_att);
///Parent process try to lock the mutex and gets EBUSY
usleep(800000);
lock_status = pthread_mutex_trylock(mutex);
if (lock_status == EOWNERDEAD)
        {printf("Pparent mutex status: EOWNERDEAD\n");}
    else if (lock_status == ENOTRECOVERABLE)
        {printf("Pparent mutex status: ENOTRECOVERABLE\n");}
    else if (lock_status == EBUSY)
        {printf("Pparent mutex status: EBUSY\n");}
    else {printf("Pparent mutex status: %d\n", lock_status);}

pthread_mutex_destroy(mutex);
munmap((void*)mutex, sizeof(pthread_mutex_t));
shm_unlink("/mutex");
wait(NULL);
wait(NULL);
wait(NULL);
exit(0);
}
1 Answers

I think what you're seeing is that the attempt to lock the mutex is being considered successful when it returns EOWNERDEAD (which is necessary for the recovery semantics, so that only one thread tries to recover) and also when it returns ENOTRECOVERABLE, which I would agree is surprising, and seems contrary to the docs. The docs say that if the thread receiving EOWNERDEAD does not make the mutex consistent before unlocking then all subsequent attempts to lock it will return ENOTRECOVERABLE. I see nothing in the docs to suggest that that ought not to apply to pthread_mutex_trylock() the same as it does to pthread_mutex_lock().

If you want to report that as a bug then you would report it against whatever library is providing your pthreads implementation. That will vary at least with OS.

in my purpouse i have to check the mutex status before doing any operation, so i cannot use the 'lock' function or a deadlock may occur.

That sounds fishy. You're already using robust mutexes, else you would not be getting EOWNERDEAD or ENOTRECOVERABLE in the first place. pthread_mutex_trylock() is probably overkill, if not simply inappropriate. You use that if there is useful work you can do in the event that you fail to lock the mutex. Maybe there is in your case, but that would be unusual. If, for example, you're just delaying before trying again, then you would be better off just using pthread_mutex_lock() in the first place.

Related