/*
 * EXERCISE 01 — BROKEN STARTING POINT.  Do not edit this file; copy it.
 *
 * ============================ UNSAFE CODE WARNING ============================
 * This program DEADLOCKS ON PURPOSE, on every run. It is a diagnosis exercise,
 * not a pattern to copy. It is safe to run only because two independent bounds
 * exist:
 *   1. an in-process watchdog thread that calls _exit(75) after WATCHDOG_S
 *   2. an external hard timeout in check.sh / run-all.sh, which SIGKILLs it
 * Never ship code shaped like transfer() below.
 * =============================================================================
 *
 * THE SCENARIO
 *   A ledger with two accounts. transfer() locks the source account, then the
 *   destination account, then moves the money. Two threads each run one
 *   transfer, in opposite directions. In production this feature "sometimes
 *   freezes, and only under load".
 *
 * Build: clang -O0 -g -Wall -Wextra -pthread transfer.c -o transfer_broken
 * Run:   ./transfer_broken [marker-file]
 *
 * Expected: two "reaching for" lines, then no further progress until the
 * watchdog fires. Exit status 75 is the correct result for this build.
 *
 * Why C rather than Swift: the evidence you are asked to recognise is a KERNEL
 * WAIT. `sample` shows both threads parked in __psynch_mutexwait with nothing
 * between the source line and the stack frame. A Swift NSLock bottoms out in
 * the same call through more layers.
 */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>

#define WATCHDOG_S     5
#define EXIT_WATCHDOG 75
#define EXIT_INTERNAL 70
#define ROUNDS     50000
#define GATE_WAIT_MS 1000

/* ------------------------------- the ledger ------------------------------- */

typedef struct {
    int             id;
    pthread_mutex_t m;
    long            balance;
} account_t;

static account_t checking = { .id = 1, .m = PTHREAD_MUTEX_INITIALIZER, .balance = 100000 };
static account_t savings  = { .id = 2, .m = PTHREAD_MUTEX_INITIALIZER, .balance = 100000 };

/* --------------------------- the rendezvous gate ---------------------------
 * Test scaffolding, not part of the bug. It removes the timing luck that
 * normally hides an ordering defect, so the failure is reproducible on the
 * first run instead of once a week in production.
 *
 * It waits at most GATE_WAIT_MS for a peer and then proceeds regardless, so it
 * can never itself be the thing that stops the program.
 * -------------------------------------------------------------------------- */

typedef struct { pthread_mutex_t m; pthread_cond_t c; int arrived, target; } gate_t;
static gate_t gate;

static void gate_init(gate_t *g, int target) {
    pthread_mutex_init(&g->m, NULL);
    pthread_cond_init(&g->c, NULL);
    g->arrived = 0;
    g->target = target;
}

static void gate_wait(gate_t *g) {
    struct timeval now;
    struct timespec deadline;
    gettimeofday(&now, NULL);
    deadline.tv_sec  = now.tv_sec + (GATE_WAIT_MS / 1000);
    deadline.tv_nsec = now.tv_usec * 1000;

    pthread_mutex_lock(&g->m);
    if (++g->arrived >= g->target) {
        pthread_cond_broadcast(&g->c);
    } else {
        /* Predicate loop with a deadline: a wakeup is a hint, not a promise. */
        while (g->arrived < g->target) {
            if (pthread_cond_timedwait(&g->c, &g->m, &deadline) == ETIMEDOUT) break;
        }
    }
    pthread_mutex_unlock(&g->m);
}

/* ------------------------------- the watchdog ------------------------------
 * The reason this fixture cannot wedge your machine. It reports through
 * write(2) and exits through _exit(2) rather than printf/exit, because a
 * deadlocked process may hold a lock that stdio or an atexit handler needs.
 * -------------------------------------------------------------------------- */

static void *watchdog(void *arg) {
    sleep((unsigned)(long)arg);
    const char msg[] = "\nWATCHDOG: no progress within budget - forcing _exit(75).\n";
    ssize_t n = write(2, msg, sizeof(msg) - 1); (void)n;
    _exit(EXIT_WATCHDOG);
}

static void arm_watchdog(int seconds) {
    pthread_t t;
    if (pthread_create(&t, NULL, watchdog, (void *)(long)seconds) != 0) {
        perror("watchdog"); _exit(EXIT_INTERNAL);
    }
    pthread_detach(t);
}

/* ------------------------------ the operation ------------------------------
 * Lock both accounts, move `amount` from `from` to `to`, release both.
 * `who` narrates the first call from each thread and is NULL for the rest.
 * -------------------------------------------------------------------------- */

static void transfer(account_t *from, account_t *to, long amount, const char *who) {
    pthread_mutex_lock(&from->m);
    if (who) { printf("  %s: holds account %d, waiting at the gate\n", who, from->id); fflush(stdout); }

    gate_wait(&gate);

    if (who) { printf("  %s: gate open, now reaching for account %d\n", who, to->id); fflush(stdout); }
    pthread_mutex_lock(&to->m);

    from->balance -= amount;
    to->balance   += amount;

    pthread_mutex_unlock(&to->m);
    pthread_mutex_unlock(&from->m);
}

static void *thread_paying_rent(void *arg) {
    (void)arg;
    transfer(&checking, &savings, 250, "thread-1 checking->savings");
    for (int i = 0; i < ROUNDS; i++) transfer(&checking, &savings, 1, NULL);
    return NULL;
}

static void *thread_topping_up(void *arg) {
    (void)arg;
    transfer(&savings, &checking, 250, "thread-2 savings->checking");
    for (int i = 0; i < ROUNDS; i++) transfer(&savings, &checking, 1, NULL);
    return NULL;
}

int main(int argc, char **argv) {
    const char *marker_path = argc > 1 ? argv[1] : NULL;
    pthread_t t1, t2;
    long opening = checking.balance + savings.balance;

    arm_watchdog(WATCHDOG_S);
    setvbuf(stdout, NULL, _IOLBF, 0);
    printf("EX01 build=broken  (watchdog budget %ds)\n", WATCHDOG_S);

    gate_init(&gate, 2);
    pthread_create(&t1, NULL, thread_paying_rent, NULL);
    pthread_create(&t2, NULL, thread_topping_up, NULL);

    usleep(400000);                       /* let both threads reach their block */
    if (marker_path) {                    /* tell an evidence script we are armed */
        FILE *f = fopen(marker_path, "w");
        if (f) { fprintf(f, "%d\n", (int)getpid()); fclose(f); }
    }
    printf("  main: joining both threads\n");

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    long closing = checking.balance + savings.balance;
    printf("EX01 build=broken result=COMPLETED transfers=%d opening=%ld closing=%ld conserved=%s\n",
           (ROUNDS + 1) * 2, opening, closing, opening == closing ? "true" : "false");
    return 0;
}
