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 WAITING 0x4
#define IS_WAITING(entry) (entry->status & WAITING) #define IS_WAITING(entry) (entry->status & WAITING)
#define GET_WAITED_THREAD(entry) ((struct context_entry*)entry->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 IS_WAITED_THREAD_FINISHED(entry) (GET_WAITED_THREAD(entry)->status & FINISHED)
#define WAITED 0x8 #define WAITED 0x8
#define IS_WAITED(entry) (entry->status & WAITED) #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 int stack_valgrind_id = 0;
static ucontext_t context_for_freeing; static ucontext_t context_for_freeing;
struct last_thread_t;
struct context_entry { struct context_entry {
TAILQ_ENTRY(context_entry) TAILQ_ENTRY(context_entry)
link; // Use to navigate inside the list link; // Use to navigate inside the list
ucontext_t context; ucontext_t context;
thread_t id; thread_t id;
void *waiting; // the thread that the entry is waiting for 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; int valgrind_id;
char status; char status;
char stack[STACK_SIZE]; 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 // Use TailQ from queue BSD
static TAILQ_HEAD(context_head, context_entry) head = TAILQ_HEAD_INITIALIZER(head); static TAILQ_HEAD(context_head, context_entry) head = TAILQ_HEAD_INITIALIZER(head);
// Current running thread // Current running thread
@ -122,6 +132,7 @@ int thread_create(thread_t* newthread, void* (*func)(void*), void* funcarg)
TRACE("ALLOCATED %p", new_entry); TRACE("ALLOCATED %p", new_entry);
new_entry->status = ALLOCATED; new_entry->status = ALLOCATED;
new_entry->retvalue = NULL; new_entry->retvalue = NULL;
new_entry->last_waited = NULL;
*newthread = new_entry->id; *newthread = new_entry->id;
@ -145,15 +156,10 @@ int thread_join(thread_t thread, void** retval)
return -1; return -1;
} }
// Check if there is a deadlock if(GET_LAST_WAITED_THREAD(entry) == running) {
struct context_entry* parent = thread;
while (IS_WAITING(parent)) {
if (GET_WAITED_THREAD(parent) == running) {
TRACE("Deadlock detected"); TRACE("Deadlock detected");
return EDEADLK; return EDEADLK;
} }
parent = GET_WAITED_THREAD(parent);
}
if (!IS_FINISHED(entry)) { if (!IS_FINISHED(entry)) {
// Use status to be in waiting state // 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 // Use retvalue to share which thread is currently waiting for this thread
entry->retvalue = running; 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); DBG("%p is waiting for %p", running, entry);
do { do {
thread_yield(); thread_yield();
@ -191,6 +228,13 @@ void thread_exit(void* retval)
{ {
TRACE("Exit thread %p", running); TRACE("Exit thread %p", running);
print_entry(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) { if (running == NULL) {
exit(0); exit(0);
} }