| 1 | import os |
| 2 | import rand |
| 3 | |
| 4 | const vexe = @VEXE |
| 5 | |
| 6 | // Regression test for vlang/v#27178: when a V `-shared` library is built |
| 7 | // with `-gc boehm`, the generated C must call `GC_allow_register_threads()` |
| 8 | // during library init. Without it, host-spawned threads (Rust workers, |
| 9 | // JNI threads, C# tasks, ...) that later enter V code cause libgc to abort |
| 10 | // with `"Collecting from unknown thread"` on the first collection. |
| 11 | fn test_shared_library_boehm_emits_allow_register_threads() { |
| 12 | workdir := os.join_path(os.vtmp_dir(), 'v_shared_gc_threads_${rand.ulid()}') |
| 13 | os.mkdir_all(workdir) or { panic(err) } |
| 14 | defer { |
| 15 | os.rmdir_all(workdir) or {} |
| 16 | } |
| 17 | lib_src := os.join_path(workdir, 'lib.v') |
| 18 | lib_c := os.join_path(workdir, 'lib.c') |
| 19 | os.write_file(lib_src, [ |
| 20 | 'module main', |
| 21 | '', |
| 22 | "@[export: 'libfoo_ping']", |
| 23 | 'fn ping() int {', |
| 24 | '\treturn 1', |
| 25 | '}', |
| 26 | ].join('\n')) or { panic(err) } |
| 27 | res := |
| 28 | os.execute('${os.quoted_path(vexe)} -gc boehm -shared -o ${os.quoted_path(lib_c)} ${os.quoted_path(lib_src)}') |
| 29 | assert res.exit_code == 0, 'shared-lib codegen failed:\n${res.output}' |
| 30 | c_src := os.read_file(lib_c) or { panic(err) } |
| 31 | // The call must appear inside `_vinit`, not in a separate constructor: |
| 32 | // `_vinit_caller` already runs `GC_INIT()` via gen_shared_library_boehm_init, |
| 33 | // and calls `_vinit(0,0)` right after, so `GC_allow_register_threads()` |
| 34 | // inside `_vinit` is sequenced correctly. |
| 35 | vinit_start := c_src.index('void _vinit(int ___argc, voidptr ___argv) {') or { |
| 36 | assert false, '`_vinit` definition not found in generated C' |
| 37 | return |
| 38 | } |
| 39 | // `_vinit`'s body has no nested top-level `\n}\n`; the next one closes it. |
| 40 | vinit_end := c_src.index_after('\n}\n', vinit_start) or { |
| 41 | assert false, '`_vinit` body terminator not found in generated C' |
| 42 | return |
| 43 | } |
| 44 | vinit_body := c_src[vinit_start..vinit_end] |
| 45 | assert vinit_body.contains('GC_allow_register_threads();'), 'expected `GC_allow_register_threads()` inside `_vinit`, got:\n${vinit_body}' |
| 46 | assert vinit_body.contains('#if defined(_VGCBOEHM) && defined(GC_THREADS)'), 'expected `GC_THREADS` guard around the call, got:\n${vinit_body}' |
| 47 | } |
| 48 | |