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
- Testing
- 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 mutex. This ensures that a log line is printed atomically and that concurrent threads cannot interleave their messages.
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 |
| Terminal output | Log 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 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.
.
βββ includes/
β βββ codexion.h
βββ src/
β βββ core/ # Coder routine, dongles, safe lock helpers
β βββ init/ # Allocation, initialization, cleanup, thread creation
β βββ monitor/ # Burnout and cooldown monitor
β βββ sheduler/ # FIFO and EDF queue policies
β βββ utils/ # Argument parsing, timing, logging, sleeps
β βββ main.c
βββ Makefile
βββ tester.sh
The repository includes a small test runner:
./tester.sh 1
./tester.sh 2
./tester.sh big
./tester.sh starvationExample stress test:
./codexion 199 60000 61 61 61 10 61 edfFor memory and thread analysis on Linux:
./tester.sh 1 mem
./tester.sh 1 helgrindRecommended commands:
valgrind --leak-check=full ./codexion 4 800 200 200 200 5 10 fifo
valgrind --tool=helgrind ./codexion 4 800 200 200 200 5 10 fifo- 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.
