[Issue 17957] New: D shared library throws asserts when called from C detached pthread but not terminated with dlclose

d-bugmail at puremagic.com d-bugmail at puremagic.com
Tue Oct 31 19:55:30 UTC 2017


https://issues.dlang.org/show_bug.cgi?id=17957

          Issue ID: 17957
           Summary: D shared library throws asserts when called from C
                    detached pthread but not terminated with dlclose
           Product: D
           Version: D2
          Hardware: x86_64
                OS: Linux
            Status: NEW
          Severity: normal
          Priority: P1
         Component: druntime
          Assignee: nobody at puremagic.com
          Reporter: ajidala at gmail.com

Okay, this is a complex one.

Say you have a shared library written in D which exports the symbol test_fun.
Now in a C application, you create a thread, which you detach, and then load
the shared library and execute test_fun. Meanwhile, the main thread waits for
the auxiliary thread to complete with pthread_join, and then simply terminate
the application.

This will lead to the D runtime throwing asserts, and the default assert
handler attempts to allocate memory with the GC, which makes the application
segfault because the GC is not enabled at this stage and the asserts happen in
a @nogc application anyway. If we replace the assert handler with something
that doesn't do GC allocations, we can see two places in the D runtime that
throw asserts; attempts to lock and unlock a pthread mutex.

Interestingly, if one calls dlclose() after executing test_fun, the application
terminates without asserts. Even more interestingly, using dlopen with the
RTLD_NODELETE flag, even dlclose() can't save us from druntime's assertiveness.

Here's an example application:

main.c:

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <pthread.h>

int run_lib() {
    printf("run_lib()\n");
    void *lib = dlopen("./libdtest.so", RTLD_NOW | RTLD_LOCAL);
    if (!lib) {
        fprintf(stderr, "dlopen failed: '%s'\n", dlerror());
    }

    int (*fun)() = dlsym(lib, "test_fun");
    char *error = dlerror();
    if (error) {
        fprintf(stderr, "dlsym failed: '%s'\n", error);
        exit(1);
    }
    (*fun)();
    // uncomment for no asserts!
    // dlclose(lib);

    return 0;
}

static void *object_thread(void *p) {
    pthread_detach(pthread_self());
    run_lib();
}

int main() {
    printf("main()\n");
    pthread_t thread;
    pthread_create(&thread, NULL, object_thread, NULL);
    pthread_join(thread, NULL);
    printf("main() done\n");
    fflush(stdout);
    return 0;
}


dtest.d:

import core.stdc.stdio;
import core.exception;

void dumb_assert_handler(string file, ulong line, string msg) nothrow {
    printf("Someone in %s on line %d is complaining.\n", file.ptr, line);
}

extern (C) int test_fun() {
    assertHandler(&dumb_assert_handler);
    printf("can confirm this is fun\n");
    return 0;
}


build.sh:

rm -rf dtest.o main.o libdtest.so main
dmd -c dtest.d -fPIC
dmd -oflibdtest.so dtest.o -shared -defaultlib=libphobos2.so -L-rpath=/usr/lib/

gcc -c main.c
gcc -rdynamic main.o -o main -ldl -lpthread


run ./build.sh, then run ./main

Interestingly, if you call test_fun directly from main() and not from a
detached thread, druntime doesn't throw asserts.

--


More information about the Digitalmars-d-bugs mailing list