--- a/transfer.c +++ b/transfer.c @@ -1,31 +1,31 @@ /* - * EXERCISE 01 — BROKEN STARTING POINT. Do not edit this file; copy it. + * EXERCISE 01 — FIXED VARIANT. The repair is a TOTAL ORDER over the locks. * - * ============================ 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". + * A ledger with two accounts. transfer() moves money between them. Two + * threads each run one transfer, in opposite directions. * - * Build: clang -O0 -g -Wall -Wextra -pthread transfer.c -o transfer_broken - * Run: ./transfer_broken [marker-file] + * THE REPAIR + * transfer() no longer acquires in caller order. It sorts the two accounts by + * a stable key — the account id — and always acquires the lower id first. + * Every acquisition site in the program now agrees on one order, so a cycle + * in the wait-for graph cannot form: Coffman condition 4, circular wait, is + * removed outright. * - * Expected: two "reaching for" lines, then no further progress until the - * watchdog fires. Exit status 75 is the correct result for this build. + * The key can be anything total and stable: an id, an address, a name. What + * matters is that no site disagrees, including sites added later. State the + * order in a comment next to the lock declarations and assert it in review. * - * 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. + * Build: clang -O0 -g -Wall -Wextra -pthread transfer.c -o transfer_fixed + * Run: ./transfer_fixed [marker-file] + * + * Expected: exit status 0, 100002 transfers, and a conserved total. The + * watchdog never fires. + * + * Note on the rendezvous gate: it now times out after one second on the first + * transfer, because with a total order the two threads deliberately no longer + * meet inside the critical window. That one-second pause is the test + * scaffolding standing down, not the repair being slow. */ #include #include @@ -114,22 +114,29 @@ /* ------------------------------ 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. + * + * LOCK ORDER (the whole repair): accounts are always acquired in increasing + * `id` order, whatever direction the money is moving. The direction of the + * transfer and the order of acquisition are now two separate things. * -------------------------------------------------------------------------- */ 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); } + account_t *first = from->id < to->id ? from : to; + account_t *second = from->id < to->id ? to : from; + pthread_mutex_lock(&first->m); + if (who) { printf(" %s: holds account %d, waiting at the gate\n", who, first->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); + if (who) { printf(" %s: gate open, now reaching for account %d\n", who, second->id); fflush(stdout); } + pthread_mutex_lock(&second->m); from->balance -= amount; to->balance += amount; - pthread_mutex_unlock(&to->m); - pthread_mutex_unlock(&from->m); + pthread_mutex_unlock(&second->m); + pthread_mutex_unlock(&first->m); } static void *thread_paying_rent(void *arg) { @@ -153,7 +160,7 @@ arm_watchdog(WATCHDOG_S); setvbuf(stdout, NULL, _IOLBF, 0); - printf("EX01 build=broken (watchdog budget %ds)\n", WATCHDOG_S); + printf("EX01 build=fixed (watchdog budget %ds)\n", WATCHDOG_S); gate_init(&gate, 2); pthread_create(&t1, NULL, thread_paying_rent, NULL); @@ -170,7 +177,7 @@ 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", + printf("EX01 build=fixed result=COMPLETED transfers=%d opening=%ld closing=%ld conserved=%s\n", (ROUNDS + 1) * 2, opening, closing, opening == closing ? "true" : "false"); return 0; }