/*
 * EXERCISE 03 — FIXED VARIANT.  The repair is a primitive that has an OWNER.
 *
 * THE SCENARIO
 *   A thumbnail cache protected by a gate. Exactly one thread may be inside the
 *   critical section at a time.
 *
 * THE REPAIR
 *   The dispatch semaphore is replaced by os_unfair_lock. Both exclude equally
 *   well; only one of them records WHICH THREAD is inside.
 *
 *   os_unfair_lock stores the owning thread's port in the lock word. When a
 *   higher-priority thread blocks on it, the kernel knows precisely which
 *   thread to boost, and boosts it until the lock is released. A semaphore is a
 *   bare count with no owner — `signal` may legitimately come from a thread
 *   that never called `wait` — so there is nothing to boost.
 *
 *   The rule this measurement supports: a semaphore is for COUNTING PERMITS or
 *   for signalling between threads. Mutual exclusion wants a lock, because only
 *   a lock can tell the kernel who is holding things up.
 *
 *   pthread_mutex and Swift's Mutex / OSAllocatedUnfairLock carry ownership in
 *   the same way and behave the same way here. NSLock and NSRecursiveLock are
 *   built on pthread_mutex; DispatchSemaphore and DispatchGroup are not.
 *
 * 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_fixed
 * Run:   ./gate_fixed [trials]        (default 3)
 *
 * Expected: donated=YES on the trials, with the holder's priority rising from
 * the BACKGROUND band into the USER_INTERACTIVE band while the high-priority
 * thread waits. Exit status 0.
 *
 * NOT CLAIMED: donation is not a guarantee you can assert to the instruction.
 * It is a kernel policy observed here on this OS build, and a trial can miss it
 * if the scheduler settles differently. The bundled check therefore requires a
 * majority of trials to donate, not all of them.
 */
#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 --------------------------------
 * An os_unfair_lock, used for mutual exclusion.
 *
 * It excludes exactly as the semaphore did. The difference is that it records
 * an OWNER, so the kernel has a specific thread it can boost while a
 * higher-priority thread is blocked on the lock.
 *
 * "Unfair" refers to hand-off policy, not to correctness: a waiter is not
 * guaranteed to acquire in arrival order. That is a starvation consideration,
 * separate from the donation behaviour measured here.
 * -------------------------------------------------------------------------- */

typedef struct { os_unfair_lock lock; } gate_t;

static const char *GATE_NAME = "os_unfair_lock";

static void gate_init(gate_t *g) {
    g->lock = (os_unfair_lock)OS_UNFAIR_LOCK_INIT;
}
static void gate_acquire(gate_t *g) {
    os_unfair_lock_lock(&g->lock);
}
static void gate_release(gate_t *g) {
    os_unfair_lock_unlock(&g->lock);
}

/* --------------------------------- 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=fixed 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=fixed primitive=%s trials=%d donatedTrials=%d\n",
           GATE_NAME, trials, donated);
    return 0;
}
