/* CSE513 optional practice. Bound deadlock mode with an external timeout. */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
static pthread_mutex_t locks[2] = {PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER};
static pthread_mutex_t gate = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t changed = PTHREAD_COND_INITIALIZER;
static int arrivals[2] = {0,0};
static const char *mode;
static void check(int err, const char *what) {
    if (err) { fprintf(stderr, "%s: %s\n", what, strerror(err)); exit(2); }
}
static void rendezvous(int phase) {
    check(pthread_mutex_lock(&gate), "gate lock");
    ++arrivals[phase];
    check(pthread_cond_broadcast(&changed), "broadcast");
    while (arrivals[phase] < 2)
        check(pthread_cond_wait(&changed, &gate), "wait");
    check(pthread_mutex_unlock(&gate), "gate unlock");
}
static void *worker(void *arg) {
    int id = *(int *)arg;
    if (!strcmp(mode, "ordered")) {
        check(pthread_mutex_lock(&locks[0]), "lock L1");
        check(pthread_mutex_lock(&locks[1]), "lock L2");
        printf("worker %d owns L1 and L2\n", id);
        check(pthread_mutex_unlock(&locks[1]), "unlock L2");
        check(pthread_mutex_unlock(&locks[0]), "unlock L1");
        return NULL;
    }
    check(pthread_mutex_lock(&locks[id]), "first lock");
    printf("worker %d holds L%d\n", id, id+1);
    rendezvous(0); /* Both first locks are held before either second request. */
    if (!strcmp(mode, "deadlock")) {
        printf("worker %d requests L%d while holding L%d\n", id, 2-id, id+1);
        check(pthread_mutex_lock(&locks[1-id]), "second lock");
        /* Unreachable under this protocol without external intervention. */
        check(pthread_mutex_unlock(&locks[1-id]), "second unlock");
    } else {
        int err = pthread_mutex_trylock(&locks[1-id]);
        if (err != EBUSY) { fprintf(stderr, "expected EBUSY, got %d\n", err); exit(3); }
        printf("worker %d: second lock EBUSY\n", id);
        rendezvous(1); /* Neither first lock is released before both attempts. */
    }
    check(pthread_mutex_unlock(&locks[id]), "first unlock");
    return NULL;
}
int main(int argc, char **argv) {
    if (argc != 2 || (strcmp(argv[1],"ordered") && strcmp(argv[1],"deadlock") && strcmp(argv[1],"try"))) {
        fprintf(stderr,"usage: %s ordered|deadlock|try\n",argv[0]); return 2;
    }
    mode=argv[1];setvbuf(stdout,NULL,_IONBF,0);
    pthread_t threads[2];int ids[2]={0,1};
    for (int i=0;i<2;++i) check(pthread_create(&threads[i],NULL,worker,&ids[i]),"create");
    for (int i=0;i<2;++i) check(pthread_join(threads[i],NULL),"join");
    for (int i=0;i<2;++i) check(pthread_mutex_destroy(&locks[i]),"destroy lock");
    check(pthread_mutex_destroy(&gate),"destroy gate");
    check(pthread_cond_destroy(&changed),"destroy condition");
    puts("completed");return 0;
}
