Thread.spawn crashes when called from Python

Hi,

I am trying to create a dynamic library that is called from python and spawns some threads.
However, std.Thread.spawn crashes before entering the executed function. The zig code in root.zig looks like this:

const std = @import("std");

pub export fn do_nothing_in_thread() void {
    var thread = std.Thread.spawn(.{}, do_nothing, .{}) catch |e| {
        std.debug.print("Failed to start thread: {any}\n", .{e});
        return;
    };
    thread.join();
}

fn do_nothing() void { }

This is the python code:

from ctypes import *


def main():
	testlib = CDLL("zig-out/lib/libtest_multithreading.so")
	testlib.do_nothing_in_thread()


if __name__ == '__main__':
	main()

If I build in debug mode, I get the message:

thread 23663 panic: reached unreachable code
aborting due to recursive panic
[1]    23663 IOT instruction (core dumped)  python3.13 test_wrapper.py

In release fast I get a segfault:

[1]    23836 segmentation fault (core dumped)  python3.13 test_wrapper.py

This anyone know what is happening here? How would one debug a scenario like this? Or did I miss something obvious?

Some notes:

  • zig 0.14.0 (fetched with zvm as master)
  • Ubuntu 24.04
  • The same happens in case the function do_nothing actually does something
  • I tried using python3.13t which has no GIL (I think it should not matter, as I am not calling into the python C API.)
  • I could not find anything that mentions a similar error. From what I understood people did this successfully in other languages.

Many thanks for your help!

Edit: fixed error in zig code

I was able to reproduce your crash by building the shared library with zig build-lib -dynamic lib.zig

When I looked at it in a debugger, I found that the crash is coming from this line: zig/lib/std/Thread.zig at 3b3c18909d5f745f607ead203f67d42d13f200d5 · ziglang/zig · GitHub At this point, linux.tls.area_desc.alignment is 0, which trips an assertion in alignForward, as the alignment must be a power of 2. It looks like linux.tls.area_desc isn’t initialized for a shared library, only for an executable during startup: zig/lib/std/start.zig at 3b3c18909d5f745f607ead203f67d42d13f200d5 · ziglang/zig · GitHub

It works fine for me when linking libc with the shared library, though: zig build-lib -dynamic -lc lib.zig (linking libc causes the C pthreads library to be responsible for threading rather than Zig’s own “direct Linux syscalls” implementation).

4 Likes

Thank you so much! After adding the equivalent lib.linkLibC() in my build script it worked fine for me as well.

1 Like

FWIW I found the corresponding issue: Crash when spawning a thread on Linux with non-default entrypoint · Issue #15336 · ziglang/zig · GitHub

2 Likes