Julia to C function to Julia function call

I am trying having a runtime framework implemented in C++ launguage. Julia application calls this framework and this framework will call some other Julia function. I am facing some issue when the framework calls Julia function. I read from Embedding Julia documentation that it is not fully thread safe. Please help us to resolve this issue.
I have created a simple test case to demonstrate the behavior.

Here is the Julia application code, which will call c_function. c_function creates a pthread and calls back Julia function julia_add.
main.jl

module MyLib
#enable(on::Bool) = ccall(:jl_gc_enable, Int32, (Int32,), on) != 0
#enable(off)
export julia_add, call_c_function, call_c_function_direct, initialize_c_function, cleanup_c_function

# Define a Julia function
#function julia_add(a::Int, b::Int)::Int
#    return a + b
#end
function julia_add(a::Cint, b::Cint)::Cint
    println("Received a:", a, "b:", b)
    return a + b
end

# Initialize the C environment by passing the function pointer
function initialize_c_function()
    println("Inside initialize_c_function\n")
    #GC.enable(false)
    func_ptr = @cfunction(julia_add, Cint, (Cint, Cint))
    global stored_func_ptr = func_ptr
    ccall((:initialize_function, "libcfunction"), Cvoid, (Ptr{Cvoid},), func_ptr)
end

# Prototype for the C function
function call_c_function(a::Int, b::Int)::Int
    #ccall((:c_function, "libcfunction"), Cint, (Cint, Cint), Int32(a), Int32(b))
    return ccall((:c_function, "libcfunction"), Cint, (Cint, Cint), Cint(a), Cint(b))
end

# Prototype for the C function
function call_c_function_direct(a::Int, b::Int)::Int
    #ccall((:c_function, "libcfunction"), Cint, (Cint, Cint), Int32(a), Int32(b))
    return ccall((:c_function_direct, "libcfunction"), Cint, (Cint, Cint), Cint(a), Cint(b))
end

# Prototype for the C cleanup function
function cleanup_c_function()
    ccall((:cleanup, "libcfunction"), Cvoid, ())
    return 1
end

end # module

using .MyLib

# Initialize the C environment
initialize_c_function()

# Call the C function
#result = call_c_function_direct(3, 5)
#println("Result of calling C function: $result")
result = call_c_function(3, 5)
println("Result of calling C function: $result")
#result = call_c_function(3, 5)
#println("Result of calling C function: $result")
#result = call_c_function(3, 5)
#println("Result of calling C function: $result")

# Call the cleanup function
cleanup_c_function()
println("Exited\n")

c_function.c

#include <julia.h>
#include <stdio.h>
#include <stdint.h>
#include <pthread.h>

// Define a type for the function pointer
typedef jl_value_t* (*julia_add_t)(jl_value_t*, jl_value_t*);

// Global variable to hold the function pointer
julia_add_t julia_add_func = NULL;

// Function to initialize the Julia runtime and get the function pointer
void initialize_function() {
    // Initialize the Julia runtime
    if (!jl_is_initialized()) {
        jl_init();
    }

    // Load the Julia module
    // Get the function from the module
    jl_function_t *julia_add = jl_get_function(jl_main_module, "julia_add");

    if (julia_add == NULL) {
        fprintf(stderr, "Error: Function julia_add not found\n");
        jl_atexit_hook(0);
        return;
    }

    // Store the function pointer
    julia_add_func = (julia_add_t)julia_add;
    printf(" Julia add:%p\n", julia_add);
}

// Function to be executed by pthreads
void* thread_function(void *args) {
    int32_t *arguments = (int32_t *)args;
    int32_t a = arguments[0];
    int32_t b = arguments[1];

    // Initialize the Julia runtime in this thread
    if (!jl_is_initialized()) {
        jl_init();
    }

    // Check if the function pointer is initialized
    if (julia_add_func == NULL) {
        fprintf(stderr, "Julia function pointer is not initialized\n");
        jl_atexit_hook(0);
        return NULL;
    }

    // Prepare the arguments
    jl_value_t *arg1 = jl_box_int32(a);
    jl_value_t *arg2 = jl_box_int32(b);

    // Call the Julia function
    jl_value_t *result = julia_add_func(arg1, arg2);

    // Check for exceptions
    if (jl_exception_occurred()) {
        fprintf(stderr, "Exception occurred while calling julia_add: %s\n", jl_typeof_str(jl_exception_occurred()));
        jl_atexit_hook(0);
        return NULL;
    }

    // Unbox the result
    int32_t c_result = jl_unbox_int32(result);
    printf("Result from thread: %d\n", c_result);

    // Cleanup Julia runtime for this thread
    jl_atexit_hook(0);

    return NULL;
}

// Define the C function to create a thread and call the Julia function
int32_t c_function(int32_t a, int32_t b) {
    pthread_t thread;
    int32_t args[2] = {a, b};

    if (pthread_create(&thread, NULL, thread_function, args)) {
        fprintf(stderr, "Error creating thread\n");
        return -1;
    }

    if (pthread_join(thread, NULL)) {
        fprintf(stderr, "Error joining thread\n");
        return -1;
    }

    return 0;
}

// Cleanup function to properly shutdown Julia
void cleanup() {
    if (jl_is_initialized()) {
        jl_atexit_hook(0);
    }
}

Build and run:

$ gcc -g -O0 -fPIC -shared  -o libcfunction.so c_function.c -I$JULIA/include/julia -L$JULIA/lib -ljulia -lpthread
$ julia main.jl

You are getting the ABI wrong.

func_ptr = @cfunction(julia_add, Cint, (Cint, Cint))

does not correspond to:

// Define a type for the function pointer
typedef jl_value_t* (*julia_add_t)(jl_value_t*, jl_value_t*);

Instead this should have been:

typedef int (*julia_add_t)(int, int);

Your code-snippets are trying to do many things at once.
Simplify your problem first so that you can find the source of your issues.

I would also recommend reading Calling C and Fortran Code · The Julia Language

Thank you for your response.
I have followed your solution and there is still an issue.
I found the core reason for this issue.
The issue is with println statement in “julia_add” julia function. I found in documentation that println is not thread safe. When I replaced it with another C function, it worked.

Yeah that’s why I recommended isolating issues first.
Get your code your code working without threads in the mix and then tackle issue around thread safety.

When you say println isn’t threadsafe. How did that issue materialize? Is the issue that the printing was interleaved or that there was a crash?

Kinda curious where you found the note in the docs since it isn’t here I/O and Network · The Julia Language

I do not think the println is interleaved. I have created a single thread in C and called Julia function which was having println. If I replace that with ccall based printf it worked without an issue. Most times it is not crashing. It is stalled there.

So println is threadsafe, but you might have held onto the io lock somehow.

In cases like this it’s often helpful to attach GDB and print a stack trace on all threads.