/*
 * EXERCISE 03 — BROKEN STARTING POINT.  Do not edit this file; copy it.
 *
 * ============================ UNSAFE CODE WARNING ============================
 * This program uses a dispatch semaphore as a mutual-exclusion primitive. That
 * is the defect you are being asked to find. Never ship it.
 * =============================================================================
 *
 * THE SCENARIO
 *   A thumbnail cache is protected by a "lock" that somebody built out of a
 *   dispatch semaphore with an initial value of 1. It excludes correctly: only
 *   one thread is ever inside the critical section. A performance engineer
 *   nonetheless reports that user-interactive work waiting on this cache runs
 *   at the speed of whatever background thread happens to hold it.
 *
 * WHAT THIS PROGRAM MEASURES
 *   A BACKGROUND-QoS thread takes the gate and holds it for 400 ms. Halfway
 *   through, a USER_INTERACTIVE thread tries to take the same gate and blocks.
 *   The holder samples its OWN current scheduling priority twice: once while
 *   nobody is waiting, and once while the high-priority thread is blocked
 *   behind it.
 *
 *   The signal is thread_info(THREAD_EXTENDED_INFO).pth_curpri — the same
 *   number `sample` and `spindump` print as "priority N". Unlike
 *   pthread_get_qos_class_np(), which reports only the QoS a thread REQUESTED,
 *   pth_curpri reflects any override the kernel has applied.
 *
 *   If the primitive tells the kernel who the owner is, the kernel can raise
 *   that owner while a higher-priority thread waits. The number goes up. If it
 *   does not, the number does not move.
 *
 * Build: clang -O0 -g -Wall -Wextra gate.c -o gate_broken
 * Run:   ./gate_broken [trials]        (default 3)
 *
 * Expected: donated=NO on every trial, and a summary line reading
 * donatedTrials=0/3. Exit status 0 — nothing here crashes or hangs. This is a
 * SCHEDULING defect, not a liveness one, which is exactly why it survives
 * testing.
 *
 * NOT CLAIMED: this program does not produce the textbook unbounded
 * priority-inversion stall. On modern Darwin the background holder still runs
 * and still finishes. What is measured here is the donation signal itself.
 */
#include <pthread.h>
#include <pthread/qos.h>
#include <os/lock.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dispatch/dispatch.h>
#include <mach/mach.h>
#include <mach/thread_info.h>

#define EXIT_WATCHDOG 75
#define EXIT_INTERNAL 70
#define HOLD_US   400000
#define SETTLE_US  50000

/* ------------------------------- the watchdog ------------------------------ */

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

/* -------------------------------- the signal ------------------------------- */

/* The calling thread's CURRENT scheduling priority, after any kernel override. */
static int current_priority(void) {
    thread_extended_info_data_t ei;
    mach_msg_type_number_t cnt = THREAD_EXTENDED_INFO_COUNT;
    kern_return_t kr = thread_info(mach_thread_self(), THREAD_EXTENDED_INFO,
                                   (thread_info_t)&ei, &cnt);
    return kr == KERN_SUCCESS ? ei.pth_curpri : -1;
}

/* --------------------------------- the gate --------------------------------
 * A counting semaphore initialised to 1, used for mutual exclusion.
 *
 * It excludes correctly. What it does not do is record an OWNER: a semaphore is
 * a bare count, and `signal` may legitimately come from a thread that never
 * called `wait`. There is therefore no thread for the kernel to boost.
 * -------------------------------------------------------------------------- */

typedef struct { dispatch_semaphore_t sem; } gate_t;

static const char *GATE_NAME = "dispatch_semaphore(1)";

static void gate_init(gate_t *g) {
    g->sem = dispatch_semaphore_create(1);
}
static void gate_acquire(gate_t *g) {
    dispatch_semaphore_wait(g->sem, DISPATCH_TIME_FOREVER);
}
static void gate_release(gate_t *g) {
    dispatch_semaphore_signal(g->sem);
}

/* --------------------------------- a trial --------------------------------- */

static int trial(int index) {
    /* Heap-allocated so the dispatch blocks below capture a mutable pointer;
       a block captures a stack struct by value, as const. */
    gate_t *g = malloc(sizeof(gate_t));
    if (!g) { perror("malloc"); _exit(EXIT_INTERNAL); }
    gate_init(g);

    dispatch_semaphore_t held = dispatch_semaphore_create(0);
    dispatch_semaphore_t done = dispatch_semaphore_create(0);
    __block int pri_uncontended = -1, pri_contended = -1;

    dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
        gate_acquire(g);
        pri_uncontended = current_priority();       /* nobody is waiting yet */
        dispatch_semaphore_signal(held);
        usleep(HOLD_US);                            /* the UI thread blocks during this window */
        pri_contended = current_priority();         /* a UI thread is now blocked behind us */
        gate_release(g);
        dispatch_semaphore_signal(done);
    });

    dispatch_semaphore_wait(held, DISPATCH_TIME_FOREVER);
    usleep(SETTLE_US);                              /* let the holder settle first */
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
        gate_acquire(g);
        gate_release(g);
    });
    dispatch_semaphore_wait(done, DISPATCH_TIME_FOREVER);
    usleep(150000);                                 /* let the UI thread drain */

    free(g);
    int donated = pri_contended > pri_uncontended;
    printf("  trial %d  %-24s holderPriUncontended=%-3d holderPriWhileUIWaits=%-3d donated=%s\n",
           index, GATE_NAME, pri_uncontended, pri_contended, donated ? "YES" : "NO");
    fflush(stdout);
    return donated;
}

int main(int argc, char **argv) {
    int trials = argc > 1 ? atoi(argv[1]) : 3;
    if (trials < 1) trials = 1;
    if (trials > 10) trials = 10;

    pthread_t w;
    if (pthread_create(&w, NULL, watchdog, (void *)(long)(15 + trials * 5)) != 0) {
        perror("watchdog"); return EXIT_INTERNAL;
    }
    pthread_detach(w);
    setvbuf(stdout, NULL, _IOLBF, 0);

    printf("EX03 build=broken primitive=%s\n", GATE_NAME);
    printf("  holder QoS=BACKGROUND, waiter QoS=USER_INTERACTIVE\n");
    printf("  signal = thread_info(THREAD_EXTENDED_INFO).pth_curpri\n");

    int donated = 0;
    for (int i = 1; i <= trials; i++) donated += trial(i);

    printf("EX03 build=broken primitive=%s trials=%d donatedTrials=%d\n",
           GATE_NAME, trials, donated);
    return 0;
}
