emscripten-core / emscripten-core/emscripten

Writing to stdout can deadlock with pthreads

Open
#20,059 9 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.