feat: add deadlock prevention, still a valgrind issue when returning EDEADLCK

This commit is contained in:
Martin Eyben 2025-03-25 13:52:50 +01:00
parent f6a78b3516
commit f6b199d601

View File

@ -18,6 +18,7 @@
#define WAITING 0x4
#define IS_WAITING(entry) (entry->status & WAITING)
#define GET_WAITED_THREAD(entry) ((struct context_entry*)entry->waiting)
#define GET_LAST_WAITED_THREAD(entry) (entry->last_waited ? entry->last_waited->last_thread : NULL)
#define IS_WAITED_THREAD_FINISHED(entry) (GET_WAITED_THREAD(entry)->status & FINISHED)
#define WAITED 0x8
#define IS_WAITED(entry) (entry->status & WAITED)
@ -31,18 +32,27 @@ static char stack_for_freeing[STACK_SIZE] = {0};
static int stack_valgrind_id = 0;
static ucontext_t context_for_freeing;
struct last_thread_t;
struct context_entry {
TAILQ_ENTRY(context_entry)
link; // Use to navigate inside the list
ucontext_t context;
thread_t id;
void *waiting; // the thread that the entry is waiting for
void *retvalue; // retun value or if the thread is waited, the id of the thread that wait for it
void *retvalue; // return value or if the thread is waited, the id of the thread that wait for it
struct last_thread_t *last_waited;
int valgrind_id;
char status;
char stack[STACK_SIZE];
};
struct last_thread_t {
struct context_entry * last_thread;
int ref; // number of reference to this struct (for free)
};
// Use TailQ from queue BSD
static TAILQ_HEAD(context_head, context_entry) head = TAILQ_HEAD_INITIALIZER(head);
// Current running thread
@ -122,6 +132,7 @@ int thread_create(thread_t* newthread, void* (*func)(void*), void* funcarg)
TRACE("ALLOCATED %p", new_entry);
new_entry->status = ALLOCATED;
new_entry->retvalue = NULL;
new_entry->last_waited = NULL;
*newthread = new_entry->id;
@ -145,15 +156,10 @@ int thread_join(thread_t thread, void** retval)
return -1;
}
// Check if there is a deadlock
struct context_entry* parent = thread;
while (IS_WAITING(parent)) {
if (GET_WAITED_THREAD(parent) == running) {
if(GET_LAST_WAITED_THREAD(entry) == running) {
TRACE("Deadlock detected");
return EDEADLK;
}
parent = GET_WAITED_THREAD(parent);
}
if (!IS_FINISHED(entry)) {
// Use status to be in waiting state
@ -164,6 +170,37 @@ int thread_join(thread_t thread, void** retval)
// Use retvalue to share which thread is currently waiting for this thread
entry->retvalue = running;
/** Deadlock **/
// if the running thread is solo (not waited by anyone)
if(!IS_WAITED(running)) {
// if the thread that we want to join is already in a "group" of waiting threads
if (IS_WAITING(entry)) {
// give the last thread waited to the running thread
running->last_waited = entry->last_waited;
}
else { // the thread we want to join is solo
running->last_waited = malloc(sizeof(struct last_thread_t));
running->last_waited->ref = 0 ;
entry->last_waited = running->last_waited;
entry->last_waited->ref++;
running->last_waited->last_thread = entry;
}
running->last_waited->ref++;
} else { // the running thread is already part of a groupe of waiting threads
if (IS_WAITING(entry)) { // the thread we want to join is part of a groupe of waiting threads
// release the last_waited of this entry
running->last_waited->last_thread = GET_LAST_WAITED_THREAD(entry);
}
else { // the thread we want to join is solo and has no last_waited allocated
running->last_waited->last_thread = entry;
entry->last_waited = running->last_waited;
entry->last_waited->ref ++;
}
}
DBG("%p is waiting for %p", running, entry);
do {
thread_yield();
@ -191,6 +228,13 @@ void thread_exit(void* retval)
{
TRACE("Exit thread %p", running);
print_entry(running);
// free the memory of the last thread struct if no one use it anymore
if(running->last_waited) {
DBG("Last waited ref : %d", running->last_waited->ref);
if(--running->last_waited->ref == 0)
free(running->last_waited);
}
if (running == NULL) {
exit(0);
}