Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unexpected error of proxyAsync with emscripten_init_websocket_to_posix_socket_bridge #21744

Open
btkhoi2001 opened this issue Apr 11, 2024 · 3 comments

Comments

@btkhoi2001
Copy link

Please include the following in your bug report:

Version of emscripten/emsdk:

emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.56 (cf90417)
clang version 19.0.0git (https:/github.com/llvm/llvm-project 34ba90745fa55777436a2429a51a3799c83c6d4c)
Target: wasm32-unknown-emscripten
Thread model: posix

Full link command

EMCC = emcc
CFLAGS = -O3 -std=c++17
EMFLAGS = -lwebsocket.js -sPROXY_POSIX_SOCKETS -pthread -sPROXY_TO_PTHREAD -sUSE_PTHREADS=1 -sPTHREAD_POOL_SIZE=2 -sNO_EXIT_RUNTIME=1 -sMODULARIZE -sEXPORT_NAME="WASMModule" -sEXPORTED_RUNTIME_METHODS=stringToNewUTF8

main:
	$(EMCC) $(CFLAGS) $(EMFLAGS) tcp_echo_client.cpp

I am trying to port my socket application to WebAssembly which uses Full POSIX Sockets over WebSocket Proxy Server. I want to initialize the bridge only in certain case, not when the main function is loaded. So I wrap it in a function with EMSCRIPTEN_KEEPALIVE and call it in the javascript module. I made some changes based on this example test code tcp_echo_client.c

// TCP client that sends a few messages to a server and prints out the replies
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>

#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#include <emscripten/websocket.h>
#include <emscripten/threading.h>
#include <emscripten/posix_socket.h>
#include <emscripten/proxying.h>

static EMSCRIPTEN_WEBSOCKET_T bridgeSocket = 0;
static pthread_t mainApplicationThreadId = 0;
emscripten::ProxyingQueue queue;

extern "C" {
EMSCRIPTEN_KEEPALIVE
void initBridgeSocket(const char *bridgeUrl) {
  queue.proxyAsync(mainApplicationThreadId, [&]() {
    printf("bridegeUrl = %s\n", bridgeUrl);
    bridgeSocket = emscripten_init_websocket_to_posix_socket_bridge(bridgeUrl);

    // Synchronously wait until connection has been established.
    uint16_t readyState = 0;
    do {
      emscripten_websocket_get_ready_state(bridgeSocket, &readyState);
      emscripten_thread_sleep(100);
    } while (readyState == 0);

    printf("Bridge socket ready\n");
  });

  queue.execute();
}
} // extern "C"
#endif

int main(int argc , char *argv[]) {
  #ifdef __EMSCRIPTEN__
    mainApplicationThreadId = pthread_self();
    emscripten_exit_with_live_runtime();
  #endif

  return 0;
}

Then initialize a bridge when a user clicks the button.

<body>
    <input type="text" placeholder="ws://localhost:8080" value="ws://localhost:8080" id="a">
    <button onclick="foo()">Trigger</button>

    <script src="a.out.js"></script>
    <script>
        var module;

        document.addEventListener("DOMContentLoaded", async () => {
            module = await WASMModule();
        })

        function foo() {
            const bridgeUrl = document.getElementById("a").value;
            module._initBridgeSocket(module.stringToNewUTF8(bridgeUrl));
        }
    </script>
</body>

Problem:
First, the task is still executed even without this line:

  queue.execute();

Second, I want to make sure the bridgeUrl successfully passed into the lambda expression, so I use printf to check it. When I place it before emscripten_init_websocket_to_posix_socket_bridge, It still prints the correct URL, but it gets "flushed" and becomes empty when passing into emscripten_init_websocket_to_posix_socket_bridge

a.out.js:8 Uncaught (in promise) DOMException: Failed to construct 'WebSocket': The URL '�' is invalid.
    at _emscripten_websocket_new (http://127.0.0.1:3000/websocket/a.out.js:8:33075)
    at __emscripten_receive_on_main_thread_js (http://127.0.0.1:3000/websocket/a.out.js:8:19685)
    at http://127.0.0.1:3000/websocket/a.out.wasm:wasm-function[103]:0x5620
    at http://127.0.0.1:3000/websocket/a.out.wasm:wasm-function[105]:0x5bd1
    at http://127.0.0.1:3000/websocket/a.out.wasm:wasm-function[107]:0x5cd7
    at http://127.0.0.1:3000/websocket/a.out.wasm:wasm-function[56]:0x2fe4
    at http://127.0.0.1:3000/websocket/a.out.wasm:wasm-function[110]:0x5d6d
    at http://127.0.0.1:3000/websocket/a.out.wasm:wasm-function[56]:0x2fe4
    at http://127.0.0.1:3000/websocket/a.out.wasm:wasm-function[94]:0x524d
    at callUserCallback (http://127.0.0.1:3000/websocket/a.out.js:8:18393)

The same error happens when I change the order, I place printf after. It successfully initiated the bridge, but printf prints empty URL. bridgeUrl is messed up whenever passed into a function.

@sbc100
Copy link
Collaborator

sbc100 commented Apr 11, 2024

That seems very odd. Can you uncommenting the POSIX_SOCKET_DEBUG line in system/lib/websocket/websocket_to_posix_socket.c and then rebuilding the library using ./embuilder build libsockets_proxy-mt --force

@sbc100
Copy link
Collaborator

sbc100 commented Apr 11, 2024

Is this something to do with the way the C++ lambda capture works for the bridgeUrl pointer? I would have though it would capture by simply copying the pointer.. but maybe not? @tlively ?

@tlively
Copy link
Member

tlively commented Apr 11, 2024

The lambda here is capturing bridgeURL by reference because of the [&] binder. Since the proxying is async, the function returns on the calling thread before the lambda is executed on the thread it was sent to, so when the lambda is eventually executed, the reference to bridgeURL is dangling.

To fix it, you could either make the lambda capture the argument by value or you can switch to synchronous proxying to keep the parameters and local variables on the calling thread alive until the work is complete.

You also don't need the queue.execute() call on the calling thread, since it isn't performing any proxied work. The work will automatically execute on the worker thread as soon as it returns to its event loop.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants