Force unlock mutex that was blocked by another thread

Consider the following test program:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <strings.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>


pthread_mutex_t mutex;
pthread_mutexattr_t mattr;
pthread_t thread1;
pthread_t thread2;
pthread_t thread3;


void mutex_force_unlock(pthread_mutex_t *mutex, pthread_mutexattr_t *mattr)
  {
    int e;
    e = pthread_mutex_destroy(mutex);
    printf("mfu: %s\n", strerror(e));
    e = pthread_mutex_init(mutex, mattr);
    printf("mfu: %s\n", strerror(e));
  }

void *thread(void *d)
  {
    int e;

    e = pthread_mutex_trylock(&mutex);
    if (e != 0)
      {
        printf("thr: %s\n", strerror(e));
        mutex_force_unlock(&mutex, &mattr);
        e = pthread_mutex_unlock(&mutex);
        printf("thr: %s\n", strerror(e));
        if (e != 0) pthread_exit(NULL);
        e = pthread_mutex_lock(&mutex);
        printf("thr: %s\n", strerror(e));
      }
    pthread_exit(NULL);
  }


void * thread_deadtest(void *d)
  {
    int e;
    e = pthread_mutex_lock(&mutex);
    printf("thr2: %s\n", strerror(e));
    e = pthread_mutex_lock(&mutex);
    printf("thr2: %s\n", strerror(e));
    pthread_exit(NULL);
  }


int main(void)
  {
    /* Setup */
    pthread_mutexattr_init(&mattr);
    pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_ERRORCHECK);
    //pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_NORMAL);
    pthread_mutex_init(&mutex, &mattr);

    /* Test */
    pthread_create(&thread1, NULL, &thread, NULL);
    pthread_join(thread1, NULL);
    if (pthread_kill(thread1, 0) != 0) printf("Thread 1 has died.\n");
    pthread_create(&thread2, NULL, &thread, NULL);
    pthread_join(thread2, NULL);
    pthread_create(&thread3, NULL, &thread_deadtest, NULL);
    pthread_join(thread3, NULL);
    return(0);
  }

Now when this program starts, I get the following output:

Thread 1 has died.
thr: Device busy
mfu: Device busy
mfu: No error: 0
thr: Operation not permitted
thr2: No error: 0
thr2: Resource deadlock avoided

Now I know that this was set several times earlier, but is there a way to force the mutex to be unlocked? It seems that the implementation will allow the mutex to be unlocked by the thread that blocked it, since it seems to be actively checking even with the usual type of mutex.

? , , , . , , . , , :

  • . , .
  • .
  • , .

, , , , ( ), . , , , .

- -, pthread_kill POSIX... .

, FreeBSD 9.3, .

+4
5

. , FreeBSD , . , .

, - . pthread_mutex_trylock, CPU, , . , , , EOWNERDEAD .

, :

/* Checks to see if we have access to robust mutexes. */
#ifndef PTHREAD_MUTEX_ROBUST
#define TSRA__ALTERNATE
#define TSRA_MAX_MUTEXABANDON   TSRA_MAX_MUTEX * 4
#endif

/* Mutex: Mutex Data Table Datatype */
typedef struct mutex_lock_table_tag__ mutexlock_t;
struct mutex_lock_table_tag__
  {
    pthread_mutex_t *mutex;     /* PThread Mutex */
    tsra_daclbk audcallbk;      /* Audit Callback Function Pointer */
    tsra_daclbk reicallbk;      /* Reinit Callback Function Pointer */
    int acbkstat;               /* Audit Callback Status */
    int rcbkstat;               /* Reinit Callback Status */
    pthread_t owner;            /* Owner TID */
    #ifdef TSRA__OVERRIDE
    tsra_clnup_t *cleanup;      /* PThread Cleanup */
    #endif
  };

/* ******** ******** Global Variables */

pthread_rwlock_t tab_lock;              /* RW lock for mutex table */
pthread_mutexattr_t mtx_attrib;         /* Mutex attributes */
mutexlock_t *mutex_table;               /* Mutex Table */
int tabsizeentry;                       /* Table Size (Entries) */
int tabsizebyte;                        /* Table Size (Bytes) */
int initialized = 0;                    /* Modules Initialized 0=no, 1=yes */
#ifdef TSRA__ALTERNATE
pthread_mutex_t *mutex_abandon[TSRA_MAX_MUTEXABANDON];
pthread_mutex_t mtx_abandon;            /* Abandoned Mutex Lock */
int mtx_abandon_count;                  /* Abandoned Mutex Count */
int mtx_abandon_init = 0;               /* Initialization Flag */
#endif
pthread_mutex_t mtx_recover;            /* Mutex Recovery Lock */

:

/* Attempts to recover a broken mutex. */
int tsra_mutex_recover(int lockid, pthread_t tid)
  {
    int result;

    /* Check Prerequisites */
    if (initialized == 0) return(EDOOFUS);
    if (lockid < 0 || lockid >= tabsizeentry) return(EINVAL);

    /* Check Mutex Owner */
    result = pthread_equal(tid, mutex_table[lockid].owner);
    if (result != 0) return(0);

    /* Lock Recovery Mutex */
    result = pthread_mutex_lock(&mtx_recover);
    if (result != 0) return(result);

    /* Check Mutex Owner, Again */
    result = pthread_equal(tid, mutex_table[lockid].owner);
    if (result != 0)
      {
        pthread_mutex_unlock(&mtx_recover);
        return(0);
      }

    /* Unless the system supports robust mutexes, there is
       really no way to recover a mutex that is being held
       by a thread that has terminated.  At least in FreeBSD,
       trying to destory a mutex that is held will result
       in EBUSY.  Trying to overwrite a held mutex results
       in a memory fault and core dump.  The only way to
       recover is to abandon the mutex and create a new one. */
    #ifdef TSRA__ALTERNATE      /* Abandon Mutex */
    pthread_mutex_t *ptr;

    /* Too many abandoned mutexes? */
    if (mtx_abandon_count >= TSRA_MAX_MUTEXABANDON)
      {
        result = TSRA_PROGRAM_ABORT;
        goto error_1;
      }

    /* Get a read lock on the mutex table. */
    result = pthread_rwlock_rdlock(&tab_lock);
    if (result != 0) goto error_1;

    /* Perform associated data audit. */
    if (mutex_table[lockid].acbkstat != 0)
      {
        result = mutex_table[lockid].audcallbk();
        if (result != 0)
          {
            result = TSRA_PROGRAM_ABORT;
            goto error_2;
          }
      }

    /* Allocate New Mutex */
    ptr = malloc(sizeof(pthread_mutex_t));
    if (ptr == NULL)
      {
        result = errno;
        goto error_2;
      }

    /* Init new mutex and abandon the old one. */
    result = pthread_mutex_init(ptr, &mtx_attrib);
    if (result != 0) goto error_3;
    mutex_abandon[mtx_abandon_count] = mutex_table[lockid].mutex;
    mutex_abandon[mtx_abandon_count] = mutex_table[lockid].mutex;
    mtx_abandon_count++;
    mutex_table[lockid].mutex = ptr;

    #else       /* Recover Mutex */

    /* Try locking the mutex and see what we get. */
    result = pthread_mutex_trylock(mutex_table[lockid].mutex);
    switch (result)
      {
        case 0:                 /* No error, unlock and return */
          pthread_unlock_mutex(mutex_table[lockid].mutex);
          return(0);
          break;
        case EBUSY:             /* No error, return */
          return(0);
          break;
        case EOWNERDEAD:        /* Error, try to recover mutex. */
          if (mutex_table[lockid].acbkstat != 0)
              {
                result = mutex_table[lockid].audcallbk();
                if (result != 0)
                  {
                    if (mutex_table[lockid].rcbkstat != 0)
                        {
                          result = mutex_table[lockid].reicallbk();
                          if (result != 0)
                            {
                              result = TSRA_PROGRAM_ABORT;
                              goto error_2;
                            }
                        }
                      else
                        {
                          result = TSRA_PROGRAM_ABORT;
                          goto error_2;
                        }
                  }
              }
            else
              {
                result = TSRA_PROGRAM_ABORT;
                goto error_2;
              }
          break;
        case EDEADLK:           /* Error, deadlock avoided, abort */
        case ENOTRECOVERABLE:   /* Error, recovery failed, abort */
          /* NOTE: We shouldn't get this, but if we do... */
          abort();
          break;
        default:
          /* Ambiguous situation, best to abort. */
          abort();
          break;
      }
    pthread_mutex_consistant(mutex_table[lockid].mutex);
    pthread_mutex_unlock(mutex_table[lockid].mutex);
    #endif

    /* Housekeeping */
    mutex_table[lockid].owner = pthread_self();
    pthread_mutex_unlock(&mtx_recover);

    /* Return */
    return(0);

    /* We only get here on errors. */
    #ifdef TSRA__ALTERNATE
    error_3:
    free(ptr);
    error_2:
    pthread_rwlock_unlock(&tab_lock);
    #else
    error_2:
    pthread_mutex_unlock(mutex_table[lockid].mutex);
    #endif
    error_1:
    pthread_mutex_unlock(&mtx_recover);
    return(result);
  }

FreeBSD - , Linux, . , , .

, . , , , , , , . , , , , . , , . , , , . , , .

. . , , . , . , abort() .

, . , . , mutex pthread_mutex_lock pthread_mutex_trylock. , . , mutex, , , , .

, /, . , , , .

0

, , pthread_mutex_consistent().

, pthread_mutex_consistent() , , .

, , mutex , , [EOWNERDEAD]. , .

, [EOWNERDEAD] pthread_mutex_consistent() pthread_mutex_unlock(), , [EOWNERDEAD].

+6

, , , pthread, , , , .

, / , pthread, , . :

void cancel_unlock_handler(void *p)
{
    pthread_mutex_unlock(p);
}

int my_pthread_mutex_lock(pthread_mutex_t *m)
{
    int rc;
    pthread_cleanup_push(cancel_unlock_handler, m);
    rc = pthread_mutex_lock(&m);
    if (rc != 0) {
        pthread_cleanup_pop(0);   
    }
    return rc;
}       

int my_pthread_mutex_unlock(pthread_mutex_t *m)
{
    pthread_cleanup_pop(0);
    return pthread_mutex_unlock(&m);
}

my_pthread_mutex_lock/my_pthread_mutex_unlock / pthread.

"", pthread_exit, , pthread_kill, ( , , , ), undefined , , .

+2

, , , , , , . , , . , , , , , .

.

, , . , .

, , . , , . , , , .

, , , , , . / .

I do not know for sure your precedent is complete, but only my 2 cents. If you like it, don't forget to vote for my answer.

0
source

You can only restart a process with a broken thread, using a function from the exec family to modify the process image. I assume that it is faster to restart the process than to restart the server.

0
source

Source: https://habr.com/ru/post/1584780/


All Articles