0

I'm implementing in locker in this thread-like library. However, it's suppose to run the statement in the main thread, check if the locker was released, back and forth, until it's the mutex released. So let's say I have this piece of code inside a function running in this thread-like:

uthread_mutex_lock(&lockObj);
debug("\t\033[32min lock...\033[0m\n");
f();
g();
// and so on...
uthread_mutex_unlock(&lockObj);
debug("\tout lock\n");

and somewhere in the main function:

thread_t* th1 = uthread_create(test1, &a1);
thread_t* th2 = uthread_create(test2, &a2);
thread_t* th3 = uthread_create(test3, NULL);
uthread_start();

uthread_join(th1);
uthread_join(th2);
uthread_join(th3);

printf("\033[31mHello, World!!!\033[0m\n");
printf("more statements over here...\n");

the issue is, when I call the lock, it's supposed to run the statements after my uthread_mutex_lock() in this case debug("\t\033[32min lock...\033[0m\n");, then f() until uthread_mutex_unlock() got called but it's switch to some other point of the code: printf("\033[31mHello, World!!!\033[0m\n"); not the imediate statements after the lock call as I've describied. What am I missing? the issue is in the lines:

    // Wait until the lock is unlocked
    while(m->locked)
    {
        debug("within loop\n");
        if(swapcontext(&threads[curr_thread_id]->context, &main_context) == -1)
        {
            perror("swapcontext");
        }
    }

swapcontext(&threads[curr_thread_id]->context, &main_context) is supposed to switch from current thread to main context. The thing is, how do I switch to the point after mutex lock is done? I've tried to set up the context in the init, so it would save the context starting from there, did not work. I've also tried to grab the context right on mutex lock call, like this:

    getcontext(&lockObj.context);
    lockObj.context.uc_stack.ss_sp = stack;
    lockObj.context.uc_stack.ss_size = STACK_SIZE;
    lockObj.context.uc_link = NULL;
    makecontext(&lockObj.context, (void (*)())uthread_mutex_dummy, 0);

then pass down that context lockObj.context in uthread_mutex_lock() so that the mutex would be like this:


    // Wait until the lock is unlocked
    while(m->locked)
    {
        debug("within loop\n");
        if(swapcontext(&threads[curr_thread_id]->context, &m->context) == -1)
        {
            perror("swapcontext");
        }
    }

but to my surprise I got very same behavior and I got printf("\033[31mHello, World!!!\033[0m\n"); executed instead of statements right after my lock mutex call: debug("\t\033[32min lock...\033[0m\n"); maybe the fact it's inside a function running in a thread changes anything? below is my code. Note completetly different ways to solve this are welcome.

uthread.h

#ifndef UTHREAD_H
#define UTHREAD_H

#include <ucontext.h>

// less space than this is resulting in a memory error
// for some reason
#define STACK_SIZE 32768
#define NUM_MAX_THREADS 32
#define TIME_SLICE 100

typedef void (*start_routine_t)(void*);

typedef enum THREAD_STATE
{
    THREAD_STATE_NONE,
    THREAD_STATE_PENDING,
    THREAD_STATE_RUNNING,
    THREAD_STATE_DONE,
} THREAD_STATE_T;

typedef struct thread
{
    ucontext_t context;
    void* stack;
    THREAD_STATE_T state;
    start_routine_t start_routine;
    void* routine_arg;
    int done;
    int id;
} thread_t;

typedef struct mutex
{
    ucontext_t context;
    void* stack;
    //int id;
    int locked;
} mutex_t;

void* create_stack(void);
void no_memory(void);
thread_t* create_thread_info(start_routine_t, void*);
thread_t* uthread_create(start_routine_t, void *restrict arg);
void uthread_release(thread_t **th);
thread_t *uthread_create_main();
void uthread_release_all();
void main_loop();
thread_t* find_next_to_run();
void uthread_join();
void thread_routine_handler(int index);
void setup_timer_slice_out();
void thread_time_run_out(int signal);
void switch_thread();
void uthread_start();
void start_thread_callback(int signal);
void start_thread();
void thread_start();
void init_scheduler();
void uthread_mutex_lock(mutex_t*);
void uthread_mutex_unlock(mutex_t*);
void uthread_mutex_init(mutex_t *);
void uthread_mutex_dummy();
#endif // UTHREAD_H

uthread.c

#include <stddef.h> // for NULL
#include <stdlib.h> // for calloc
#include <stdio.h> // fprintf
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <ucontext.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/wait.h>
#include "uthread.h"
#include "debug.h"

static thread_t *threads[NUM_MAX_THREADS] = {0};
static volatile int threads_count = 0;
static ucontext_t main_context = {0};
static ucontext_t scheduler_context;
static volatile int curr_thread_id = -1;
// enabled by default
static volatile int timeout_enabled = 1;

void no_memory(void)
{
    fprintf(stderr, "run out of memory!\n");
    //exit(EXIT_FAILURE);
}

void thread_routine_handler(int index)
{
    debug("thread_routine_handler() got called!\n");

    thread_t *th = threads[index];
    start_routine_t start_routine = th->start_routine; 
    void *arg_routine = th->routine_arg;

    start_routine(arg_routine);
    th->state = THREAD_STATE_DONE;
    swapcontext(&th->context, &scheduler_context);
}

void thread_time_run_out(int signal)
{
    (void)signal; // remove unused warning

    // Thread's time slice has run out, switch to another thread
    // ...
    //printf("\ttime run out!!! switch thread...\n");
    debug_safe("time run out!!!\n");

    if(timeout_enabled)
    {
        switch_thread();
    }
}

void setup_timer_slice_out()
{
    // Set up the signal handler for the thread's time slice
    struct sigaction sa;
    sa.sa_handler = thread_time_run_out;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGALRM, &sa, NULL);

    // Set up the timer for the thread's time slice
    struct itimerval timer;
    timer.it_value.tv_sec = 5;
    timer.it_value.tv_usec = 0;
    timer.it_interval.tv_sec = 0;
    timer.it_interval.tv_usec = 0;
    setitimer(ITIMER_REAL, &timer, NULL);
}

thread_t* create_thread_info(start_routine_t start_routine, void *arg)
{
    void *th_mem = NULL;
    void *stack_mem = NULL;

    th_mem = calloc(1, sizeof(thread_t));
    if(!th_mem)
    {
        goto failure;
    }

    stack_mem = calloc(sizeof(char), STACK_SIZE);
    if(!stack_mem)
    {
        goto failure;
    }

    if(threads_count > NUM_MAX_THREADS)
    {
        fprintf(stderr, "number max of threads reached!\n");
        exit(EXIT_FAILURE);
    }

    thread_t *th = th_mem;
    th->stack = stack_mem;
    th->start_routine = start_routine;
    th->routine_arg = arg;
    th->id = threads_count;
    th->state = THREAD_STATE_PENDING;

    // push into list to run
    // TODO: find a empty place to put
    threads[threads_count] = th;
    threads_count++;

    if(getcontext(&th->context) == -1)
    {
        perror("getcontext");
        exit(EXIT_FAILURE);
    }
    
    th->context.uc_stack.ss_sp = th->stack;
    th->context.uc_stack.ss_size = STACK_SIZE;
    th->context.uc_link = &scheduler_context;
    // cast to get rid of imcompativle types warnings
    makecontext(&th->context,  (void (*)())thread_routine_handler, 1, th->id);
    return th;

    // goto well-used isnt evil
    // even linux code uses that.
    // free what we got, print an error,
    // then exit
    failure:
        if(th_mem)
        {
            free(th_mem);
            th_mem = NULL;
        }
        if(stack_mem)
        {
            free(stack_mem);
            stack_mem = NULL;
        }
        no_memory();
        exit(EXIT_FAILURE);
}
                                                                                                                                                                                          
// if the return value is not NULL,
// it's up to the caller to free it.
thread_t *uthread_create(start_routine_t start_routine, void *restrict arg)
{
    thread_t *t = create_thread_info(start_routine, arg);
    //debug("returning from create()\n");
    return t;
}

void scheduler()
{
    debug("scheduler() got called!\n");

    while(1)
    {
        int all_done = 1;
        for(int i = 0; i < threads_count; i++)
        {
            thread_t *t = threads[i];
            int ts = t->state;
            if(ts == THREAD_STATE_PENDING || ts == THREAD_STATE_RUNNING)
            {
                all_done = 0;
                //debug("got a thread to run id = %d...\n", t->id);
                setup_timer_slice_out();
                curr_thread_id = t->id;
                swapcontext(&scheduler_context, &t->context);
                break;
            }
        }

        if(all_done)
        {
            break;
        }

        ucontext_t *from = &threads[curr_thread_id]->context;
        //debug("nothing to run, back to main thread!!\n");
        if(swapcontext(from, &main_context) == -1)
        {
            perror("swapcontext");
        }
    }
}

void init_scheduler()
{
    // Set up the scheduler context
    getcontext(&scheduler_context);
    scheduler_context.uc_stack.ss_sp = malloc(STACK_SIZE);
    scheduler_context.uc_stack.ss_size = STACK_SIZE;
    scheduler_context.uc_link = &main_context;
    makecontext(&scheduler_context, scheduler, 0);
}

void uthread_start()
{
    debug("uthread_start() got called!\n");

    init_scheduler();
    // Start running the threads
    swapcontext(&main_context, &scheduler_context);
}

void uthread_join(thread_t* th)
{
    // it's finished, no neeed to do
    // anything...
    if(th->state == THREAD_STATE_DONE)
    {
        return;
    }

    // save prev state beforing resetting
    // timeout_enabled flag
    int timeout_enabled_save = timeout_enabled;
    int curr_id_save = curr_thread_id;
    // prevent from switch the thread in this run
    timeout_enabled = 0;
    ucontext_t *from = &threads[curr_thread_id]->context;
    swapcontext(from, &th->context);
    // mark as finished
    th->state = THREAD_STATE_DONE;
    // restore state
    timeout_enabled = timeout_enabled_save;
    curr_thread_id = curr_id_save;
}

// free stack member and the thread_t pointer itself.
// each freed pointer is set to NULL, to prevent
// futher use.
void uthread_release(thread_t **th)
{
    thread_t *p = *th;
    free(p->stack);
    p->stack = NULL;
    free(*th);
    *th = NULL;
}

thread_t* find_next_to_run()
{
    debug("find_next_to_run() got called!\n");
    //debug("threads_count = %d\n", threads_count);

    for(int i = 0; i < threads_count; i++)
    {
        //debug("i = %d\n", i);
        thread_t *t = threads[i];
        if(t->id != curr_thread_id &&
          (t->state == THREAD_STATE_PENDING ||
           t->state == THREAD_STATE_RUNNING))
        {
            return t;
        }
    }

    return NULL;
}

void switch_thread()
{
    debug("switch_thread() got called!\n");

    assert(curr_thread_id != -1);

    thread_t *from = threads[curr_thread_id];
    from->state = THREAD_STATE_RUNNING;

    thread_t *to = find_next_to_run();
    if(to)
    {
        debug("switch from thread from %d to %d\n", curr_thread_id, to->id);
        curr_thread_id = to->id;
        swapcontext(&from->context, &to->context);
    }
}

void uthread_release_all()
{
    for(int i = 0; i < threads_count; i++)
    {
        thread_t *t = threads[i];
        if(t)
        {
            uthread_release(&t);
        }
    }
}

void uthread_mutex_lock(mutex_t *m)
{
#if 0
    void *stack = malloc(STACK_SIZE);
    if(!stack)
    {
        perror("malloc");
        exit(EXIT_FAILURE);
    }

    getcontext(&lockObj.context);
    lockObj.context.uc_stack.ss_sp = stack;
    lockObj.context.uc_stack.ss_size = STACK_SIZE;
    lockObj.context.uc_link = NULL;
    makecontext(&lockObj.context, (void (*)())uthread_mutex_dummy, 0);
#endif

    debug("uthread_mutex_lock() got called!\n");
    assert(curr_thread_id != -1);
    debug("curr_thread_id = %d\n",curr_thread_id);

    // Disable interrupts to prevent race conditions
    sigset_t oldmask, newmask;
    sigfillset(&newmask);
    sigprocmask(SIG_BLOCK, &newmask, &oldmask);

    // Wait until the lock is unlocked
    while(m->locked)
    {
        debug("within loop\n");
        if(swapcontext(&threads[curr_thread_id]->context, &main_context) == -1)
        {
            perror("swapcontext");
        }
    }

    // Enable interrupts again
    sigprocmask(SIG_SETMASK, &oldmask, NULL);

    debug("exiting from uthread_mutex_lock()!\n");
}

void uthread_mutex_unlock(mutex_t *m)
{
    // Disable interrupts to prevent race conditions
    sigset_t oldmask, newmask;
    sigfillset(&newmask);
    sigprocmask(SIG_BLOCK, &newmask, &oldmask);
    
    m->locked = 0;

    // Enable interrupts again
    sigprocmask(SIG_SETMASK, &oldmask, NULL);
}

void uthread_mutex_init(mutex_t *m)
{
    m->locked = 1;
}

void uthread_mutex_dummy() { }

solution.c

#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>
#include <stdarg.h>
#include <assert.h>
#include <unistd.h>
#include "uthread.h"
#include "debug.h"

static thread_t *thread1 = NULL, *thread2 = NULL;
static mutex_t lockObj;

#if 1
void thread1_function() {

    //printf("a = %d, b = %d\n", a, b);

    for (int i = 0; i < 10; i++) {
        printf("Thread 1 count: %d\n", i);
        if ( !thread2->done ) {
            swapcontext(&thread1->context, &thread2->context);
        }
    }

    thread1->done = 1;
    printf("Thread 1 done\n");
}

void thread2_function() {
    for (int i = 0; i < 10; i++) {
        printf("Thread 2 count: %d\n", i);
        if ( !thread1->done ) {
            swapcontext(&thread2->context, &thread1->context);
        }
    }

    printf("Thread 2 done\n");
    thread2->done = 1;
}
#endif

typedef struct Arg_type
{
    int id;
    char *s;
} Arg_t;

void test1(void *arg)
{
    printf("hello from test1()\n");

    printf("hold on...\n");
    printf("in sleep...\n");
    sleep(15);
    printf("out sleep...\n");
    
    Arg_t *a  = (Arg_t*) arg;
    printf("id = %d, message = %s\n", a->id, a->s);

    printf("exiting from test1()!\n");
}

void test2(void* arg)
{
    printf("hello from test2()\n");
    Arg_t *a  = (Arg_t*) arg;
    printf("id = %d, message = %s\n", a->id, a->s);
    printf("exiting from test2()\n");
}

void test3(void *arg)
{
    (void) arg;

    printf("test3() got called!\n");

    int c = 0;
    for(int i = 0; i < 1000; i++)
    {
        c += i;
    }

#if 1
    uthread_mutex_lock(&lockObj);
    debug("\t\033[32min lock...\033[0m\n");
    uthread_mutex_unlock(&lockObj);
    debug("\tout lock\n");
#endif

    printf("c = %d\n", c);
    printf("end of test3 function\n");
}

int main()
{

    Arg_t a1 = {10, "foo"};
    Arg_t a2 = {20, "baa"};

    uthread_mutex_init(&lockObj);
    
    thread_t* th1 = uthread_create(test1, &a1);
    thread_t* th2 = uthread_create(test2, &a2);
    thread_t* th3 = uthread_create(test3, NULL);
    uthread_start();

    uthread_join(th1);
    uthread_join(th2);
    uthread_join(th3);
    
    printf("\033[31mHello, World!!!\033[0m\n");
    printf("more statements over here...\n");

#if 1
    while(1)
    {
        printf("press 'q' to exit...\n");
        int ch = getchar();
        if(ch == 'q' || ch == 'Q') break;
    }
#endif

    uthread_release_all();
    debug("return from main\n");
    return 0;
  
}

edit 1:

debug function definition

void debug(const char* format, ...)
{
#ifdef DEBUG
    va_list args;
    va_start(args, format);
    vfprintf(stderr, format, args);
    va_end(args);
    //fflush(stderr);
#endif
}

// this version use write() and not *printf() function
// so it can be called safely within signal handlers
void debug_safe(const char* format, ...)
{
#ifdef DEBUG
    va_list args;
    va_start(args, format);
    char buffer[1024] = {0};
    vsnprintf(buffer, sizeof(buffer), format, args);
    va_end(args);
    write(STDERR_FILENO, buffer, strlen(buffer));
#endif
}
Jack
  • 16,276
  • 55
  • 159
  • 284
  • @AndreasWenzel to stderr, i'll edit and include the function definition – Jack Mar 03 '23 at 05:13
  • 1
    not having searched through all that code, it looks VERY strange to poll upon a "mutex": 'while(m->locked) ...' a mutex is meant to be aquired and released. If you want to do something with a mutex protected ressource, aquire the lock and the system will block until you get the lock. polling just wastes CPU time and relies upon volatility of the member "locked". – Synopsis Mar 03 '23 at 11:55

0 Answers0