This project has been created as part of the 42 curriculum by jbarreir.
codexion is a concurrent-programming simulation written in C with POSIX threads. A group of coders work in a shared coworking space, where each compilation requires two adjacent quantum dongles. After compiling, a coder debugs and refactors before requesting access to the dongles again.
The project focuses on coordinating multiple threads safely while preserving liveness: coders must not deadlock, shared dongles must not be used simultaneously, access must follow the selected scheduling policy, and a dedicated monitor must detect burnout and stop the simulation cleanly.
- Instructions
- Scheduling policies
- Blocking cases handled
- Thread synchronization mechanisms
- Project structure
- Resources
Compile the project with:
makeThis creates the codexion executable.
Useful Makefile rules:
make clean # Remove object files
make fclean # Remove object files and the executable
make re # Rebuild everything./codexion \
<number_of_coders> \
<time_to_burnout> \
<time_to_compile> \
<time_to_debug> \
<time_to_refactor> \
<compiles_required> \
<dongle_cooldown> \
<scheduler>All times are expressed in milliseconds.
Example:
./codexion 4 800 200 200 200 5 10 fifoThis starts four coders. A coder burns out if more than 800 ms pass without starting a new compilation. Each compilation, debugging phase, and refactoring phase lasts 200 ms; every coder must compile five times; dongles have a 10 ms cooldown; and access is scheduled with FIFO.
Valid schedulers are:
fifo
edf
The First In, First Out scheduler gives priority to the coder that first entered a dongle queue.
The Earliest Deadline First scheduler prioritizes the waiting coder with the earliest recorded compilation timestamp. This aims to favor the coder that has waited longest since its last compilation.
Concurrency introduces several failure modes that must be handled explicitly.
Each coder needs two dongles, which could create circular waiting. Dongles are always locked in a deterministic order based on their identifier. By removing circular wait, the implementation prevents Coffman-style deadlocks.
Every dongle owns a mutex. A coder may mark a dongle as PLUGGED only while holding the corresponding lock, preventing simultaneous access by two coders.
When one or both dongles are unavailable, the coder waits on a condition variable instead of continuously polling. Once a dongle becomes available, its condition variable is broadcast so waiting coders can re-evaluate access safely.
After a compilation starts, both dongles enter COOLING_DOWN. The monitor periodically checks every dongle and makes it available again once its cooldown has elapsed.
Cooldown updates are independent of whether the coder associated with a dongle has already completed its work. This prevents a completed coder from leaving a dongle permanently unavailable to its neighbor.
A dedicated monitor thread checks each coder's last compilation start time. If the configured burnout limit is exceeded, the monitor marks the coder as burned out, sends the shutdown signal, wakes any threads blocked on dongle condition variables, and stops the simulation.
All output is protected by a logging mechanism. This ensures that log lines are printed atomically and concurrent threads cannot interleave their messages.
Since printf() is a relatively slow function, we offload this responsibility to a dedicated printer thread. Other threads simply enqueue their logs into a linked-list queue, while the printer outputs them at its own pace.
MAX_CODERS is capped at 1,024. Approaching 2,000 threads cannot guarantee a 10ms burnout detection threshold. Also, 7,000 threads can cause test machines to fail due to the heavy overhead of concurrent POSIX threads. parse_rules rejects any number_of_coders outside the [1, MAX_CODERS] range with MAX_COD_ERR before the simulation starts.
Mutexes protect all shared mutable state:
| Shared resource | Protection |
|---|---|
| Dongle state, queue, cooldown timestamp | One mutex per dongle |
| Coder state and compilation counters | One mutex per coder |
| Global simulation status | Simulation mutex |
For example, a coder checks whether both dongles are available only after locking both dongle mutexes. This makes the availability check and the transition to PLUGGED atomic from the perspective of other coders.
Each dongle has a condition variable. A coder that cannot obtain both dongles releases the appropriate mutex and sleeps with pthread_cond_wait.
When a coder releases dongles or the monitor finishes their cooldown, pthread_cond_broadcast wakes all waiting coders. Every awakened thread locks the resources again and re-checks the condition before proceeding, which is essential because a wake-up does not guarantee ownership.
The monitor and submonitor pool updates the shared simulation status to SHUTDOWN_SIGNAL under the simulation mutex. Coders check this state during their routine and while sleeping. When shutdown occurs, the monitor also broadcasts all dongle condition variables, ensuring that no thread remains blocked forever.
Worker threads append their status logs to a linked-list queue protected by the printer mutex. Upon enqueuing a message, they broadcast a signal via the printer condition variable to wake up the dedicated printer thread asynchronously. This decouples printf() operations from the simulation logic, preventing I/O blocking during concurrent execution.
.
βββ includes/
β βββ codexion.h
βββ src/
β βββ core/ # Coder routine, init data
β βββ monitor/ # Burnout and cooldown monitor
β βββ printer/ # Logging printer
β βββ pthreads/ # Thread creation and cleanup
β βββ sheduler/ # FIFO and EDF queue policies
β βββ utils/ # Argument parsing, timing, sleeps
β βββ main.c
βββ Makefile
- POSIX Threads Programming
pthread_mutex_lock(3)pthread_cond_wait(3)- The Dining Philosophers Problem
- Coffman's deadlock conditions
- Valgrind: Helgrind
AI was used as a learning and review assistant during development. It helped with:
- Reviewing the synchronization design and identifying possible deadlock and cooldown issues.
- Explaining POSIX mutex and condition-variable behavior.
- Discussing test cases for starvation, high thread counts, and shutdown behavior.
- Drafting and improving this README.
All design choices, implementation, testing, and final code decisions were reviewed and applied by the author.
