Merge remote-tracking branch 'origin/main' into threadutil
diff --git a/ChangeLog.md b/ChangeLog.md
index d4ca7d1..cd3ac1e 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -20,6 +20,8 @@
 
 2.0.30
 ------
+- Add `emscripten/thread_utils.h` helper header, which includes C++ utilities
+  for proxying code to other threads.
 2.0.29
 -----
 - Bug fixes
diff --git a/site/source/docs/porting/pthreads.rst b/site/source/docs/porting/pthreads.rst
index 422ab88..46b9a3e 100644
--- a/site/source/docs/porting/pthreads.rst
+++ b/site/source/docs/porting/pthreads.rst
@@ -148,6 +148,16 @@
 
 Also note that when compiling code that uses pthreads, an additional JavaScript file ``NAME.worker.js`` is generated alongside the output .js file (where ``NAME`` is the basename of the main file being emitted). That file must be deployed with the rest of the generated code files. By default, ``NAME.worker.js`` will be loaded relative to the main HTML page URL. If it is desirable to load the file from a different location e.g. in a CDN environment, then one can define the ``Module.locateFile(filename)`` function in the main HTML ``Module`` object to return the URL of the target location of the ``NAME.worker.js`` entry point. If this function is not defined in ``Module``, then the default location relative to the main HTML file is used.
 
+C++ helpers
+===========
+
+High-level C++ helper code is available by including
+``emscripten/thread_utils.h``. See example code in the relevant tests:
+
+* `Async proxying to the main thread <https://github.com/emscripten-core/emscripten/tree/main/tests/core/pthread/invoke_on_main_thread.cpp>`_.
+* `Blocking while running async code on a pthread <https://github.com/emscripten-core/emscripten/tree/main/tests/core/pthread/sync_to_async.cpp>`_.
+
+
 Running code and tests
 ======================
 
diff --git a/system/include/emscripten/thread_utils.h b/system/include/emscripten/thread_utils.h
new file mode 100644
index 0000000..81826d4
--- /dev/null
+++ b/system/include/emscripten/thread_utils.h
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2021 The Emscripten Authors.  All rights reserved.
+ * Emscripten is available under two separate licenses, the MIT license and the
+ * University of Illinois/NCSA Open Source License.  Both these licenses can be
+ * found in the LICENSE file.
+ */
+
+#pragma once
+
+#include <assert.h>
+#include <emscripten.h>
+#include <emscripten/threading.h>
+#include <pthread.h>
+
+#include <functional>
+#include <thread>
+#include <utility>
+
+namespace emscripten {
+
+// Helper function to call a callable object on the main thread in a fully
+// asynchronous manner.
+//
+// Normal proxying to the main thread runs into the possible issue of the main
+// thread pumping the event queue while it blocks. That is necessary for e.g.
+// WebGL proxying where both sync and async events must be received and run in
+// order. With this class, only async events are allowed, and they are run in
+// an async manner on the main thread, that is, with nothing else on the stack
+// while they run (even if this event blocks on something, which causes the
+// main event loop to run events, if another event like this arrives, it would
+// be queued to run later, avoiding a collision).
+
+template <class F>
+void invoke_on_main_thread_async(F&& f) {
+  using function_type = typename std::remove_reference<F>::type;
+
+  auto run_on_main_thread = [](void* f1) {
+    // Once on the main thread, run as an event from the JS event queue
+    // directly, so nothing else is on the stack when we execute. This means
+    // we are no longer ordered with respect to synchronous proxied calls,
+    // but that is ok in this case as all we care about are async ones.
+    emscripten_async_call(
+      [](void* f2) {
+        auto f = static_cast<function_type*>(f2);
+        (*f)();
+        delete f;
+      },
+      f1,
+      0
+    );
+  };
+
+  // Proxy the call to the main thread.
+  emscripten_async_run_in_main_runtime_thread(
+    EM_FUNC_SIG_VI,
+    static_cast<void (*)(void*)>(run_on_main_thread),
+    // If we were passed in something we need to copy, copy it; we end up with
+    // a new allocation here on the heap that we will free later.
+    new function_type(std::forward<F>(f))
+  );
+}
+
+// Helper class for generic sync-to-async conversion. Creating an instance of
+// this class will spin up a pthread. You can then call invoke() to run code
+// on that pthread. The work done on the pthread receives a callback method
+// which lets you indicate when it finished working. The call to invoke() is
+// synchronous, while the work done on the other thread can be asynchronous,
+// which allows bridging async JS APIs to sync C++ code.
+//
+// This can be useful if you are in a location where blocking is possible (like
+// a thread, or when using PROXY_TO_PTHREAD), but you have code that is hard to
+// refactor to be async, but that requires some async operation (like waiting
+// for a JS event).
+class sync_to_async {
+
+// Public API
+//==============================================================================
+public:
+
+  // Pass around the callback as a pointer to a std::function. Using a pointer
+  // means that it can be sent easily to JS, as a void* parameter to a C API,
+  // etc., and also means we do not need to worry about the lifetime of the
+  // std::function in user code.
+  using Callback = std::function<void()>*;
+
+  //
+  // Run some work on thread. This is a synchronous (blocking) call. The thread
+  // where the work actually runs can do async work for us - all it needs to do
+  // is call the given callback function when it is done.
+  //
+  // Note that you need to call the callback even if you are not async, as the
+  // code here does not know if you are async or not. For example,
+  //
+  //  instance.invoke([](emscripten::sync_to_async::Callback resume) {
+  //    std::cout << "Hello from sync C++ on the pthread\n";
+  //    (*resume)();
+  //  });
+  //
+  // In the async case, you would call resume() at some later time.
+  //
+  void invoke(std::function<void(Callback)> newWork);
+
+//==============================================================================
+// End Public API
+
+private:
+  std::unique_ptr<std::thread> thread;
+  std::mutex mutex;
+  std::condition_variable condition;
+  std::function<void(Callback)> work;
+  bool readyToWork = false;
+  bool finishedWork;
+  bool quit = false;
+  std::unique_ptr<std::function<void()>> resume;
+
+  // The child will be asynchronous, and therefore we cannot rely on RAII to
+  // unlock for us, we must do it manually.
+  std::unique_lock<std::mutex> childLock;
+
+  static void* threadMain(void* arg) {
+    emscripten_async_call(threadIter, arg, 0);
+    return 0;
+  }
+
+  static void threadIter(void* arg) {
+    auto* parent = (sync_to_async*)arg;
+    if (parent->quit) {
+      pthread_exit(0);
+    }
+    // Wait until we get something to do.
+    parent->childLock.lock();
+    parent->condition.wait(parent->childLock, [&]() {
+      return parent->readyToWork;
+    });
+    auto work = parent->work;
+    parent->readyToWork = false;
+    // Allocate a resume function, and stash it on the parent.
+    parent->resume = std::make_unique<std::function<void()>>([parent, arg]() {
+      // We are called, so the work was finished. Notify the caller.
+      parent->finishedWork = true;
+      parent->childLock.unlock();
+      parent->condition.notify_one();
+      // Look for more work. Doing this asynchronously ensures that we continue
+      // after the current call stack unwinds (avoiding constantly adding to the
+      // stack, and also running any remaining code the caller had, like
+      // destructors). TODO: add an option to do a synchronous call here in some
+      // cases, which would avoid the time delay caused by a browser setTimeout.
+      emscripten_async_call(threadIter, arg, 0);
+    });
+    // Run the work function the user gave us. Give it a pointer to the resume
+    // function.
+    work(parent->resume.get());
+  }
+
+public:
+  sync_to_async() : childLock(mutex) {
+    // The child lock is associated with the mutex, which takes the lock as we
+    // connect them, and so we must free it here so that the child can use it.
+    // Only the child will lock/unlock it from now on.
+    childLock.unlock();
+
+    // Create the thread after the lock is ready.
+    thread = std::make_unique<std::thread>(threadMain, this);
+  }
+
+  ~sync_to_async() {
+    // Wake up the child to tell it to quit.
+    invoke([&](Callback func){
+      quit = true;
+      (*func)();
+    });
+
+    thread->join();
+  }
+};
+
+void sync_to_async::invoke(std::function<void(Callback)> newWork) {
+  // Send the work over.
+  {
+    std::lock_guard<std::mutex> lock(mutex);
+    work = newWork;
+    finishedWork = false;
+    readyToWork = true;
+  }
+  condition.notify_one();
+
+  // Wait for it to be complete.
+  std::unique_lock<std::mutex> lock(mutex);
+  condition.wait(lock, [&]() {
+    return finishedWork;
+  });
+}
+
+} // namespace emscripten
diff --git a/tests/core/pthread/invoke_on_main_thread.cpp b/tests/core/pthread/invoke_on_main_thread.cpp
new file mode 100644
index 0000000..f5eb43e
--- /dev/null
+++ b/tests/core/pthread/invoke_on_main_thread.cpp
@@ -0,0 +1,40 @@
+#include <iostream>
+
+#include <emscripten.h>
+
+#include <emscripten/thread_utils.h>
+
+int main() {
+  struct Foo {
+    void operator()() {
+      // Print whether we are on the main thread.
+      auto mainThread = EM_ASM_INT({
+        var mainThread = typeof importScripts === 'undefined';
+        console.log("hello. mainThread=", mainThread);
+        return mainThread;
+      });
+
+      // If we are on the main thread, it is time to end this test. Do so
+      // asynchronously, as if we exit right now then the object Foo() we are
+      // called on will not yet be destroyed, which causes a false positive in
+      // LSan leak detection.
+      if (mainThread) {
+        emscripten_async_call(
+          [](void*) {
+            exit(0);
+          },
+          nullptr,
+          0
+        );
+      }
+    }
+  };
+
+  // Call it on this thread.
+  Foo()();
+
+  // Call it on the main thread.
+  emscripten::invoke_on_main_thread_async(Foo());
+
+  emscripten_exit_with_live_runtime();
+}
diff --git a/tests/core/pthread/invoke_on_main_thread.out b/tests/core/pthread/invoke_on_main_thread.out
new file mode 100644
index 0000000..c359722
--- /dev/null
+++ b/tests/core/pthread/invoke_on_main_thread.out
@@ -0,0 +1,2 @@
+hello. mainThread= false
+hello. mainThread= true
diff --git a/tests/core/pthread/sync_to_async.cpp b/tests/core/pthread/sync_to_async.cpp
new file mode 100644
index 0000000..ca1cfbd
--- /dev/null
+++ b/tests/core/pthread/sync_to_async.cpp
@@ -0,0 +1,53 @@
+#include <iostream>
+
+#include <emscripten/thread_utils.h>
+
+int main() {
+  emscripten::sync_to_async sync_to_async;
+
+  std::cout << "Perform a synchronous task.\n";
+
+  sync_to_async.invoke([](emscripten::sync_to_async::Callback resume) {
+    std::cout << "  Hello from sync C++\n";
+    (*resume)();
+  });
+
+  std::cout << "Perform an async task.\n";
+
+  sync_to_async.invoke([](emscripten::sync_to_async::Callback resume) {
+    std::cout << "  Hello from sync C++ before the async\n";
+
+    // Set up async JS, just to prove an async JS callback happens before the
+    // async C++.
+    EM_ASM({
+      setTimeout(function() {
+        console.log("  Hello from async JS");
+      }, 0);
+    });
+
+    // Set up async C++..
+    emscripten_async_call([](void* arg) {
+      auto resume = (emscripten::sync_to_async::Callback)arg;
+      std::cout << "  Hello from async C++\n";
+
+      // We are done with all the async things we want to do, and can call
+      // resume to continue execution on the calling thread.
+      (*resume)();
+    }, resume, 1);
+  });
+
+  std::cout << "Perform another synchronous task, also showing var capture.\n";
+
+  int var = 41;
+
+  sync_to_async.invoke([&](emscripten::sync_to_async::Callback resume) {
+    std::cout << "  Hello again from sync C++, we captured " << var << '\n';
+    var++;
+    (*resume)();
+  });
+
+  std::cout << "Captured var is now " << var << '\n';
+  assert(var == 42);
+
+  return 0;
+}
diff --git a/tests/core/pthread/sync_to_async.out b/tests/core/pthread/sync_to_async.out
new file mode 100644
index 0000000..07b9bad
--- /dev/null
+++ b/tests/core/pthread/sync_to_async.out
@@ -0,0 +1,9 @@
+Perform a synchronous task.
+  Hello from sync C++
+Perform an async task.
+  Hello from sync C++ before the async
+  Hello from async JS
+  Hello from async C++
+Perform another synchronous task, also showing var capture.
+  Hello again from sync C++, we captured 41
+Captured var is now 42
diff --git a/tests/test_core.py b/tests/test_core.py
index 08f744f..5d2cc12 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -8373,6 +8373,22 @@
       self.emcc_args += ['-DDEBUG']
     self.do_runf(test_file('core/test_return_address.c'), 'passed')
 
+  @node_pthreads
+  @no_wasm2js('wasm2js does not support PROXY_TO_PTHREAD (custom section support)')
+  def test_pthread_sync_to_async(self):
+    self.set_setting('PROXY_TO_PTHREAD')
+    self.set_setting('EXIT_RUNTIME')
+    self.do_run_in_out_file_test('core/pthread/sync_to_async.cpp')
+
+  @node_pthreads
+  @no_wasm2js('wasm2js does not support PROXY_TO_PTHREAD (custom section support)')
+  def test_pthread_invoke_on_main_thread(self):
+    self.set_setting('PROXY_TO_PTHREAD')
+    self.set_setting('EXIT_RUNTIME')
+    # increase memory for ASan to not hit "internal allocator is out of memory"
+    self.set_setting('INITIAL_MEMORY', '32MB')
+    self.do_run_in_out_file_test('core/pthread/invoke_on_main_thread.cpp')
+
   def test_emscripten_atomics_stub(self):
     self.do_run_in_out_file_test('core/pthread/emscripten_atomics.c')