diff --git a/rendu/deadlock_demo.c b/rendu/deadlock_demo.c new file mode 100644 index 0000000..1a469a0 --- /dev/null +++ b/rendu/deadlock_demo.c @@ -0,0 +1,129 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* deadlock_demo.c :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: ruiferna +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2025/10/08 18:39:44 by ruiferna #+# #+# */ +/* Updated: 2025/10/08 19:26:47 by ruiferna ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#include +#include +#include +#include +#include +#include + +typedef struct s_info +{ + pthread_mutex_t mutex_a; + pthread_mutex_t mutex_b; +} t_info; + +void *deadlock_1(void *arg) +{ + t_info *shared; + shared = (t_info *) arg; + printf("Thread 1: Locking mutex_a first\n"); + pthread_mutex_lock(&shared->mutex_a); + printf("Thread 1: Got mutex_a, sleeping before trying mutex_b\n"); + usleep(100000); + printf("Thread 1: Now trying to lock mutex_b (potential deadlock)\n"); + pthread_mutex_lock(&shared->mutex_b); + printf("Thread 1: Got both mutexes, releasing them\n"); + pthread_mutex_unlock(&shared->mutex_a); + pthread_mutex_unlock(&shared->mutex_b); + return (NULL); +} +void *deadlock_2(void *arg) +{ + t_info *shared; + shared = (t_info *) arg; + printf("Thread 2: Locking mutex_b first\n"); + pthread_mutex_lock(&shared->mutex_b); + printf("Thread 2: Got mutex_b, sleeping before trying mutex_a\n"); + usleep(100000); + printf("Thread 2: Now trying to lock mutex_a (potential deadlock)\n"); + pthread_mutex_lock(&shared->mutex_a); + printf("Thread 2: Got both mutexes, releasing them\n"); + pthread_mutex_unlock(&shared->mutex_b); + pthread_mutex_unlock(&shared->mutex_a); + return (NULL); +} + +void *nolock_1(void *arg) +{ + t_info *shared; + shared = (t_info *) arg; + printf("Thread 1: Attempting to lock mutex_a first\n"); + pthread_mutex_lock(&shared->mutex_a); + printf("Thread 1: Locked mutex_a successfully\n"); + usleep(100000); + printf("Thread 1: Attempting to lock mutex_b second\n"); + pthread_mutex_lock(&shared->mutex_b); + printf("Thread 1: Locked mutex_b successfully - both mutexes acquired\n"); + pthread_mutex_unlock(&shared->mutex_b); + printf("Thread 1: Released mutex_b\n"); + pthread_mutex_unlock(&shared->mutex_a); + printf("Thread 1: Released mutex_a - task completed\n"); + return (NULL); +} +void *nolock_2(void *arg) +{ + t_info *shared; + shared = (t_info *) arg; + printf("Thread 2: Attempting to lock mutex_a first (same order as thread 1)\n"); + pthread_mutex_lock(&shared->mutex_a); + printf("Thread 2: Locked mutex_a successfully\n"); + usleep(100000); + printf("Thread 2: Attempting to lock mutex_b second\n"); + pthread_mutex_lock(&shared->mutex_b); + printf("Thread 2: Locked mutex_b successfully - both mutexes acquired\n"); + pthread_mutex_unlock(&shared->mutex_b); + printf("Thread 2: Released mutex_b\n"); + pthread_mutex_unlock(&shared->mutex_a); + printf("Thread 2: Released mutex_a - task completed\n"); + return (NULL); +} + +int main(int ac, char **av) +{ + pthread_t thread1; + pthread_t thread2; + t_info shared; + + if (ac != 2) + return (0); + + pthread_mutex_init(&shared.mutex_a, NULL); + pthread_mutex_init(&shared.mutex_b, NULL); + + int mode = atoi(av[1]); + if (mode != 0 && mode != 1) + { + printf("Insert either a 0 (deadlock) or a 1 (no lock)!\n"); + pthread_mutex_destroy(&shared.mutex_a); + pthread_mutex_destroy(&shared.mutex_b); + return (0); + } + switch (mode) + { + case 0: + pthread_create(&thread1, NULL, deadlock_1, &shared); + pthread_create(&thread2, NULL, deadlock_2, &shared); + break; + case 1: + pthread_create(&thread1, NULL, nolock_1, &shared); + pthread_create(&thread2, NULL, nolock_2, &shared); + break; + } + pthread_join(thread1, NULL); + pthread_join(thread2, NULL); + + pthread_mutex_destroy(&shared.mutex_a); + pthread_mutex_destroy(&shared.mutex_b); + return (0); +} \ No newline at end of file diff --git a/rendu/producer_consumer.c b/rendu/producer_consumer.c index 1a94683..dfd9324 100644 --- a/rendu/producer_consumer.c +++ b/rendu/producer_consumer.c @@ -6,131 +6,135 @@ /* By: ruiferna +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2025/10/07 22:17:29 by ruiferna #+# #+# */ -/* Updated: 2025/10/07 22:22:14 by ruiferna ### ########.fr */ +/* Updated: 2025/10/08 18:38:50 by ruiferna ### ########.fr */ /* */ /* ************************************************************************** */ +#include #include #include -#include // Para memset -#include -#include // Para usleep e write -#include // Para gettimeofday +#include +#include -#define BUFFER_SIZE 10 +#define BUFFER_SIZE 100 #define NUM_PRODUCERS 2 #define NUM_CONSUMERS 2 #define ITEMS_TO_PRODUCE 100 -/** - * @brief Estrutura de dados partilhada entre as threads. - * - * @param buffer O buffer partilhado (array de inteiros). - * @param in Índice onde o próximo produtor irá escrever. - * @param out Índice de onde o próximo consumidor irá ler. - * @param count Número de itens atualmente no buffer. - * @param items_produced Contador total de itens produzidos para controlo de fim. - * @param mutex Mutex para garantir acesso exclusivo e atómico às - * variáveis desta estrutura. - */ -typedef struct { - int buffer[BUFFER_SIZE]; - int in; - int out; - int count; - int items_produced; - - pthread_mutex_t mutex; -} shared_buffer_t; - -// Protótipos das funções das threads -void *producer_thread(void *arg); -void *consumer_thread(void *arg); - -int main(void) +typedef struct s_buffer { - // 1. Inicializar a estrutura partilhada - shared_buffer_t shared_data; - pthread_t producer_threads[NUM_PRODUCERS]; - pthread_t consumer_threads[NUM_CONSUMERS]; + int items[BUFFER_SIZE]; + int count; + int in; + int out; + int produced_count; + pthread_mutex_t mutex; +} t_buffer; - // Limpar e inicializar os dados - memset(&shared_data, 0, sizeof(shared_buffer_t)); - // Inicializar o mutex (função permitida) - if (pthread_mutex_init(&shared_data.mutex, NULL) != 0) { - printf("Erro a inicializar o mutex\\n"); - return 1; - } +long get_time(void) +{ + struct timeval tv; - printf("A iniciar threads...\n"); - - // 2. Criar as threads produtoras e consumidoras (função permitida) - for (int i = 0; i < NUM_PRODUCERS; i++) { - if (pthread_create(&producer_threads[i], NULL, producer_thread, &shared_data) != 0) { - printf("Erro a criar thread produtora\\n"); - return 1; - } - } - for (int i = 0; i < NUM_CONSUMERS; i++) { - if (pthread_create(&consumer_threads[i], NULL, consumer_thread, &shared_data) != 0) { - printf("Erro a criar thread consumidora\\n"); - return 1; - } - } - - // 3. Esperar que as threads produtoras terminem (função permitida) - for (int i = 0; i < NUM_PRODUCERS; i++) { - pthread_join(producer_threads[i], NULL); - } - - printf("Produtores terminaram. A aguardar consumidores...\n"); - - // 4. Esperar que as threads consumidoras terminem - // A lógica dentro da thread consumidora deve garantir que ela termina - // quando não há mais itens a serem produzidos e o buffer está vazio. - for (int i = 0; i < NUM_CONSUMERS; i++) { - pthread_join(consumer_threads[i], NULL); - } - - // 5. Destruir o mutex (função permitida) - pthread_mutex_destroy(&shared_data.mutex); - - printf("Programa terminado.\n"); - - return (0); + gettimeofday(&tv, NULL); + return ((tv.tv_sec * 1000) + (tv.tv_usec / 1000)); } -// Lógica da Thread Produtora (a ser implementada) -void *producer_thread(void *arg) +void *producer(void *arg) { - shared_buffer_t *shared = (shared_buffer_t *)arg; - - // TODO: Implementar a lógica de produção aqui. - // Lembre-se do ciclo: - // 1. Lock mutex. - // 2. WHILE buffer está cheio: unlock, usleep, lock de novo. - // 3. Produzir item. - // 4. Unlock mutex. + t_buffer *buffer; + int item; - printf("Thread produtora a terminar.\n"); - - return (NULL); + buffer = (t_buffer *)arg; + while (1) + { + pthread_mutex_lock(&buffer->mutex); + if (buffer->count < BUFFER_SIZE) + { + if (buffer->produced_count >= ITEMS_TO_PRODUCE) + { + pthread_mutex_unlock(&buffer->mutex); + break ; + } + buffer->produced_count++; + item = buffer->produced_count; + buffer->items[buffer->in] = item; + buffer->in = (buffer->in + 1) % BUFFER_SIZE; + buffer->count++; + printf("%ldms - Producer produced item: %d\n", get_time(), item); + pthread_mutex_unlock(&buffer->mutex); + } + else + { + pthread_mutex_unlock(&buffer->mutex); + usleep(100); + } + } + return (NULL); } -// Lógica da Thread Consumidora (a ser implementada) -void *consumer_thread(void *arg) +void *consumer(void *arg) { - shared_buffer_t *shared = (shared_buffer_t *)arg; + t_buffer *buffer; + int item; - // TODO: Implementar a lógica de consumo aqui. - // Lembre-se do ciclo: - // 1. Lock mutex. - // 2. WHILE buffer está vazio (e ainda há itens a produzir): unlock, usleep, lock de novo. - // 3. Consumir item. - // 4. Unlock mutex. - - printf("Thread consumidora a terminar.\n"); - - return (NULL); + buffer = (t_buffer *)arg; + while (1) + { + pthread_mutex_lock(&buffer->mutex); + if (buffer->count > 0) + { + item = buffer->items[buffer->out]; + buffer->out = (buffer->out + 1) % BUFFER_SIZE; + buffer->count--; + printf("%ldms - Consumer consumed item: %d\n", get_time(), item); + pthread_mutex_unlock(&buffer->mutex); + } + else + { + if (buffer->produced_count >= ITEMS_TO_PRODUCE) + { + pthread_mutex_unlock(&buffer->mutex); + break ; + } + pthread_mutex_unlock(&buffer->mutex); + usleep(100); + } + } + return (NULL); +} + +int main(void) +{ + pthread_t producers[NUM_PRODUCERS]; + pthread_t consumers[NUM_CONSUMERS]; + t_buffer *buffer; + int i; + + buffer = malloc(sizeof(t_buffer)); + if (!buffer) + return (1); + buffer->count = 0; + buffer->in = 0; + buffer->out = 0; + buffer->produced_count = 0; + pthread_mutex_init(&buffer->mutex, NULL); + + i = -1; + while (++i < NUM_PRODUCERS) + pthread_create(&producers[i], NULL, producer, buffer); + i = -1; + while (++i < NUM_CONSUMERS) + pthread_create(&consumers[i], NULL, consumer, buffer); + + i = -1; + while (++i < NUM_PRODUCERS) + pthread_join(producers[i], NULL); + i = -1; + while (++i < NUM_CONSUMERS) + pthread_join(consumers[i], NULL); + + pthread_mutex_destroy(&buffer->mutex); + free(buffer); + return (0); } diff --git a/testers/test_6_deadlock_demo.sh b/testers/test_6_deadlock_demo.sh index 44cb3f1..8873815 100755 --- a/testers/test_6_deadlock_demo.sh +++ b/testers/test_6_deadlock_demo.sh @@ -175,19 +175,42 @@ else echo -e "${YELLOW}⊘ Test 6: Valgrind not installed, skipping memory test${NC}" fi -# Test 7: Helgrind deadlock detection +# Test 7: Deadlock behavior verification if command -v valgrind &> /dev/null; then - echo -n "Test 7: Helgrind deadlock detection (mode 0)... " - HELGRIND_OUTPUT=$(valgrind --tool=helgrind "$EXE_PATH" 0 2>&1) - if echo "$HELGRIND_OUTPUT" | grep -q "deadlock"; then + echo -n "Test 7: Deadlock behavior verification (mode 0)... " + # Test that the program hangs in deadlock mode and times out + timeout 3 "$EXE_PATH" 0 > /tmp/deadlock_test7.txt 2>&1 + EXIT_CODE=$? + + if [ $EXIT_CODE -eq 124 ]; then + # Program timed out - this is expected for deadlock + OUTPUT=$(cat /tmp/deadlock_test7.txt 2>/dev/null) + if echo "$OUTPUT" | grep -q "trying to lock" && echo "$OUTPUT" | wc -l | awk '{if ($1 >= 4) exit 0; else exit 1}'; then + echo -e "${GREEN}✓ PASSED${NC} (deadlock behavior confirmed)" + ((PASSED++)) + else + echo -e "${YELLOW}⚠ WARNING${NC} - Partial deadlock detection" + ((PASSED++)) + fi + else + echo -e "${RED}✗ FAILED${NC}" + echo "Program should hang in deadlock mode" + echo "Exit code: $EXIT_CODE" + ((FAILED++)) + fi + rm -f /tmp/deadlock_test7.txt +else + echo -e "${YELLOW}⊘ Test 7: Valgrind not installed, using basic deadlock test${NC}" + # Fallback test without valgrind + echo -n "Test 7: Basic deadlock test (mode 0)... " + timeout 3 "$EXE_PATH" 0 > /dev/null 2>&1 + if [ $? -eq 124 ]; then echo -e "${GREEN}✓ PASSED${NC}" ((PASSED++)) else - echo -e "${YELLOW}⚠ WARNING${NC} - Helgrind did not report a deadlock" - ((PASSED++)) + echo -e "${RED}✗ FAILED${NC}" + ((FAILED++)) fi -else - echo -e "${YELLOW}⊘ Test 7: Valgrind not installed, skipping Helgrind test${NC}" fi # Cleanup