From 3025c989dcd72a04d521c20f75a92f2a25136f74 Mon Sep 17 00:00:00 2001
From: nickpdemarco <nickpdemarco@gmail.com>
Date: Tue, 30 May 2023 17:32:25 -0400
Subject: [PATCH 01/13] Contracts

---
 CMakeLists.txt                         |  13 +-
 rustport/.gitignore                    |   1 +
 rustport/CMakeLists.txt                |  35 ++
 rustport/Cargo.lock                    | 520 +++++++++++++++++++++++++
 rustport/Cargo.toml                    |  14 +
 rustport/cppshim/include/bindings.hpp  |  67 ++++
 rustport/src/lib.rs                    |  26 ++
 rustport/src/stlab.rs                  | 446 +++++++++++++++++++++
 stlab/concurrency/default_executor.hpp |   9 +
 9 files changed, 1130 insertions(+), 1 deletion(-)
 create mode 100644 rustport/.gitignore
 create mode 100644 rustport/CMakeLists.txt
 create mode 100644 rustport/Cargo.lock
 create mode 100644 rustport/Cargo.toml
 create mode 100644 rustport/cppshim/include/bindings.hpp
 create mode 100644 rustport/src/lib.rs
 create mode 100644 rustport/src/stlab.rs

diff --git a/CMakeLists.txt b/CMakeLists.txt
index fca77b6a..90a97738 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -31,6 +31,8 @@ set(STLAB_TASK_SYSTEM ${STLAB_DEFAULT_TASK_SYSTEM} CACHE STRING "Task system to
 stlab_detect_main_executor(STLAB_DEFAULT_MAIN_EXECUTOR)
 set(STLAB_MAIN_EXECUTOR ${STLAB_DEFAULT_MAIN_EXECUTOR} CACHE STRING "Main executor to use (qt5|qt6|libdispatch|emscripten|none).")
 
+option( STLAB_USE_RUST_DEFAULT_EXECUTOR "Use a Rust port of the default_executor. Defaults to OFF." ON )
+
 if( BUILD_TESTING AND NOT Boost_unit_test_framework_FOUND )
   message( SEND_ERROR "BUILD_TESTING is enabled, but an installation of Boost.Test was not found." )
 endif()
@@ -109,6 +111,8 @@ elseif (STLAB_MAIN_EXECUTOR STREQUAL "qt6")
   target_link_libraries( stlab INTERFACE Qt6::Core )
 endif()
 
+
+
 message(STATUS "stlab: Use Boost C++17 Shims: ${STLAB_USE_BOOST_CPP17_SHIMS}")
 message(STATUS "stlab: Disable Coroutines: ${STLAB_DEFAULT_NO_STD_COROUTINES}")
 message(STATUS "stlab: Thread System: ${STLAB_THREAD_SYSTEM}")
@@ -124,7 +128,7 @@ if ( BUILD_TESTING )
   #
   add_library( testing INTERFACE )
   add_library( stlab::testing ALIAS testing )
-
+  
   #
   # CMake targets linking to the stlab::testing target will (transitively)
   # link to the Boost::unit_test_framework and to stlab::stlab target.
@@ -134,6 +138,13 @@ if ( BUILD_TESTING )
     stlab::development
     stlab::stlab )
 
+    if ( STLAB_USE_RUST_DEFAULT_EXECUTOR )
+      add_definitions( -DSTLAB_USE_RUST_DEFAULT_EXECUTOR )
+      add_subdirectory( rustport )
+      include_directories( rustport )
+      target_link_libraries( testing INTERFACE RustyDefaultExecutor )
+    endif()
+
   #
   # Linking to the Boost unit test framework requires an additional
   # preprocessor definition when the unit test compiled resources are
diff --git a/rustport/.gitignore b/rustport/.gitignore
new file mode 100644
index 00000000..eb5a316c
--- /dev/null
+++ b/rustport/.gitignore
@@ -0,0 +1 @@
+target
diff --git a/rustport/CMakeLists.txt b/rustport/CMakeLists.txt
new file mode 100644
index 00000000..361d91c4
--- /dev/null
+++ b/rustport/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.25)
+
+include(FetchContent)
+
+project("RustyDefaultExecutor")
+
+FetchContent_Declare(
+    Corrosion
+    GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
+    GIT_TAG master # Needed for experimental feature `corrosion_experimental_cbindgen`.
+)
+
+# Set any global configuration variables such as `Rust_TOOLCHAIN` before this line!
+
+FetchContent_MakeAvailable(Corrosion)
+
+corrosion_import_crate(MANIFEST_PATH ./Cargo.toml)
+
+corrosion_experimental_cbindgen(
+        TARGET default_executor
+        HEADER_NAME "bindings.h"
+)
+
+add_library(${PROJECT_NAME} INTERFACE)
+
+target_include_directories(${PROJECT_NAME}
+    # PRIVATE
+    #     # where the library itself will look for its internal headers
+    #     ${CMAKE_CURRENT_SOURCE_DIR}/src
+    INTERFACE
+        # where top-level project will look for the library's public headers
+        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/cppshim/include>
+)
+
+target_link_libraries(${PROJECT_NAME} INTERFACE default_executor)
diff --git a/rustport/Cargo.lock b/rustport/Cargo.lock
new file mode 100644
index 00000000..2a4be3dd
--- /dev/null
+++ b/rustport/Cargo.lock
@@ -0,0 +1,520 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi 0.1.19",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "cbindgen"
+version = "0.24.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6358dedf60f4d9b8db43ad187391afe959746101346fe51bb978126bec61dfb"
+dependencies = [
+ "clap",
+ "heck",
+ "indexmap",
+ "log",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_json",
+ "syn 1.0.109",
+ "tempfile",
+ "toml",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clap"
+version = "3.2.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eef2b3ded6a26dfaec672a742c93c8cf6b689220324da509ec5caa20de55dc83"
+dependencies = [
+ "atty",
+ "bitflags",
+ "clap_lex",
+ "indexmap",
+ "strsim",
+ "termcolor",
+ "textwrap",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
+dependencies = [
+ "os_str_bytes",
+]
+
+[[package]]
+name = "default_executor"
+version = "0.1.0"
+dependencies = [
+ "cbindgen",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "fastrand"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "io-lifetimes"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
+dependencies = [
+ "hermit-abi 0.3.1",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
+
+[[package]]
+name = "libc"
+version = "0.2.142"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf"
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "os_str_bytes"
+version = "6.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "rustix"
+version = "0.37.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0661814f891c57c930a610266415528da53c4933e6dea5fb350cbfe048a9ece"
+dependencies = [
+ "bitflags",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
+
+[[package]]
+name = "serde"
+version = "1.0.160"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.160"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.15",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.96"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "redox_syscall",
+ "rustix",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
+
+[[package]]
+name = "toml"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.0",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.0",
+ "windows_aarch64_msvc 0.48.0",
+ "windows_i686_gnu 0.48.0",
+ "windows_i686_msvc 0.48.0",
+ "windows_x86_64_gnu 0.48.0",
+ "windows_x86_64_gnullvm 0.48.0",
+ "windows_x86_64_msvc 0.48.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
diff --git a/rustport/Cargo.toml b/rustport/Cargo.toml
new file mode 100644
index 00000000..a58c1de4
--- /dev/null
+++ b/rustport/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "default_executor"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["staticlib"]
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+
+[build-dependencies]
+cbindgen = "0.24.0"
diff --git a/rustport/cppshim/include/bindings.hpp b/rustport/cppshim/include/bindings.hpp
new file mode 100644
index 00000000..a5a0cd0b
--- /dev/null
+++ b/rustport/cppshim/include/bindings.hpp
@@ -0,0 +1,67 @@
+#include "bindings.h"
+
+#include <utility>
+#include <iostream>
+
+namespace stlab {
+inline namespace v1 {
+namespace detail {
+
+// REVISIT - have to invert the priority encoding to match old API.
+enum class executor_priority { high = 2, medium = 1, low = 0 };
+
+/// @brief Invokes `f` on the default_executor.
+/// @tparam F function object type
+/// @param f a function object with signature `void()`.
+/// @return the value returned by `execute`.
+template <class F>
+auto enqueue(F f) {
+    using f_t = decltype(f);
+    return execute(new f_t(std::move(f)), [](void* f_) {
+        auto f = static_cast<f_t*>(f_);
+        (*f)();
+        delete f;
+    });
+}
+
+/// @brief Invokes `f` on the default_executor at the given `priority`.
+/// @tparam F function object type
+/// @param f a function object with signature `void()`.
+/// @param priority 
+/// @return the value returned by `execute_priority`.
+template <class F>
+auto enqueue_priority(F f, executor_priority priority) {
+    using f_t = decltype(f);
+    return execute_priority(new f_t(std::move(f)), [](void* f_) {
+        auto f = static_cast<f_t*>(f_);
+        (*f)();
+        delete f;
+    }, static_cast<std::size_t>(priority));
+}
+
+/// @brief A thin invokable wrapper around `enqueue_priority`.
+/// @tparam Priority the priority at which all given function objects will be enqueued.
+template <executor_priority Priority>
+struct executor_type {
+    using result_type = void;
+
+    /// @brief Enqueues the given task on the default_executor with this object's Priority value.
+    /// @tparam F function object type
+    /// @param f function object
+    template <class F>
+    void operator()(F&& f) const {
+        enqueue_priority(std::forward<F>(f), Priority);
+    }
+};
+
+} // namespace detail
+
+/// @brief An executor for low priority tasks, enqueued with the call operator.
+constexpr auto low_executor = detail::executor_type<detail::executor_priority::low>{};
+/// @brief An executor for standard priority tasks, enqueued with the call operator.
+constexpr auto default_executor = detail::executor_type<detail::executor_priority::medium>{};
+/// @brief An executor for high priority tasks, enqueued with the call operator.
+constexpr auto high_executor = detail::executor_type<detail::executor_priority::high>{};
+
+} // inline namespace v1
+} // namespace stlab
diff --git a/rustport/src/lib.rs b/rustport/src/lib.rs
new file mode 100644
index 00000000..95f69f04
--- /dev/null
+++ b/rustport/src/lib.rs
@@ -0,0 +1,26 @@
+use std::ffi::c_void;
+
+mod stlab;
+
+/// Enqueues a the execution of `f(context)` on the PriorityTaskSystem.
+#[no_mangle]
+pub extern "C" fn execute(context: *mut c_void, f: extern fn(*mut c_void)) -> i32 {
+  stlab::PriorityTaskSystem::singleton().execute(move||{
+    f(context)
+  }, stlab::Priority(0));
+  0
+}
+
+/// Enqueues a the execution of `f(context)` on the PriorityTaskSystem at the given `priority`.
+#[no_mangle]
+pub extern "C" fn execute_priority(context: *mut c_void, f: extern fn(*mut c_void), priority: usize) -> i32 {
+  stlab::PriorityTaskSystem::singleton().execute(move||{
+    f(context)
+  }, stlab::Priority(priority));
+  0
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+}
diff --git a/rustport/src/stlab.rs b/rustport/src/stlab.rs
new file mode 100644
index 00000000..a498cd44
--- /dev/null
+++ b/rustport/src/stlab.rs
@@ -0,0 +1,446 @@
+use std::cmp::{Ordering, max, Eq, Ord, PartialEq, PartialOrd};
+use std::mem::MaybeUninit;
+use std::num::NonZeroUsize;
+use std::sync::{Mutex, Once};
+use std::sync::atomic::{AtomicUsize, Ordering as MemoryOrdering};
+
+/// A type-erased, heap-allocated function object.
+type Task = Box<dyn FnOnce()->()>;
+
+/// A `usize` constraining valid values to [0, 4) with runtime assertions.
+#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
+pub struct Priority(pub usize);
+
+impl Priority {
+    pub fn new(value: usize) -> Self {
+        assert!((0..4).contains(&value), "Priorities must be in [0, 4)");
+        Self(value)
+    }
+
+    /// Returns a usize of the form 0bXX000000 where XX is a binary representation of this priority.
+    /// The value of this priority is guaranteed to fit in two bits because `Priority` values are constrained to [0b00, 0b11].
+    pub fn to_highbit_mask(&self) -> usize {
+        &self.0 << (usize::BITS - 2)
+    }
+}
+
+/// Pairs an instance of `T` with a `Priority`. 
+/// Equality and ordering of a Prioritized<T> only considers `priority`, disregarding `element`.
+struct Prioritized <T> {
+    priority: Priority,
+    element: T
+}
+
+impl<T> PartialEq for Prioritized <T> {
+    #[inline]
+    fn eq(&self, other: &Self) -> bool {
+        self.priority == other.priority
+    }
+}
+
+impl <T> Eq for Prioritized <T> {}
+
+impl<T> PartialOrd for Prioritized <T> {
+    #[inline]
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        self.priority.partial_cmp(&other.priority)
+    }
+}
+
+impl<T> Ord for Prioritized <T> {
+    #[inline]
+    fn cmp(&self, other: &Self) -> Ordering {
+        self.priority.cmp(&other.priority)
+    }
+}
+
+/// The fields of `Waiter` which must be protected by a `Mutex`.
+struct WaiterProtectedData {
+    waiting: bool,
+    done: bool,
+}
+
+/// A utility for suspending a thread using a condition variable.
+struct Waiter {
+    protected: Mutex<WaiterProtectedData>,
+    ready: std::sync::Condvar,
+}
+
+impl Waiter {
+
+    /// Constructs a new Waiter, with `waiting` and `done` set to `false`. 
+    pub fn new() -> Self {
+        Self {
+            protected: Mutex::new(WaiterProtectedData {
+                waiting: false,
+                done: false,
+            }),
+            ready: std::sync::Condvar::new()
+        }
+    }
+
+    /// Sets `done` to `true`, and notifies one waiter of our condition variable.
+    pub fn done(&self) {
+        {
+            let mut this = self.protected.lock().expect("the mutex is not poisoned");
+            this.done = true;
+        }
+        self.ready.notify_one();
+    }
+
+    /// Sets waiting to `false`. If waiting was `true`, wake one waiter and return `true`. Otherwise, return `false`.
+    /// If `try_lock` fails, return `false`. (REVIEW: why?)
+    /// (REVIEW: is it redundant to express that `waiting` and `done` are accesed under a mutex?)
+    pub fn wake(&self) -> bool {
+        if let Ok(ref mut this) = self.protected.try_lock() {
+            if !this.waiting {
+                return false;
+            }
+            this.waiting = false;
+        } else {
+            return false;
+        }
+        self.ready.notify_one();
+        return true;
+    }
+
+    /// Block this thread until `wake()` or `done()` is called.
+    /// Returns `true` if `done()` has been called, otherwise `false`.
+    pub fn wait(&self) -> bool {
+        let mut this = self.protected.lock().expect("the mutex is not poisoned");
+        this.waiting = true;
+        while this.waiting && !this.done {
+            this = self.ready.wait(this).expect("the mutex is not poisoned");
+        }
+        this.waiting = false;
+        return this.done;
+    }
+}
+
+/// The fields of `NotificationQueue` which must be protected by a `Mutex`.
+struct NotificationQueueProtectedData <T> {
+    heap: std::collections::BinaryHeap<Prioritized<T>>,
+    count: usize,
+    done: bool,
+    waiting: bool,
+}
+
+impl<T> std::default::Default for NotificationQueueProtectedData<T> {
+    fn default() -> Self {
+        Self {
+            heap: std::collections::BinaryHeap::new(),
+            count: 0,
+            done: false,
+            waiting: false
+        }
+    }
+}
+
+/// A threadsafe priority queue.
+struct NotificationQueue<T> {
+    // In the C++ implementation, we use a single lock for multiple data fields.
+    // In Rust, we require exactly one mutex per protected field. 
+    // So, put protected fields into a separate struct, and lock on that.
+    // Note the transformation to use multiple locks is non-trivial, because 
+    // this would require the ordering of acquired locks to be identical in all
+    // code paths to prevent deadlock.
+    protected: Mutex<NotificationQueueProtectedData<T>>,
+    ready: std::sync::Condvar,
+}
+
+impl<T> std::default::Default for NotificationQueue<T> {
+    fn default() -> Self {
+        Self {
+            protected: Mutex::new(NotificationQueueProtectedData::<T>::default()),
+            ready: std::sync::Condvar::new(),
+        }
+    }
+}
+
+impl<T> NotificationQueue<T> {
+
+    /// Merge priority and count into a single usize, storing the former in the
+    /// two highest bits of the result. This requires priority be in [0, 4) i.e.,
+    /// takes up two bits.
+    fn merge_priority_count(priority: Priority, count: usize) -> Priority {
+        Priority(priority.to_highbit_mask() | count)
+    }
+
+    /// Try to pop from the queue without blocking.
+    /// Returns `None` if our mutex is already locked or if the queue is empty.
+    pub fn try_pop(&mut self) -> Option<T> {
+        if let Ok(ref mut this) = self.protected.try_lock() {
+            if !this.heap.is_empty() { 
+                return Some(this.heap.pop().unwrap().element);
+            }
+        } 
+        return None;
+    }
+
+    /// If waiting in `pop()`, wakes and returns true. Otherwise returns false.
+    pub fn wake(&self) -> bool {
+        if let Ok(ref mut this) = self.protected.try_lock() {
+            if !this.waiting {
+                return false;
+            }
+            this.waiting = false; // triggers wake
+        }
+        return false;
+    }
+
+    /// Pop from the queue, suspending the current thread until an element is available.
+    /// The returned `bool` indicates if this object is `done()`.
+    pub fn pop(&self) -> (bool, Option<T>) {
+        let mut this = self.protected.lock().expect("the mutex is not poisoned");
+        this.waiting = true;
+        while !this.heap.is_empty() && !this.done && this.waiting {
+            this = self.ready.wait(this).expect("the mutex is not poisoned");
+        }
+        this.waiting = false;
+        if this.heap.is_empty() {
+            return (this.done, None);
+        } 
+        return (false, Some(this.heap.pop().unwrap().element));
+    }
+
+    /// Mark this object for teardown, and wake any thread awaiting an available element in `pop()`.
+    pub fn done(&self) {
+        {
+            let mut this = self.protected.lock().expect("the mutex is not poisoned");
+            this.done = true
+        }
+        self.ready.notify_one();
+    }
+
+    /// Try to push `element` to the queue without blocking, returning `element` if our mutex is already locked.
+    /// If the push succeeds, wake a thread which may be awaiting an element in `pop()`.
+    pub fn try_push(&self, element: T, priority: Priority) -> Option<T> {
+        if let Ok(ref mut this) = self.protected.try_lock() {
+            let priority = Self::merge_priority_count(priority, this.count);
+            this.count += 1;
+            this.heap.push(Prioritized{ element, priority });
+        } else {
+            return Some(element);
+        }
+        
+        // We successfully locked the mutex, did our push, and released the lock.
+        self.ready.notify_one();
+        return None;
+    }
+
+    /// Push `element` to the queue, blocking if our mutex is already locked.
+    /// When the push succeeds, wake a thread which may be awaiting an element in `pop()`.
+    pub fn push(&self, element: T, priority: Priority) {
+        {
+            let mut this = self.protected.lock().expect("the mutex is not poisoned");
+            let priority = Self::merge_priority_count(priority, this.count);
+            this.count += 1;
+            this.heap.push(Prioritized{ element, priority });
+        }
+        self.ready.notify_one();
+    }
+}
+
+/// The fields of `PriorityTaskSystem` which must be protected by a `Mutex`.
+#[derive(Default)]
+struct PriorityTaskSystemProtectedData {
+    threads: Vec<std::thread::JoinHandle<()>>,
+    waiters: Vec<Waiter>,
+}
+
+impl PriorityTaskSystemProtectedData {
+    fn spawn_thread<F>(thread_name: &'static str, f: F) -> std::thread::JoinHandle<()> 
+        where F: FnOnce() -> () + 'static + Send {
+        std::thread::Builder::new().name(thread_name.to_string())
+            .spawn(f).expect("spawning a thread does not fail")
+    }
+
+    /// Spawn `thread_count` threads, each of which pops tasks from an associated queue in `queues`, 
+    /// or steals tasks from other queues if no task is available.
+    pub(self) fn spawn_baseline_threads(&mut self, thread_count: usize, available_parallelism: usize) {
+        self.threads.extend((0..thread_count).map(|i| {
+            Self::spawn_thread("cc.stlab.default_executor", move || {
+                loop {
+                    let this = PriorityTaskSystem::singleton(); // why doesn't this have to be mut?
+                    let mut task: Option<Task> = None;
+                    for n in 0..available_parallelism {
+                        match task {
+                            Some(_) => { break } 
+                            _ => {
+                                task = this.queues[(i + n) % available_parallelism].try_pop()
+                            }
+                        }
+                    }
+
+                    if task.is_none() {
+                        let done: bool;
+                        (done, task) = this.queues[i].pop();
+                        if done { break }
+                    }
+
+                    if task.is_some() {
+                        task.unwrap()();
+                    }
+                }
+            })
+        }))
+    }
+
+    /// Spawn one thread which will repeatedly check each queue in `queues` for tasks, and execute them.
+    /// If no tasks are found, the thread is suspended.
+    pub(self) fn spawn_expansion_thread(&mut self, available_parallelism: usize) {
+        let protected = PriorityTaskSystem::singleton().protected.get_mut().expect("the mutex is not poisoned");
+        let i = protected.threads.len();
+
+        if i == PriorityTaskSystem::singleton().available_parallelism {
+            eprintln!("Thread limit reached")
+        }
+        
+        self.threads.push(Self::spawn_thread("cc.stlab.default_executor.expansion", move || {
+            loop {
+                let this = PriorityTaskSystem::singleton(); // why doesn't this have to be mut?
+                let mut task: Option<Task> = None;
+                for n in 0..available_parallelism {
+                    match task {
+                        Some(_) => { break } 
+                        _ => {
+                            task = this.queues[(i + n) % available_parallelism].try_pop()
+                        }
+                    }
+                }
+
+                if task.is_some() {
+                    task.unwrap()();
+                    continue;
+                }
+
+                let protected = this.protected.get_mut().expect("the mutex is not poisoned");
+
+                // Note: The following means multiple threads may wait on a single `Waiter`.
+                if protected.waiters[i - available_parallelism].wait() { break; }
+            }
+        }));
+    }
+}
+
+/// A thread-scalable priority task system.
+pub struct PriorityTaskSystem {
+    available_parallelism: usize, // _count in C++ implementation.
+    thread_limit: usize,
+    queues: Vec<NotificationQueue<Task>>,
+    index: AtomicUsize,
+    protected: Mutex<PriorityTaskSystemProtectedData>
+}
+
+impl PriorityTaskSystem {
+    
+    // TODO: Roll back the singleton pattern, and find a way to make instances immovable.
+    /// Return the singleton instance of `PriorityTaskSystem`, creating it on the first invocation.
+    pub fn singleton() -> &'static mut Self {
+        static mut INSTANCE: MaybeUninit<PriorityTaskSystem> = MaybeUninit::uninit();
+        static ONCE: Once = Once::new();
+        
+        ONCE.call_once(|| {
+            // This clunky expression is to convert from a NonZeroUsize to a usize for the subsequent arithmetic. I'd like a better way.
+            // SAFETY: we know 1 is not 0.
+            let nonzero_available_parallelism = std::thread::available_parallelism().unwrap_or(unsafe { NonZeroUsize::new_unchecked(1) });
+            let available_parallelism = max(usize::from(nonzero_available_parallelism), 2) - 1;
+            let thread_limit = max(9, available_parallelism * 4 + 1);
+
+            unsafe { 
+                INSTANCE.as_mut_ptr().write(Self {
+                    available_parallelism,
+                    thread_limit,
+                    queues: {
+                        let mut v = Vec::with_capacity(available_parallelism);
+                        for _ in 0..available_parallelism { v.push(NotificationQueue::<Task>::default()) }
+                        v
+                    },
+                    index: AtomicUsize::new(0),
+                    protected: Mutex::new(PriorityTaskSystemProtectedData {
+                        waiters: Vec::with_capacity(thread_limit - available_parallelism),
+                        threads: Vec::with_capacity(available_parallelism),
+                    })
+                }); 
+            }
+
+            // Defer spawning threads so they can never witness an uninitialized `INSTANCE`.
+            // Note `get_mut` statically enforces exclusive access; no locking will take place.
+            unsafe {
+                // `unwrap` should never panic; that would imply the mutex we just created, and never
+                // handed out, has been poisoned.
+                (*INSTANCE.as_mut_ptr()).protected.get_mut().unwrap().spawn_baseline_threads(available_parallelism, available_parallelism);
+            }
+        });
+
+        unsafe { &mut *INSTANCE.as_mut_ptr() }
+    }
+
+    /// Mark all subobjects for teardown, and join all threads spawned by this object.
+    pub fn join(&mut self) {
+        for queue in &self.queues {
+            queue.done();
+        }
+
+        // Consume the contents under the mutex, leaving them empty.
+        let this = std::mem::take(self.protected.get_mut().expect("the mutex is not poisoned"));
+        for e in this.waiters {
+            e.done();
+        }
+        for e in this.threads {
+            let _ = e.join();
+        }
+
+        self.queues.clear();
+    }
+
+    /// Push `f` to the first queue in `queues` whose mutex is not under contention.
+    /// If no such queue is found after a single pass, blockingly push `f` to one queue.
+    // REVIEW: I'm not sure `execute` is a good name. I think we want `push`, or `push_with_priority`.
+    pub fn execute<F>(&mut self, f: F, p: Priority) where F: FnOnce() -> () + 'static {
+        let mut task: Option<Task> = Some(Box::new(f));
+        // Use SeqCst to match the default for C++'s std::atomic<unsigned>::operator++(int).
+        let i = self.index.fetch_add(1, MemoryOrdering::SeqCst); 
+        for n in 0..i {
+            task = self.queues[(i + n) % self.available_parallelism].try_push(task.unwrap(), p);
+            if task.is_none() { return }
+        }
+
+        self.queues[i % self.available_parallelism].push(task.unwrap(), p);
+    }
+
+    /// Spawn an expansion thread which will steal tasks from `queues`.
+    pub fn add_thread(&mut self) {
+        let mut this = self.protected.lock().expect("the mutex is not poisoned");
+        if this.threads.len() == self.thread_limit {
+            eprintln!("Unable to add thread; thread_limit reached: {}", self.thread_limit);
+            return;
+        }
+
+        this.spawn_expansion_thread(self.available_parallelism);
+    }
+
+    /// Get the number of waiters.
+    fn waiters_size(&self) -> usize {
+        let this = self.protected.lock().expect("the mutex is not poisoned");
+        this.threads.len() - self.available_parallelism
+    }
+
+    /// Wake every `Waiter` in `waiters`. Return true if any indicate they are `done()`, otherwise `false`.
+    pub fn wake(&mut self) -> bool {
+        for queue in &self.queues {
+            if queue.wake() { return true } 
+        }
+
+        let size = self.waiters_size();
+        // Note: get_mut ensures no locking occurs; uniqueness enforced by the type system.
+        let this = self.protected.get_mut().expect("the mutex is not poisoned");
+        for n in 0..size {
+            if this.waiters[n].wake() {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/stlab/concurrency/default_executor.hpp b/stlab/concurrency/default_executor.hpp
index a4a966fd..a4831f9c 100644
--- a/stlab/concurrency/default_executor.hpp
+++ b/stlab/concurrency/default_executor.hpp
@@ -9,6 +9,12 @@
 #ifndef STLAB_CONCURRENCY_DEFAULT_EXECUTOR_HPP
 #define STLAB_CONCURRENCY_DEFAULT_EXECUTOR_HPP
 
+#ifdef STLAB_USE_RUST_DEFAULT_EXECUTOR
+
+#include "bindings.hpp"
+
+#else
+
 #include <stlab/concurrency/set_current_thread_name.hpp>
 #include <stlab/concurrency/task.hpp>
 #include <stlab/config.hpp>
@@ -490,6 +496,7 @@ constexpr auto low_executor = detail::executor_type<detail::executor_priority::l
 constexpr auto default_executor = detail::executor_type<detail::executor_priority::medium>{};
 constexpr auto high_executor = detail::executor_type<detail::executor_priority::high>{};
 
+
 /**************************************************************************************************/
 
 } // namespace v1
@@ -500,6 +507,8 @@ constexpr auto high_executor = detail::executor_type<detail::executor_priority::
 
 /**************************************************************************************************/
 
+#endif // STLAB_USE_RUST_DEFAULT_EXECUTOR
+
 #endif // STLAB_CONCURRENCY_DEFAULT_EXECUTOR_HPP
 
 /**************************************************************************************************/

From 3a964fc734d262dc3803b0cbe666a9c553eede0b Mon Sep 17 00:00:00 2001
From: nickpdemarco <nickpdemarco@gmail.com>
Date: Tue, 20 Jun 2023 15:06:22 -0400
Subject: [PATCH 02/13] STLAB_TASK_SYSTEM(EXPERIMENTAL_RUST)

---
 CMakeLists.txt                         | 18 +++++++++---------
 cmake/StlabUtil.cmake                  |  2 ++
 stlab/concurrency/default_executor.hpp |  7 ++++---
 stlab/concurrency/system_timer.hpp     |  6 +++---
 stlab/config.hpp.in                    |  1 +
 5 files changed, 19 insertions(+), 15 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 90a97738..2da42ac7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -26,13 +26,11 @@ stlab_detect_thread_system(STLAB_DEFAULT_THREAD_SYSTEM)
 set( STLAB_THREAD_SYSTEM ${STLAB_DEFAULT_THREAD_SYSTEM} CACHE STRING "Thread system to use (win32|pthread|pthread-emscripten|pthread-apple|none)")
 
 stlab_detect_task_system(STLAB_DEFAULT_TASK_SYSTEM)
-set(STLAB_TASK_SYSTEM ${STLAB_DEFAULT_TASK_SYSTEM} CACHE STRING "Task system to use (portable|libdispatch|windows).")
+set(STLAB_TASK_SYSTEM ${STLAB_DEFAULT_TASK_SYSTEM} CACHE STRING "Task system to use (portable|libdispatch|windows|experimental_rust).")
 
 stlab_detect_main_executor(STLAB_DEFAULT_MAIN_EXECUTOR)
 set(STLAB_MAIN_EXECUTOR ${STLAB_DEFAULT_MAIN_EXECUTOR} CACHE STRING "Main executor to use (qt5|qt6|libdispatch|emscripten|none).")
 
-option( STLAB_USE_RUST_DEFAULT_EXECUTOR "Use a Rust port of the default_executor. Defaults to OFF." ON )
-
 if( BUILD_TESTING AND NOT Boost_unit_test_framework_FOUND )
   message( SEND_ERROR "BUILD_TESTING is enabled, but an installation of Boost.Test was not found." )
 endif()
@@ -103,6 +101,11 @@ if (STLAB_TASK_SYSTEM STREQUAL "libdispatch")
   target_link_libraries(stlab INTERFACE libdispatch::libdispatch)
 endif()
 
+if (STLAB_TASK_SYSTEM STREQUAL "experimental_rust")
+  add_subdirectory( rustport )
+  include_directories( rustport )
+endif()
+
 if (STLAB_MAIN_EXECUTOR STREQUAL "libdispatch")
   target_link_libraries(stlab INTERFACE libdispatch::libdispatch)
 elseif (STLAB_MAIN_EXECUTOR STREQUAL "qt5")
@@ -138,12 +141,9 @@ if ( BUILD_TESTING )
     stlab::development
     stlab::stlab )
 
-    if ( STLAB_USE_RUST_DEFAULT_EXECUTOR )
-      add_definitions( -DSTLAB_USE_RUST_DEFAULT_EXECUTOR )
-      add_subdirectory( rustport )
-      include_directories( rustport )
-      target_link_libraries( testing INTERFACE RustyDefaultExecutor )
-    endif()
+  if (STLAB_TASK_SYSTEM STREQUAL "experimental_rust")
+    target_link_libraries( testing INTERFACE RustyDefaultExecutor )
+  endif ()
 
   #
   # Linking to the Boost unit test framework requires an additional
diff --git a/cmake/StlabUtil.cmake b/cmake/StlabUtil.cmake
index b5bba2da..28ff7f0b 100644
--- a/cmake/StlabUtil.cmake
+++ b/cmake/StlabUtil.cmake
@@ -190,6 +190,8 @@ function( stlab_generate_config_file )
     set( STLAB_TASK_SYSTEM_EMSCRIPTEN TRUE )
   elseif (STLAB_TASK_SYSTEM STREQUAL "windows")
     set( STLAB_TASK_SYSTEM_WINDOWS TRUE )
+  elseif (STLAB_TASK_SYSTEM STREQUAL "experimental_rust")
+    set( STLAB_TASK_SYSTEM_EXPERIMENTAL_RUST TRUE )
   endif()
 
   if (STLAB_MAIN_EXECUTOR STREQUAL "libdispatch")
diff --git a/stlab/concurrency/default_executor.hpp b/stlab/concurrency/default_executor.hpp
index a4831f9c..98bb0e13 100644
--- a/stlab/concurrency/default_executor.hpp
+++ b/stlab/concurrency/default_executor.hpp
@@ -9,7 +9,9 @@
 #ifndef STLAB_CONCURRENCY_DEFAULT_EXECUTOR_HPP
 #define STLAB_CONCURRENCY_DEFAULT_EXECUTOR_HPP
 
-#ifdef STLAB_USE_RUST_DEFAULT_EXECUTOR
+#include <stlab/config.hpp>
+
+#if STLAB_TASK_SYSTEM(EXPERIMENTAL_RUST)
 
 #include "bindings.hpp"
 
@@ -17,7 +19,6 @@
 
 #include <stlab/concurrency/set_current_thread_name.hpp>
 #include <stlab/concurrency/task.hpp>
-#include <stlab/config.hpp>
 #include <stlab/pre_exit.hpp>
 
 #include <cassert>
@@ -507,7 +508,7 @@ constexpr auto high_executor = detail::executor_type<detail::executor_priority::
 
 /**************************************************************************************************/
 
-#endif // STLAB_USE_RUST_DEFAULT_EXECUTOR
+#endif // STLAB_TASK_SYSTEM(EXPERIMENTAL_RUST)
 
 #endif // STLAB_CONCURRENCY_DEFAULT_EXECUTOR_HPP
 
diff --git a/stlab/concurrency/system_timer.hpp b/stlab/concurrency/system_timer.hpp
index 6ac84f37..91dc433a 100755
--- a/stlab/concurrency/system_timer.hpp
+++ b/stlab/concurrency/system_timer.hpp
@@ -22,7 +22,7 @@
 #elif STLAB_TASK_SYSTEM(WINDOWS)
 #include <Windows.h>
 #include <memory>
-#elif STLAB_TASK_SYSTEM(PORTABLE)
+#elif STLAB_TASK_SYSTEM(PORTABLE) || STLAB_TASK_SYSTEM(EXPERIMENTAL_RUST)
 
 #include <algorithm>
 #include <condition_variable>
@@ -167,7 +167,7 @@ class system_timer {
 
 /**************************************************************************************************/
 
-#elif STLAB_TASK_SYSTEM(PORTABLE)
+#elif STLAB_TASK_SYSTEM(PORTABLE) || STLAB_TASK_SYSTEM(PORTABLE_RUST)
 
 class system_timer {
     using element_t = std::pair<std::chrono::steady_clock::time_point, task<void()>>;
@@ -246,7 +246,7 @@ class system_timer {
 
 /**************************************************************************************************/
 
-#if STLAB_TASK_SYSTEM(WINDOWS) || STLAB_TASK_SYSTEM(PORTABLE)
+#if STLAB_TASK_SYSTEM(WINDOWS) || STLAB_TASK_SYSTEM(PORTABLE) || STLAB_TASK_SYSTEM(EXPERIMENTAL_RUST)
 
 struct system_timer_type {
     using result_type = void;
diff --git a/stlab/config.hpp.in b/stlab/config.hpp.in
index 953093ee..5c3cf98e 100644
--- a/stlab/config.hpp.in
+++ b/stlab/config.hpp.in
@@ -24,6 +24,7 @@
 #cmakedefine01 STLAB_TASK_SYSTEM_LIBDISPATCH()
 #cmakedefine01 STLAB_TASK_SYSTEM_EMSCRIPTEN()
 #cmakedefine01 STLAB_TASK_SYSTEM_WINDOWS()
+#cmakedefine01 STLAB_TASK_SYSTEM_EXPERIMENTAL_RUST()
 
 #define STLAB_MAIN_EXECUTOR(X) (STLAB_MAIN_EXECUTOR_##X())
 #cmakedefine01 STLAB_MAIN_EXECUTOR_LIBDISPATCH()

From 2cf0ee781dfb4ff23630c70908c3d575995efa5c Mon Sep 17 00:00:00 2001
From: nickpdemarco <nickpdemarco@gmail.com>
Date: Tue, 20 Jun 2023 15:41:35 -0400
Subject: [PATCH 03/13] Responding to feedback

---
 CMakeLists.txt                        |  1 -
 rustport/CMakeLists.txt               |  8 +++---
 rustport/cppshim/include/bindings.hpp | 39 ++++++++++++++-------------
 stlab/concurrency/system_timer.hpp    |  2 +-
 4 files changed, 25 insertions(+), 25 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2da42ac7..9a9e3001 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -103,7 +103,6 @@ endif()
 
 if (STLAB_TASK_SYSTEM STREQUAL "experimental_rust")
   add_subdirectory( rustport )
-  include_directories( rustport )
 endif()
 
 if (STLAB_MAIN_EXECUTOR STREQUAL "libdispatch")
diff --git a/rustport/CMakeLists.txt b/rustport/CMakeLists.txt
index 361d91c4..cba82edc 100644
--- a/rustport/CMakeLists.txt
+++ b/rustport/CMakeLists.txt
@@ -2,8 +2,6 @@ cmake_minimum_required(VERSION 3.25)
 
 include(FetchContent)
 
-project("RustyDefaultExecutor")
-
 FetchContent_Declare(
     Corrosion
     GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
@@ -21,9 +19,9 @@ corrosion_experimental_cbindgen(
         HEADER_NAME "bindings.h"
 )
 
-add_library(${PROJECT_NAME} INTERFACE)
+add_library(RustyDefaultExecutor INTERFACE)
 
-target_include_directories(${PROJECT_NAME}
+target_include_directories(RustyDefaultExecutor
     # PRIVATE
     #     # where the library itself will look for its internal headers
     #     ${CMAKE_CURRENT_SOURCE_DIR}/src
@@ -32,4 +30,4 @@ target_include_directories(${PROJECT_NAME}
         $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/cppshim/include>
 )
 
-target_link_libraries(${PROJECT_NAME} INTERFACE default_executor)
+target_link_libraries(RustyDefaultExecutor INTERFACE default_executor)
diff --git a/rustport/cppshim/include/bindings.hpp b/rustport/cppshim/include/bindings.hpp
index a5a0cd0b..03407ae1 100644
--- a/rustport/cppshim/include/bindings.hpp
+++ b/rustport/cppshim/include/bindings.hpp
@@ -1,7 +1,8 @@
 #include "bindings.h"
 
-#include <utility>
 #include <iostream>
+#include <type_traits>
+#include <utility>
 
 namespace stlab {
 inline namespace v1 {
@@ -10,33 +11,35 @@ namespace detail {
 // REVISIT - have to invert the priority encoding to match old API.
 enum class executor_priority { high = 2, medium = 1, low = 0 };
 
-/// @brief Invokes `f` on the default_executor.
+/// @brief Asynchronously invoke f on the default_executor.
 /// @tparam F function object type
-/// @param f a function object with signature `void()`.
-/// @return the value returned by `execute`.
-template <class F>
+/// @param f a function object.
+/// @return the result of calling `execute`.
+template <class F, typename = std::enable_if_t<std::is_invocable_r<void, F>::value>>
 auto enqueue(F f) {
-    using f_t = decltype(f);
-    return execute(new f_t(std::move(f)), [](void* f_) {
-        auto f = static_cast<f_t*>(f_);
-        (*f)();
+    return execute(new F(std::move(f)), [](void* f_) {
+        auto f = static_cast<F*>(f_);
+        try {
+            (*f)();
+        } catch (...) {}
         delete f;
     });
 }
 
-/// @brief Invokes `f` on the default_executor at the given `priority`.
+/// @brief Asynchronously invoke `f` on the default_executor with priority `p`.
 /// @tparam F function object type
-/// @param f a function object with signature `void()`.
+/// @param f a function object.
 /// @param priority 
 /// @return the value returned by `execute_priority`.
-template <class F>
-auto enqueue_priority(F f, executor_priority priority) {
-    using f_t = decltype(f);
-    return execute_priority(new f_t(std::move(f)), [](void* f_) {
-        auto f = static_cast<f_t*>(f_);
-        (*f)();
+template <class F, class = std::enable_if_t<std::is_invocable_r< void, F >::value> >
+auto enqueue_priority(F f, executor_priority p) {
+    return execute_priority(new F(std::move(f)), [](void* f_) {
+        auto f = static_cast<F*>(f_);
+        try {
+            (*f)();
+        } catch (...) {}
         delete f;
-    }, static_cast<std::size_t>(priority));
+    }, static_cast<std::uint64_t>(p));
 }
 
 /// @brief A thin invokable wrapper around `enqueue_priority`.
diff --git a/stlab/concurrency/system_timer.hpp b/stlab/concurrency/system_timer.hpp
index 91dc433a..eb58aeb6 100755
--- a/stlab/concurrency/system_timer.hpp
+++ b/stlab/concurrency/system_timer.hpp
@@ -167,7 +167,7 @@ class system_timer {
 
 /**************************************************************************************************/
 
-#elif STLAB_TASK_SYSTEM(PORTABLE) || STLAB_TASK_SYSTEM(PORTABLE_RUST)
+#elif STLAB_TASK_SYSTEM(PORTABLE) || STLAB_TASK_SYSTEM(EXPERIMENTAL_RUST)
 
 class system_timer {
     using element_t = std::pair<std::chrono::steady_clock::time_point, task<void()>>;

From 1518caf87a8b167bb80e5af44a3dd598d4305b6a Mon Sep 17 00:00:00 2001
From: nickpdemarco <nickpdemarco@gmail.com>
Date: Thu, 12 Oct 2023 15:22:43 -0400
Subject: [PATCH 04/13] Break prioritized out into its own file

---
 rustport/src/{stlab.rs => stlab/mod.rs} | 60 ++++---------------------
 rustport/src/stlab/prioritized.rs       | 58 ++++++++++++++++++++++++
 2 files changed, 66 insertions(+), 52 deletions(-)
 rename rustport/src/{stlab.rs => stlab/mod.rs} (90%)
 create mode 100644 rustport/src/stlab/prioritized.rs

diff --git a/rustport/src/stlab.rs b/rustport/src/stlab/mod.rs
similarity index 90%
rename from rustport/src/stlab.rs
rename to rustport/src/stlab/mod.rs
index a498cd44..a98979c7 100644
--- a/rustport/src/stlab.rs
+++ b/rustport/src/stlab/mod.rs
@@ -1,59 +1,15 @@
-use std::cmp::{Ordering, max, Eq, Ord, PartialEq, PartialOrd};
+use std::cmp::max;
 use std::mem::MaybeUninit;
 use std::num::NonZeroUsize;
 use std::sync::{Mutex, Once};
 use std::sync::atomic::{AtomicUsize, Ordering as MemoryOrdering};
 
+mod prioritized;
+pub use prioritized::{Priority, Prioritized};
+
 /// A type-erased, heap-allocated function object.
 type Task = Box<dyn FnOnce()->()>;
 
-/// A `usize` constraining valid values to [0, 4) with runtime assertions.
-#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
-pub struct Priority(pub usize);
-
-impl Priority {
-    pub fn new(value: usize) -> Self {
-        assert!((0..4).contains(&value), "Priorities must be in [0, 4)");
-        Self(value)
-    }
-
-    /// Returns a usize of the form 0bXX000000 where XX is a binary representation of this priority.
-    /// The value of this priority is guaranteed to fit in two bits because `Priority` values are constrained to [0b00, 0b11].
-    pub fn to_highbit_mask(&self) -> usize {
-        &self.0 << (usize::BITS - 2)
-    }
-}
-
-/// Pairs an instance of `T` with a `Priority`. 
-/// Equality and ordering of a Prioritized<T> only considers `priority`, disregarding `element`.
-struct Prioritized <T> {
-    priority: Priority,
-    element: T
-}
-
-impl<T> PartialEq for Prioritized <T> {
-    #[inline]
-    fn eq(&self, other: &Self) -> bool {
-        self.priority == other.priority
-    }
-}
-
-impl <T> Eq for Prioritized <T> {}
-
-impl<T> PartialOrd for Prioritized <T> {
-    #[inline]
-    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-        self.priority.partial_cmp(&other.priority)
-    }
-}
-
-impl<T> Ord for Prioritized <T> {
-    #[inline]
-    fn cmp(&self, other: &Self) -> Ordering {
-        self.priority.cmp(&other.priority)
-    }
-}
-
 /// The fields of `Waiter` which must be protected by a `Mutex`.
 struct WaiterProtectedData {
     waiting: bool,
@@ -171,7 +127,7 @@ impl<T> NotificationQueue<T> {
     pub fn try_pop(&mut self) -> Option<T> {
         if let Ok(ref mut this) = self.protected.try_lock() {
             if !this.heap.is_empty() { 
-                return Some(this.heap.pop().unwrap().element);
+                return Some(this.heap.pop().unwrap().take_element());
             }
         } 
         return None;
@@ -200,7 +156,7 @@ impl<T> NotificationQueue<T> {
         if this.heap.is_empty() {
             return (this.done, None);
         } 
-        return (false, Some(this.heap.pop().unwrap().element));
+        return (false, Some(this.heap.pop().unwrap().take_element()));
     }
 
     /// Mark this object for teardown, and wake any thread awaiting an available element in `pop()`.
@@ -218,7 +174,7 @@ impl<T> NotificationQueue<T> {
         if let Ok(ref mut this) = self.protected.try_lock() {
             let priority = Self::merge_priority_count(priority, this.count);
             this.count += 1;
-            this.heap.push(Prioritized{ element, priority });
+            this.heap.push(Prioritized::new(priority, element));
         } else {
             return Some(element);
         }
@@ -235,7 +191,7 @@ impl<T> NotificationQueue<T> {
             let mut this = self.protected.lock().expect("the mutex is not poisoned");
             let priority = Self::merge_priority_count(priority, this.count);
             this.count += 1;
-            this.heap.push(Prioritized{ element, priority });
+            this.heap.push(Prioritized::new(priority, element));
         }
         self.ready.notify_one();
     }
diff --git a/rustport/src/stlab/prioritized.rs b/rustport/src/stlab/prioritized.rs
new file mode 100644
index 00000000..f00075b8
--- /dev/null
+++ b/rustport/src/stlab/prioritized.rs
@@ -0,0 +1,58 @@
+use std::cmp::{Ordering};
+
+/// A `usize` constraining valid values to [0, 4) with runtime assertions.
+#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
+pub struct Priority(pub usize);
+
+impl Priority {
+    pub fn new(value: usize) -> Self {
+        assert!((0..4).contains(&value), "Priorities must be in [0, 4)");
+        Self(value)
+    }
+
+    /// Returns a usize of the form 0bXX000000 where XX is a binary representation of this priority.
+    /// The value of this priority is guaranteed to fit in two bits because `Priority` values are constrained to [0b00, 0b11].
+    pub fn to_highbit_mask(&self) -> usize {
+        &self.0 << (usize::BITS - 2)
+    }
+}
+
+/// Pairs an instance of `T` with a `Priority`. 
+/// Equality and ordering of a Prioritized<T> only considers `priority`, disregarding `element`.
+pub struct Prioritized <T> {
+    priority: Priority,
+    element: T
+}
+
+impl <T> Prioritized<T> {
+    pub fn new(priority: Priority, element: T) -> Self {
+        Prioritized { priority, element }
+    }
+
+    pub fn take_element(self) -> T {
+        self.element
+    }
+}
+
+impl<T> PartialEq for Prioritized <T> {
+    #[inline]
+    fn eq(&self, other: &Self) -> bool {
+        self.priority == other.priority
+    }
+}
+
+impl <T> Eq for Prioritized <T> {}
+
+impl<T> PartialOrd for Prioritized <T> {
+    #[inline]
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        self.priority.partial_cmp(&other.priority)
+    }
+}
+
+impl<T> Ord for Prioritized <T> {
+    #[inline]
+    fn cmp(&self, other: &Self) -> Ordering {
+        self.priority.cmp(&other.priority)
+    }
+}
\ No newline at end of file

From 32ce01cb47191e21762602115c902c3790541086 Mon Sep 17 00:00:00 2001
From: nickpdemarco <nickpdemarco@gmail.com>
Date: Fri, 3 Nov 2023 10:39:04 -0400
Subject: [PATCH 05/13] remove unsafe

---
 rustport/Cargo.lock               | 281 ++++++++++--------------------
 rustport/Cargo.toml               |   2 +
 rustport/src/lib.rs               |  45 +++--
 rustport/src/stlab/mod.rs         | 239 +++++++++++--------------
 rustport/src/stlab/prioritized.rs |   3 +-
 5 files changed, 229 insertions(+), 341 deletions(-)

diff --git a/rustport/Cargo.lock b/rustport/Cargo.lock
index 2a4be3dd..e97cef7c 100644
--- a/rustport/Cargo.lock
+++ b/rustport/Cargo.lock
@@ -8,7 +8,7 @@ version = "0.2.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
 dependencies = [
- "hermit-abi 0.1.19",
+ "hermit-abi",
  "libc",
  "winapi",
 ]
@@ -25,11 +25,17 @@ version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
+[[package]]
+name = "bitflags"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+
 [[package]]
 name = "cbindgen"
-version = "0.24.3"
+version = "0.24.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a6358dedf60f4d9b8db43ad187391afe959746101346fe51bb978126bec61dfb"
+checksum = "4b922faaf31122819ec80c4047cc684c6979a087366c069611e33649bf98e18d"
 dependencies = [
  "clap",
  "heck",
@@ -44,12 +50,6 @@ dependencies = [
  "toml",
 ]
 
-[[package]]
-name = "cc"
-version = "1.0.79"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
-
 [[package]]
 name = "cfg-if"
 version = "1.0.0"
@@ -58,12 +58,12 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
 name = "clap"
-version = "3.2.24"
+version = "3.2.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eef2b3ded6a26dfaec672a742c93c8cf6b689220324da509ec5caa20de55dc83"
+checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
 dependencies = [
  "atty",
- "bitflags",
+ "bitflags 1.3.2",
  "clap_lex",
  "indexmap",
  "strsim",
@@ -85,37 +85,24 @@ name = "default_executor"
 version = "0.1.0"
 dependencies = [
  "cbindgen",
+ "once_cell",
 ]
 
 [[package]]
 name = "errno"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
-dependencies = [
- "errno-dragonfly",
- "libc",
- "windows-sys 0.48.0",
-]
-
-[[package]]
-name = "errno-dragonfly"
-version = "0.1.2"
+version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860"
 dependencies = [
- "cc",
  "libc",
+ "windows-sys",
 ]
 
 [[package]]
 name = "fastrand"
-version = "1.9.0"
+version = "2.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
-dependencies = [
- "instant",
-]
+checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
 
 [[package]]
 name = "hashbrown"
@@ -138,12 +125,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "hermit-abi"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
-
 [[package]]
 name = "indexmap"
 version = "1.9.3"
@@ -154,131 +135,113 @@ dependencies = [
  "hashbrown",
 ]
 
-[[package]]
-name = "instant"
-version = "0.1.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
-name = "io-lifetimes"
-version = "1.0.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
-dependencies = [
- "hermit-abi 0.3.1",
- "libc",
- "windows-sys 0.48.0",
-]
-
 [[package]]
 name = "itoa"
-version = "1.0.6"
+version = "1.0.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
+checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
 
 [[package]]
 name = "libc"
-version = "0.2.142"
+version = "0.2.149"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
+checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
 
 [[package]]
 name = "linux-raw-sys"
-version = "0.3.4"
+version = "0.4.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf"
+checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
 
 [[package]]
 name = "log"
-version = "0.4.17"
+version = "0.4.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
-dependencies = [
- "cfg-if",
-]
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "once_cell"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
 
 [[package]]
 name = "os_str_bytes"
-version = "6.5.0"
+version = "6.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267"
+checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.56"
+version = "1.0.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
+checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.26"
+version = "1.0.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
 dependencies = [
  "proc-macro2",
 ]
 
 [[package]]
 name = "redox_syscall"
-version = "0.3.5"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
 dependencies = [
- "bitflags",
+ "bitflags 1.3.2",
 ]
 
 [[package]]
 name = "rustix"
-version = "0.37.15"
+version = "0.38.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a0661814f891c57c930a610266415528da53c4933e6dea5fb350cbfe048a9ece"
+checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
 dependencies = [
- "bitflags",
+ "bitflags 2.4.1",
  "errno",
- "io-lifetimes",
  "libc",
  "linux-raw-sys",
- "windows-sys 0.48.0",
+ "windows-sys",
 ]
 
 [[package]]
 name = "ryu"
-version = "1.0.13"
+version = "1.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
+checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
 
 [[package]]
 name = "serde"
-version = "1.0.160"
+version = "1.0.190"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
+checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.160"
+version = "1.0.190"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
+checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.15",
+ "syn 2.0.38",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.96"
+version = "1.0.108"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
+checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
 dependencies = [
  "itoa",
  "ryu",
@@ -304,9 +267,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.15"
+version = "2.0.38"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
+checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -315,22 +278,22 @@ dependencies = [
 
 [[package]]
 name = "tempfile"
-version = "3.5.0"
+version = "3.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998"
+checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
 dependencies = [
  "cfg-if",
  "fastrand",
  "redox_syscall",
  "rustix",
- "windows-sys 0.45.0",
+ "windows-sys",
 ]
 
 [[package]]
 name = "termcolor"
-version = "1.2.0"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
+checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64"
 dependencies = [
  "winapi-util",
 ]
@@ -352,9 +315,9 @@ dependencies = [
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.8"
+version = "1.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
 
 [[package]]
 name = "winapi"
@@ -374,9 +337,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 
 [[package]]
 name = "winapi-util"
-version = "0.1.5"
+version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
 dependencies = [
  "winapi",
 ]
@@ -387,134 +350,68 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
-[[package]]
-name = "windows-sys"
-version = "0.45.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
-dependencies = [
- "windows-targets 0.42.2",
-]
-
 [[package]]
 name = "windows-sys"
 version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
 dependencies = [
- "windows-targets 0.48.0",
+ "windows-targets",
 ]
 
 [[package]]
 name = "windows-targets"
-version = "0.42.2"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
 dependencies = [
- "windows_aarch64_gnullvm 0.42.2",
- "windows_aarch64_msvc 0.42.2",
- "windows_i686_gnu 0.42.2",
- "windows_i686_msvc 0.42.2",
- "windows_x86_64_gnu 0.42.2",
- "windows_x86_64_gnullvm 0.42.2",
- "windows_x86_64_msvc 0.42.2",
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
 ]
 
-[[package]]
-name = "windows-targets"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
-dependencies = [
- "windows_aarch64_gnullvm 0.48.0",
- "windows_aarch64_msvc 0.48.0",
- "windows_i686_gnu 0.48.0",
- "windows_i686_msvc 0.48.0",
- "windows_x86_64_gnu 0.48.0",
- "windows_x86_64_gnullvm 0.48.0",
- "windows_x86_64_msvc 0.48.0",
-]
-
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.42.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
-
 [[package]]
 name = "windows_aarch64_gnullvm"
-version = "0.48.0"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
 [[package]]
 name = "windows_aarch64_msvc"
-version = "0.42.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
-
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
-
-[[package]]
-name = "windows_i686_gnu"
-version = "0.42.2"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
 [[package]]
 name = "windows_i686_gnu"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
-
-[[package]]
-name = "windows_i686_msvc"
-version = "0.42.2"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
 [[package]]
 name = "windows_i686_msvc"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
-
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.42.2"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
 [[package]]
 name = "windows_x86_64_gnu"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.42.2"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
-version = "0.48.0"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
 [[package]]
 name = "windows_x86_64_msvc"
-version = "0.42.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
-
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.48.0"
+version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
diff --git a/rustport/Cargo.toml b/rustport/Cargo.toml
index a58c1de4..4caf0fbd 100644
--- a/rustport/Cargo.toml
+++ b/rustport/Cargo.toml
@@ -9,6 +9,8 @@ crate-type = ["staticlib"]
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
+once_cell = "1.18.0"
 
 [build-dependencies]
 cbindgen = "0.24.0"
+
diff --git a/rustport/src/lib.rs b/rustport/src/lib.rs
index 95f69f04..c80807d6 100644
--- a/rustport/src/lib.rs
+++ b/rustport/src/lib.rs
@@ -1,26 +1,43 @@
-use std::ffi::c_void;
+use std::{ffi::c_void, sync::Mutex};
+use once_cell::sync::Lazy;
 
+use stlab::PriorityTaskSystem;
 mod stlab;
 
+static TASK_SYSTEM: Lazy<Mutex<PriorityTaskSystem>> = Lazy::new(|| Mutex::new(PriorityTaskSystem::new()) );
+struct ThreadsafeCFnWrapper {
+  context: *mut c_void,
+  fn_ptr: extern fn(*mut c_void)
+}
+
+impl ThreadsafeCFnWrapper {
+  pub(crate) fn new(context: *mut c_void, fn_ptr: extern fn(*mut c_void)) -> Self {
+      Self {
+          context,
+          fn_ptr
+      }
+  }
+
+  pub(crate) fn call(&self) {
+      (self.fn_ptr)(self.context)
+  }
+}
+
+unsafe impl Send for ThreadsafeCFnWrapper {}
+
 /// Enqueues a the execution of `f(context)` on the PriorityTaskSystem.
 #[no_mangle]
-pub extern "C" fn execute(context: *mut c_void, f: extern fn(*mut c_void)) -> i32 {
-  stlab::PriorityTaskSystem::singleton().execute(move||{
-    f(context)
-  }, stlab::Priority(0));
+pub extern "C" fn execute(context: *mut c_void, fn_ptr: extern fn(*mut c_void)) -> i32 {
+  execute_priority(context, fn_ptr, 0);
   0
 }
 
 /// Enqueues a the execution of `f(context)` on the PriorityTaskSystem at the given `priority`.
 #[no_mangle]
-pub extern "C" fn execute_priority(context: *mut c_void, f: extern fn(*mut c_void), priority: usize) -> i32 {
-  stlab::PriorityTaskSystem::singleton().execute(move||{
-    f(context)
-  }, stlab::Priority(priority));
+pub extern "C" fn execute_priority(context: *mut c_void, fn_ptr: extern fn(*mut c_void), p: usize) -> i32 {
+  let wrap = ThreadsafeCFnWrapper::new(context, fn_ptr);
+  TASK_SYSTEM.lock().unwrap().execute(move||{
+    wrap.call()
+  }, stlab::Priority(p));
   0
 }
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-}
diff --git a/rustport/src/stlab/mod.rs b/rustport/src/stlab/mod.rs
index a98979c7..50995acc 100644
--- a/rustport/src/stlab/mod.rs
+++ b/rustport/src/stlab/mod.rs
@@ -1,14 +1,14 @@
 use std::cmp::max;
-use std::mem::MaybeUninit;
 use std::num::NonZeroUsize;
-use std::sync::{Mutex, Once};
+use std::sync::{Mutex, Arc};
 use std::sync::atomic::{AtomicUsize, Ordering as MemoryOrdering};
+use std::sync::OnceLock;
 
 mod prioritized;
 pub use prioritized::{Priority, Prioritized};
 
 /// A type-erased, heap-allocated function object.
-type Task = Box<dyn FnOnce()->()>;
+type Task = Box<dyn FnOnce()->() + Send>;
 
 /// The fields of `Waiter` which must be protected by a `Mutex`.
 struct WaiterProtectedData {
@@ -22,6 +22,12 @@ struct Waiter {
     ready: std::sync::Condvar,
 }
 
+impl Default for Waiter {
+    fn default() -> Self {
+        Waiter::new() 
+    }
+}
+
 impl Waiter {
 
     /// Constructs a new Waiter, with `waiting` and `done` set to `false`. 
@@ -124,7 +130,7 @@ impl<T> NotificationQueue<T> {
 
     /// Try to pop from the queue without blocking.
     /// Returns `None` if our mutex is already locked or if the queue is empty.
-    pub fn try_pop(&mut self) -> Option<T> {
+    pub fn try_pop(&self) -> Option<T> {
         if let Ok(ref mut this) = self.protected.try_lock() {
             if !this.heap.is_empty() { 
                 return Some(this.heap.pop().unwrap().take_element());
@@ -197,6 +203,7 @@ impl<T> NotificationQueue<T> {
     }
 }
 
+unsafe impl <T> Sync for NotificationQueue<T> {}
 /// The fields of `PriorityTaskSystem` which must be protected by a `Mutex`.
 #[derive(Default)]
 struct PriorityTaskSystemProtectedData {
@@ -204,176 +211,141 @@ struct PriorityTaskSystemProtectedData {
     waiters: Vec<Waiter>,
 }
 
-impl PriorityTaskSystemProtectedData {
-    fn spawn_thread<F>(thread_name: &'static str, f: F) -> std::thread::JoinHandle<()> 
-        where F: FnOnce() -> () + 'static + Send {
-        std::thread::Builder::new().name(thread_name.to_string())
-            .spawn(f).expect("spawning a thread does not fail")
-    }
-
-    /// Spawn `thread_count` threads, each of which pops tasks from an associated queue in `queues`, 
-    /// or steals tasks from other queues if no task is available.
-    pub(self) fn spawn_baseline_threads(&mut self, thread_count: usize, available_parallelism: usize) {
-        self.threads.extend((0..thread_count).map(|i| {
-            Self::spawn_thread("cc.stlab.default_executor", move || {
-                loop {
-                    let this = PriorityTaskSystem::singleton(); // why doesn't this have to be mut?
-                    let mut task: Option<Task> = None;
-                    for n in 0..available_parallelism {
-                        match task {
-                            Some(_) => { break } 
-                            _ => {
-                                task = this.queues[(i + n) % available_parallelism].try_pop()
-                            }
-                        }
-                    }
-
-                    if task.is_none() {
-                        let done: bool;
-                        (done, task) = this.queues[i].pop();
-                        if done { break }
-                    }
-
-                    if task.is_some() {
-                        task.unwrap()();
-                    }
-                }
-            })
-        }))
-    }
-
-    /// Spawn one thread which will repeatedly check each queue in `queues` for tasks, and execute them.
-    /// If no tasks are found, the thread is suspended.
-    pub(self) fn spawn_expansion_thread(&mut self, available_parallelism: usize) {
-        let protected = PriorityTaskSystem::singleton().protected.get_mut().expect("the mutex is not poisoned");
-        let i = protected.threads.len();
-
-        if i == PriorityTaskSystem::singleton().available_parallelism {
-            eprintln!("Thread limit reached")
-        }
-        
-        self.threads.push(Self::spawn_thread("cc.stlab.default_executor.expansion", move || {
-            loop {
-                let this = PriorityTaskSystem::singleton(); // why doesn't this have to be mut?
-                let mut task: Option<Task> = None;
-                for n in 0..available_parallelism {
-                    match task {
-                        Some(_) => { break } 
-                        _ => {
-                            task = this.queues[(i + n) % available_parallelism].try_pop()
-                        }
-                    }
-                }
-
-                if task.is_some() {
-                    task.unwrap()();
-                    continue;
-                }
-
-                let protected = this.protected.get_mut().expect("the mutex is not poisoned");
-
-                // Note: The following means multiple threads may wait on a single `Waiter`.
-                if protected.waiters[i - available_parallelism].wait() { break; }
-            }
-        }));
-    }
-}
-
 /// A thread-scalable priority task system.
 pub struct PriorityTaskSystem {
     available_parallelism: usize, // _count in C++ implementation.
     thread_limit: usize,
-    queues: Vec<NotificationQueue<Task>>,
+    queues: Arc<Vec<NotificationQueue<Task>>>,
     index: AtomicUsize,
-    protected: Mutex<PriorityTaskSystemProtectedData>
+    protected: Arc<Mutex<PriorityTaskSystemProtectedData>>
 }
 
 impl PriorityTaskSystem {
-    
-    // TODO: Roll back the singleton pattern, and find a way to make instances immovable.
-    /// Return the singleton instance of `PriorityTaskSystem`, creating it on the first invocation.
-    pub fn singleton() -> &'static mut Self {
-        static mut INSTANCE: MaybeUninit<PriorityTaskSystem> = MaybeUninit::uninit();
-        static ONCE: Once = Once::new();
-        
-        ONCE.call_once(|| {
-            // This clunky expression is to convert from a NonZeroUsize to a usize for the subsequent arithmetic. I'd like a better way.
-            // SAFETY: we know 1 is not 0.
-            let nonzero_available_parallelism = std::thread::available_parallelism().unwrap_or(unsafe { NonZeroUsize::new_unchecked(1) });
-            let available_parallelism = max(usize::from(nonzero_available_parallelism), 2) - 1;
-            let thread_limit = max(9, available_parallelism * 4 + 1);
-
-            unsafe { 
-                INSTANCE.as_mut_ptr().write(Self {
-                    available_parallelism,
-                    thread_limit,
-                    queues: {
-                        let mut v = Vec::with_capacity(available_parallelism);
-                        for _ in 0..available_parallelism { v.push(NotificationQueue::<Task>::default()) }
-                        v
-                    },
-                    index: AtomicUsize::new(0),
-                    protected: Mutex::new(PriorityTaskSystemProtectedData {
-                        waiters: Vec::with_capacity(thread_limit - available_parallelism),
-                        threads: Vec::with_capacity(available_parallelism),
-                    })
-                }); 
-            }
 
-            // Defer spawning threads so they can never witness an uninitialized `INSTANCE`.
-            // Note `get_mut` statically enforces exclusive access; no locking will take place.
-            unsafe {
-                // `unwrap` should never panic; that would imply the mutex we just created, and never
-                // handed out, has been poisoned.
-                (*INSTANCE.as_mut_ptr()).protected.get_mut().unwrap().spawn_baseline_threads(available_parallelism, available_parallelism);
-            }
+    pub fn new() -> Self {
+        // SAFETY: We know 1 is not 0.
+        let nonzero_available_parallelism = std::thread::available_parallelism().unwrap_or(unsafe { NonZeroUsize::new_unchecked(1) });
+        let available_parallelism = max(usize::from(nonzero_available_parallelism), 2) - 1;
+        let thread_limit = max(9, available_parallelism * 4 + 1);
+
+        
+        let queues = Arc::new({
+            let mut v = Vec::with_capacity(available_parallelism);
+            for _ in 0..available_parallelism { v.push(NotificationQueue::<Task>::default()) }
+            v
         });
 
-        unsafe { &mut *INSTANCE.as_mut_ptr() }
+        Self {
+            available_parallelism,
+            thread_limit,
+            queues: queues.clone(),
+            index: AtomicUsize::new(0),
+            protected: Arc::new(Mutex::new(PriorityTaskSystemProtectedData { 
+                waiters: (0..(thread_limit - available_parallelism)).map(|_| { Waiter::default() }).collect(),
+                threads: (0..available_parallelism).map(|i| {
+                    let queues = queues.clone();
+                    std::thread::spawn(move ||{
+                        loop {
+                            let mut task: Option<Task> = None;
+                            for n in 0..available_parallelism {
+                                match task {
+                                    Some(_) => { break } 
+                                    _ => {
+                                        task = queues.get((i + n) % available_parallelism).unwrap().try_pop();
+                                    }
+                                }
+                            }
+        
+                            if task.is_none() {
+                                let done: bool;
+                                (done, task) = queues.get(i).unwrap().pop();
+                                if done { break }
+                            }
+        
+                            if task.is_some() {
+                                task.unwrap()();
+                            }
+                        }
+                    })
+                }).collect() 
+            }))
+        }
     }
 
     /// Mark all subobjects for teardown, and join all threads spawned by this object.
-    pub fn join(&mut self) {
-        for queue in &self.queues {
+    pub fn join(&self) {
+        for queue in self.queues.iter() {
             queue.done();
         }
 
         // Consume the contents under the mutex, leaving them empty.
-        let this = std::mem::take(self.protected.get_mut().expect("the mutex is not poisoned"));
-        for e in this.waiters {
+        let mut this = self.protected.lock().expect("the mutex is not poisoned");
+        for e in &this.waiters {
             e.done();
         }
-        for e in this.threads {
+        for e in std::mem::take(&mut this.threads) {
             let _ = e.join();
         }
 
-        self.queues.clear();
+        // self.queues.clear(); BIG REVISIT
     }
 
     /// Push `f` to the first queue in `queues` whose mutex is not under contention.
     /// If no such queue is found after a single pass, blockingly push `f` to one queue.
     // REVIEW: I'm not sure `execute` is a good name. I think we want `push`, or `push_with_priority`.
-    pub fn execute<F>(&mut self, f: F, p: Priority) where F: FnOnce() -> () + 'static {
-        let mut task: Option<Task> = Some(Box::new(f));
+    pub fn execute<F>(&mut self, f: F, p: Priority) where F: FnOnce() -> () + Send + 'static {
+        self.execute_task(Box::new(f), p)
+    }
+
+    pub fn execute_task(&mut self, task: Task, priority: Priority) {
+        let mut task: Option<Task> = Some(task);
         // Use SeqCst to match the default for C++'s std::atomic<unsigned>::operator++(int).
         let i = self.index.fetch_add(1, MemoryOrdering::SeqCst); 
         for n in 0..i {
-            task = self.queues[(i + n) % self.available_parallelism].try_push(task.unwrap(), p);
+            task = self.queues.get((i + n) % self.available_parallelism).unwrap().try_push(task.unwrap(), priority);
+            // task = self.queues[(i + n) % self.available_parallelism].try_push(task.unwrap(), priority);
             if task.is_none() { return }
         }
 
-        self.queues[i % self.available_parallelism].push(task.unwrap(), p);
+        self.queues.get(i % self.available_parallelism).unwrap().push(task.unwrap(), priority);
     }
 
     /// Spawn an expansion thread which will steal tasks from `queues`.
-    pub fn add_thread(&mut self) {
+    pub fn add_thread(&self) {
         let mut this = self.protected.lock().expect("the mutex is not poisoned");
         if this.threads.len() == self.thread_limit {
             eprintln!("Unable to add thread; thread_limit reached: {}", self.thread_limit);
             return;
         }
 
-        this.spawn_expansion_thread(self.available_parallelism);
+        let queues = self.queues.clone();
+        let protected = self.protected.clone();
+        let available_parallelism = self.available_parallelism.clone();
+        let i = this.threads.len();
+
+        this.threads.push(std::thread::spawn(move ||{
+            loop {
+                let mut task: Option<Task> = None;
+                for n in 0..available_parallelism {
+                    match task {
+                        Some(_) => { break } 
+                        _ => {
+                            task = queues.get((i + n) % available_parallelism).unwrap().try_pop()
+                        }
+                    }
+                }
+
+                if task.is_some() {
+                    task.unwrap()();
+                    continue;
+                }
+
+                let protected = protected.lock().expect("the mutex is not poisoned");
+
+                // Note: The following means multiple threads may wait on a single `Waiter`.
+                if protected.waiters[i - available_parallelism].wait() { break; }
+            }
+        }))
     }
 
     /// Get the number of waiters.
@@ -384,13 +356,12 @@ impl PriorityTaskSystem {
 
     /// Wake every `Waiter` in `waiters`. Return true if any indicate they are `done()`, otherwise `false`.
     pub fn wake(&mut self) -> bool {
-        for queue in &self.queues {
+        for queue in self.queues.iter() {
             if queue.wake() { return true } 
         }
 
         let size = self.waiters_size();
-        // Note: get_mut ensures no locking occurs; uniqueness enforced by the type system.
-        let this = self.protected.get_mut().expect("the mutex is not poisoned");
+        let this = self.protected.lock().expect("the mutex is not poisoned");
         for n in 0..size {
             if this.waiters[n].wake() {
                 return true;
diff --git a/rustport/src/stlab/prioritized.rs b/rustport/src/stlab/prioritized.rs
index f00075b8..fdf6cfd5 100644
--- a/rustport/src/stlab/prioritized.rs
+++ b/rustport/src/stlab/prioritized.rs
@@ -1,7 +1,8 @@
-use std::cmp::{Ordering};
+use std::cmp::Ordering;
 
 /// A `usize` constraining valid values to [0, 4) with runtime assertions.
 #[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
+#[repr(C)]
 pub struct Priority(pub usize);
 
 impl Priority {

From 33ab6fff245c6fab5b1f8a0b924b7c2374679655 Mon Sep 17 00:00:00 2001
From: nickpdemarco <nickpdemarco@gmail.com>
Date: Mon, 20 Nov 2023 15:29:54 -0500
Subject: [PATCH 06/13] Version 2 of Rust Default Executor, with better
 abstractions and less unsafe

---
 rustport/cppshim/include/bindings.hpp       |  14 +-
 rustport/src/lib.rs                         |  11 +-
 rustport/src/stlab/coarse_priority_queue.rs | 100 +++++
 rustport/src/stlab/mod.rs                   | 389 ++++----------------
 rustport/src/stlab/notification_queue.rs    | 114 ++++++
 rustport/src/stlab/prioritized.rs           |  59 ---
 rustport/src/stlab/scoped_thread_pool.rs    |  54 +++
 rustport/src/stlab/waiter.rs                |  75 ++++
 8 files changed, 438 insertions(+), 378 deletions(-)
 create mode 100644 rustport/src/stlab/coarse_priority_queue.rs
 create mode 100644 rustport/src/stlab/notification_queue.rs
 delete mode 100644 rustport/src/stlab/prioritized.rs
 create mode 100644 rustport/src/stlab/scoped_thread_pool.rs
 create mode 100644 rustport/src/stlab/waiter.rs

diff --git a/rustport/cppshim/include/bindings.hpp b/rustport/cppshim/include/bindings.hpp
index 03407ae1..224fa442 100644
--- a/rustport/cppshim/include/bindings.hpp
+++ b/rustport/cppshim/include/bindings.hpp
@@ -8,8 +8,16 @@ namespace stlab {
 inline namespace v1 {
 namespace detail {
 
-// REVISIT - have to invert the priority encoding to match old API.
-enum class executor_priority { high = 2, medium = 1, low = 0 };
+enum class executor_priority { high, medium, low };
+
+inline auto bridge_priority(executor_priority p) -> Priority {
+    switch (p) {
+        case executor_priority::high: return Priority::High;
+        case executor_priority::medium: return Priority::Default;
+        case executor_priority::low: return Priority::Low;
+        default: return Priority::Default;
+    }
+}
 
 /// @brief Asynchronously invoke f on the default_executor.
 /// @tparam F function object type
@@ -39,7 +47,7 @@ auto enqueue_priority(F f, executor_priority p) {
             (*f)();
         } catch (...) {}
         delete f;
-    }, static_cast<std::uint64_t>(p));
+    }, bridge_priority(p));
 }
 
 /// @brief A thin invokable wrapper around `enqueue_priority`.
diff --git a/rustport/src/lib.rs b/rustport/src/lib.rs
index c80807d6..f09e979c 100644
--- a/rustport/src/lib.rs
+++ b/rustport/src/lib.rs
@@ -1,10 +1,13 @@
 use std::{ffi::c_void, sync::Mutex};
 use once_cell::sync::Lazy;
 
-use stlab::PriorityTaskSystem;
+use stlab::{PriorityTaskSystem, Priority};
 mod stlab;
 
 static TASK_SYSTEM: Lazy<Mutex<PriorityTaskSystem>> = Lazy::new(|| Mutex::new(PriorityTaskSystem::new()) );
+
+// "Threadsafe" is not a guarantee, it is a requirement. These pointers are assumed to be able to
+// be sent to another thread, and therefore must not rely on thread-local state.
 struct ThreadsafeCFnWrapper {
   context: *mut c_void,
   fn_ptr: extern fn(*mut c_void)
@@ -28,16 +31,16 @@ unsafe impl Send for ThreadsafeCFnWrapper {}
 /// Enqueues a the execution of `f(context)` on the PriorityTaskSystem.
 #[no_mangle]
 pub extern "C" fn execute(context: *mut c_void, fn_ptr: extern fn(*mut c_void)) -> i32 {
-  execute_priority(context, fn_ptr, 0);
+  execute_priority(context, fn_ptr, Priority::Default);
   0
 }
 
 /// Enqueues a the execution of `f(context)` on the PriorityTaskSystem at the given `priority`.
 #[no_mangle]
-pub extern "C" fn execute_priority(context: *mut c_void, fn_ptr: extern fn(*mut c_void), p: usize) -> i32 {
+pub extern "C" fn execute_priority(context: *mut c_void, fn_ptr: extern fn(*mut c_void), p: Priority) -> i32 {
   let wrap = ThreadsafeCFnWrapper::new(context, fn_ptr);
   TASK_SYSTEM.lock().unwrap().execute(move||{
     wrap.call()
-  }, stlab::Priority(p));
+  }, p);
   0
 }
diff --git a/rustport/src/stlab/coarse_priority_queue.rs b/rustport/src/stlab/coarse_priority_queue.rs
new file mode 100644
index 00000000..c32676aa
--- /dev/null
+++ b/rustport/src/stlab/coarse_priority_queue.rs
@@ -0,0 +1,100 @@
+use std::cmp::Ordering;
+
+#[derive(Eq, PartialEq, Copy, Clone)]
+#[repr(C)]
+pub enum Priority {
+    Low, 
+    Default, 
+    High
+}
+
+impl Priority {
+    const fn highbit_mask(&self) -> usize {
+        match self {
+            Priority::Low => 0 << usize::BITS - 2,
+            Priority::Default => 1 << usize::BITS - 2,
+            Priority::High => 2 << usize::BITS - 2
+        }
+    }
+
+    fn merge_priority_count(&self, count: usize) -> usize {
+        self.highbit_mask() | count
+    }
+}
+
+impl PartialOrd for Priority {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        self.highbit_mask().partial_cmp(&other.highbit_mask()) 
+    }
+}
+
+/// Pairs an instance of `T` with a `Priority`. 
+/// Equality and ordering of a Prioritized<T> only considers `priority`, disregarding `inner`.
+struct Prioritized<T> {
+    inner: T,
+    priority: usize
+}
+
+impl <T> Prioritized<T> {
+    pub fn new(inner: T, priority: Priority, count: usize) -> Self {
+        Prioritized { inner, priority: priority.merge_priority_count(count) }
+    }
+
+    pub fn take_inner(self) -> T {
+        self.inner
+    }
+}
+
+impl<T> PartialEq for Prioritized <T> {
+    #[inline]
+    fn eq(&self, other: &Self) -> bool {
+        self.priority == other.priority
+    }
+}
+
+impl <T> Eq for Prioritized <T> {}
+
+impl<T> PartialOrd for Prioritized <T> {
+    #[inline]
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        self.priority.partial_cmp(&other.priority)
+    }
+}
+
+impl<T> Ord for Prioritized <T> {
+    #[inline]
+    fn cmp(&self, other: &Self) -> Ordering {
+        self.priority.cmp(&other.priority)
+    }
+}
+
+// A priority queue with three priorities. Elements with equal priorities are popped in FIFO order.
+pub struct CoarsePriorityQueue<T> {
+    inner: std::collections::BinaryHeap<Prioritized<T>>,
+    count: usize,
+}
+
+impl <T> CoarsePriorityQueue<T> {
+
+    pub fn new() -> Self {
+        Self {
+            inner: std::collections::BinaryHeap::new(),
+            count: 0
+        }
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.inner.is_empty()
+    }
+
+    pub fn pop(&mut self) -> Option<T> {
+        self.inner.pop().and_then(|prioritized| {
+            Some(prioritized.take_inner())
+        })
+    }
+
+    pub fn push(&mut self, item: T, priority: Priority) {
+        self.inner.push(Prioritized::new(item, priority, self.count));
+        self.count += 1;
+    }
+}
\ No newline at end of file
diff --git a/rustport/src/stlab/mod.rs b/rustport/src/stlab/mod.rs
index 50995acc..d3cc2152 100644
--- a/rustport/src/stlab/mod.rs
+++ b/rustport/src/stlab/mod.rs
@@ -1,329 +1,107 @@
 use std::cmp::max;
 use std::num::NonZeroUsize;
-use std::sync::{Mutex, Arc};
+use std::sync::Arc;
 use std::sync::atomic::{AtomicUsize, Ordering as MemoryOrdering};
-use std::sync::OnceLock;
 
-mod prioritized;
-pub use prioritized::{Priority, Prioritized};
+mod coarse_priority_queue;
+pub use coarse_priority_queue::{Priority, CoarsePriorityQueue};
 
-/// A type-erased, heap-allocated function object.
-type Task = Box<dyn FnOnce()->() + Send>;
-
-/// The fields of `Waiter` which must be protected by a `Mutex`.
-struct WaiterProtectedData {
-    waiting: bool,
-    done: bool,
-}
-
-/// A utility for suspending a thread using a condition variable.
-struct Waiter {
-    protected: Mutex<WaiterProtectedData>,
-    ready: std::sync::Condvar,
-}
-
-impl Default for Waiter {
-    fn default() -> Self {
-        Waiter::new() 
-    }
-}
-
-impl Waiter {
-
-    /// Constructs a new Waiter, with `waiting` and `done` set to `false`. 
-    pub fn new() -> Self {
-        Self {
-            protected: Mutex::new(WaiterProtectedData {
-                waiting: false,
-                done: false,
-            }),
-            ready: std::sync::Condvar::new()
-        }
-    }
-
-    /// Sets `done` to `true`, and notifies one waiter of our condition variable.
-    pub fn done(&self) {
-        {
-            let mut this = self.protected.lock().expect("the mutex is not poisoned");
-            this.done = true;
-        }
-        self.ready.notify_one();
-    }
-
-    /// Sets waiting to `false`. If waiting was `true`, wake one waiter and return `true`. Otherwise, return `false`.
-    /// If `try_lock` fails, return `false`. (REVIEW: why?)
-    /// (REVIEW: is it redundant to express that `waiting` and `done` are accesed under a mutex?)
-    pub fn wake(&self) -> bool {
-        if let Ok(ref mut this) = self.protected.try_lock() {
-            if !this.waiting {
-                return false;
-            }
-            this.waiting = false;
-        } else {
-            return false;
-        }
-        self.ready.notify_one();
-        return true;
-    }
-
-    /// Block this thread until `wake()` or `done()` is called.
-    /// Returns `true` if `done()` has been called, otherwise `false`.
-    pub fn wait(&self) -> bool {
-        let mut this = self.protected.lock().expect("the mutex is not poisoned");
-        this.waiting = true;
-        while this.waiting && !this.done {
-            this = self.ready.wait(this).expect("the mutex is not poisoned");
-        }
-        this.waiting = false;
-        return this.done;
-    }
-}
+mod scoped_thread_pool;
+use scoped_thread_pool::ScopedThreadPool;
 
-/// The fields of `NotificationQueue` which must be protected by a `Mutex`.
-struct NotificationQueueProtectedData <T> {
-    heap: std::collections::BinaryHeap<Prioritized<T>>,
-    count: usize,
-    done: bool,
-    waiting: bool,
-}
+mod waiter;
+pub use waiter::Waiter;
 
-impl<T> std::default::Default for NotificationQueueProtectedData<T> {
-    fn default() -> Self {
-        Self {
-            heap: std::collections::BinaryHeap::new(),
-            count: 0,
-            done: false,
-            waiting: false
-        }
-    }
-}
-
-/// A threadsafe priority queue.
-struct NotificationQueue<T> {
-    // In the C++ implementation, we use a single lock for multiple data fields.
-    // In Rust, we require exactly one mutex per protected field. 
-    // So, put protected fields into a separate struct, and lock on that.
-    // Note the transformation to use multiple locks is non-trivial, because 
-    // this would require the ordering of acquired locks to be identical in all
-    // code paths to prevent deadlock.
-    protected: Mutex<NotificationQueueProtectedData<T>>,
-    ready: std::sync::Condvar,
-}
+pub mod notification_queue;
+use notification_queue::NotificationQueue;
 
-impl<T> std::default::Default for NotificationQueue<T> {
-    fn default() -> Self {
-        Self {
-            protected: Mutex::new(NotificationQueueProtectedData::<T>::default()),
-            ready: std::sync::Condvar::new(),
-        }
-    }
+/// A type-erased, heap-allocated function object.
+pub type Task = Box<dyn FnOnce()->() + Send>;
+pub struct PriorityTaskSystem {
+    pool: ScopedThreadPool<Vec<NotificationQueue<Task>>>,
+    waiters: Arc<Vec<Waiter>>,
+    index: AtomicUsize,
+    available_parallelism: usize,
+    thread_limit: usize,
 }
 
-impl<T> NotificationQueue<T> {
-
-    /// Merge priority and count into a single usize, storing the former in the
-    /// two highest bits of the result. This requires priority be in [0, 4) i.e.,
-    /// takes up two bits.
-    fn merge_priority_count(priority: Priority, count: usize) -> Priority {
-        Priority(priority.to_highbit_mask() | count)
-    }
-
-    /// Try to pop from the queue without blocking.
-    /// Returns `None` if our mutex is already locked or if the queue is empty.
-    pub fn try_pop(&self) -> Option<T> {
-        if let Ok(ref mut this) = self.protected.try_lock() {
-            if !this.heap.is_empty() { 
-                return Some(this.heap.pop().unwrap().take_element());
+impl Drop for PriorityTaskSystem {
+    fn drop(&mut self) {
+        self.pool.execute_immediately(|queues| {
+            for queue in queues.iter() {
+                queue.done()
             }
-        } 
-        return None;
-    }
-
-    /// If waiting in `pop()`, wakes and returns true. Otherwise returns false.
-    pub fn wake(&self) -> bool {
-        if let Ok(ref mut this) = self.protected.try_lock() {
-            if !this.waiting {
-                return false;
-            }
-            this.waiting = false; // triggers wake
-        }
-        return false;
-    }
-
-    /// Pop from the queue, suspending the current thread until an element is available.
-    /// The returned `bool` indicates if this object is `done()`.
-    pub fn pop(&self) -> (bool, Option<T>) {
-        let mut this = self.protected.lock().expect("the mutex is not poisoned");
-        this.waiting = true;
-        while !this.heap.is_empty() && !this.done && this.waiting {
-            this = self.ready.wait(this).expect("the mutex is not poisoned");
-        }
-        this.waiting = false;
-        if this.heap.is_empty() {
-            return (this.done, None);
-        } 
-        return (false, Some(this.heap.pop().unwrap().take_element()));
-    }
-
-    /// Mark this object for teardown, and wake any thread awaiting an available element in `pop()`.
-    pub fn done(&self) {
-        {
-            let mut this = self.protected.lock().expect("the mutex is not poisoned");
-            this.done = true
-        }
-        self.ready.notify_one();
-    }
-
-    /// Try to push `element` to the queue without blocking, returning `element` if our mutex is already locked.
-    /// If the push succeeds, wake a thread which may be awaiting an element in `pop()`.
-    pub fn try_push(&self, element: T, priority: Priority) -> Option<T> {
-        if let Ok(ref mut this) = self.protected.try_lock() {
-            let priority = Self::merge_priority_count(priority, this.count);
-            this.count += 1;
-            this.heap.push(Prioritized::new(priority, element));
-        } else {
-            return Some(element);
-        }
-        
-        // We successfully locked the mutex, did our push, and released the lock.
-        self.ready.notify_one();
-        return None;
-    }
+        });
 
-    /// Push `element` to the queue, blocking if our mutex is already locked.
-    /// When the push succeeds, wake a thread which may be awaiting an element in `pop()`.
-    pub fn push(&self, element: T, priority: Priority) {
-        {
-            let mut this = self.protected.lock().expect("the mutex is not poisoned");
-            let priority = Self::merge_priority_count(priority, this.count);
-            this.count += 1;
-            this.heap.push(Prioritized::new(priority, element));
+        for waiter in self.waiters.iter() {
+            waiter.done()
         }
-        self.ready.notify_one();
     }
 }
 
-unsafe impl <T> Sync for NotificationQueue<T> {}
-/// The fields of `PriorityTaskSystem` which must be protected by a `Mutex`.
-#[derive(Default)]
-struct PriorityTaskSystemProtectedData {
-    threads: Vec<std::thread::JoinHandle<()>>,
-    waiters: Vec<Waiter>,
-}
-
-/// A thread-scalable priority task system.
-pub struct PriorityTaskSystem {
-    available_parallelism: usize, // _count in C++ implementation.
-    thread_limit: usize,
-    queues: Arc<Vec<NotificationQueue<Task>>>,
-    index: AtomicUsize,
-    protected: Arc<Mutex<PriorityTaskSystemProtectedData>>
-}
-
 impl PriorityTaskSystem {
-
     pub fn new() -> Self {
         // SAFETY: We know 1 is not 0.
         let nonzero_available_parallelism = std::thread::available_parallelism().unwrap_or(unsafe { NonZeroUsize::new_unchecked(1) });
         let available_parallelism = max(usize::from(nonzero_available_parallelism), 2) - 1;
         let thread_limit = max(9, available_parallelism * 4 + 1);
-
-        
-        let queues = Arc::new({
-            let mut v = Vec::with_capacity(available_parallelism);
-            for _ in 0..available_parallelism { v.push(NotificationQueue::<Task>::default()) }
-            v
-        });
-
         Self {
-            available_parallelism,
-            thread_limit,
-            queues: queues.clone(),
-            index: AtomicUsize::new(0),
-            protected: Arc::new(Mutex::new(PriorityTaskSystemProtectedData { 
-                waiters: (0..(thread_limit - available_parallelism)).map(|_| { Waiter::default() }).collect(),
-                threads: (0..available_parallelism).map(|i| {
-                    let queues = queues.clone();
-                    std::thread::spawn(move ||{
-                        loop {
-                            let mut task: Option<Task> = None;
-                            for n in 0..available_parallelism {
-                                match task {
-                                    Some(_) => { break } 
-                                    _ => {
-                                        task = queues.get((i + n) % available_parallelism).unwrap().try_pop();
-                                    }
-                                }
-                            }
-        
-                            if task.is_none() {
-                                let done: bool;
-                                (done, task) = queues.get(i).unwrap().pop();
-                                if done { break }
-                            }
-        
-                            if task.is_some() {
-                                task.unwrap()();
+            pool: ScopedThreadPool::new(42, move |i, queues| {
+                loop {
+                    let mut task: Option<Task> = None;
+                    for n in 0..available_parallelism {
+                        match task {
+                            Some(_) => { break } 
+                            _ => {
+                                task = queues.get((i + n) % available_parallelism).unwrap().try_pop();
                             }
                         }
-                    })
-                }).collect() 
-            }))
-        }
-    }
+                    }
 
-    /// Mark all subobjects for teardown, and join all threads spawned by this object.
-    pub fn join(&self) {
-        for queue in self.queues.iter() {
-            queue.done();
-        }
+                    if task.is_none() {
+                        let done: bool;
+                        (done, task) = queues.get(i).unwrap().pop();
+                        if done { break }
+                    }
 
-        // Consume the contents under the mutex, leaving them empty.
-        let mut this = self.protected.lock().expect("the mutex is not poisoned");
-        for e in &this.waiters {
-            e.done();
-        }
-        for e in std::mem::take(&mut this.threads) {
-            let _ = e.join();
+                    if task.is_some() {
+                        task.unwrap()();
+                    }
+                }
+            }, (0..available_parallelism).map(|_| { NotificationQueue::default() }).collect()),
+            waiters: Arc::new((0..(thread_limit - available_parallelism)).map(|_| { Waiter::default() }).collect()),
+            index: AtomicUsize::new(0),
+            available_parallelism,
+            thread_limit
         }
-
-        // self.queues.clear(); BIG REVISIT
     }
 
     /// Push `f` to the first queue in `queues` whose mutex is not under contention.
     /// If no such queue is found after a single pass, blockingly push `f` to one queue.
     // REVIEW: I'm not sure `execute` is a good name. I think we want `push`, or `push_with_priority`.
-    pub fn execute<F>(&mut self, f: F, p: Priority) where F: FnOnce() -> () + Send + 'static {
+    pub fn execute<F>(&self, f: F, p: Priority) where F: FnOnce() -> () + Send + 'static {
         self.execute_task(Box::new(f), p)
     }
 
-    pub fn execute_task(&mut self, task: Task, priority: Priority) {
-        let mut task: Option<Task> = Some(task);
-        // Use SeqCst to match the default for C++'s std::atomic<unsigned>::operator++(int).
-        let i = self.index.fetch_add(1, MemoryOrdering::SeqCst); 
-        for n in 0..i {
-            task = self.queues.get((i + n) % self.available_parallelism).unwrap().try_push(task.unwrap(), priority);
-            // task = self.queues[(i + n) % self.available_parallelism].try_push(task.unwrap(), priority);
-            if task.is_none() { return }
-        }
-
-        self.queues.get(i % self.available_parallelism).unwrap().push(task.unwrap(), priority);
-    }
+    pub fn execute_task(&self, task: Task, priority: Priority) {
+        self.pool.execute_immediately(|queues| {
+            let mut task: Option<Task> = Some(task);
+            let i = self.index.fetch_add(1, MemoryOrdering::SeqCst);
+            let n = self.available_parallelism;
 
-    /// Spawn an expansion thread which will steal tasks from `queues`.
-    pub fn add_thread(&self) {
-        let mut this = self.protected.lock().expect("the mutex is not poisoned");
-        if this.threads.len() == self.thread_limit {
-            eprintln!("Unable to add thread; thread_limit reached: {}", self.thread_limit);
-            return;
-        }
+            for i in (i..i+n).map(|i| i % n) {
+                task = queues.get(i).unwrap().try_push(task.unwrap(), priority);
+                if task.is_none() { return }   
+            }
 
-        let queues = self.queues.clone();
-        let protected = self.protected.clone();
-        let available_parallelism = self.available_parallelism.clone();
-        let i = this.threads.len();
+            queues.get(i % n).unwrap().push(task.unwrap(), priority);
+        });
+    }
 
-        this.threads.push(std::thread::spawn(move ||{
+    pub fn add_thread(&mut self) {
+        let waiters = self.waiters.clone();
+        let available_parallelism = self.available_parallelism;
+        self.pool.add_thread(move |i, queues|{
             loop {
                 let mut task: Option<Task> = None;
                 for n in 0..available_parallelism {
@@ -340,34 +118,21 @@ impl PriorityTaskSystem {
                     continue;
                 }
 
-                let protected = protected.lock().expect("the mutex is not poisoned");
-
                 // Note: The following means multiple threads may wait on a single `Waiter`.
-                if protected.waiters[i - available_parallelism].wait() { break; }
-            }
-        }))
-    }
-
-    /// Get the number of waiters.
-    fn waiters_size(&self) -> usize {
-        let this = self.protected.lock().expect("the mutex is not poisoned");
-        this.threads.len() - self.available_parallelism
+                if waiters[i - available_parallelism].wait() { break; }
+            } 
+        });
     }
 
-    /// Wake every `Waiter` in `waiters`. Return true if any indicate they are `done()`, otherwise `false`.
-    pub fn wake(&mut self) -> bool {
-        for queue in self.queues.iter() {
-            if queue.wake() { return true } 
-        }
-
-        let size = self.waiters_size();
-        let this = self.protected.lock().expect("the mutex is not poisoned");
-        for n in 0..size {
-            if this.waiters[n].wake() {
-                return true;
-            }
-        }
+    // Returns true if any thread was woken.
+    pub fn wake(&self) -> bool {
+        let any_queue_woken = self.pool.execute_immediately(|queues| {
+            return queues.iter().any(|queue| queue.wake())
+        });
 
-        return false;
+        if any_queue_woken { true }
+        else { self.waiters.iter().any(|waiter| waiter.wake()) }
     }
 }
+
+unsafe impl Send for PriorityTaskSystem {}
\ No newline at end of file
diff --git a/rustport/src/stlab/notification_queue.rs b/rustport/src/stlab/notification_queue.rs
new file mode 100644
index 00000000..a344a412
--- /dev/null
+++ b/rustport/src/stlab/notification_queue.rs
@@ -0,0 +1,114 @@
+use crate::stlab::coarse_priority_queue::{Priority, CoarsePriorityQueue};
+
+/// The fields of `NotificationQueue` which must be protected by a `Mutex`.
+struct NotificationQueueProtectedData <T> {
+    queue: CoarsePriorityQueue<T>,
+    done: bool,
+    waiting: bool,
+}
+
+impl<T> std::default::Default for NotificationQueueProtectedData<T> {
+    fn default() -> Self {
+        Self {
+            queue: CoarsePriorityQueue::new(),
+            done: false,
+            waiting: false
+        }
+    }
+}
+
+/// A threadsafe priority queue.
+pub struct NotificationQueue<T> {
+    // In the C++ implementation, we use a single lock for multiple data fields.
+    // In Rust, we require exactly one mutex per protected field. 
+    // So, put protected fields into a separate struct, and lock on that.
+    // Note the transformation to use multiple locks is non-trivial, because 
+    // this would require the ordering of acquired locks to be identical in all
+    // code paths to prevent deadlock.
+    protected: std::sync::Mutex<NotificationQueueProtectedData<T>>,
+    ready: std::sync::Condvar,
+}
+
+impl<T> std::default::Default for NotificationQueue<T> {
+    fn default() -> Self {
+        Self {
+            protected: std::sync::Mutex::new(NotificationQueueProtectedData::<T>::default()),
+            ready: std::sync::Condvar::new(),
+        }
+    }
+}
+
+impl<T> NotificationQueue<T> {
+
+    /// Try to pop from the queue without blocking.
+    /// Returns `None` if our mutex is already locked or if the queue is empty.
+    pub fn try_pop(&self) -> Option<T> {
+        if let Ok(ref mut this) = self.protected.try_lock() {
+            return this.queue.pop();
+        } 
+        return None;
+    }
+
+    /// If waiting in `pop()`, wakes and returns true. Otherwise returns false.
+    pub fn wake(&self) -> bool {
+        if let Ok(ref mut this) = self.protected.try_lock() {
+            if !this.waiting {
+                return false;
+            }
+            this.waiting = false; // triggers wake
+        }
+        self.ready.notify_one();
+        return true;
+    }
+
+    /// Pop from the queue, suspending the current thread until an element is available.
+    /// The returned `bool` indicates if this object is `done()`.
+    pub fn pop(&self) -> (bool, Option<T>) {
+        let mut this = self.protected.lock().expect("the mutex is not poisoned");
+        this.waiting = true;
+        while !this.queue.is_empty() && !this.done && this.waiting {
+            this = self.ready.wait(this).expect("the mutex is not poisoned");
+        }
+        this.waiting = false;
+        if this.queue.is_empty() {
+            return (this.done, None);
+        } 
+        return (false, this.queue.pop());
+    }
+
+    /// Mark this object for teardown, and wake any thread awaiting an available element in `pop()`.
+    pub fn done(&self) {
+        {
+            let mut this = self.protected.lock().expect("the mutex is not poisoned");
+            this.done = true
+        }
+        self.ready.notify_one();
+    }
+
+    /// Try to push `element` to the queue without blocking, returning `element` if our mutex is already locked.
+    /// If the push succeeds, wake a thread which may be awaiting an element in `pop()`.
+    pub fn try_push(&self, element: T, priority: Priority) -> Option<T> {
+        if let Ok(ref mut this) = self.protected.try_lock() {
+            this.queue.push(element, priority);
+        } else {
+            return Some(element);
+        }
+        
+        // We successfully locked the mutex, did our push, and released the lock.
+        self.ready.notify_one();
+        return None;
+    }
+
+    /// Push `element` to the queue, blocking if our mutex is already locked.
+    /// When the push succeeds, wake a thread which may be awaiting an element in `pop()`.
+    pub fn push(&self, element: T, priority: Priority) {
+        {
+            let mut this = self.protected.lock().expect("the mutex is not poisoned");
+            this.queue.push(element, priority);
+        }
+        self.ready.notify_one();
+    }
+}
+
+unsafe impl <T> Send for NotificationQueue<T> {}
+unsafe impl <T> Sync for NotificationQueue<T> {}
\ No newline at end of file
diff --git a/rustport/src/stlab/prioritized.rs b/rustport/src/stlab/prioritized.rs
deleted file mode 100644
index fdf6cfd5..00000000
--- a/rustport/src/stlab/prioritized.rs
+++ /dev/null
@@ -1,59 +0,0 @@
-use std::cmp::Ordering;
-
-/// A `usize` constraining valid values to [0, 4) with runtime assertions.
-#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
-#[repr(C)]
-pub struct Priority(pub usize);
-
-impl Priority {
-    pub fn new(value: usize) -> Self {
-        assert!((0..4).contains(&value), "Priorities must be in [0, 4)");
-        Self(value)
-    }
-
-    /// Returns a usize of the form 0bXX000000 where XX is a binary representation of this priority.
-    /// The value of this priority is guaranteed to fit in two bits because `Priority` values are constrained to [0b00, 0b11].
-    pub fn to_highbit_mask(&self) -> usize {
-        &self.0 << (usize::BITS - 2)
-    }
-}
-
-/// Pairs an instance of `T` with a `Priority`. 
-/// Equality and ordering of a Prioritized<T> only considers `priority`, disregarding `element`.
-pub struct Prioritized <T> {
-    priority: Priority,
-    element: T
-}
-
-impl <T> Prioritized<T> {
-    pub fn new(priority: Priority, element: T) -> Self {
-        Prioritized { priority, element }
-    }
-
-    pub fn take_element(self) -> T {
-        self.element
-    }
-}
-
-impl<T> PartialEq for Prioritized <T> {
-    #[inline]
-    fn eq(&self, other: &Self) -> bool {
-        self.priority == other.priority
-    }
-}
-
-impl <T> Eq for Prioritized <T> {}
-
-impl<T> PartialOrd for Prioritized <T> {
-    #[inline]
-    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-        self.priority.partial_cmp(&other.priority)
-    }
-}
-
-impl<T> Ord for Prioritized <T> {
-    #[inline]
-    fn cmp(&self, other: &Self) -> Ordering {
-        self.priority.cmp(&other.priority)
-    }
-}
\ No newline at end of file
diff --git a/rustport/src/stlab/scoped_thread_pool.rs b/rustport/src/stlab/scoped_thread_pool.rs
new file mode 100644
index 00000000..ea32bced
--- /dev/null
+++ b/rustport/src/stlab/scoped_thread_pool.rs
@@ -0,0 +1,54 @@
+use std::thread::JoinHandle;
+
+pub struct ScopedThreadPool<T: Default + Send + Sync + 'static> {
+    threads: Vec<JoinHandle<()>>,
+    // SAFETY: We store this pointer as a `*mut` so it can be dropped via Box::from_raw, but
+    // we only ever hand out const-refs to the underlying data. 
+    data: *mut T
+}
+
+impl <T: Default + Send + Sync + 'static> Drop for ScopedThreadPool <T> {
+    fn drop(&mut self) {
+        for thread in std::mem::take(&mut self.threads) {
+            let _ = thread.join(); // TODO handle error? what's the rule for drop?
+        }
+
+        // SAFETY: We only call from_raw once, in this `drop`. We do not permit copies of this
+        // object, which would present the risk of a double-free on the copied pointer.
+        unsafe { drop(Box::from_raw(self.data)) }; 
+    }
+}
+
+impl <T: Default + Send + Sync + 'static> ScopedThreadPool <T> {
+    pub fn new<F: Fn(usize, &T) + Copy + Send + 'static>(num_threads: usize, f: F, data: T) -> Self {
+        let data = Box::into_raw(Box::new(data));
+        Self {
+            threads: (0..num_threads).map(|i| {
+                let f = f.clone();
+                // SAFETY: We only hand out immutable references to this data.
+                let data: &T = unsafe { &*data };
+                std::thread::spawn(move || {
+                    f(i, data);
+                })
+            }).collect(),
+            data
+        }
+    }
+
+    pub fn execute_immediately<Return, F: FnOnce(&T) -> Return>(&self, f: F) -> Return {
+        // SAFETY: We only hand out immutable references to this data.
+        let data: &T = unsafe { &*self.data };
+        f(data)
+    }
+
+    pub fn add_thread<F: Fn(usize, &T) + Send + 'static>(&mut self, f: F) {
+        let i = self.threads.len();
+        self.threads.push({
+            // SAFETY: We only hand out immutable references to this data.
+            let data: &T = unsafe { &*self.data };
+            std::thread::spawn(move || {
+                f(i, data);
+            })
+        });
+    }
+}
\ No newline at end of file
diff --git a/rustport/src/stlab/waiter.rs b/rustport/src/stlab/waiter.rs
new file mode 100644
index 00000000..59947507
--- /dev/null
+++ b/rustport/src/stlab/waiter.rs
@@ -0,0 +1,75 @@
+use std::sync::Mutex;
+
+// REVISIT: It may be wise to reimplement this in terms of std::thread::park.
+
+/// The fields of `Waiter` which must be protected by a `Mutex`.
+struct WaiterProtectedData {
+    waiting: bool,
+    done: bool,
+}
+
+/// A utility for suspending a thread using a condition variable.
+pub struct Waiter {
+    protected: Mutex<WaiterProtectedData>,
+    ready: std::sync::Condvar,
+}
+
+impl Default for Waiter {
+    fn default() -> Self {
+        Waiter::new() 
+    }
+}
+
+impl Waiter {
+
+    /// Constructs a new Waiter, with `waiting` and `done` set to `false`. 
+    pub fn new() -> Self {
+        Self {
+            protected: Mutex::new(WaiterProtectedData {
+                waiting: false,
+                done: false,
+            }),
+            ready: std::sync::Condvar::new()
+        }
+    }
+
+    /// Sets `done` to `true`, and notifies one waiter of our condition variable.
+    pub fn done(&self) {
+        {
+            let mut this = self.protected.lock().expect("the mutex is not poisoned");
+            this.done = true;
+        }
+        self.ready.notify_one();
+    }
+
+    /// Sets waiting to `false`. If waiting was `true`, wake one waiter and return `true`. Otherwise, return `false`.
+    /// If `try_lock` fails, return `false`. (REVIEW: why?)
+    /// (REVIEW: is it redundant to express that `waiting` and `done` are accesed under a mutex?)
+    pub fn wake(&self) -> bool {
+        if let Ok(ref mut this) = self.protected.try_lock() {
+            if !this.waiting {
+                return false;
+            }
+            this.waiting = false;
+        } else {
+            return false;
+        }
+        self.ready.notify_one();
+        return true;
+    }
+
+    /// Block this thread until `wake()` or `done()` is called.
+    /// Returns `true` if `done()` has been called, otherwise `false`.
+    pub fn wait(&self) -> bool {
+        let mut this = self.protected.lock().expect("the mutex is not poisoned");
+        this.waiting = true;
+        while this.waiting && !this.done {
+            this = self.ready.wait(this).expect("the mutex is not poisoned");
+        }
+        this.waiting = false;
+        return this.done;
+    }
+}
+
+unsafe impl Send for Waiter {}
+unsafe impl Sync for Waiter {}
\ No newline at end of file

From ca418b8cfbee8d2830e49dd2126d315d36f2c636 Mon Sep 17 00:00:00 2001
From: nickpdemarco <nickpdemarco@gmail.com>
Date: Tue, 28 Nov 2023 17:41:09 -0500
Subject: [PATCH 07/13] Contracts

---
 rustport/src/stlab/coarse_priority_queue.rs |  81 +++++++++------
 rustport/src/stlab/drop_join_thread_pool.rs | 101 +++++++++++++++++++
 rustport/src/stlab/mod.rs                   | 106 +++++++++++---------
 rustport/src/stlab/notification_queue.rs    |  42 ++++----
 rustport/src/stlab/scoped_thread_pool.rs    |  54 ----------
 rustport/src/stlab/waiter.rs                |  14 +--
 6 files changed, 236 insertions(+), 162 deletions(-)
 create mode 100644 rustport/src/stlab/drop_join_thread_pool.rs
 delete mode 100644 rustport/src/stlab/scoped_thread_pool.rs

diff --git a/rustport/src/stlab/coarse_priority_queue.rs b/rustport/src/stlab/coarse_priority_queue.rs
index c32676aa..a14166ab 100644
--- a/rustport/src/stlab/coarse_priority_queue.rs
+++ b/rustport/src/stlab/coarse_priority_queue.rs
@@ -1,5 +1,40 @@
 use std::cmp::Ordering;
 
+/// A priority queue with three priorities. Elements with equal priorities are popped in FIFO order.
+pub struct CoarsePriorityQueue<T> {
+    inner: std::collections::BinaryHeap<Prioritized<T>>,
+    count: usize,
+}
+
+impl <T> CoarsePriorityQueue<T> {
+    /// Creates a new, empty `CoarsePriorityQueue`.
+    pub fn new() -> Self {
+        Self {
+            inner: std::collections::BinaryHeap::new(),
+            count: 0
+        }
+    }
+
+    /// Checks if the queue is empty.
+    pub fn is_empty(&self) -> bool {
+        self.inner.is_empty()
+    }
+
+    /// Removes the greatest item from the queue at returns it, or None if it is empty.
+    pub fn pop(&mut self) -> Option<T> {
+        self.inner.pop().and_then(|prioritized| {
+            Some(prioritized.take_inner())
+        })
+    }
+
+    /// Pushes an item onto the queue at the given `priority`.
+    pub fn push(&mut self, item: T, priority: Priority) {
+        self.inner.push(Prioritized::new(item, priority, self.count));
+        self.count += 1;
+    }
+}
+
+/// The three priorities used by `CoarsePriorityQueue`.
 #[derive(Eq, PartialEq, Copy, Clone)]
 #[repr(C)]
 pub enum Priority {
@@ -8,6 +43,16 @@ pub enum Priority {
     High
 }
 
+/// Use of `Priority` and `Prioritized` requires the client maintain a nondecreasing count which
+/// is supplied to the constructor of `Prioritized<T>`. That count is bitwise-OR'd with the
+/// result of `highbit_mask` for a given `Priority`. The resulting `usize` is used for ordering and
+/// equality of a `Prioritized<T>`. The purpose of this is to implement FIFO ordering (with coarse
+/// priorities) with a single BinaryHeap.
+///
+/// Note: This implies the value of the count must not exceed
+/// 0b001111111111111111111111111111111111111111111111111111111111111, or 2,305,843,009,213,693,951,
+/// but this is considered unlikely enough to not be exposed in the public documentation for
+/// CoarsePriorityQueue.
 impl Priority {
     const fn highbit_mask(&self) -> usize {
         match self {
@@ -17,6 +62,7 @@ impl Priority {
         }
     }
 
+    /// Precondition: count < 2,305,843,009,213,693,951
     fn merge_priority_count(&self, count: usize) -> usize {
         self.highbit_mask() | count
     }
@@ -29,13 +75,15 @@ impl PartialOrd for Priority {
 }
 
 /// Pairs an instance of `T` with a `Priority`. 
-/// Equality and ordering of a Prioritized<T> only considers `priority`, disregarding `inner`.
+/// Equality and ordering of a Prioritized<T> only considers `priority`, and disregards `inner`.
 struct Prioritized<T> {
     inner: T,
     priority: usize
 }
 
 impl <T> Prioritized<T> {
+
+    /// Precondition: count must be less than 2,305,843,009,213,693,951.
     pub fn new(inner: T, priority: Priority, count: usize) -> Self {
         Prioritized { inner, priority: priority.merge_priority_count(count) }
     }
@@ -66,35 +114,4 @@ impl<T> Ord for Prioritized <T> {
     fn cmp(&self, other: &Self) -> Ordering {
         self.priority.cmp(&other.priority)
     }
-}
-
-// A priority queue with three priorities. Elements with equal priorities are popped in FIFO order.
-pub struct CoarsePriorityQueue<T> {
-    inner: std::collections::BinaryHeap<Prioritized<T>>,
-    count: usize,
-}
-
-impl <T> CoarsePriorityQueue<T> {
-
-    pub fn new() -> Self {
-        Self {
-            inner: std::collections::BinaryHeap::new(),
-            count: 0
-        }
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.inner.is_empty()
-    }
-
-    pub fn pop(&mut self) -> Option<T> {
-        self.inner.pop().and_then(|prioritized| {
-            Some(prioritized.take_inner())
-        })
-    }
-
-    pub fn push(&mut self, item: T, priority: Priority) {
-        self.inner.push(Prioritized::new(item, priority, self.count));
-        self.count += 1;
-    }
 }
\ No newline at end of file
diff --git a/rustport/src/stlab/drop_join_thread_pool.rs b/rustport/src/stlab/drop_join_thread_pool.rs
new file mode 100644
index 00000000..5e271b61
--- /dev/null
+++ b/rustport/src/stlab/drop_join_thread_pool.rs
@@ -0,0 +1,101 @@
+use std::{thread::JoinHandle, io::Write};
+
+/// A thread pool which joins all spawned threads when dropped.
+///
+/// This pool also holds a pointer to a heap-allocated object which is shared by all spawned
+/// threads. As such, it must be Send + Sync, and 'static (having no non-static references). When
+/// tasks in this pool are spawned, they are passed an immutable reference to said object (there is
+/// no way to acquire a mutable reference).
+///
+/// Note: it remains to be seen if it is possible (or useful) to loosen the 'static requirement here
+/// to instead allow any lifetimes which do not outlive this pool. Early attempts at this polluted
+/// the API with explicit lifetimes. See
+/// https://users.rust-lang.org/t/access-to-implicit-lifetime-of-containing-object-aka-self-lifetime/18917
+/// for a relevant disucssion.
+pub struct DropJoinThreadPool<T: Default + Send + Sync + 'static> {
+    threads: Vec<JoinHandle<()>>,
+    // SAFETY: We store this pointer as a `*mut` so it can be dropped via Box::from_raw, but
+    // we only ever hand out immutable references to the pointee.
+    data: *mut T
+}
+
+impl <T: Default + Send + Sync + 'static> Drop for DropJoinThreadPool <T> {
+
+    /// Join all spawned threads, and drop `data` manually.
+    fn drop(&mut self) {
+        for thread in std::mem::take(&mut self.threads) {
+            let _ = thread.join(); // TODO handle error? what's the rule for drop?
+        }
+
+        // SAFETY: We only call from_raw once, in this `drop`. We do not permit copies of this
+        // object, which would present the risk of a double-free on the copied pointer.
+        unsafe { drop(Box::from_raw(self.data)) };
+    }
+}
+
+impl <T: Default + Send + Sync + 'static> DropJoinThreadPool <T> {
+
+    /// Creates a new `DropJoinThreadPool` with a maximum thread capacity of `thread_limit`, moving
+    /// `data` onto the heap.
+    pub fn new(thread_limit: usize, data: T) -> Self {
+        Self {
+            threads: Vec::<JoinHandle<()>>::with_capacity(thread_limit),
+            data: Box::into_raw(Box::new(data))
+        }
+    }
+
+    /// Performs an operation with an immutable reference to this pool's `data` on the current
+    /// thread.
+    pub fn execute_immediately<Return, F: FnOnce(&T) -> Return>(&self, f: F) -> Return {
+        // SAFETY: We only hand out immutable references to this data.
+        let data: &T = unsafe { &*self.data };
+        f(data)
+    }
+
+    /// Spawns at most `n` threads with the task `f` until this pool's thread capacity is reached.
+    ///
+    /// If spawning all `n` threads would exceed this pool's thread capacity, a message is logged to
+    /// stderr.
+    pub fn spawn_n<F: Fn(usize, &T) + Copy + Send + 'static>(&mut self, f: F, mut n: usize) {
+        if self.threads.len() + n > self.threads.capacity() {
+            Self::log_err(b"stlab: Unable to spawn all threads; capacity reached.");
+            n = self.threads.capacity() - self.threads.len();
+        }
+
+        self.threads.extend((0..n).map(|i| {
+            let f = f.clone();
+            // SAFETY: We only hand out immutable references to this data.
+            let data: &T = unsafe { &*self.data };
+            std::thread::spawn(move||{
+                f(i, data);
+            })
+        }));
+    }
+
+    /// Spawns a single thread with the task `f`, unless doing so would exceed this pool's thread
+    /// capacity.
+    ///
+    /// If the thread is not spawned due to inadequate space, a message is logged to stderr.
+    pub fn spawn<F: Fn(usize, &T) + Send + 'static>(&mut self, f: F) {
+        let i = self.threads.len();
+
+        if i == self.threads.capacity() {
+            Self::log_err(b"stlab: Unable to spawn thread; capacity reached.");
+            return;
+        }
+
+        self.threads.push({
+            // SAFETY: We only hand out immutable references to this data.
+            let data: &T = unsafe { &*self.data };
+            std::thread::spawn(move || {
+                f(i, data);
+            })
+        });
+    }
+
+    /// Write `buf` to stderr, discarding the ioresult.
+    fn log_err(buf: &[u8]) {
+        let mut stderr = std::io::stderr().lock();
+        let _ = stderr.write_all(buf);
+    }
+}
\ No newline at end of file
diff --git a/rustport/src/stlab/mod.rs b/rustport/src/stlab/mod.rs
index d3cc2152..14caee35 100644
--- a/rustport/src/stlab/mod.rs
+++ b/rustport/src/stlab/mod.rs
@@ -4,10 +4,10 @@ use std::sync::Arc;
 use std::sync::atomic::{AtomicUsize, Ordering as MemoryOrdering};
 
 mod coarse_priority_queue;
-pub use coarse_priority_queue::{Priority, CoarsePriorityQueue};
+pub use coarse_priority_queue::Priority;
 
-mod scoped_thread_pool;
-use scoped_thread_pool::ScopedThreadPool;
+mod drop_join_thread_pool;
+use drop_join_thread_pool::DropJoinThreadPool;
 
 mod waiter;
 pub use waiter::Waiter;
@@ -17,12 +17,25 @@ use notification_queue::NotificationQueue;
 
 /// A type-erased, heap-allocated function object.
 pub type Task = Box<dyn FnOnce()->() + Send>;
+
+/// A portable work-stealing task scheduler with three priorities.
+///
+/// By default, this scheduler spins up a number of threads corresponding to the amount of
+/// parallelism available on the target platform, namely, std::thread::available_parallelism() - 1.
+/// Each thread is assigned a threadsafe priority queue. To reduce contention on push and pop
+/// operations, a thread will first attempt to acquire the lock for its own queue without blocking.
+/// If that fails, it will attempt the same non-blocking push/pop for each other priority queue in
+/// the scheduler. Finally, if each of those attempts also fail, the thread will attempt a blocking
+/// push/pop on its own priority queue.
+///
+/// The `add_thread` API is intended to mitigate the possibility of deadlock by spinning up a new
+/// worker thread that non-blockingly polls all of the system's priority queues, and then sleeps
+/// until `wake()` is called.
 pub struct PriorityTaskSystem {
-    pool: ScopedThreadPool<Vec<NotificationQueue<Task>>>,
+    pool: DropJoinThreadPool<Vec<NotificationQueue<Task>>>,
     waiters: Arc<Vec<Waiter>>,
     index: AtomicUsize,
     available_parallelism: usize,
-    thread_limit: usize,
 }
 
 impl Drop for PriorityTaskSystem {
@@ -40,39 +53,36 @@ impl Drop for PriorityTaskSystem {
 }
 
 impl PriorityTaskSystem {
+    /// Creates a new PriorityTaskSystem.
     pub fn new() -> Self {
         // SAFETY: We know 1 is not 0.
         let nonzero_available_parallelism = std::thread::available_parallelism().unwrap_or(unsafe { NonZeroUsize::new_unchecked(1) });
         let available_parallelism = max(usize::from(nonzero_available_parallelism), 2) - 1;
         let thread_limit = max(9, available_parallelism * 4 + 1);
-        Self {
-            pool: ScopedThreadPool::new(42, move |i, queues| {
-                loop {
-                    let mut task: Option<Task> = None;
-                    for n in 0..available_parallelism {
-                        match task {
-                            Some(_) => { break } 
-                            _ => {
-                                task = queues.get((i + n) % available_parallelism).unwrap().try_pop();
-                            }
-                        }
-                    }
-
-                    if task.is_none() {
-                        let done: bool;
-                        (done, task) = queues.get(i).unwrap().pop();
-                        if done { break }
-                    }
-
-                    if task.is_some() {
-                        task.unwrap()();
-                    }
+        let queues = (0..available_parallelism).map(|_| { NotificationQueue::default() }).collect();
+
+        let mut pool = DropJoinThreadPool::new(thread_limit, queues);
+        pool.spawn_n(move |i, queues| {
+            loop {
+                let mut task = Self::try_pop(queues, i, available_parallelism);
+
+                if task.is_none() {
+                    let done: bool;
+                    (done, task) = queues.get(i).unwrap().pop();
+                    if done { break }
                 }
-            }, (0..available_parallelism).map(|_| { NotificationQueue::default() }).collect()),
+
+                if task.is_some() {
+                    task.unwrap()();
+                }
+            }
+        }, available_parallelism);
+
+        Self {
+            pool,
             waiters: Arc::new((0..(thread_limit - available_parallelism)).map(|_| { Waiter::default() }).collect()),
             index: AtomicUsize::new(0),
             available_parallelism,
-            thread_limit
         }
     }
 
@@ -89,37 +99,30 @@ impl PriorityTaskSystem {
             let i = self.index.fetch_add(1, MemoryOrdering::SeqCst);
             let n = self.available_parallelism;
 
+            // Attempt to push to a queue without blocking, starting with ours.
             for i in (i..i+n).map(|i| i % n) {
                 task = queues.get(i).unwrap().try_push(task.unwrap(), priority);
-                if task.is_none() { return }   
+                if task.is_none() { return } // An empty return means push was successful.
             }
 
+            // Otherwise, attempt to push to our queue, with blocking.
             queues.get(i % n).unwrap().push(task.unwrap(), priority);
         });
     }
 
+    /// Add a work-stealing thread to the scheduler to mitigate deadlock.
     pub fn add_thread(&mut self) {
         let waiters = self.waiters.clone();
-        let available_parallelism = self.available_parallelism;
-        self.pool.add_thread(move |i, queues|{
+        let n = self.available_parallelism;
+        self.pool.spawn(move |i, queues|{
             loop {
-                let mut task: Option<Task> = None;
-                for n in 0..available_parallelism {
-                    match task {
-                        Some(_) => { break } 
-                        _ => {
-                            task = queues.get((i + n) % available_parallelism).unwrap().try_pop()
-                        }
-                    }
-                }
-
-                if task.is_some() {
-                    task.unwrap()();
+                if let Some(task) = Self::try_pop(queues, i, n) {
+                    task();
                     continue;
                 }
 
                 // Note: The following means multiple threads may wait on a single `Waiter`.
-                if waiters[i - available_parallelism].wait() { break; }
+                if waiters[i - n].wait() { break; }
             } 
         });
     }
@@ -133,6 +136,19 @@ impl PriorityTaskSystem {
         if any_queue_woken { true }
         else { self.waiters.iter().any(|waiter| waiter.wake()) }
     }
+
+    /// Attempt to non-blockingly pop a task from each queue in the system, starting at index
+    /// `starting_at`.
+    fn try_pop(queues: &Vec<NotificationQueue<Task>>, starting_at: usize, modulo: usize) -> Option<Task> {
+        for i in (starting_at..starting_at+modulo).map(|i| i % modulo)  {
+            match queues.get(i).unwrap().try_pop() {
+                Some(t) => Some(t),
+                None => continue
+            };
+        }
+        None
+    }
+
 }
 
 unsafe impl Send for PriorityTaskSystem {}
\ No newline at end of file
diff --git a/rustport/src/stlab/notification_queue.rs b/rustport/src/stlab/notification_queue.rs
index a344a412..c9807ee5 100644
--- a/rustport/src/stlab/notification_queue.rs
+++ b/rustport/src/stlab/notification_queue.rs
@@ -1,30 +1,7 @@
 use crate::stlab::coarse_priority_queue::{Priority, CoarsePriorityQueue};
 
-/// The fields of `NotificationQueue` which must be protected by a `Mutex`.
-struct NotificationQueueProtectedData <T> {
-    queue: CoarsePriorityQueue<T>,
-    done: bool,
-    waiting: bool,
-}
-
-impl<T> std::default::Default for NotificationQueueProtectedData<T> {
-    fn default() -> Self {
-        Self {
-            queue: CoarsePriorityQueue::new(),
-            done: false,
-            waiting: false
-        }
-    }
-}
-
 /// A threadsafe priority queue.
 pub struct NotificationQueue<T> {
-    // In the C++ implementation, we use a single lock for multiple data fields.
-    // In Rust, we require exactly one mutex per protected field. 
-    // So, put protected fields into a separate struct, and lock on that.
-    // Note the transformation to use multiple locks is non-trivial, because 
-    // this would require the ordering of acquired locks to be identical in all
-    // code paths to prevent deadlock.
     protected: std::sync::Mutex<NotificationQueueProtectedData<T>>,
     ready: std::sync::Condvar,
 }
@@ -111,4 +88,21 @@ impl<T> NotificationQueue<T> {
 }
 
 unsafe impl <T> Send for NotificationQueue<T> {}
-unsafe impl <T> Sync for NotificationQueue<T> {}
\ No newline at end of file
+unsafe impl <T> Sync for NotificationQueue<T> {}
+
+/// The fields of `NotificationQueue` which must be protected by a `Mutex`.
+struct NotificationQueueProtectedData <T> {
+    queue: CoarsePriorityQueue<T>,
+    done: bool,
+    waiting: bool,
+}
+
+impl<T> std::default::Default for NotificationQueueProtectedData<T> {
+    fn default() -> Self {
+        Self {
+            queue: CoarsePriorityQueue::new(),
+            done: false,
+            waiting: false
+        }
+    }
+}
diff --git a/rustport/src/stlab/scoped_thread_pool.rs b/rustport/src/stlab/scoped_thread_pool.rs
deleted file mode 100644
index ea32bced..00000000
--- a/rustport/src/stlab/scoped_thread_pool.rs
+++ /dev/null
@@ -1,54 +0,0 @@
-use std::thread::JoinHandle;
-
-pub struct ScopedThreadPool<T: Default + Send + Sync + 'static> {
-    threads: Vec<JoinHandle<()>>,
-    // SAFETY: We store this pointer as a `*mut` so it can be dropped via Box::from_raw, but
-    // we only ever hand out const-refs to the underlying data. 
-    data: *mut T
-}
-
-impl <T: Default + Send + Sync + 'static> Drop for ScopedThreadPool <T> {
-    fn drop(&mut self) {
-        for thread in std::mem::take(&mut self.threads) {
-            let _ = thread.join(); // TODO handle error? what's the rule for drop?
-        }
-
-        // SAFETY: We only call from_raw once, in this `drop`. We do not permit copies of this
-        // object, which would present the risk of a double-free on the copied pointer.
-        unsafe { drop(Box::from_raw(self.data)) }; 
-    }
-}
-
-impl <T: Default + Send + Sync + 'static> ScopedThreadPool <T> {
-    pub fn new<F: Fn(usize, &T) + Copy + Send + 'static>(num_threads: usize, f: F, data: T) -> Self {
-        let data = Box::into_raw(Box::new(data));
-        Self {
-            threads: (0..num_threads).map(|i| {
-                let f = f.clone();
-                // SAFETY: We only hand out immutable references to this data.
-                let data: &T = unsafe { &*data };
-                std::thread::spawn(move || {
-                    f(i, data);
-                })
-            }).collect(),
-            data
-        }
-    }
-
-    pub fn execute_immediately<Return, F: FnOnce(&T) -> Return>(&self, f: F) -> Return {
-        // SAFETY: We only hand out immutable references to this data.
-        let data: &T = unsafe { &*self.data };
-        f(data)
-    }
-
-    pub fn add_thread<F: Fn(usize, &T) + Send + 'static>(&mut self, f: F) {
-        let i = self.threads.len();
-        self.threads.push({
-            // SAFETY: We only hand out immutable references to this data.
-            let data: &T = unsafe { &*self.data };
-            std::thread::spawn(move || {
-                f(i, data);
-            })
-        });
-    }
-}
\ No newline at end of file
diff --git a/rustport/src/stlab/waiter.rs b/rustport/src/stlab/waiter.rs
index 59947507..30712389 100644
--- a/rustport/src/stlab/waiter.rs
+++ b/rustport/src/stlab/waiter.rs
@@ -2,12 +2,6 @@ use std::sync::Mutex;
 
 // REVISIT: It may be wise to reimplement this in terms of std::thread::park.
 
-/// The fields of `Waiter` which must be protected by a `Mutex`.
-struct WaiterProtectedData {
-    waiting: bool,
-    done: bool,
-}
-
 /// A utility for suspending a thread using a condition variable.
 pub struct Waiter {
     protected: Mutex<WaiterProtectedData>,
@@ -72,4 +66,10 @@ impl Waiter {
 }
 
 unsafe impl Send for Waiter {}
-unsafe impl Sync for Waiter {}
\ No newline at end of file
+unsafe impl Sync for Waiter {}
+
+/// The fields of `Waiter` which must be protected by a `Mutex`.
+struct WaiterProtectedData {
+    waiting: bool,
+    done: bool,
+}
\ No newline at end of file

From 7aa74bc7e7849e61291f172a4656609c561ea574 Mon Sep 17 00:00:00 2001
From: nickpdemarco <nickpdemarco@gmail.com>
Date: Tue, 28 Nov 2023 18:28:39 -0500
Subject: [PATCH 08/13] cleanup

---
 rustport/cppshim/include/bindings.hpp  | 50 ++++----------------------
 rustport/src/stlab/mod.rs              |  4 +--
 stlab/concurrency/default_executor.hpp | 40 ++++++++++++++++-----
 3 files changed, 40 insertions(+), 54 deletions(-)

diff --git a/rustport/cppshim/include/bindings.hpp b/rustport/cppshim/include/bindings.hpp
index 224fa442..f6b88b30 100644
--- a/rustport/cppshim/include/bindings.hpp
+++ b/rustport/cppshim/include/bindings.hpp
@@ -4,22 +4,9 @@
 #include <type_traits>
 #include <utility>
 
-namespace stlab {
-inline namespace v1 {
-namespace detail {
+namespace rust {
 
-enum class executor_priority { high, medium, low };
-
-inline auto bridge_priority(executor_priority p) -> Priority {
-    switch (p) {
-        case executor_priority::high: return Priority::High;
-        case executor_priority::medium: return Priority::Default;
-        case executor_priority::low: return Priority::Low;
-        default: return Priority::Default;
-    }
-}
-
-/// @brief Asynchronously invoke f on the default_executor.
+/// @brief Asynchronously invoke f on the rust-backed executor.
 /// @tparam F function object type
 /// @param f a function object.
 /// @return the result of calling `execute`.
@@ -34,45 +21,20 @@ auto enqueue(F f) {
     });
 }
 
-/// @brief Asynchronously invoke `f` on the default_executor with priority `p`.
+/// @brief Asynchronously invoke `f` on the rust-backed executor with priority `p`.
 /// @tparam F function object type
 /// @param f a function object.
 /// @param priority 
 /// @return the value returned by `execute_priority`.
 template <class F, class = std::enable_if_t<std::is_invocable_r< void, F >::value> >
-auto enqueue_priority(F f, executor_priority p) {
+auto enqueue_priority(F f, Priority p) {
     return execute_priority(new F(std::move(f)), [](void* f_) {
         auto f = static_cast<F*>(f_);
         try {
             (*f)();
         } catch (...) {}
         delete f;
-    }, bridge_priority(p));
+    }, p);
 }
 
-/// @brief A thin invokable wrapper around `enqueue_priority`.
-/// @tparam Priority the priority at which all given function objects will be enqueued.
-template <executor_priority Priority>
-struct executor_type {
-    using result_type = void;
-
-    /// @brief Enqueues the given task on the default_executor with this object's Priority value.
-    /// @tparam F function object type
-    /// @param f function object
-    template <class F>
-    void operator()(F&& f) const {
-        enqueue_priority(std::forward<F>(f), Priority);
-    }
-};
-
-} // namespace detail
-
-/// @brief An executor for low priority tasks, enqueued with the call operator.
-constexpr auto low_executor = detail::executor_type<detail::executor_priority::low>{};
-/// @brief An executor for standard priority tasks, enqueued with the call operator.
-constexpr auto default_executor = detail::executor_type<detail::executor_priority::medium>{};
-/// @brief An executor for high priority tasks, enqueued with the call operator.
-constexpr auto high_executor = detail::executor_type<detail::executor_priority::high>{};
-
-} // inline namespace v1
-} // namespace stlab
+}
\ No newline at end of file
diff --git a/rustport/src/stlab/mod.rs b/rustport/src/stlab/mod.rs
index 14caee35..eb9b13ba 100644
--- a/rustport/src/stlab/mod.rs
+++ b/rustport/src/stlab/mod.rs
@@ -142,11 +142,11 @@ impl PriorityTaskSystem {
     fn try_pop(queues: &Vec<NotificationQueue<Task>>, starting_at: usize, modulo: usize) -> Option<Task> {
         for i in (starting_at..starting_at+modulo).map(|i| i % modulo)  {
             match queues.get(i).unwrap().try_pop() {
-                Some(t) => Some(t),
+                Some(t) => { return Some(t) }
                 None => continue
             };
         }
-        None
+        return None;
     }
 
 }
diff --git a/stlab/concurrency/default_executor.hpp b/stlab/concurrency/default_executor.hpp
index 8edd3fd0..5b3f9080 100644
--- a/stlab/concurrency/default_executor.hpp
+++ b/stlab/concurrency/default_executor.hpp
@@ -11,12 +11,6 @@
 
 #include <stlab/config.hpp>
 
-#if STLAB_TASK_SYSTEM(EXPERIMENTAL_RUST)
-
-#include "bindings.hpp"
-
-#else
-
 #include <stlab/pre_exit.hpp>
 
 #include <stlab/concurrency/set_current_thread_name.hpp>
@@ -39,6 +33,8 @@
 #include <condition_variable>
 #include <thread>
 #include <vector>
+#elif STLAB_TASK_SYSTEM(EXPERIMENTAL_RUST)
+#include "bindings.hpp"
 #endif
 
 /**************************************************************************************************/
@@ -491,6 +487,36 @@ struct executor_type {
 
 /**************************************************************************************************/
 
+#if STLAB_TASK_SYSTEM(EXPERIMENTAL_RUST)
+
+inline auto bridge_priority(executor_priority p) -> Priority {
+    switch (p) {
+        case executor_priority::high: return Priority::High;
+        case executor_priority::medium: return Priority::Default;
+        case executor_priority::low: return Priority::Low;
+        default: return Priority::Default;
+    }
+}
+
+/// @brief A thin invokable wrapper around `enqueue_priority`.
+/// @tparam Priority the priority at which all given function objects will be enqueued.
+template <executor_priority Priority>
+struct executor_type {
+    using result_type = void;
+
+    /// @brief Enqueues the given task on the default_executor with this object's Priority value.
+    /// @tparam F function object type
+    /// @param f function object
+    template <class F>
+    void operator()(F&& f) const {
+        rust::enqueue_priority(std::forward<F>(f), bridge_priority(Priority));
+    }
+};
+
+#endif // STLAB_TASK_SYSTEM(EXPERIMENTAL_RUST)
+
+/**************************************************************************************************/
+
 } // namespace detail
 
 /**************************************************************************************************/
@@ -510,8 +536,6 @@ constexpr auto high_executor = detail::executor_type<detail::executor_priority::
 
 /**************************************************************************************************/
 
-#endif // STLAB_TASK_SYSTEM(EXPERIMENTAL_RUST)
-
 #endif // STLAB_CONCURRENCY_DEFAULT_EXECUTOR_HPP
 
 /**************************************************************************************************/

From 47de5dbea5e8c9fc1e67a34139e6ac6dda787f76 Mon Sep 17 00:00:00 2001
From: nickpdemarco <nickpdemarco@gmail.com>
Date: Tue, 28 Nov 2023 18:35:15 -0500
Subject: [PATCH 09/13] Run rustfmt

---
 rustport/cppshim/include/bindings.hpp       |  2 +-
 rustport/src/lib.rs                         | 48 ++++++-------
 rustport/src/stlab/coarse_priority_queue.rs | 45 ++++++------
 rustport/src/stlab/drop_join_thread_pool.rs | 16 ++---
 rustport/src/stlab/mod.rs                   | 80 +++++++++++++--------
 rustport/src/stlab/notification_queue.rs    | 17 +++--
 rustport/src/stlab/waiter.rs                |  9 ++-
 7 files changed, 120 insertions(+), 97 deletions(-)

diff --git a/rustport/cppshim/include/bindings.hpp b/rustport/cppshim/include/bindings.hpp
index f6b88b30..98f8358c 100644
--- a/rustport/cppshim/include/bindings.hpp
+++ b/rustport/cppshim/include/bindings.hpp
@@ -37,4 +37,4 @@ auto enqueue_priority(F f, Priority p) {
     }, p);
 }
 
-}
\ No newline at end of file
+}
diff --git a/rustport/src/lib.rs b/rustport/src/lib.rs
index f09e979c..41b1c1e5 100644
--- a/rustport/src/lib.rs
+++ b/rustport/src/lib.rs
@@ -1,46 +1,46 @@
-use std::{ffi::c_void, sync::Mutex};
 use once_cell::sync::Lazy;
+use std::{ffi::c_void, sync::Mutex};
 
-use stlab::{PriorityTaskSystem, Priority};
+use stlab::{Priority, PriorityTaskSystem};
 mod stlab;
 
-static TASK_SYSTEM: Lazy<Mutex<PriorityTaskSystem>> = Lazy::new(|| Mutex::new(PriorityTaskSystem::new()) );
+static TASK_SYSTEM: Lazy<Mutex<PriorityTaskSystem>> =
+    Lazy::new(|| Mutex::new(PriorityTaskSystem::new()));
 
 // "Threadsafe" is not a guarantee, it is a requirement. These pointers are assumed to be able to
 // be sent to another thread, and therefore must not rely on thread-local state.
 struct ThreadsafeCFnWrapper {
-  context: *mut c_void,
-  fn_ptr: extern fn(*mut c_void)
+    context: *mut c_void,
+    fn_ptr: extern "C" fn(*mut c_void),
 }
 
 impl ThreadsafeCFnWrapper {
-  pub(crate) fn new(context: *mut c_void, fn_ptr: extern fn(*mut c_void)) -> Self {
-      Self {
-          context,
-          fn_ptr
-      }
-  }
-
-  pub(crate) fn call(&self) {
-      (self.fn_ptr)(self.context)
-  }
+    pub(crate) fn new(context: *mut c_void, fn_ptr: extern "C" fn(*mut c_void)) -> Self {
+        Self { context, fn_ptr }
+    }
+
+    pub(crate) fn call(&self) {
+        (self.fn_ptr)(self.context)
+    }
 }
 
 unsafe impl Send for ThreadsafeCFnWrapper {}
 
 /// Enqueues a the execution of `f(context)` on the PriorityTaskSystem.
 #[no_mangle]
-pub extern "C" fn execute(context: *mut c_void, fn_ptr: extern fn(*mut c_void)) -> i32 {
-  execute_priority(context, fn_ptr, Priority::Default);
-  0
+pub extern "C" fn execute(context: *mut c_void, fn_ptr: extern "C" fn(*mut c_void)) -> i32 {
+    execute_priority(context, fn_ptr, Priority::Default);
+    0
 }
 
 /// Enqueues a the execution of `f(context)` on the PriorityTaskSystem at the given `priority`.
 #[no_mangle]
-pub extern "C" fn execute_priority(context: *mut c_void, fn_ptr: extern fn(*mut c_void), p: Priority) -> i32 {
-  let wrap = ThreadsafeCFnWrapper::new(context, fn_ptr);
-  TASK_SYSTEM.lock().unwrap().execute(move||{
-    wrap.call()
-  }, p);
-  0
+pub extern "C" fn execute_priority(
+    context: *mut c_void,
+    fn_ptr: extern "C" fn(*mut c_void),
+    p: Priority,
+) -> i32 {
+    let wrap = ThreadsafeCFnWrapper::new(context, fn_ptr);
+    TASK_SYSTEM.lock().unwrap().execute(move || wrap.call(), p);
+    0
 }
diff --git a/rustport/src/stlab/coarse_priority_queue.rs b/rustport/src/stlab/coarse_priority_queue.rs
index a14166ab..81327fcc 100644
--- a/rustport/src/stlab/coarse_priority_queue.rs
+++ b/rustport/src/stlab/coarse_priority_queue.rs
@@ -6,12 +6,12 @@ pub struct CoarsePriorityQueue<T> {
     count: usize,
 }
 
-impl <T> CoarsePriorityQueue<T> {
+impl<T> CoarsePriorityQueue<T> {
     /// Creates a new, empty `CoarsePriorityQueue`.
     pub fn new() -> Self {
         Self {
             inner: std::collections::BinaryHeap::new(),
-            count: 0
+            count: 0,
         }
     }
 
@@ -22,14 +22,15 @@ impl <T> CoarsePriorityQueue<T> {
 
     /// Removes the greatest item from the queue at returns it, or None if it is empty.
     pub fn pop(&mut self) -> Option<T> {
-        self.inner.pop().and_then(|prioritized| {
-            Some(prioritized.take_inner())
-        })
+        self.inner
+            .pop()
+            .and_then(|prioritized| Some(prioritized.take_inner()))
     }
 
     /// Pushes an item onto the queue at the given `priority`.
     pub fn push(&mut self, item: T, priority: Priority) {
-        self.inner.push(Prioritized::new(item, priority, self.count));
+        self.inner
+            .push(Prioritized::new(item, priority, self.count));
         self.count += 1;
     }
 }
@@ -38,9 +39,9 @@ impl <T> CoarsePriorityQueue<T> {
 #[derive(Eq, PartialEq, Copy, Clone)]
 #[repr(C)]
 pub enum Priority {
-    Low, 
-    Default, 
-    High
+    Low,
+    Default,
+    High,
 }
 
 /// Use of `Priority` and `Prioritized` requires the client maintain a nondecreasing count which
@@ -58,7 +59,7 @@ impl Priority {
         match self {
             Priority::Low => 0 << usize::BITS - 2,
             Priority::Default => 1 << usize::BITS - 2,
-            Priority::High => 2 << usize::BITS - 2
+            Priority::High => 2 << usize::BITS - 2,
         }
     }
 
@@ -70,22 +71,24 @@ impl Priority {
 
 impl PartialOrd for Priority {
     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-        self.highbit_mask().partial_cmp(&other.highbit_mask()) 
+        self.highbit_mask().partial_cmp(&other.highbit_mask())
     }
 }
 
-/// Pairs an instance of `T` with a `Priority`. 
+/// Pairs an instance of `T` with a `Priority`.
 /// Equality and ordering of a Prioritized<T> only considers `priority`, and disregards `inner`.
 struct Prioritized<T> {
     inner: T,
-    priority: usize
+    priority: usize,
 }
 
-impl <T> Prioritized<T> {
-
+impl<T> Prioritized<T> {
     /// Precondition: count must be less than 2,305,843,009,213,693,951.
     pub fn new(inner: T, priority: Priority, count: usize) -> Self {
-        Prioritized { inner, priority: priority.merge_priority_count(count) }
+        Prioritized {
+            inner,
+            priority: priority.merge_priority_count(count),
+        }
     }
 
     pub fn take_inner(self) -> T {
@@ -93,25 +96,25 @@ impl <T> Prioritized<T> {
     }
 }
 
-impl<T> PartialEq for Prioritized <T> {
+impl<T> PartialEq for Prioritized<T> {
     #[inline]
     fn eq(&self, other: &Self) -> bool {
         self.priority == other.priority
     }
 }
 
-impl <T> Eq for Prioritized <T> {}
+impl<T> Eq for Prioritized<T> {}
 
-impl<T> PartialOrd for Prioritized <T> {
+impl<T> PartialOrd for Prioritized<T> {
     #[inline]
     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
         self.priority.partial_cmp(&other.priority)
     }
 }
 
-impl<T> Ord for Prioritized <T> {
+impl<T> Ord for Prioritized<T> {
     #[inline]
     fn cmp(&self, other: &Self) -> Ordering {
         self.priority.cmp(&other.priority)
     }
-}
\ No newline at end of file
+}
diff --git a/rustport/src/stlab/drop_join_thread_pool.rs b/rustport/src/stlab/drop_join_thread_pool.rs
index 5e271b61..7f9161af 100644
--- a/rustport/src/stlab/drop_join_thread_pool.rs
+++ b/rustport/src/stlab/drop_join_thread_pool.rs
@@ -1,4 +1,4 @@
-use std::{thread::JoinHandle, io::Write};
+use std::{io::Write, thread::JoinHandle};
 
 /// A thread pool which joins all spawned threads when dropped.
 ///
@@ -16,11 +16,10 @@ pub struct DropJoinThreadPool<T: Default + Send + Sync + 'static> {
     threads: Vec<JoinHandle<()>>,
     // SAFETY: We store this pointer as a `*mut` so it can be dropped via Box::from_raw, but
     // we only ever hand out immutable references to the pointee.
-    data: *mut T
+    data: *mut T,
 }
 
-impl <T: Default + Send + Sync + 'static> Drop for DropJoinThreadPool <T> {
-
+impl<T: Default + Send + Sync + 'static> Drop for DropJoinThreadPool<T> {
     /// Join all spawned threads, and drop `data` manually.
     fn drop(&mut self) {
         for thread in std::mem::take(&mut self.threads) {
@@ -33,14 +32,13 @@ impl <T: Default + Send + Sync + 'static> Drop for DropJoinThreadPool <T> {
     }
 }
 
-impl <T: Default + Send + Sync + 'static> DropJoinThreadPool <T> {
-
+impl<T: Default + Send + Sync + 'static> DropJoinThreadPool<T> {
     /// Creates a new `DropJoinThreadPool` with a maximum thread capacity of `thread_limit`, moving
     /// `data` onto the heap.
     pub fn new(thread_limit: usize, data: T) -> Self {
         Self {
             threads: Vec::<JoinHandle<()>>::with_capacity(thread_limit),
-            data: Box::into_raw(Box::new(data))
+            data: Box::into_raw(Box::new(data)),
         }
     }
 
@@ -66,7 +64,7 @@ impl <T: Default + Send + Sync + 'static> DropJoinThreadPool <T> {
             let f = f.clone();
             // SAFETY: We only hand out immutable references to this data.
             let data: &T = unsafe { &*self.data };
-            std::thread::spawn(move||{
+            std::thread::spawn(move || {
                 f(i, data);
             })
         }));
@@ -98,4 +96,4 @@ impl <T: Default + Send + Sync + 'static> DropJoinThreadPool <T> {
         let mut stderr = std::io::stderr().lock();
         let _ = stderr.write_all(buf);
     }
-}
\ No newline at end of file
+}
diff --git a/rustport/src/stlab/mod.rs b/rustport/src/stlab/mod.rs
index eb9b13ba..f8ce7ed5 100644
--- a/rustport/src/stlab/mod.rs
+++ b/rustport/src/stlab/mod.rs
@@ -1,7 +1,7 @@
 use std::cmp::max;
 use std::num::NonZeroUsize;
-use std::sync::Arc;
 use std::sync::atomic::{AtomicUsize, Ordering as MemoryOrdering};
+use std::sync::Arc;
 
 mod coarse_priority_queue;
 pub use coarse_priority_queue::Priority;
@@ -16,7 +16,7 @@ pub mod notification_queue;
 use notification_queue::NotificationQueue;
 
 /// A type-erased, heap-allocated function object.
-pub type Task = Box<dyn FnOnce()->() + Send>;
+pub type Task = Box<dyn FnOnce() -> () + Send>;
 
 /// A portable work-stealing task scheduler with three priorities.
 ///
@@ -56,31 +56,42 @@ impl PriorityTaskSystem {
     /// Creates a new PriorityTaskSystem.
     pub fn new() -> Self {
         // SAFETY: We know 1 is not 0.
-        let nonzero_available_parallelism = std::thread::available_parallelism().unwrap_or(unsafe { NonZeroUsize::new_unchecked(1) });
+        let nonzero_available_parallelism = std::thread::available_parallelism()
+            .unwrap_or(unsafe { NonZeroUsize::new_unchecked(1) });
         let available_parallelism = max(usize::from(nonzero_available_parallelism), 2) - 1;
         let thread_limit = max(9, available_parallelism * 4 + 1);
-        let queues = (0..available_parallelism).map(|_| { NotificationQueue::default() }).collect();
+        let queues = (0..available_parallelism)
+            .map(|_| NotificationQueue::default())
+            .collect();
 
         let mut pool = DropJoinThreadPool::new(thread_limit, queues);
-        pool.spawn_n(move |i, queues| {
-            loop {
+
+        pool.spawn_n(
+            move |i, queues| loop {
                 let mut task = Self::try_pop(queues, i, available_parallelism);
 
                 if task.is_none() {
                     let done: bool;
                     (done, task) = queues.get(i).unwrap().pop();
-                    if done { break }
+                    if done {
+                        break;
+                    }
                 }
 
                 if task.is_some() {
                     task.unwrap()();
                 }
-            }
-        }, available_parallelism);
+            },
+            available_parallelism,
+        );
 
         Self {
             pool,
-            waiters: Arc::new((0..(thread_limit - available_parallelism)).map(|_| { Waiter::default() }).collect()),
+            waiters: Arc::new(
+                (0..(thread_limit - available_parallelism))
+                    .map(|_| Waiter::default())
+                    .collect(),
+            ),
             index: AtomicUsize::new(0),
             available_parallelism,
         }
@@ -89,7 +100,10 @@ impl PriorityTaskSystem {
     /// Push `f` to the first queue in `queues` whose mutex is not under contention.
     /// If no such queue is found after a single pass, blockingly push `f` to one queue.
     // REVIEW: I'm not sure `execute` is a good name. I think we want `push`, or `push_with_priority`.
-    pub fn execute<F>(&self, f: F, p: Priority) where F: FnOnce() -> () + Send + 'static {
+    pub fn execute<F>(&self, f: F, p: Priority)
+    where
+        F: FnOnce() -> () + Send + 'static,
+    {
         self.execute_task(Box::new(f), p)
     }
 
@@ -100,9 +114,11 @@ impl PriorityTaskSystem {
             let n = self.available_parallelism;
 
             // Attempt to push to a queue without blocking, starting with ours.
-            for i in (i..i+n).map(|i| i % n) {
+            for i in (i..i + n).map(|i| i % n) {
                 task = queues.get(i).unwrap().try_push(task.unwrap(), priority);
-                if task.is_none() { return } // An empty return means push was successful.
+                if task.is_none() {
+                    return;
+                } // An empty return means push was successful.
             }
 
             // Otherwise, attempt to push to our queue, with blocking.
@@ -114,7 +130,7 @@ impl PriorityTaskSystem {
     pub fn add_thread(&mut self) {
         let waiters = self.waiters.clone();
         let n = self.available_parallelism;
-        self.pool.spawn(move |i, queues|{
+        self.pool.spawn(move |i, queues| {
             loop {
                 if let Some(task) = Self::try_pop(queues, i, n) {
                     task();
@@ -122,33 +138,41 @@ impl PriorityTaskSystem {
                 }
 
                 // Note: The following means multiple threads may wait on a single `Waiter`.
-                if waiters[i - n].wait() { break; }
-            } 
+                if waiters[i - n].wait() {
+                    break;
+                }
+            }
         });
     }
 
     // Returns true if any thread was woken.
     pub fn wake(&self) -> bool {
-        let any_queue_woken = self.pool.execute_immediately(|queues| {
-            return queues.iter().any(|queue| queue.wake())
-        });
-
-        if any_queue_woken { true }
-        else { self.waiters.iter().any(|waiter| waiter.wake()) }
+        let any_queue_woken = self
+            .pool
+            .execute_immediately(|queues| return queues.iter().any(|queue| queue.wake()));
+
+        if any_queue_woken {
+            true
+        } else {
+            self.waiters.iter().any(|waiter| waiter.wake())
+        }
     }
 
     /// Attempt to non-blockingly pop a task from each queue in the system, starting at index
     /// `starting_at`.
-    fn try_pop(queues: &Vec<NotificationQueue<Task>>, starting_at: usize, modulo: usize) -> Option<Task> {
-        for i in (starting_at..starting_at+modulo).map(|i| i % modulo)  {
+    fn try_pop(
+        queues: &Vec<NotificationQueue<Task>>,
+        starting_at: usize,
+        modulo: usize,
+    ) -> Option<Task> {
+        for i in (starting_at..starting_at + modulo).map(|i| i % modulo) {
             match queues.get(i).unwrap().try_pop() {
-                Some(t) => { return Some(t) }
-                None => continue
+                Some(t) => return Some(t),
+                None => continue,
             };
         }
         return None;
     }
-
 }
 
-unsafe impl Send for PriorityTaskSystem {}
\ No newline at end of file
+unsafe impl Send for PriorityTaskSystem {}
diff --git a/rustport/src/stlab/notification_queue.rs b/rustport/src/stlab/notification_queue.rs
index c9807ee5..2773f1ae 100644
--- a/rustport/src/stlab/notification_queue.rs
+++ b/rustport/src/stlab/notification_queue.rs
@@ -1,4 +1,4 @@
-use crate::stlab::coarse_priority_queue::{Priority, CoarsePriorityQueue};
+use crate::stlab::coarse_priority_queue::{CoarsePriorityQueue, Priority};
 
 /// A threadsafe priority queue.
 pub struct NotificationQueue<T> {
@@ -16,13 +16,12 @@ impl<T> std::default::Default for NotificationQueue<T> {
 }
 
 impl<T> NotificationQueue<T> {
-
     /// Try to pop from the queue without blocking.
     /// Returns `None` if our mutex is already locked or if the queue is empty.
     pub fn try_pop(&self) -> Option<T> {
         if let Ok(ref mut this) = self.protected.try_lock() {
             return this.queue.pop();
-        } 
+        }
         return None;
     }
 
@@ -49,7 +48,7 @@ impl<T> NotificationQueue<T> {
         this.waiting = false;
         if this.queue.is_empty() {
             return (this.done, None);
-        } 
+        }
         return (false, this.queue.pop());
     }
 
@@ -70,7 +69,7 @@ impl<T> NotificationQueue<T> {
         } else {
             return Some(element);
         }
-        
+
         // We successfully locked the mutex, did our push, and released the lock.
         self.ready.notify_one();
         return None;
@@ -87,11 +86,11 @@ impl<T> NotificationQueue<T> {
     }
 }
 
-unsafe impl <T> Send for NotificationQueue<T> {}
-unsafe impl <T> Sync for NotificationQueue<T> {}
+unsafe impl<T> Send for NotificationQueue<T> {}
+unsafe impl<T> Sync for NotificationQueue<T> {}
 
 /// The fields of `NotificationQueue` which must be protected by a `Mutex`.
-struct NotificationQueueProtectedData <T> {
+struct NotificationQueueProtectedData<T> {
     queue: CoarsePriorityQueue<T>,
     done: bool,
     waiting: bool,
@@ -102,7 +101,7 @@ impl<T> std::default::Default for NotificationQueueProtectedData<T> {
         Self {
             queue: CoarsePriorityQueue::new(),
             done: false,
-            waiting: false
+            waiting: false,
         }
     }
 }
diff --git a/rustport/src/stlab/waiter.rs b/rustport/src/stlab/waiter.rs
index 30712389..844e7683 100644
--- a/rustport/src/stlab/waiter.rs
+++ b/rustport/src/stlab/waiter.rs
@@ -10,20 +10,19 @@ pub struct Waiter {
 
 impl Default for Waiter {
     fn default() -> Self {
-        Waiter::new() 
+        Waiter::new()
     }
 }
 
 impl Waiter {
-
-    /// Constructs a new Waiter, with `waiting` and `done` set to `false`. 
+    /// Constructs a new Waiter, with `waiting` and `done` set to `false`.
     pub fn new() -> Self {
         Self {
             protected: Mutex::new(WaiterProtectedData {
                 waiting: false,
                 done: false,
             }),
-            ready: std::sync::Condvar::new()
+            ready: std::sync::Condvar::new(),
         }
     }
 
@@ -72,4 +71,4 @@ unsafe impl Sync for Waiter {}
 struct WaiterProtectedData {
     waiting: bool,
     done: bool,
-}
\ No newline at end of file
+}

From 9823b9116dcfbedbeb6154ddbf5c46430a04cb8a Mon Sep 17 00:00:00 2001
From: nickpdemarco <nickpdemarco@gmail.com>
Date: Tue, 28 Nov 2023 18:38:42 -0500
Subject: [PATCH 10/13] Run cargo update

---
 CMakeLists.txt      |   2 +-
 rustport/Cargo.lock | 126 +++++++++++++++++++++++++++++++++-----------
 2 files changed, 97 insertions(+), 31 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 70d90da8..f6730a85 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -118,7 +118,7 @@ if ( BUILD_TESTING )
   #
   add_library( testing INTERFACE )
   add_library( stlab::testing ALIAS testing )
-  
+
   #
   # CMake targets linking to the stlab::testing target will (transitively)
   # link to the Boost::unit_test_framework and to stlab::stlab target.
diff --git a/rustport/Cargo.lock b/rustport/Cargo.lock
index e97cef7c..2a4c4733 100644
--- a/rustport/Cargo.lock
+++ b/rustport/Cargo.lock
@@ -90,12 +90,12 @@ dependencies = [
 
 [[package]]
 name = "errno"
-version = "0.3.5"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
 dependencies = [
  "libc",
- "windows-sys",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
@@ -143,15 +143,15 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
 
 [[package]]
 name = "libc"
-version = "0.2.149"
+version = "0.2.150"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
+checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
 
 [[package]]
 name = "linux-raw-sys"
-version = "0.4.10"
+version = "0.4.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
+checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
 
 [[package]]
 name = "log"
@@ -173,9 +173,9 @@ checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.69"
+version = "1.0.70"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
+checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
 dependencies = [
  "unicode-ident",
 ]
@@ -200,15 +200,15 @@ dependencies = [
 
 [[package]]
 name = "rustix"
-version = "0.38.21"
+version = "0.38.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
+checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e"
 dependencies = [
  "bitflags 2.4.1",
  "errno",
  "libc",
  "linux-raw-sys",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -219,22 +219,22 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
 
 [[package]]
 name = "serde"
-version = "1.0.190"
+version = "1.0.193"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7"
+checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.190"
+version = "1.0.193"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
+checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.38",
+ "syn 2.0.39",
 ]
 
 [[package]]
@@ -267,9 +267,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.38"
+version = "2.0.39"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
+checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -286,14 +286,14 @@ dependencies = [
  "fastrand",
  "redox_syscall",
  "rustix",
- "windows-sys",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
 name = "termcolor"
-version = "1.3.0"
+version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64"
+checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449"
 dependencies = [
  "winapi-util",
 ]
@@ -356,7 +356,16 @@ version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
 dependencies = [
- "windows-targets",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.0",
 ]
 
 [[package]]
@@ -365,13 +374,28 @@ version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
 dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.0",
+ "windows_aarch64_msvc 0.52.0",
+ "windows_i686_gnu 0.52.0",
+ "windows_i686_msvc 0.52.0",
+ "windows_x86_64_gnu 0.52.0",
+ "windows_x86_64_gnullvm 0.52.0",
+ "windows_x86_64_msvc 0.52.0",
 ]
 
 [[package]]
@@ -380,38 +404,80 @@ version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+
 [[package]]
 name = "windows_i686_gnu"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+
 [[package]]
 name = "windows_i686_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+
 [[package]]
 name = "windows_x86_64_gnu"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+
 [[package]]
 name = "windows_x86_64_gnullvm"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"

From ac8e6920ef409d975deffea0127253219c6842c5 Mon Sep 17 00:00:00 2001
From: nickpdemarco <nickpdemarco@gmail.com>
Date: Tue, 28 Nov 2023 18:51:50 -0500
Subject: [PATCH 11/13] Add propagating panic to threadpool destructor

---
 rustport/src/stlab/drop_join_thread_pool.rs | 9 ++++++++-
 rustport/src/stlab/mod.rs                   | 8 ++++----
 2 files changed, 12 insertions(+), 5 deletions(-)

diff --git a/rustport/src/stlab/drop_join_thread_pool.rs b/rustport/src/stlab/drop_join_thread_pool.rs
index 7f9161af..cf461e29 100644
--- a/rustport/src/stlab/drop_join_thread_pool.rs
+++ b/rustport/src/stlab/drop_join_thread_pool.rs
@@ -6,6 +6,8 @@ use std::{io::Write, thread::JoinHandle};
 /// threads. As such, it must be Send + Sync, and 'static (having no non-static references). When
 /// tasks in this pool are spawned, they are passed an immutable reference to said object (there is
 /// no way to acquire a mutable reference).
+/// 
+/// If a thread panics, the panic will be propagated while dropping this pool.
 ///
 /// Note: it remains to be seen if it is possible (or useful) to loosen the 'static requirement here
 /// to instead allow any lifetimes which do not outlive this pool. Early attempts at this polluted
@@ -21,9 +23,14 @@ pub struct DropJoinThreadPool<T: Default + Send + Sync + 'static> {
 
 impl<T: Default + Send + Sync + 'static> Drop for DropJoinThreadPool<T> {
     /// Join all spawned threads, and drop `data` manually.
+    /// 
+    /// If a thread in the pool paniced, that panic will propagate here.
     fn drop(&mut self) {
         for thread in std::mem::take(&mut self.threads) {
-            let _ = thread.join(); // TODO handle error? what's the rule for drop?
+            match thread.join() {
+                Ok(..) => continue,
+                Err(e) => std::panic::resume_unwind(e)
+            }
         }
 
         // SAFETY: We only call from_raw once, in this `drop`. We do not permit copies of this
diff --git a/rustport/src/stlab/mod.rs b/rustport/src/stlab/mod.rs
index f8ce7ed5..9865fa24 100644
--- a/rustport/src/stlab/mod.rs
+++ b/rustport/src/stlab/mod.rs
@@ -20,10 +20,10 @@ pub type Task = Box<dyn FnOnce() -> () + Send>;
 
 /// A portable work-stealing task scheduler with three priorities.
 ///
-/// By default, this scheduler spins up a number of threads corresponding to the amount of
-/// parallelism available on the target platform, namely, std::thread::available_parallelism() - 1.
-/// Each thread is assigned a threadsafe priority queue. To reduce contention on push and pop
-/// operations, a thread will first attempt to acquire the lock for its own queue without blocking.
+/// This scheduler spins up a number of threads corresponding to the amount of parallelism available
+/// on the target platform, namely, std::thread::available_parallelism() - 1. Each thread is
+/// assigned a threadsafe priority queue. To reduce contention on push and pop operations, a thread 
+/// will first attempt to acquire the lock for its own queue without blocking.
 /// If that fails, it will attempt the same non-blocking push/pop for each other priority queue in
 /// the scheduler. Finally, if each of those attempts also fail, the thread will attempt a blocking
 /// push/pop on its own priority queue.

From 4f5362e9f65ff9ada04ef5d6bc13064d684085fa Mon Sep 17 00:00:00 2001
From: nickpdemarco <nickpdemarco@gmail.com>
Date: Wed, 29 Nov 2023 12:19:42 -0500
Subject: [PATCH 12/13] Better contracts

---
 rustport/src/lib.rs          | 14 ++++++++++++--
 rustport/src/stlab/mod.rs    |  1 -
 rustport/src/stlab/waiter.rs |  3 +--
 3 files changed, 13 insertions(+), 5 deletions(-)

diff --git a/rustport/src/lib.rs b/rustport/src/lib.rs
index 41b1c1e5..ee2e377a 100644
--- a/rustport/src/lib.rs
+++ b/rustport/src/lib.rs
@@ -4,11 +4,15 @@ use std::{ffi::c_void, sync::Mutex};
 use stlab::{Priority, PriorityTaskSystem};
 mod stlab;
 
+/// A static instance of the task system which is invoked through the `execute` functions below.
 static TASK_SYSTEM: Lazy<Mutex<PriorityTaskSystem>> =
     Lazy::new(|| Mutex::new(PriorityTaskSystem::new()));
 
-// "Threadsafe" is not a guarantee, it is a requirement. These pointers are assumed to be able to
-// be sent to another thread, and therefore must not rely on thread-local state.
+
+/// A function pointer paired with a context, akin to a C++ lambda and its captures.
+/// 
+/// "Threadsafe" is not a guarantee, it is a requirement. These pointers are assumed to be able to
+/// be sent to another thread, and therefore must not rely on thread-local state. 
 struct ThreadsafeCFnWrapper {
     context: *mut c_void,
     fn_ptr: extern "C" fn(*mut c_void),
@@ -19,14 +23,18 @@ impl ThreadsafeCFnWrapper {
         Self { context, fn_ptr }
     }
 
+    // Note: there is no way in stable rust to make a struct invocable with () syntax.
     pub(crate) fn call(&self) {
         (self.fn_ptr)(self.context)
     }
 }
 
+/// `ThreadsafeCFnWrapper` may not rely on thread-local state.
 unsafe impl Send for ThreadsafeCFnWrapper {}
 
 /// Enqueues a the execution of `f(context)` on the PriorityTaskSystem.
+/// 
+/// Precondition: Neither `context` nor `fn_ptr` may rely on thread-local state.
 #[no_mangle]
 pub extern "C" fn execute(context: *mut c_void, fn_ptr: extern "C" fn(*mut c_void)) -> i32 {
     execute_priority(context, fn_ptr, Priority::Default);
@@ -34,6 +42,8 @@ pub extern "C" fn execute(context: *mut c_void, fn_ptr: extern "C" fn(*mut c_voi
 }
 
 /// Enqueues a the execution of `f(context)` on the PriorityTaskSystem at the given `priority`.
+/// 
+/// Precondition: Neither `context` nor `fn_ptr` may rely on thread-local state.
 #[no_mangle]
 pub extern "C" fn execute_priority(
     context: *mut c_void,
diff --git a/rustport/src/stlab/mod.rs b/rustport/src/stlab/mod.rs
index 9865fa24..e50392e1 100644
--- a/rustport/src/stlab/mod.rs
+++ b/rustport/src/stlab/mod.rs
@@ -99,7 +99,6 @@ impl PriorityTaskSystem {
 
     /// Push `f` to the first queue in `queues` whose mutex is not under contention.
     /// If no such queue is found after a single pass, blockingly push `f` to one queue.
-    // REVIEW: I'm not sure `execute` is a good name. I think we want `push`, or `push_with_priority`.
     pub fn execute<F>(&self, f: F, p: Priority)
     where
         F: FnOnce() -> () + Send + 'static,
diff --git a/rustport/src/stlab/waiter.rs b/rustport/src/stlab/waiter.rs
index 844e7683..9d6c6b06 100644
--- a/rustport/src/stlab/waiter.rs
+++ b/rustport/src/stlab/waiter.rs
@@ -36,8 +36,7 @@ impl Waiter {
     }
 
     /// Sets waiting to `false`. If waiting was `true`, wake one waiter and return `true`. Otherwise, return `false`.
-    /// If `try_lock` fails, return `false`. (REVIEW: why?)
-    /// (REVIEW: is it redundant to express that `waiting` and `done` are accesed under a mutex?)
+    /// If `try_lock` fails, return `false`.
     pub fn wake(&self) -> bool {
         if let Ok(ref mut this) = self.protected.try_lock() {
             if !this.waiting {

From 19505c6ce87a47b9bbf4d2e23ecea76f5c0d4f1b Mon Sep 17 00:00:00 2001
From: nickpdemarco <nickpdemarco@gmail.com>
Date: Thu, 30 Nov 2023 14:49:26 -0500
Subject: [PATCH 13/13] Clang 15, improve a comment

---
 .github/matrix.json       | 4 ++--
 rustport/src/stlab/mod.rs | 6 ++++--
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/.github/matrix.json b/.github/matrix.json
index c32dc4d7..883e8af1 100644
--- a/.github/matrix.json
+++ b/.github/matrix.json
@@ -7,9 +7,9 @@
             "os": "ubuntu-22.04"
         },
         {
-            "name": "Linux Clang 14",
+            "name": "Linux Clang 15",
             "compiler": "clang",
-            "version": "14",
+            "version": "15",
             "os": "ubuntu-22.04"
         },
         {
diff --git a/rustport/src/stlab/mod.rs b/rustport/src/stlab/mod.rs
index e50392e1..b868d1b7 100644
--- a/rustport/src/stlab/mod.rs
+++ b/rustport/src/stlab/mod.rs
@@ -106,13 +106,15 @@ impl PriorityTaskSystem {
         self.execute_task(Box::new(f), p)
     }
 
+    /// Push `task` to the first queue in `queues` whose mutex is not under contention.
+    /// If no such queue is found after a single pass, blockingly push `task` to one queue.
     pub fn execute_task(&self, task: Task, priority: Priority) {
         self.pool.execute_immediately(|queues| {
             let mut task: Option<Task> = Some(task);
             let i = self.index.fetch_add(1, MemoryOrdering::SeqCst);
             let n = self.available_parallelism;
 
-            // Attempt to push to a queue without blocking, starting with ours.
+            // Attempt to push to each queue without blocking.
             for i in (i..i + n).map(|i| i % n) {
                 task = queues.get(i).unwrap().try_push(task.unwrap(), priority);
                 if task.is_none() {
@@ -120,7 +122,7 @@ impl PriorityTaskSystem {
                 } // An empty return means push was successful.
             }
 
-            // Otherwise, attempt to push to our queue, with blocking.
+            // Otherwise, attempt to blockingly push to one queue.
             queues.get(i % n).unwrap().push(task.unwrap(), priority);
         });
     }