/*
 * Copyright ©2026 Soham Pardeshi. All rights reserved.
 * Permission is hereby granted to students registered for University of
 * Washington CSE 333 for use solely during Summer Quarter 2026 for
 * purposes of the course. No other use, copying, distribution, or
 * modification is permitted without prior written consent. Copyrights
 * for third-party components of this work must be honored. Instructors
 * interested in reusing these course materials should contact the author.
 */

// Exposes usleep() under strict -std=c17, which hides glibc extensions
// unless a feature test macro is defined before the system headers.
#define _DEFAULT_SOURCE

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define NUM_THREADS 5

// No free here: the caller does not own the memory being pointed at.
// Used by the buggy version below, where the pointer addresses a
// stack variable that main still controls.
//
// The short sleep is not part of the bug; it only widens the timing
// window so the race reliably shows up instead of getting lucky. main
// keeps looping and changing i while every worker is asleep here, so
// by the time a worker wakes up and reads *raw_number, i has usually
// already reached its final value.
static void* PrintNumber(void* raw_number) {
  usleep(2000);
  int number = *(int*) raw_number;
  printf("%d\n", number);
  return NULL;
}

// Frees the memory after reading it: the worker is the sole owner.
// Used by the fixed version below, where each pointer addresses a
// heap object created just for that thread.
static void* PrintNumberOwned(void* raw_number) {
  int* number = raw_number;
  printf("%d\n", *number);
  free(number);
  return NULL;
}

// BUG: every pthread_create call receives &i, the address of one
// shared loop variable. main keeps changing i while workers are still
// starting, so a worker can read a value that was never "assigned" to
// it, and more than one worker can read the same value.
static void RunBuggyVersion(void) {
  printf("Buggy version (every worker points at the same &i):\n");
  pthread_t threads[NUM_THREADS];

  int i;  // one int, reused on every iteration
  for (i = 0; i < NUM_THREADS; i++) {
    if (pthread_create(&threads[i], NULL, PrintNumber, &i) != 0) {
      break;
    }
  }
  for (int index = 0; index < NUM_THREADS; index++) {
    pthread_join(threads[index], NULL);
  }
}

// FIX: give every thread its own heap-allocated int. Each pointer
// addresses a distinct object that only one worker ever reads, so it
// does not matter that main keeps looping while workers are running.
static void RunFixedVersion(void) {
  printf("Fixed version (each worker owns one heap int):\n");
  pthread_t threads[NUM_THREADS];
  int created = 0;

  for (int value = 0; value < NUM_THREADS; value++) {
    int* number = malloc(sizeof(*number));
    if (number == NULL) {
      break;
    }
    *number = value;

    if (pthread_create(&threads[created], NULL, PrintNumberOwned,
                       number) != 0) {
      free(number);
      break;
    }
    created++;
  }
  for (int index = 0; index < created; index++) {
    pthread_join(threads[index], NULL);
  }
}

int main(void) {
  // Run the buggy version first: expect skipped, repeated, or
  // out-of-range numbers, and the exact output to vary between runs.
  RunBuggyVersion();
  RunFixedVersion();
  return EXIT_SUCCESS;
}