cmultithreadingucontext

Why is my function not running and getting an invalid memory error?


I'm trying to simulate threads swapcontext() and whatnot but I have some issues:

  1. The callback function doesn't run
  2. I get a memory error from free(): free(): invalid size Aborted
  3. How do I wait until all the threads are done?

here's my code:

#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>
#include <stdarg.h>

#define STACK_SIZE 1024

typedef struct thread {
    ucontext_t context;
    void* stack;
    int done;
} thread_t;

thread_t thread1, thread2;

void thread1_function(int argc, ...) {
    va_list args;
    va_start(args, argc);

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

    thread1.done = 1;
    printf("Thread 1 done\n");

    va_end(args);
}

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

    printf("Thread 2 done\n");
    thread2.done = 1;
}

int main() {
    // Allocate stacks for each thread
    thread1.stack = malloc(STACK_SIZE);
    thread2.stack = malloc(STACK_SIZE);

    // Initialize the execution context for each thread
    getcontext(&thread1.context);
    thread1.context.uc_stack.ss_sp = thread1.stack;
    thread1.context.uc_stack.ss_size = STACK_SIZE;
    makecontext(&thread1.context, (void (*)())thread1_function, 1, 2, 0);

    getcontext(&thread2.context);
    thread2.context.uc_stack.ss_sp = thread2.stack;
    thread2.context.uc_stack.ss_size = STACK_SIZE;
    makecontext(&thread2.context, (void (*)())thread2_function, 0);

    // Switch to thread1
    swapcontext(&thread1.context, &thread2.context);

    // Free stacks
    free(thread1.stack);
    free(thread2.stack);

    return 0;
}

When I run the code is supposed to run both my callback function instead of I get below error and without the function at all (as far i know):

Thread 2 count: 0 free(): invalid size Aborted


Solution

  • In my tests, STACK_SIZE must be set to at least 6 KB, otherwise I get the error message free(): invalid size or a segmentation fault. Therefore, I recommend setting STACK_SIZE to at least 32 KB in order to be reasonably sure that the stack size is sufficient.

    In your posted code, you set up

    However, in the line

    swapcontext(&thread1.context, &thread2.context);
    

    of the function main, you overwrite thread1.context with the current context and execute thread2.context. This means that thread1.context is no longer set up to execute the function thread1_function, but is instead set up to execute the remainder of main.

    Because you are now executing thread2.context, the function thread2_function will be executed. This function will first print

    Thread 2 count: 0
    

    and then it will execute this line:

    swapcontext(&thread2.context, &thread1.context);
    

    As previously stated, thread1.context is no longer set up to execute the function thread1_function, but is instead set up to execute the remainder of main. Therefore, after calling swapcontext, the program will execute these lines of main:

    // Free stacks
    free(thread1.stack);
    free(thread2.stack);
    
    return 0;
    

    These lines will terminate the program, causing nothing else to be printed.

    In order to fix this, you should not store the context of main in thread1.context. It should be stored in a separate object.

    Also, if you don't set the value of thread1.context.uclink and thread2.context.uclink to a non-zero value, then the program will terminate as soon as thread1_function or thread2_function returns.

    One possible solution is to set the member uclink to the context that is executing the function main. The function main can then be programmed to terminate if both threads are done, otherwise it will set the context to the thread that is not yet done.

    Another issue is that the line

    swapcontext(&thread1.context, &thread2.context);
    

    in the function thread1_function is not meaningful if thread 2 is already done, because in that case, setting the context to thread 2 will cause thread 2 to repeat its final time slice (with the stack that has already been used by the final time slice of thread 2, which may cause the program to crash).

    For this reason, that line should be changed to

    if ( !thread2.done ) {
        swapcontext(&thread1.context, &thread2.context);
    }
    

    The function thread2_function has the same problem.

    Here is a solution which fixes all errors mentioned above:

    #include <stdio.h>
    #include <stdlib.h>
    #include <ucontext.h>
    #include <stdarg.h>
    
    #define STACK_SIZE 32768
    
    typedef struct thread {
        ucontext_t context;
        void* stack;
        int done;
    } thread_t;
    
    thread_t thread1, thread2;
    ucontext_t main_context;
    
    void thread1_function(int argc, ...) {
        va_list args;
        va_start(args, argc);
    
        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");
    
        va_end(args);
    }
    
    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;
    }
    
    int main() {
        // Allocate stacks for each thread
        thread1.stack = malloc(STACK_SIZE);
        thread2.stack = malloc(STACK_SIZE);
    
        // Initialize the execution context for each thread
        getcontext(&thread1.context);
        thread1.context.uc_stack.ss_sp = thread1.stack;
        thread1.context.uc_stack.ss_size = STACK_SIZE;
        thread1.context.uc_link = &main_context;
        makecontext(&thread1.context, (void (*)())thread1_function, 1, 2, 0);
    
        getcontext(&thread2.context);
        thread2.context.uc_stack.ss_sp = thread2.stack;
        thread2.context.uc_stack.ss_size = STACK_SIZE;
        thread2.context.uc_link = &main_context;
        makecontext(&thread2.context, (void (*)())thread2_function, 0);
    
        // Switch to thread1
        swapcontext( &main_context, &thread1.context );
    
        // If a thread is still running, set context to that thread
        if ( !thread1.done ) {
            setcontext( &thread1.context );
        }
        if ( !thread2.done ) {
            setcontext( &thread2.context );
        }
    
        // Free stacks
        free(thread1.stack);
        free(thread2.stack);
    
        return 0;
    }
    

    This program has the following output:

    Thread 1 count: 0
    Thread 2 count: 0
    Thread 1 count: 1
    Thread 2 count: 1
    Thread 1 count: 2
    Thread 2 count: 2
    Thread 1 count: 3
    Thread 2 count: 3
    Thread 1 count: 4
    Thread 2 count: 4
    Thread 1 count: 5
    Thread 2 count: 5
    Thread 1 count: 6
    Thread 2 count: 6
    Thread 1 count: 7
    Thread 2 count: 7
    Thread 1 count: 8
    Thread 2 count: 8
    Thread 1 count: 9
    Thread 2 count: 9
    Thread 1 done
    Thread 2 done