emscripten-core / emscripten

Emscripten: An LLVM-to-WebAssembly Compiler
Other
25.37k stars 3.25k forks source link

Thread safety issue of pipe read&write #22029

Open hly2019 opened 1 month ago

hly2019 commented 1 month ago

Please include the following in your bug report:

Version of emscripten/emsdk: 3.1.54

Failing command line in full:

# emscripten
emcc pipe.cpp -pthread  -o main.js 
node main.js

# g++
g++ pipe.cpp  -o main
./main

Hi, I was using the pipe in a multi-threading scenario. I found if I write a pipe in one thread, then read in another thread, it seems the process will not work as expected.

In the following code, it uses a variable, cnt to count the number of "xy" read from the pipe. Here, cnt is expected to be 100 because in the write end, we write "xy" 100 times to the pipe, and so does it perform in the native version.

However, in the wasm execution, the result is not as expected as shown below. The value of cnt is not 100, and it even can't get a stable result (found to be 24, 22, 0, etc.), which seems to indicate a possible thread safety issue.

It'd be great if you could check it. Thank you!

Code

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <atomic>
#include <thread>
int main() {
    int fds[2] = {};

    if (pipe(fds) == -1) {
        perror("pipe error");
    }

    std::thread write_thread([&]() {
        std::string input = "xy";
        for (int i = 0; i < 100; ++i) {
            int len = write(fds[1], input.c_str(), input.size());
            if(len <= 0) perror("write error");
        }
        close(fds[1]);
    });

    std::atomic<int> cnt(0);

    auto fun = [&]() {
        char value[2];
        int len = 0;
        while ((len = read(fds[0], value, sizeof(value))) > 0) {
            if(!(value[0] == 'x' && value[1] == 'y') || len != 2) {
                break;
            }
            ++cnt;
        }
    };
    std::cout << "Is there error?: " << strerror(errno) << std::endl;

    std::thread read_thread(fun);
    write_thread.join();

    read_thread.join();

    std::cout << "cnt: " << cnt << std::endl;
    close(fds[0]);
    return 0;
}

Results

Here is the result of wasm and native execution. (For the wasm result, here it just shows a sample: here the result of wasm execution is actually not stable and could be various values.)

# emscripten
Is there error?: No error information
count: 22

# g++
Is there error?: Success
cnt: 100
sbc100 commented 1 month ago

I think this is because the emscripten FS operations are all non-blocking, and this means all pipe's are effectively non-blocking.

If you make sure the write thread is finished before starting the read thread then I would hope this would work. I would possibly investing allowing some kind of blocking I/O, perhaps via ASYNCIFY, or perhaps allowing it only on threads, but that would be quite a large change.