This project has been created as part of the 42 curriculum by gviola-l.
- Description
- Instructions
- Command-line arguments
- Logging format
- Resources
- Blocking cases handled
- Thread synchronization mechanisms
Codexion is a concurrency simulation in C, based on the Dining Philosophers problem. Several "coder" threads compete for a limited pool of USB dongles, and a coder must hold a dongle before it can compile.
It shows how competing threads can share a scarce resource without deadlock or starvation, while each still meets its deadline.
Build with the provided Makefile. The project compiles with -Wall -Wextra -Werror and links pthread.
# from project root
make
# run the program (example)
./codexion 4 1500 200 200 200 3 100 fifoAll arguments are mandatory and must be positive integers except scheduler which must be fifo or edf.
- number_of_coders -> number of coders
- time_to_burnout (ms) -> deadline: if a coder does not start compiling before this window since their last compile or simulation start, they burn out
- time_to_compile (ms)
- time_to_debug (ms)
- time_to_refactor (ms)
- number_of_compiles_required -> simulation ends when every coder reached this compile count
- dongle_cooldown (ms) -> after a dongle is released it is unavailable for this cooldown
- scheduler ->
fifo(First In, First Out) oredf(Earliest Deadline First)
Example:
./codexion 4 1500 200 200 200 5 100 edfEvery state change is printed on its own line with a timestamp in milliseconds and the coder id.
Example:
0 1 has taken a dongle
1 1 has taken a dongle
1 1 is compiling
201 1 is debugging
401 1 is refactoring
1204 3 burned out
- Identifying edge-case failures.
- Drafting and polishing this README.
A deadlock requires all four Coffman conditions to hold simultaneously:
| # | Condition | Meaning |
|---|---|---|
| 1 | Mutual exclusion | A resource can only be held by one thread at a time |
| 2 | Hold and wait | A thread holds a resource while waiting for another |
| 3 | No preemption | Resources cannot be forcibly taken from a thread |
| 4 | Circular wait | A cycle of threads exists, each waiting on the next |
Here we break circular wait: dongles are always acquired in a fixed global order (lowest index first, see src/dongles/dongle_order.c), so no cycle of waits can form.
The scheduler itself prevents starvation: every request carries an arrival_order and a deadline, and dongles are granted in strict FIFO or EDF order. A coder may wait behind others, but it cannot be passed over forever.
After a release, a dongle stores its last_release_ms and cannot be taken again until dongle_cooldown milliseconds have passed.
A separate monitor thread watches for burnout. It periodically reads each coder's last_compile_ms and starts a clean shutdown when a coder has gone past time_to_burnout without starting a new compile.
Logging goes through a single log_mutex; lines never interleave and timestamps stay in order.
The project uses one pthread_mutex_t per shared object. coder->mutex guards last_compile_ms and compiles_done; the monitor takes that same lock to read them, so it never sees a half-written update. dongle->mutex guards available, last_release_ms, and the wait queue. Three global mutexes cover the rest: log_mutex for logging, simulation_mutex for the running flag, and counter_mutex for the global request counter.
A coder that wants a dongle does not spin: it pushes a {coder_id, deadline, arrival_order} record onto the dongle's wait queue and blocks on dongle->cond. The queue is only ever read or modified under dongle->mutex. When the dongle is released, pthread_cond_broadcast wakes every waiter, the scheduler picks the next owner, and the others go back to sleep.
Shutdown goes through the same path. The monitor flips running to false under simulation_mutex, then calls wake_all(), which broadcasts on every dongle's condition variable. Blocked coders wake up, notice that is_running() now returns false, and exit instead of waiting for a dongle that will never come.
