#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h> 

pthread_mutex_t lock; 

/*
  Mutexes robustos lidam com o caso em que a thread/processo que
  possui o lock morre/termina abruptamente, sem antes liberar o
  lock.

  Isto possivelmente impediria todas as outras threads de obterem
  o lock novamente, mas lock robustos podem ser recuperados.  

  Quando uma thread que possuia o lock morre, uma das threads que
  esperava pelo mutex o consegue, recebendo como retorno o erro 
  EOWNERDEAD. Esta thread precisa então verificar o estado da
  aplicação e corrigi-lo, já que a thread que morreu foi interrompida
  sem finalizar a região crítica, o que pode ter deixado a aplicação
  em um estado inconsistente.

  Após esta possível correção, a thread que obteve o lock deve
  invocar a função pthread_mutex_consistent_np, sinalizando que o
  mutex agora está novamente consistente e pode ser usado como antes.
  Esta thread pode então executar sua RC e continuar o fluxo da
  aplicação.
  
 */

                       
void* f_thread1(void *v) {

  pthread_mutex_lock(&lock);
  printf("Thr1 - Lock obtido.\n");
  sleep(5);

  /*
    A Thread morre sem liberar o lock.

   */
  printf("Thr1 - Suicídio!\n");
  pthread_exit(0);

  return NULL;
}

void* f_thread2(void *v) {
  /* 
     Sleep para garantir que a thread1 obtenha o lock.
   */
  sleep(1);

  /*
    Verifica se o retorno é EOWNERDEAD. Neste caso, é
    responsabilidade desta thread corrigir o estado da
    aplicação e invocar pthread_mutex_consistent_np para
    tornar o mutex novamente consistente.
   */
  if (pthread_mutex_lock(&lock) == EOWNERDEAD) {
    printf("Thr2 - Thread que mantinha o lock morreu.\n");
    printf("Thr2 - Tornando o lock consistente.\n");
    pthread_mutex_consistent_np(&lock);
  } else {
    printf("Thr2 - Obtendo o lock normalmente.\n");
  }

  printf("Thr2 - Executando região crítica.\n");
  pthread_mutex_unlock(&lock);

  return NULL;
}

void* f_thread3(void *v) {
  sleep(1);

  /*
    A Thread 3 possui o mesmo código que a Thread 2,
    simulando concorrência pelo lock e demonstrando que
    qualquer que uma das threads pode receber EOWNERDEAD.
   */
  if (pthread_mutex_lock(&lock) == EOWNERDEAD) {
    printf("Thr3 - Thread que mantinha o lock morreu.\n");
    printf("Thr3 - Tornando o lock consistente.\n");
    pthread_mutex_consistent_np(&lock);
  } else {
    printf("Thr3 - Obtendo o lock normalmente.\n");
  }

  printf("Thr3 - Executando região crítica.\n");
  pthread_mutex_unlock(&lock);

  return NULL;
}

int main() {

  pthread_mutexattr_t attr;
  pthread_mutexattr_init(&attr);
  pthread_mutexattr_setrobust_np(&attr, PTHREAD_MUTEX_ROBUST_NP);

  pthread_mutex_init(&lock, &attr);
  
  pthread_t thr1, thr2, thr3;
  pthread_create(&thr1, NULL, f_thread1, NULL);
  pthread_create(&thr2, NULL, f_thread2, NULL);
  pthread_create(&thr3, NULL, f_thread3, NULL);
  pthread_join(thr1, NULL);
  pthread_join(thr2, NULL);
  pthread_join(thr3, NULL);

  printf("Finalizando programa.\n");

  return 0;
}