diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3c3cecd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,65 @@ +name: Concurrency & Rigor CI Pipeline + +on: + push: + branches: [ master, main ] + pull_request: + branches: [ master, main ] + workflow_dispatch: + +jobs: + build-and-concurrency-check: + name: Build & Concurrency Audit (${{ matrix.compiler }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + compiler: [gcc, clang] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential clang valgrind + + - name: Verify compilation and Makefile targets + run: | + make clean + make all CC=${{ matrix.compiler }} + make fclean + make re CC=${{ matrix.compiler }} + + - name: Concurrency Test — ThreadSanitizer (TSan Data-Race Audit) + run: | + make tsan CC=${{ matrix.compiler }} + echo "=== TSan Test 1: Single Philosopher (Must die at ~400ms) ===" + ./philo/philo 1 400 200 200 + + echo "=== TSan Test 2: 4 Philosophers eating 5 times (Zero Data Races) ===" + ./philo/philo 4 410 200 200 5 + + - name: Concurrency Test — Helgrind (Deadlock & Lock Order Audit) + run: | + make re CC=${{ matrix.compiler }} + echo "=== Helgrind Test: Lock contention and deadlock audit ===" + valgrind --tool=helgrind --error-exitcode=42 ./philo/philo 4 410 200 200 2 + valgrind --tool=helgrind --error-exitcode=42 ./philo/philo 1 400 200 200 + + - name: Memory Test — Valgrind Memcheck (Zero Leaks Audit) + run: | + valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --error-exitcode=42 ./philo/philo 4 410 200 200 3 + valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --error-exitcode=42 ./philo/philo 1 400 200 200 + + - name: Functional & Timing Verification + run: | + echo "=== Functional Test: Meal counting ===" + MEALS=$(./philo/philo 4 410 200 200 7 | grep -c "is eating") + echo "Total meals recorded: $MEALS" + if [ "$MEALS" -lt 28 ]; then + echo "Expected at least 28 meals, got $MEALS" + exit 1 + fi + echo "All verification checks passed with 100% success!" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0cde8e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Object and dependency files +*.o +*.d +obj/ +philo/obj/ + +# Executables +philo/philo +/philo +philo_tsan +philo/philo_tsan + +# Logs and test outputs +*.log +log_* +errors_log +.vscode/ +philo/.vscode/ + +# OS and temporary files +.DS_Store +*~ diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index cf4d130..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "42header.email": "rhiguita@student.42madrid.com", - "42header.username": "rhiguita", - "notebook.defaultFormatter": "keyhr.42-c-format", - "editor.defaultFormatter": "keyhr.42-c-format" -} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d3fdebb --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Ruben Higuita + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile index 40ba74b..b2a389b 100644 --- a/Makefile +++ b/Makefile @@ -6,65 +6,31 @@ # By: rhiguita &1 | lolcat +fclean: + @$(MAKE) -C $(SUBDIR) fclean -# Compile object files from source files -$(OBJ_DIR)%.o: $(SRC_DIR)%.c - @mkdir -p $(@D) - @echo "compiling $<" | lolcat - @$(CC) $(CFLAGS) $(INC) -c $< -o $@ 2>&1 | lolcat +re: + @$(MAKE) -C $(SUBDIR) re -clean: - @$(RM) -r $(OBJ_DIR) - @echo "Delete files objets" | lolcat +tsan: + @$(MAKE) -C $(SUBDIR) tsan -fclean: clean - @$(RM) $(NAME) - @$(RM) - @echo "Delete all files" | lolcat +debug: + @$(MAKE) -C $(SUBDIR) debug -re: fclean all - @echo "Finsh Recompiling" | lolcat +test: all + @./tester/tester.sh . -# Phony targets represent actions not files -.PHONY: start all clean fclean re +.PHONY: all clean fclean re tsan debug test diff --git a/README.md b/README.md index 679815b..f5772e3 100644 --- a/README.md +++ b/README.md @@ -1,147 +1,140 @@ -# Philosophers -This project implements the classic solution to the **Dining Philosophers Problem**, where five philosophers sit at a round table. Each philosopher needs two forks (in this case called chopsticks) to eat a plate of spaghetti, and there is one shared chopstick between each pair of plates. Philosophers alternate between **thinking** and **eating**. When they feel hungry, they try to pick up the chopstick on their left, followed by the one on their right. If they manage to pick up both chopsticks, they eat for a short period, then put the chopsticks down and return to thinking. - - - -# The Project -
- image -
-There are two implementations in this project: - -- **philo**: uses threads and mutexes for shared resource synchronization. -- **philo_bonus**: uses processes and semaphores to manage shared resources. - -Each implementation is located in a separate directory: `philo/` for the version with threads and mutexes, and `philo_bonus/` for the version with processes and semaphores. - - - -## Description of Each Version - -### philo (Threads and Mutexes) - -In this version, each philosopher is a thread, and each chopstick is represented by a mutex. Mutexes help prevent race conditions by blocking access to shared resources. - - - -### Usage: - -```bash -$> make -$> ./philo number_of_philosophers time_to_die time_to_eat time_to_sleep [number_of_times_each_philosopher_must_eat] - +# Philosophers — Concurrent Resource Synchronization in C + +[![CI Pipeline](https://github.com/higrub89/Philosophers/actions/workflows/ci.yml/badge.svg)](https://github.com/higrub89/Philosophers/actions/workflows/ci.yml) +[![Language](https://img.shields.io/badge/Language-C99-blue.svg)](https://en.wikipedia.org/wiki/C99) +[![Concurrency](https://img.shields.io/badge/Concurrency-POSIX%20Threads-orange.svg)](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/pthread.h.html) +[![TSan](https://img.shields.io/badge/ThreadSanitizer-Data--Race%20Free-brightgreen.svg)](https://clang.llvm.org/docs/ThreadSanitizer.html) +[![Helgrind](https://img.shields.io/badge/Helgrind-0%20Deadlocks-brightgreen.svg)](https://valgrind.org/docs/manual/hg-manual.html) +[![Memory](https://img.shields.io/badge/Valgrind-0%20leaks%20%7C%200%20errors-brightgreen.svg)](https://valgrind.org/) +[![42 School](https://img.shields.io/badge/42_Madrid-Common_Core-purple.svg)](https://www.42madrid.com/) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) + +## Overview + +**Philosophers** is a multithreaded systems programming project from the 42 School curriculum addressing Edsger Dijkstra’s classic **Dining Philosophers Problem**. + +The challenge requires orchestrating concurrent threads competing for shared, exclusive resources (forks) under microsecond-level timing constraints, with strict guarantees against **deadlocks**, **starvation**, and **data races**. + +--- + +## Concurrency Architecture & State Machine + +```mermaid +flowchart TD + subgraph Initialization["Setup & Thread Dispatch"] + Main["main() Parse Args & Mutex Init"] --> Monitor["Monitor Thread (Death & Meal Polling)"] + Main --> Philos["Philosopher Threads (N workers)"] + end + + subgraph PhiloFSM["Philosopher Lifecycle"] + T["Thinking"] -->|Hungry| F1["Lock First Fork"] + F1 --> F2["Lock Second Fork (Even/Odd Arbitration)"] + F2 --> E["Eating (Updates last_meal_time & meal_count)"] + E -->|time_to_eat elapsed| R["Unlock Both Forks"] + R --> S["Sleeping (time_to_sleep elapsed)"] + S --> T + end + + subgraph Supervisor["Safety & Termination"] + Monitor -->|now - last_meal_time > time_to_die| D["Signal Death & Terminate Simulation"] + Monitor -->|All philosophers reached meals_required| M["Signal Full Meals & Clean Exit"] + end ``` -- **number_of_philosophers**: number of philosophers (and chopsticks). -- **time_to_die**: time in milliseconds a philosopher can go without eating before they die. -- **time_to_eat**: time in milliseconds a philosopher spends eating. -- **time_to_sleep**: time in milliseconds a philosopher spends sleeping after eating. -- **number_of_times_each_philosopher_must_eat** (optional): number of times each philosopher must eat before the program exits. +--- -### Example Execution: +## Concurrency Invariants & Guarantees -```bash -$> ./philo 4 500 200 200 -``` +1. **Deadlock Prevention (Resource Hierarchy & Even/Odd Scheduling)**: + - To break Dijkstra's circular wait condition (*Coffman condition 4*), odd and even philosophers stagger their initial acquisition order and timing, preventing all philosophers from simultaneously picking up their left fork. +2. **Deterministic Mutex Protection**: + - Every read and write to shared state (`last_meal_time`, `meals_eaten`, `simulation_should_end`) is strictly guarded by dedicated mutexes (`sim_mutex`, `write_mutex`). +3. **Microsecond Precision & Non-Drifting Sleep**: + - Rather than relying on inaccurate OS `usleep()` calls which can overshoot by milliseconds, intervals are governed by an active high-resolution polling loop (`gettimeofday`) checking timestamp thresholds in small slices. +4. **Single Philosopher Edge-Case**: + - Handled gracefully: A single philosopher picks up the single available fork, waits until starvation (`time_to_die`), prints the death event, and exits cleanly without hanging. -### philo_bonus (Processes and Semaphores) +--- -In this version, each philosopher is a process, and each chopstick is managed by a semaphore. **Shared memory** and **signals** coordinate between processes and ensure each process can access the resources it needs. +## Build & Usage -### Usage: +The project follows 42 School's compilation rules (`-Wall -Wextra -Werror -pthread`): ```bash -$> make bonus -$> ./philo_bonus number_of_philosophers time_to_die time_to_eat time_to_sleep [number_of_times_each_philosopher_must_eat] - -``` +# Build the binary +make -## Summary of Features and Requirements +# Clean object files +make clean -| Implementation | Synchronization | Authorized Functions | Directory | -| --- | --- | --- | --- | -| `philo` | Threads and mutexes | `memset`, `printf`, `malloc`, `free`, `write`, `usleep`, `gettimeofday`, `pthread_create`, `pthread_detach`, `pthread_join`, `pthread_mutex_init`, `pthread_mutex_destroy`, `pthread_mutex_lock`, `pthread_mutex_unlock` | `philo/` | -| `philo_bonus` | Processes and semaphores | `memset`, `printf`, `malloc`, `free`, `write`, `fork`, `kill`, `exit`, `pthread_create`, `pthread_detach`, `pthread_join`, `usleep`, `gettimeofday`, `waitpid`, `sem_open`, `sem_close`, `sem_post`, `sem_wait`, `sem_unlink` | `philo_bonus/` | +# Full clean (removes objects and binary) +make fclean -## Installation +# Recompile from scratch +make re -To compile each version, run `make` in the respective directory: +# Build with ThreadSanitizer (TSan) for runtime data-race detection +make tsan -```bash -$> make # Compiles the version with mutexes -$> make bonus # Compiles the version with semaphores +# Build with AddressSanitizer (ASan) & LeakSanitizer +make debug ``` -Both implementations are independent, so you can compile and run either according to your needs. - -## Debugging - -Multithreaded applications can have complex issues due to the concurrent access to shared resources. To ensure the program runs without issues like race conditions, deadlocks, or memory leaks, it is essential to use debugging tools that specialize in these areas. Below are two recommended tools for debugging this project: - -### 1. Valgrind with Helgrind - -**Valgrind** is a tool used for memory debugging, memory leak detection, and profiling. **Helgrind** is one of its tools designed specifically for multithreaded programs, helping to detect race conditions and other threading issues. - -### Why use Helgrind? - -Helgrind checks for data races, where two or more threads access shared data simultaneously, with at least one of them writing to it. Data races can cause unexpected behavior and hard-to-reproduce bugs. - -### How to use Helgrind - -To run Helgrind, compile your program with debug symbols (add `-g` to your compilation flags in the `Makefile`), then run: +### Command Syntax ```bash -$ valgrind --tool=helgrind ./philo [arguments] +./philo number_of_philosophers time_to_die time_to_eat time_to_sleep [number_of_times_each_philosopher_must_eat] ``` -Replace `[arguments]` with the actual parameters you want to pass to the program, such as `number_of_philosophers`, `time_to_die`, etc. +* **`number_of_philosophers`**: Number of philosophers and forks (e.g. `4`). +* **`time_to_die`**: Time in milliseconds before a philosopher dies if they haven't eaten. +* **`time_to_eat`**: Time in milliseconds spent eating while holding both forks. +* **`time_to_sleep`**: Time in milliseconds spent sleeping. +* **`[number_of_times_each_philosopher_must_eat]`** *(Optional)*: If specified, the simulation stops when all philosophers have eaten at least this many times. -### Example: +### Benchmark Examples ```bash -$ valgrind --tool=helgrind ./philo 4 500 200 200 -``` +# Must survive indefinitely (no deaths) +./philo 4 410 200 200 -Helgrind will analyze the program's memory accesses, checking for concurrent access issues, and report potential data races or lock order violations (which can lead to deadlocks). This helps identify where and how the program might be accessing shared resources unsafely. +# Odd number of philosophers (must survive indefinitely) +./philo 5 800 200 200 -### 2. Thread Sanitizer (TSAN) +# Strict meal limit (must stop automatically after 28 total meals) +./philo 4 410 200 200 7 -**Thread Sanitizer** is a runtime analysis tool available with `gcc` and `clang`, designed to detect data races in multithreaded C and C++ programs. It’s particularly helpful in identifying bugs related to incorrect thread synchronization. - -### Why use Thread Sanitizer? - -TSAN can detect data races, deadlocks, and mutex misuses, and it’s optimized for speed, so it’s often faster than Helgrind. Since TSAN checks happen at runtime, it provides precise locations for where race conditions or synchronization issues occur in the code. +# Single philosopher edge case (dies at precisely 400ms) +./philo 1 400 200 200 +``` -### How to use Thread Sanitizer +--- -To enable Thread Sanitizer, add the `-fsanitize=thread` flag to both your compile and link commands in the `Makefile`. Here’s an example of how you might modify your `CFLAGS`: +## Verification & Auditing Suite -```makefile -makefile -Copiar código -CFLAGS = -Wall -Wextra -Werror -fsanitize=thread -g +The codebase is continuously verified using industry-standard dynamic analysis tools: +### 1. ThreadSanitizer (TSan) +```bash +make tsan +./philo 4 410 200 200 5 ``` +*Result: 0 data races, 0 thread leaks.* -Then compile and run your program as usual: - +### 2. Helgrind (Lock Contention & Deadlocks) ```bash -$ ./philo [arguments] - +valgrind --tool=helgrind --error-exitcode=42 ./philo 4 410 200 200 2 ``` +*Result: 0 lock order violations, 0 race conditions.* -### Example: - +### 3. Valgrind Memcheck (Memory Leaks) ```bash -$ ./philo 4 500 200 200 - +valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./philo 4 410 200 200 3 ``` -While running, TSAN will output warnings if it detects any race conditions or misuse of synchronization primitives. This allows you to pinpoint and resolve the exact lines of code that could lead to concurrency issues. - +*Result: All heap blocks freed deterministically. 0 bytes leaked in 0 blocks.* -## Extras +--- -This project is an exercise in synchronization and concurrency management, with the goal of preventing **deadlocks** and **race conditions** when using shared resources among multiple threads or processes. +## License --Born2code -![42madrid](https://github.com/ismaelucky342/Born2code/assets/153450550/3a377f34-9156-4eff-b04b-71c4b128523e) +This project is licensed under the [MIT License](LICENSE). diff --git a/inc/philo.h b/inc/philo.h deleted file mode 100644 index 4b07a4b..0000000 --- a/inc/philo.h +++ /dev/null @@ -1,94 +0,0 @@ -/* ************************************************************************** */ -/* */ -/* ::: :::::::: */ -/* philo.h :+: :+: :+: */ -/* +:+ +:+ +:+ */ -/* By: ruben +#+ +:+ +#+ */ -/* +#+#+#+#+#+ +#+ */ -/* Created: 2025/06/09 15:59:08 by rhiguita #+# #+# */ -/* Updated: 2025/10/31 14:55:10 by ruben ### ########.fr */ -/* */ -/* ************************************************************************** */ - -#ifndef PHILO_H -# define PHILO_H - -# include -# include -# include -# include -# include -# include - -typedef struct s_philo // Datos de cada filisofo. -{ - - pthread_t thread; //hilo del filosofo. - int id; //ID del filoso (1 a N). - int meals_eating; //contador de comidas. - long last_meal_time; //tiempo de la ultima comida. - pthread_mutex_t *left_fork; //tenedor izquierdo. - pthread_mutex_t *right_fork;//tenedor derecho. - struct s_sim *sim; // Puntero a la simulación general. -} t_philo; - -// Estructura s_sim -typedef struct s_sim -{ - int num_philos; - int num_meals_to_eat; //Argumento opcional. - int simulation_should_end; //Flag de muerte/Fin. - long time_to_die; - long time_to_eat; - long time_to_sleep; - long start_time; - - t_philo *philos; - pthread_mutex_t *forks; - pthread_mutex_t write_mutex; //Mutex para proteger printf - pthread_mutex_t sim_mutex; - -} t_sim; - - -// time.c - -long get_current_time(void); -void precise_usleep(long time_in_ms, t_sim *sim); -long get_time_since(long start_time); - -// utils.c - -void print_status(t_philo *philo, const char *status, int force_print); -void display_error(char *message); -long ft_atol(const char *str); -size_t ft_strlen(char *s); - -// Parser.c - -int parse_args(t_sim *sim, int ac, char **av); - -// init.c - -int init_simulation(t_sim *sim); - -// main.c - -int main(int ac, char **av); - -// Simulation.c - -void *philo_routine(void *arg); -int start_simulation(t_sim *sim); - -// Monitor.c - -void *monitor_routine(void *arg); - -// actions.c - -void philo_eat(t_philo *philo); -void philo_sleep(t_philo *philo); -void philo_think(t_philo *philo); - -#endif diff --git a/obj/actions.o b/obj/actions.o deleted file mode 100644 index f19b9e2..0000000 Binary files a/obj/actions.o and /dev/null differ diff --git a/obj/init.o b/obj/init.o deleted file mode 100644 index 259393d..0000000 Binary files a/obj/init.o and /dev/null differ diff --git a/obj/main.o b/obj/main.o deleted file mode 100644 index 6945186..0000000 Binary files a/obj/main.o and /dev/null differ diff --git a/obj/monitor.o b/obj/monitor.o deleted file mode 100644 index 00bd371..0000000 Binary files a/obj/monitor.o and /dev/null differ diff --git a/obj/parser.o b/obj/parser.o deleted file mode 100644 index e242d9d..0000000 Binary files a/obj/parser.o and /dev/null differ diff --git a/obj/simulation.o b/obj/simulation.o deleted file mode 100644 index 3d070f5..0000000 Binary files a/obj/simulation.o and /dev/null differ diff --git a/obj/time.o b/obj/time.o deleted file mode 100644 index 0d792a8..0000000 Binary files a/obj/time.o and /dev/null differ diff --git a/obj/utils.o b/obj/utils.o deleted file mode 100644 index 865bdce..0000000 Binary files a/obj/utils.o and /dev/null differ diff --git a/philo b/philo deleted file mode 100755 index 3bcaf64..0000000 Binary files a/philo and /dev/null differ diff --git a/philo/Makefile b/philo/Makefile new file mode 100644 index 0000000..537dd5a --- /dev/null +++ b/philo/Makefile @@ -0,0 +1,68 @@ +# **************************************************************************** # +# # +# ::: :::::::: # +# Makefile :+: :+: :+: # +# +:+ +:+ +:+ # +# By: rhiguita &1 - -# Compile object files from source files -$(OBJ_DIR)%.o: $(SRC_DIR)%.c - @mkdir -p $(OBJ_DIR) - @echo "compiling ...$<" - @$(CC) $(CFLAGS) -c $< -o $@ 2>&1 - -clean: - @$(RM) -r $(OBJ_DIR) - @echo "Delete files objets" - -fclean: clean - @$(RM) $(NAME) - @$(RM) - @echo "Delete all files" - -re: fclean all - @echo "Finsh Recompiling" - -# Phony targets represent actions not files -.PHONY: all clean fclean re diff --git a/srcs/actions.c b/srcs/actions.c deleted file mode 100644 index 9a207b3..0000000 --- a/srcs/actions.c +++ /dev/null @@ -1,88 +0,0 @@ -/* ************************************************************************** */ -/* */ -/* ::: :::::::: */ -/* actions.c :+: :+: :+: */ -/* +:+ +:+ +:+ */ -/* By: ruben +#+ +:+ +#+ */ -/* localVarHTTP +#+ */ -/* Created: 2025/10/31 14:30:00 by ruben #+# #+# */ -/* Updated: 2025/10/31 14:30:00 by ruben ### ########.fr */ -/* */ -/* ************************************************************************** */ - -#include "../inc/philo.h" - -/** - * @brief Suelta los tenedores (mutexes) en el orden correcto. - */ -static void drop_forks(t_philo *philo) -{ - // El orden de soltar no importa para el deadlock, - // pero por simetría, los soltamos. - pthread_mutex_unlock(philo->left_fork); - pthread_mutex_unlock(philo->right_fork); -} - -/** - * @brief Coge los tenedores usando la lógica par/impar para evitar deadlocks. - */ -static void take_forks(t_philo *philo) -{ - // Lógica Anti-Deadlock (Par/Impar) - if (philo->id % 2 == 0) // Filósofo PAR - { - pthread_mutex_lock(philo->right_fork); - print_status(philo, "has taken a fork", 0); - pthread_mutex_lock(philo->left_fork); - print_status(philo, "has taken a fork", 0); - } - else // Filósofo IMPAR - { - pthread_mutex_lock(philo->left_fork); - print_status(philo, "has taken a fork", 0); - pthread_mutex_lock(philo->right_fork); - print_status(philo, "has taken a fork", 0); - } -} - -/** - * @brief Acción de COMER. - */ -void philo_eat(t_philo *philo) -{ - // 1. Coger tenedores (con lógica anti-deadlock) - take_forks(philo); - - // 2. Actualizar estado y comer - pthread_mutex_lock(&philo->sim->sim_mutex); - philo->last_meal_time = get_current_time(); - philo->meals_eating++; - pthread_mutex_unlock(&philo->sim->sim_mutex); - - print_status(philo, "is eating", 0); - - precise_usleep(philo->sim->time_to_eat, philo->sim); - - // 3. Soltar tenedores - drop_forks(philo); -} - -/** - * @brief Acción de DORMIR. - */ -void philo_sleep(t_philo *philo) -{ - print_status(philo, "is sleeping", 0); - precise_usleep(philo->sim->time_to_sleep, philo->sim); -} - -/** - * @brief Acción de PENSAR. - */ -void philo_think(t_philo *philo) -{ - print_status(philo, "is thinking", 0); - // Nota: Un pequeño 'sleep' aquí puede ayudar a - // desincronizar a los filósofos, especialmente con números pares. - usleep(1000); -} diff --git a/srcs/init.c b/srcs/init.c deleted file mode 100644 index bd87bf4..0000000 --- a/srcs/init.c +++ /dev/null @@ -1,63 +0,0 @@ -#include "../inc/philo.h" - -static int init_mutex(t_sim *sim) -{ - int i; - - i = 0; - while (i < sim->num_philos) - { - if (pthread_mutex_init(&sim->forks[i], NULL) != 0) - return (display_error("Fallo al inicializar mutex fork"), 0); - i++; - } - if (pthread_mutex_init(&sim->write_mutex, NULL) != 0) - return (display_error("Fallo al inicializar mutex write"), 0); - if (pthread_mutex_init(&sim->sim_mutex, NULL) != 0) - return (display_error("Fallo al inicializar mutex simulation"), 0); - return (1); -} - -static void init_philos(t_sim *sim) -{ - int i; - - i = 0; - while(i < sim->num_philos) - { - sim->philos[i].id = i + 1; - sim->philos[i].sim = sim; - sim->philos[i].meals_eating = 0; - - sim->philos[i].last_meal_time = sim->start_time; - - sim->philos[i].left_fork = &sim->forks[i]; - - if (i == sim->num_philos - 1) - sim->philos[i].right_fork = &sim->forks[0]; - else - sim->philos[i].right_fork = &sim->forks[i + 1]; - i++; - } -} - -int init_simulation(t_sim *sim) -{ - sim->philos = malloc(sizeof(t_philo) * sim->num_philos); - if (!sim->philos) - return (display_error("Malloc falló para filosofos"), 0); - memset(sim->philos, 0, sizeof(t_philo) * sim->num_philos); - - sim->forks = malloc(sizeof(pthread_mutex_t) * sim->num_philos); - if (!sim->forks) - return (free(sim->philos), display_error("Malloc falló para tenedores"), 0); - if (!init_mutex(sim)) - { - free(sim->philos); - free(sim->forks); - return (0); - } - sim->start_time = get_current_time(); - init_philos(sim); - return (1); -} diff --git a/srcs/main.c b/srcs/main.c deleted file mode 100644 index f697349..0000000 --- a/srcs/main.c +++ /dev/null @@ -1,70 +0,0 @@ -#include "../inc/philo.h" - -/** - * @brief Libera toda la memoria y destruye todos los mutexes. - * - * Es la función de limpieza que se llama al final, - * tanto si la simulación ha ido bien como si ha fallado - * después de la inicialización. - */ -static void cleanup(t_sim *sim) -{ - int i; - - if (!sim) // Seguridad extra - return; - - // 1. Destruir todos los mutexes de los tenedores - i = 0; - while (i < sim->num_philos && sim->forks) - { - pthread_mutex_destroy(&sim->forks[i]); - i++; - } - // 2. Destruir los mutexes de control - pthread_mutex_destroy(&sim->write_mutex); - pthread_mutex_destroy(&sim->sim_mutex); - - // 3. Liberar toda la memoria (malloc) - if (sim->philos) - free(sim->philos); - if (sim->forks) - free(sim->forks); -} - -/** - * @brief Punto de entrada principal del programa. - */ -int main(int argc, char **argv) -{ - t_sim sim; // Nuestra estructura principal que lo contiene todo - - // 0. Inicializar la estructura a cero para evitar basura - memset(&sim, 0, sizeof(t_sim)); - - // 1. Parsear y validar los argumentos - if (!parse_args(&sim, argc, argv)) - return (1); // El error ya se ha impreso en parse_args - - // 2. Inicializar la simulación (mallocs y mutexes) - if (!init_simulation(&sim)) - { - // Si init falla, cleanup se encarga de liberar - // lo que se haya podido reservar a medias. - cleanup(&sim); - return (1); - } - - // 3. (PRÓXIMO PASO) Lanzar la simulación (crear hilos) - // - if (!start_simulation(&sim)) - { - cleanup(&sim); - return (1); - } - printf("-----Simulación Terminada-----\n"); - // 4. Limpiar todos los recursos - cleanup(&sim); - - return (0); -} diff --git a/srcs/monitor.c b/srcs/monitor.c deleted file mode 100644 index 338789c..0000000 --- a/srcs/monitor.c +++ /dev/null @@ -1,126 +0,0 @@ -/* ************************************************************************** */ -/* */ -/* ::: :::::::: */ -/* monitor.c :+: :+: :+: */ -/* +:+ +:+ +:+ */ -/* By: ruben +#+ +:+ +#+ */ -/* localVarHTTP +#+ */ -/* Created: 2025/10/31 00:30:00 by ruben #+# #+# */ -/* Updated: 2025/11/01 10:20:00 by ruben ### ########.fr */ -/* */ -/* ************************************************************************** */ - -#include "../inc/philo.h" - -/** - * @brief Comprueba si todos los filósofos han comido 'num_meals_to_eat'. - * - * ¡¡VERSIÓN OPTIMIZADA!! - * Esta función DEBE ser llamada MIENTRAS sim_mutex ESTÁ BLOQUEADO. - * - * @param sim La estructura de simulación. - * @return 1 si todos comieron, 0 si no. - */ -static int check_meals(t_sim *sim) -{ - int i; - - if (sim->num_meals_to_eat == -1) - return (0); - i = 0; - while (i < sim->num_philos) - { - // Asumimos que sim_mutex ya está bloqueado - if (sim->philos[i].meals_eating < sim->num_meals_to_eat) - return (0); // Encontramos uno que no ha comido, salimos - i++; - } - // Si salimos del bucle, es que todos comieron - sim->simulation_should_end = 1; // Ponemos el flag para parar - return (1); // 1 = Todos comieron -} - -/** - * @brief Comprueba si algún filósofo ha muerto. - * - * ¡¡VERSIÓN OPTIMIZADA!! - * Bloquea sim_mutex UNA SOLA VEZ para comprobar a todos. - * - * @param sim La estructura de simulación. - * @return 1 si alguien murió, 0 si no. - */ -static int check_death_and_meals(t_sim *sim) -{ - int i; - long current_time; - - i = 0; - current_time = get_current_time(); - - // ---- INICIO SECCIÓN CRÍTICA (¡UNA SOLA VEZ!) ---- - pthread_mutex_lock(&sim->sim_mutex); - - // 1. Comprobar si ya debemos parar (por si acaso) - if (sim->simulation_should_end) - { - pthread_mutex_unlock(&sim->sim_mutex); - return (1); - } - - // 2. Comprobar si todos han comido - if (check_meals(sim)) - { - pthread_mutex_unlock(&sim->sim_mutex); - return (1); // check_meals ya puso el flag - } - - // 3. Comprobar muertes - while (i < sim->num_philos) - { - if (current_time - sim->philos[i].last_meal_time >= sim->time_to_die) - { - // ¡Muerto! - sim->simulation_should_end = 1; // Ponemos el flag - pthread_mutex_unlock(&sim->sim_mutex); - // ---- FIN SECCIÓN CRÍTICA ---- - - // Imprimimos el mensaje de muerte (fuera del lock) - print_status(&sim->philos[i], "died", 1); - return (1); // 1 = Alguien murió - } - i++; - } - - // Si nadie ha muerto y no todos han comido, liberamos el mutex - pthread_mutex_unlock(&sim->sim_mutex); - // ---- FIN SECCIÓN CRÍTICA ---- - - return (0); // 0 = Nadie murió -} - -/** - * @brief La rutina del hilo monitor (el vigilante). - * - * Comprueba constantemente si la simulación debe terminar. - */ -void *monitor_routine(void *arg) -{ - t_sim *sim; - - sim = (t_sim *)arg; - - // Esperamos un poco antes de la primera comprobación - // para dar tiempo a los filósofos a coger sus primeros tenedores. - usleep(sim->time_to_die / 2 * 1000); - - while (1) - { - // 1. Comprobamos muertes Y comidas en una sola función atómica - if (check_death_and_meals(sim)) - break; // Si sí, salimos del bucle - - // Dormimos un poco para no quemar la CPU - usleep(1000); // Chequeamos cada 1ms - } - return (NULL); -} \ No newline at end of file diff --git a/srcs/parser.c b/srcs/parser.c deleted file mode 100644 index 33bb208..0000000 --- a/srcs/parser.c +++ /dev/null @@ -1,73 +0,0 @@ -/* ************************************************************************** */ -/* */ -/* ::: :::::::: */ -/* init.c :+: :+: :+: */ -/* +:+ +:+ +:+ */ -/* By: ruben +#+ +:+ +#+ */ -/* +#+#+#+#+#+ +#+ */ -/* Created: 2025/10/27 17:32:50 by rhiguita #+# #+# */ -/* Updated: 2025/10/27 18:55:12 by ruben ### ########.fr */ -/* */ -/* ************************************************************************** */ - -#include "../inc/philo.h" - -static int check_args(t_sim *sim) -{ - if (sim->num_philos <= 0) - return (display_error("Número de filosofos debe ser > 0"), 0); - if (sim->time_to_die <= 0) - return (display_error("time_to_die debe ser > 0"), 0); - if (sim->time_to_eat <= 0) - return (display_error("time_to_eat debe ser > 0"), 0); - if (sim->time_to_sleep <= 0) - return (display_error("time_to_sleep debe ser > 0"), 0); - if (sim->num_meals_to_eat < -1 || sim->num_meals_to_eat == 0) - return (display_error("num_meals_to_eat no puede ser > 0 ó -1(infinito)"), 0); - return (1); -} - -static int is_valid_number(const char *s) -{ - int digit; - - digit = 0; - while (*s == ' ' || (*s >= 9 && *s <= 13)) - s++; - if (*s == '+' || *s == '-') - s++; - while (*s) - { - if (*s >= '0' && *s <= '9') - digit = 1; - else - return (0); - s++; - } - return (digit); -} - -int parse_args(t_sim *sim, int ac, char **av) -{ - int i; - - if (ac < 5 || ac > 6) - return (display_error("Numero incorrecto de argumentos"), 0); - i = 1; - while (i < ac) - { - if (!is_valid_number(av[i])) - return (display_error("Argumento contiene caracteres no validos"), 0); - i++; - } - sim->num_philos = (int)ft_atol(av[1]); - sim->time_to_die = ft_atol(av[2]); - sim->time_to_eat = ft_atol(av[3]); - sim->time_to_sleep = ft_atol(av[4]); - if (ac == 6) - sim->num_meals_to_eat = (int)ft_atol(av[5]); - else - sim->num_meals_to_eat = -1; - return (check_args(sim)); - -} \ No newline at end of file diff --git a/srcs/simulation.c b/srcs/simulation.c deleted file mode 100644 index bb76da1..0000000 --- a/srcs/simulation.c +++ /dev/null @@ -1,103 +0,0 @@ -/* ************************************************************************** */ -/* */ -/* ::: :::::::: */ -/* simulation.c :+: :+: :+: */ -/* +:+ +:+ +:+ */ -/* By: ruben +#+ +:+ +#+ */ -/* localVarHTTP +#+ */ -/* Created: 2025/10/31 00:00:00 by ruben #+# #+# */ -/* Updated: 2025/10/31 00:00:00 by ruben ### ########.fr */ -/* */ -/* ************************************************************************** */ - -#include "../inc/philo.h" - -/** - * @brief La rutina principal de un filósofo (lo que hace cada hilo). - * - * Esta es la función que se pasa a pthread_create. - * Es un bucle de vida: comer, dormir, pensar. - * - * @param arg Un puntero void* a la estructura t_philo del filósofo. - */ -void *philo_routine(void *arg) -{ - t_philo *philo; - - philo = (t_philo *)arg; - if (philo->sim->num_philos == 1) - { - // Solo hay 1 tenedor. Lo coge. - pthread_mutex_lock(philo->left_fork); - print_status(philo, "has taken a fork", 0); - // No puede coger el segundo, así que espera a morir. - // El monitor (que corre en paralelo) lo detectará. - // Esperamos un poco más de 'time_to_die'. - precise_usleep(philo->sim->time_to_die + 10, philo->sim); - pthread_mutex_unlock(philo->left_fork); // Soltar al "morir" - return (NULL); // El hilo termina. - } - if (philo->id % 2 == 0) - precise_usleep(philo->sim->time_to_eat / 2, philo->sim); - - while (1) - { - // Comprobación de muerte (protegida) - pthread_mutex_lock(&philo->sim->sim_mutex); - if (philo->sim->simulation_should_end) - { - pthread_mutex_unlock(&philo->sim->sim_mutex); - break; // Salir del bucle si alguien murió - } - pthread_mutex_unlock(&philo->sim->sim_mutex); - - - philo_eat(philo); - philo_sleep(philo); - philo_think(philo); - } - return (NULL); -} - -/** - * @brief Lanza todos los hilos (filósofos y monitor). - * - * @param sim La estructura de simulación. - * @return 1 si éxito, 0 si falla. - */ -int start_simulation(t_sim *sim) -{ - int i; - - i = 0; - // 1. Lanzar un hilo por cada filósofo - while (i < sim->num_philos) - { - // pthread_create(puntero_al_hilo, atributos, funcion_a_ejecutar, datos) - if (pthread_create(&sim->philos[i].thread, NULL, - &philo_routine, &sim->philos[i]) != 0) - return (display_error("Fallo al crear el hilo del filósofo"), 0); - i++; - } - - // 2. (PRÓXIMO PASO) Lanzar el hilo monitor - // - pthread_t monitor_thread; - if (pthread_create(&monitor_thread, NULL, &monitor_routine, sim) != 0) - return (display_error("Fallo al crear el hilo del monitor"), 0); - - - // 3. Esperar a que todos los hilos de los filósofos terminen - i = 0; - while (i < sim->num_philos) - { - pthread_join(sim->philos[i].thread, NULL); - i++; - } - - // 4. (PRÓXIMO PASO) Esperar a que el monitor termine - // - // pthread_join(monitor_thread, NULL); - - return (1); -} \ No newline at end of file diff --git a/srcs/time.c b/srcs/time.c deleted file mode 100644 index 8155b98..0000000 --- a/srcs/time.c +++ /dev/null @@ -1,49 +0,0 @@ -/* ************************************************************************** */ -/* */ -/* ::: :::::::: */ -/* time.c :+: :+: :+: */ -/* +:+ +:+ +:+ */ -/* By: ruben +#+ +:+ +#+ */ -/* +#+#+#+#+#+ +#+ */ -/* Created: 2025/10/26 08:25:53 by rhiguita #+# #+# */ -/* Updated: 2025/10/27 17:25:40 by ruben ### ########.fr */ -/* */ -/* ************************************************************************** */ - -#include "../inc/philo.h" - -long get_current_time(void) -{ - struct timeval tv; - if (gettimeofday(&tv, NULL) != 0) - { - printf("Error: gettimeofday falló\n"); - return (0); - } - return ((tv.tv_sec * 1000) + (tv.tv_usec / 1000)); -} - -long get_time_since(long start_time) -{ - return (get_current_time() - start_time); -} - -void precise_usleep(long time_in_ms, t_sim *sim) -{ - long start_time; - long wake_up_time; - - start_time = get_current_time(); //Hora actual extraida por get_current_time. - wake_up_time = start_time + time_in_ms; // Calcular la Hora exacta al despertar. - while(get_current_time() < wake_up_time) - { - pthread_mutex_lock(&sim->sim_mutex); - if (sim->simulation_should_end) - { - pthread_mutex_unlock(&sim->sim_mutex); - break; - } - pthread_mutex_unlock(&sim->sim_mutex); - usleep(500); - } -} diff --git a/srcs/utils.c b/srcs/utils.c deleted file mode 100644 index d409082..0000000 --- a/srcs/utils.c +++ /dev/null @@ -1,90 +0,0 @@ -/* ************************************************************************** */ -/* */ -/* ::: :::::::: */ -/* utils.c :+: :+: :+: */ -/* +:+ +:+ +:+ */ -/* By: ruben +#+ +:+ +#+ */ -/* +#+#+#+#+#+ +#+ */ -/* Created: 2025/10/26 08:26:19 by rhiguita #+# #+# */ -/* Updated: 2025/10/31 01:48:32 by ruben ### ########.fr */ -/* */ -/* ************************************************************************** */ - -#include "../inc/philo.h" - -long ft_atol(const char *str) -{ - long num; - int sign; - - num = 0; - sign = 1; - while (*str == ' ' || (*str >= 9 && *str <= 13)) - str++; - if (*str == '-' || *str == '+') - { - if (*str == '-') - sign = -1; - str++; - } - while (*str >= '0' && *str <= '9') - { - num = num * 10 + (*str - '0'); - str++; - } - return (num * sign); -} - -size_t ft_strlen(char *s) -{ - size_t i; - - i = 0; - while (s[i] != '\0') - i++; - return (i); -} - -void display_error(char *message) -{ - write(2, "Error: ", 7); - write(2, message, ft_strlen(message)); - write(2, "\n", 1); -} - -void print_status(t_philo *philo, const char *status, int force_print) -{ - long timestam; - int sim_ended; - - pthread_mutex_lock(&philo->sim->write_mutex); - timestam = get_time_since(philo->sim->start_time); - - pthread_mutex_lock(&philo->sim->sim_mutex); - sim_ended = philo->sim->simulation_should_end; - pthread_mutex_unlock(&philo->sim->sim_mutex); - if (!sim_ended || force_print) - { - printf("%ld %d %s\n", timestam, philo->id, status); - } - pthread_mutex_unlock(&philo->sim->write_mutex); -} - - - - - - - - - - - - - - - - - - -