diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e965e4e..6cd6955 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,6 +5,7 @@ jobs: test: name: Dub Tests strategy: + fail-fast: false matrix: os: [ubuntu-latest, windows-latest] dc: [dmd-latest, ldc-latest, dmd-2.098.1, ldc-1.28.1] diff --git a/dub.sdl b/dub.sdl index 4b36060..9e7568d 100644 --- a/dub.sdl +++ b/dub.sdl @@ -17,6 +17,7 @@ configuration "unittest" { dflags "-dip1000" sourcePaths "source" "tests" importPaths "source" "tests" + lflags "/EXPORT:concurrency_getLocalThreadExecutor" "/EXPORT:concurrency_globalStopSourcePointer" platform="windows" } configuration "unittest-release" { dependency "unit-threaded" version="*" @@ -25,6 +26,7 @@ configuration "unittest-release" { dflags "-dip1000" "-g" sourcePaths "source" "tests/ut" importPaths "source" "tests/ut" + lflags "/EXPORT:concurrency_getLocalThreadExecutor" "/EXPORT:concurrency_globalStopSourcePointer" platform="windows" # buildOptions "unittests" "optimize" "inline" buildOptions "unittests" "optimize" } @@ -39,6 +41,7 @@ configuration "unittest-asan" { dflags "-dip1000" "-fsanitize=address" sourcePaths "source" "tests/ut" importPaths "source" "tests/ut" + lflags "/EXPORT:concurrency_getLocalThreadExecutor" "/EXPORT:concurrency_globalStopSourcePointer" platform="windows" # buildOptions "unittests" "optimize" "inline" buildOptions "unittests" "optimize" } diff --git a/source/concurrency/signal.d b/source/concurrency/signal.d index f03b597..15696c9 100644 --- a/source/concurrency/signal.d +++ b/source/concurrency/signal.d @@ -73,18 +73,36 @@ void setupCtrlCHandler(shared StopSource stopSource) @trusted { private static shared StopSource globalSource; -// we export this function so that dynamic libraries can load it to access -// the host's globalStopSource pointer. -// Otherwise they would access their own local instance. -// should not be called directly by usercode, instead use `globalStopSource`. -export extern(C) -shared(StopSource*) concurrency_globalStopSourcePointer() @safe { - return &globalSource; +// a mixin for OS-specific visibility +private mixin template globalStopSourcePointerImpl() { + // should not be called directly by usercode, call `getGlobalStopSourcePointer` instead + pragma(inline, false) + extern(C) shared(StopSource*) concurrency_globalStopSourcePointer() @safe { + return &globalSource; + } } -private shared(StopSource*) getGlobalStopSourcePointer() @safe { - import concurrency.utils : dynamicLoad; - return dynamicLoad!concurrency_globalStopSourcePointer()(); +// We need to make sure all binaries (executable and shared libraries) in the +// process share a single StopSource instance. +version(Windows) { + // On Windows, the executable can export `concurrency_globalStopSourcePointer` + // explicitly via linker flag `/EXPORT:concurrency_globalStopSourcePointer`. + // DLLs containing `concurrency` use the executable's then, falling back to + // their own definition. + private mixin globalStopSourcePointerImpl; + + private shared(StopSource*) getGlobalStopSourcePointer() @safe { + import concurrency.utils : dynamicLoad; + return dynamicLoad!concurrency_globalStopSourcePointer()(); + } +} else { + // Make sure the `concurrency_globalStopSourcePointer` function gets public + // visibility; coupled with the `--export-dynamic-symbol=…` linker flag in + // dub.sdl, the symbol is then exported as dynamic symbol from every binary + // containing this `concurrency` library, and the dynamic loader uniques the + // symbol across the whole process for us. + export mixin globalStopSourcePointerImpl; + alias getGlobalStopSourcePointer = concurrency_globalStopSourcePointer; } struct SignalHandler { diff --git a/source/concurrency/thread.d b/source/concurrency/thread.d index 2b62c37..9b21d31 100644 --- a/source/concurrency/thread.d +++ b/source/concurrency/thread.d @@ -10,29 +10,45 @@ import core.time : Duration; import concurrency.data.queue.waitable; import concurrency.data.queue.mpsc; -// we export the getLocalThreadExecutor function so that dynamic libraries -// can load it to access the host's localThreadExecutor TLS instance. -// Otherwise they would access their own local instance. -// should not be called directly by usercode, call `silThreadExecutor` instead. -export extern(C) -LocalThreadExecutor concurrency_getLocalThreadExecutor() @safe { - static LocalThreadExecutor localThreadExecutor; - if (localThreadExecutor is null) { - localThreadExecutor = new LocalThreadExecutor(); - } - - return localThreadExecutor; +// a mixin for OS-specific visibility +private mixin template getLocalThreadExecutorImpl() { + // should not be called directly by usercode, call `getLocalThreadExecutor` instead + pragma(inline, false) + extern(C) LocalThreadExecutor concurrency_getLocalThreadExecutor() @safe { + static LocalThreadExecutor localThreadExecutor; + if (localThreadExecutor is null) { + localThreadExecutor = new LocalThreadExecutor(); + } + + return localThreadExecutor; + } } -LocalThreadExecutor getLocalThreadExecutor() @trusted { - import concurrency.utils : dynamicLoad; - static LocalThreadExecutor localThreadExecutor; - if (localThreadExecutor is null) { - localThreadExecutor = - dynamicLoad!concurrency_getLocalThreadExecutor()(); - } - - return localThreadExecutor; +// We need to make sure all binaries (executable and shared libraries) in the +// process share a single (thread-local) LocalThreadExecutor instance. +version(Windows) { + // On Windows, the executable can export `concurrency_getLocalThreadExecutor` + // explicitly via linker flag `/EXPORT:concurrency_getLocalThreadExecutor`. + // DLLs containing `concurrency` use the executable's then, falling back to + // their own definition. + mixin getLocalThreadExecutorImpl; + + LocalThreadExecutor getLocalThreadExecutor() @trusted { + import concurrency.utils : dynamicLoad; + static LocalThreadExecutor localThreadExecutor; + if (localThreadExecutor is null) { + localThreadExecutor = dynamicLoad!concurrency_getLocalThreadExecutor()(); + } + return localThreadExecutor; + } +} else { + // Make sure the `concurrency_getLocalThreadExecutor` function gets public + // visibility; coupled with the `--export-dynamic-symbol=…` linker flag in + // dub.sdl, the symbol is then exported as dynamic symbol from every binary + // containing this `concurrency` library, and the dynamic loader uniques the + // symbol across the whole process for us. + export mixin getLocalThreadExecutorImpl; + alias getLocalThreadExecutor = concurrency_getLocalThreadExecutor; } private struct AddTimer {