Possible Runtime Collision Between FFTW.jl and an External Threaded FFTW Library on Linux
- Dominant language
- Julia
- Stars
- 300
- Forks
- 61
- PR merge metrics
- No merged PRs in 30d
Description
Hi, thanks for maintaining FFTW.jl.
I am seeing a Linux-only runtime interaction between FFTW.jl and an external C++ shared library that links against a separately built threaded FFTW. I am not sure whether this should be considered a bug, an expected dynamic-linking limitation, or a documentation caveat, but the failure mode is surprising and hard to diagnose.
## Environment
- OS: CentOS Linux 7 (Core), x86_64
- Hardware: 2-socket Intel Xeon Gold 6132 CPU @ 2.60 GHz, 28 physical cores total, 14 cores per socket, 1 hardware thread per core, 2 NUMA nodes
- Julia: 1.12.1
- FFTW.jl: 1.10.0
- Julia threads: 14
- C/C++ compiler: GCC/G++ 4.8.5, Red Hat 4.8.5-44
- External FFTW: FFTW 3.3.11, built separately under a user prefix with pthread/thread support
- External C++ library: linked against the external FFTW with `-lfftw3_threads -lfftw3`
- External C++ library rpath: points to the external FFTW prefix
The FFTW.jl artifact on this system appears to provide the base FFTW libraries:
```text
libfftw3.so.3
libfftw3f.so.3
```
It does not appear to provide the threaded FFTW library:
```text
libfftw3_threads.so.3
```
## Observed Behavior
If FFTW.jl is loaded before calling into my external C++ FFTW code, the C++ code can hang inside the first threaded FFTW execution:
```julia
import FFTW
ccall((:my_function, "my_external_cpp_library.so"), ...)
```
The same behavior is observed with:
```julia
using FFTW
FFTW.set_num_threads(14)
ccall((:my_function, "my_external_cpp_library.so"), ...)
```
The external C++ code hangs at a call equivalent to:
```cpp
fftw_execute_dft(plan, in, out);
```
The hang occurs with 14 FFTW threads.
The same C++ workload succeeds if the external C++ library is loaded first, before importing FFTW.jl:
```julia
using Libdl
Libdl.dlopen("my_external_cpp_library.so")
import FFTW
ccall((:my_function, "my_external_cpp_library.so"), ...)
```
## Dynamic Library State
When FFTW.jl is imported first, `/proc/self/maps` shows a mixed FFTW runtime:
```text
/path/to/julia/artifacts/.../libfftw3.so.3
/path/to/external/fftw/lib/libfftw3_threads.so.3
```
The external threaded FFTW library has a dependency on the base FFTW library:
```text
NEEDED libfftw3.so.3
```
Since FFTW.jl's artifact `libfftw3.so.3` is already loaded with the same SONAME, the Linux dynamic loader appears to satisfy the external `libfftw3_threads.so.3` dependency with FFTW.jl's artifact base library instead of the external base `libfftw3.so.3`.
So the process can end up with this mixed runtime:
```text
FFTW.jl artifact libfftw3.so.3
external libfftw3_threads.so.3
```
instead of this matched runtime:
```text
external libfftw3.so.3
external libfftw3_threads.so.3
```
This mixed runtime seems to be enough to make threaded `fftw_execute_dft` hang in my workload.
## Why I Suspect FFTW.jl Is Involved
From reading FFTW.jl's threading setup, it looks like FFTW.jl calls `fftw_init_threads()`.
When Julia has multiple threads, FFTW.jl also appears to install an FFTW thread callback via `fftw_threads_set_callback`, so that FFTW's internal parallel execution can be routed through Julia tasks or threads.
That seems reasonable when FFTW.jl controls the FFTW runtime. However, if an external `libfftw3_threads.so.3` binds to FFTW.jl's artifact `libfftw3.so.3`, then the external C++ code may be using a base FFTW runtime whose thread and planner state was initialized by FFTW.jl, while the thread library itself comes from a different FFTW build.
## Question
I do not necessarily expect FFTW.jl to support mixing arbitrary external FFTW libraries in the same process. However, the current failure mode is difficult to diagnose because the C++ library is linked with an rpath to its own FFTW prefix, yet Linux SONAME resolution can still bind it to FFTW.jl's already-loaded `libfftw3.so.3`.
Would it make sense for FFTW.jl to document this caveat?
The caveat would be something like:
- On Linux, importing FFTW.jl before loading an external C or C++ library linked to `libfftw3_threads.so.3` may cause that external thread library to bind to FFTW.jl's artifact `libfftw3.so.3`.
- This can be unsafe if the external threaded FFTW and FFTW.jl artifact were built separately.
- A practical workaround is to load the external FFTW/C++ library first, or ensure that all FFTW symbols in the process come from the same FFTW build.
- Another workaround is to avoid importing FFTW.jl in workflows where all FFT work is delegated to external C or C++ code.
## Minimal Diagnostic
The key diagnostic was comparing `/proc/self/maps` after different load orders.
The bad or hanging order is:
```julia
import FFTW
using Libdl
Libdl.dlopen("my_external_cpp_library.so")
ccall((:my_function, "my_external_cpp_library.so"), ...)
```
In this case, `/proc/self/maps` includes:
```text
julia artifact libfftw3.so.3
external libfftw3_threads.so.3
```
The good or non-hanging order is:
```julia
using Libdl
Libdl.dlopen("my_external_cpp_library.so")
import FFTW
ccall((:my_function, "my_external_cpp_library.so"), ...)
```
In this case, the external C++ FFTW call completes.
I can try to produce a smaller standalone reproducer if that would be useful. At the moment, the smallest reliable reproducer I have is a Julia `ccall` into a C++ shared library that creates threaded FFTW guru plans and executes them on a 5D `ComplexF64` array.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.