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
}