dante
dante

Reputation: 1254

What happens if I do not call pthread_mutex_destroy when using PTHREAD_PROCESS_SHARED

On Linux, a mutex can be shared between processes by using PTHREAD_PROCESS_SHARED attribute then saved in a mapped file that may be used by many processes.

This is an example in https://linux.die.net/man/3/pthread_mutexattr_init that do the job above:

For example, the following code implements a simple counting semaphore in a mapped file that may be used by many processes.

/* sem.h */
struct semaphore {
    pthread_mutex_t lock;
    pthread_cond_t nonzero;
    unsigned count;
};
typedef struct semaphore semaphore_t;

semaphore_t *semaphore_create(char *semaphore_name);
semaphore_t *semaphore_open(char *semaphore_name);
void semaphore_post(semaphore_t *semap);
void semaphore_wait(semaphore_t *semap);
void semaphore_close(semaphore_t *semap);

/* sem.c */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <pthread.h>
#include "sem.h"

semaphore_t *
semaphore_create(char *semaphore_name)
{
int fd;
    semaphore_t *semap;
    pthread_mutexattr_t psharedm;
    pthread_condattr_t psharedc;

    fd = open(semaphore_name, O_RDWR | O_CREAT | O_EXCL, 0666);
    if (fd < 0)
        return (NULL);
    (void) ftruncate(fd, sizeof(semaphore_t));
    (void) pthread_mutexattr_init(&psharedm);
    (void) pthread_mutexattr_setpshared(&psharedm,
        PTHREAD_PROCESS_SHARED);
    (void) pthread_condattr_init(&psharedc);
    (void) pthread_condattr_setpshared(&psharedc,
        PTHREAD_PROCESS_SHARED);
    semap = (semaphore_t *) mmap(NULL, sizeof(semaphore_t),
            PROT_READ | PROT_WRITE, MAP_SHARED,
            fd, 0);
    close (fd);
    (void) pthread_mutex_init(&semap->lock, &psharedm);
    (void) pthread_cond_init(&semap->nonzero, &psharedc);
    semap->count = 0;
    return (semap);
}

semaphore_t *
semaphore_open(char *semaphore_name)
{
    int fd;
    semaphore_t *semap;

    fd = open(semaphore_name, O_RDWR, 0666);
    if (fd < 0)
        return (NULL);
    semap = (semaphore_t *) mmap(NULL, sizeof(semaphore_t),
            PROT_READ | PROT_WRITE, MAP_SHARED,
            fd, 0);
    close (fd);
    return (semap);
}

void
semaphore_post(semaphore_t *semap)
{
    pthread_mutex_lock(&semap->lock);
    if (semap->count == 0)
        pthread_cond_signal(&semapx->nonzero);
    semap->count++;
    pthread_mutex_unlock(&semap->lock);
}

void
semaphore_wait(semaphore_t *semap)
{
    pthread_mutex_lock(&semap->lock);
    while (semap->count == 0)
        pthread_cond_wait(&semap->nonzero, &semap->lock);
    semap->count--;
    pthread_mutex_unlock(&semap->lock);
}

void
semaphore_close(semaphore_t *semap)
{
    munmap((void *) semap, sizeof(semaphore_t));
}
The following code is for three separate processes that create, post, and wait on a semaphore in the file /tmp/semaphore. Once the file is created, the post and wait programs increment and decrement the counting semaphore (waiting and waking as required) even though they did not initialize the semaphore.

/* create.c */
#include "pthread.h"
#include "sem.h"

int
main()
{
    semaphore_t *semap;

    semap = semaphore_create("/tmp/semaphore");
    if (semap == NULL)
        exit(1);
    semaphore_close(semap);
    return (0);
}

/* post */
#include "pthread.h"
#include "sem.h"

int
main()
{
    semaphore_t *semap;

    semap = semaphore_open("/tmp/semaphore");
    if (semap == NULL)
        exit(1);
    semaphore_post(semap);
    semaphore_close(semap);
    return (0);
}

/* wait */
#include "pthread.h"
#include "sem.h"

int
main()
{
    semaphore_t *semap;

    semap = semaphore_open("/tmp/semaphore");
    if (semap == NULL)
        exit(1);
    semaphore_wait(semap);
    semaphore_close(semap);
    return (0);
}

But calling pthread_mutex_destroy() on a shared mutex is tricky because it can cause error on other process and the example above also does not call pthread_mutex_destroy(). So I am thinking not to destroy it.

My question is: Is it safe if I init a PTHREAD_PROCESS_SHARED mutex, save it to a mapped file and use it forever on many processes without calling pthread_mutex_destroy() or re-initialize it?

Upvotes: 1

Views: 554

Answers (1)

John Bollinger
John Bollinger

Reputation: 180201

My question is: Is it safe if I init a PTHREAD_PROCESS_SHARED mutex, save it to a mapped file and use it forever on many processes without calling pthread_mutex_destroy() or re-initialize it?

It is allowed for a process-shared mutex to outlive the process that initialized it. If you map such a mutex to a persistent regular file, then its state will persist indefinitely, even while no process has it mapped. As long as the integrity of its state is maintained -- including, but not limited to, no process destroying it via pthread_mutex_destroy() -- new processes can map it and use it. That is to say, the semantics of what you describe are well-defined.

But is it safe? Not especially.

The first issue is that you need to know when to create it, and you need to avoid race conditions when you do. If you rely on the processes that regularly use the mutex to initialize it at need, then you have to make sure that exactly one creates and initializes it when the file does not already exist.

Another issue is that using a long-lived shared mutex like that produces a great deal of exposure to failures. For example, if a program crashes while holding the mutex locked then it will remain locked until you take some kind of manual corrective action. Or if the mapped file is manipulated directly then the mutex state can easily be corrupted, producing undefined behavior in all programs using it -- even across reboots.

If you really need a long-persisting synchronization object, then I would suggest considering a POSIX named semaphore. It is designed for the purpose, taking the above considerations and others into account. It differs somewhat, however, in that such semaphores reside in the kernel and have kernel persistence, so they do not persist across reboots (which is generally a good thing), and they are not susceptible to ordinary file manipulation.

Alternatively, you could consider a System V semaphore. This is an older semaphore implementation that also has kernel persistence. Its interface is considerably clunkier than that of the POSIX semaphore, but it has a few useful features that the POSIX semaphore does not, such as providing for automatic unlocking when the process holding one locked terminates (even abnormally).

Upvotes: 2

Related Questions