emscripten-core / emscripten-core/emscripten
Writing to stdout can deadlock with pthreads
- Dominant language
- C++
- Stars
- 27.6k
- Forks
- 3.6k
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 105
Description
When writing to stdout simultaneously from a pthread and from the main runtime thread, a deadlock can occur.
The mechanism involves the details of `fwrite()`, so it may be that this is an issue when writing to files in general.
Is this a known behavior, and if so, how should the developer account for it? Do we simply have to guarantee that no writes occur on the main runtime thread?
# Reproduction
I am using 3.1.40, but I think the behavior should be the same on the latest version, too.
`lib.cpp`:
```cpp
#include
#include
#include
#include
#include
void print_async() {
std::this_thread::sleep_for(std::chrono::seconds(2));
EM_ASM(console.log("console.log from print_async"));
std::printf("printf from print_async\n");
}
void print_sync() {
std::this_thread::sleep_for(std::chrono::seconds(1));
EM_ASM(console.log("console.log from print_sync"));
std::printf("printf from print_sync\n");
}
int main() {
emscripten_async_run_in_main_runtime_thread(EM_FUNC_SIG_V, print_async);
print_sync();
}
```
`index.html`:
```html
printf deadlock with emscripten
const worker = new Worker("lib.js");
```
Build and run:
```sh
em++ lib.cpp -o lib.js -s USE_PTHREADS=1 -s PROXY_TO_PTHREAD=1
emrun --browser=chrome index.html
```
Expected output:
```
console.log from print_sync
printf from print_sync
console.log from print_async
printf from print_async
```
Actual output:
```
console.log from print_sync
console.log from print_async
```
# Apparent mechanism
Writes to stdout internally use `fwrite()`, which does the following:
1. Acquire a lock on the target file `f` (in this case, the stdout file) by `FLOCK(f)`
2. Synchronously execute the required `_fd_write()` on the main runtime thread
3. release the lock by `FUNLOCK(f)`
Note that at step 2., we have to wait for the JS event loop on the main runtime thread to get around to our `_fd_write()` request, *while holding the lock*. If the main runtime thread happens to be busy with a task that will also write to stdout, that task will block when it reaches the `FLOCK(f)` stage of its call to `fwrite()`, and thus a deadlock occurs.
Contributor guide
Assessment
This issue has not been assessed yet.