diff --git a/.bazelignore b/.bazelignore index 755b702..797a44a 100644 --- a/.bazelignore +++ b/.bazelignore @@ -1,3 +1 @@ bazel-toolbelt -_deps -build diff --git a/.bazelrc b/.bazelrc index ace2519..7b1f772 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,3 +1,10 @@ +build:asan --strip=never +build:asan --copt -fsanitize=address +build:asan --copt -DADDRESS_SANITIZER +build:asan --copt -g +build:asan --copt -fno-omit-frame-pointer +build:asan --linkopt -fsanitize=address + # For all builds, use C++17 build --cxxopt="-std=c++17" @@ -5,53 +12,3 @@ build --cxxopt="-std=c++17" build:apple_silicon --cpu=darwin_arm64 build:apple_silicon --features=oso_prefix_is_pwd -# ----------------------------------------------------------------------------- -# Sanitizer / dynamic-analysis configurations. -# -# Pick one with `--config=`, e.g. -# bazel test --config=asan //toolbelt:sockets_test -# bazel test --config=tsan //toolbelt:sockets_test -# bazel test --config=valgrind //toolbelt:sockets_test -# -# All three configs preserve debug information so failures point back to -# meaningful source locations. -# ----------------------------------------------------------------------------- - -# AddressSanitizer. Detects use-after-free, heap/stack/global buffer -# overflows, use-after-return, etc. -build:asan --strip=never -build:asan --copt=-fsanitize=address -build:asan --copt=-DADDRESS_SANITIZER -build:asan --copt=-g -build:asan --copt=-O1 -build:asan --copt=-fno-omit-frame-pointer -build:asan --linkopt=-fsanitize=address -# Note: leak detection (LSan) is only supported on Linux; opting out keeps the -# config portable to macOS. On Linux you can enable it with -# --test_env=ASAN_OPTIONS=halt_on_error=1:detect_leaks=1 -test:asan --test_env=ASAN_OPTIONS=halt_on_error=1:abort_on_error=1:detect_leaks=0:print_summary=1 - -# ThreadSanitizer. Detects data races and several kinds of synchronization -# bugs. -build:tsan --strip=never -build:tsan --copt=-fsanitize=thread -build:tsan --copt=-DTHREAD_SANITIZER -build:tsan --copt=-g -build:tsan --copt=-O1 -build:tsan --copt=-fno-omit-frame-pointer -build:tsan --linkopt=-fsanitize=thread -test:tsan --test_env=TSAN_OPTIONS=halt_on_error=1:second_deadlock_stack=1:history_size=7 - -# Valgrind. Runs the unmodified binary under Memcheck. -# -# Notes: -# * Valgrind is not available on macOS arm64. Use Linux (or x86_64) to -# exercise this config. -# * --error-exitcode=1 makes the test fail on any reported error. -# * --child-silent-after-fork=yes suppresses noise from forking helpers. -build:valgrind --strip=never -build:valgrind --copt=-g -build:valgrind --copt=-O1 -build:valgrind --copt=-fno-omit-frame-pointer -test:valgrind --run_under='valgrind --error-exitcode=1 --leak-check=full --show-leak-kinds=definite,possible --track-origins=yes --child-silent-after-fork=yes --trace-children=yes' -test:valgrind --test_timeout=300 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index e530d1c..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,127 +0,0 @@ -name: CI - -on: [push, pull_request] - -jobs: - # Plain build + test on each supported host. Mirrors the workflow used by - # the dallison/co repository so behavior stays consistent across - # projects. - test: - name: Build & test (${{ matrix.os }}) - runs-on: ${{ matrix.os }} - timeout-minutes: 30 - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-latest - - os: ubuntu-24.04-arm - - os: macos-latest - bazel_flags: --config=apple_silicon - - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Install Bazel - uses: bazel-contrib/setup-bazel@0.18.0 - with: - # Avoid downloading Bazel every time. - bazelisk-cache: true - # Store the build cache per workflow. - disk-cache: ${{ github.workflow }}-${{ matrix.os }} - # Share the repository cache between workflows. - repository-cache: true - - - name: Build all targets - run: | - bazel build //... \ - --verbose_failures \ - ${{ matrix.bazel_flags }} - - - name: Run tests - run: | - bazel test //... \ - --verbose_failures \ - --test_output=errors \ - ${{ matrix.bazel_flags }} - - - name: Upload Bazel test logs - uses: actions/upload-artifact@v7 - if: failure() - with: - name: bazel-test-logs-${{ matrix.os }} - path: bazel-testlogs - - # Run the test suite under AddressSanitizer and ThreadSanitizer. Linux - # only because LSan + ASan, and TSan, behave most uniformly there. - sanitizers: - name: ${{ matrix.config }} - runs-on: ubuntu-latest - timeout-minutes: 30 - strategy: - fail-fast: false - matrix: - config: [asan, tsan] - - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Install Bazel - uses: bazel-contrib/setup-bazel@0.18.0 - with: - bazelisk-cache: true - disk-cache: ${{ github.workflow }}-${{ matrix.config }} - repository-cache: true - - - name: Run tests under ${{ matrix.config }} - run: | - bazel test //... \ - --config=${{ matrix.config }} \ - --verbose_failures \ - --test_output=errors - - - name: Upload Bazel test logs - uses: actions/upload-artifact@v7 - if: failure() - with: - name: bazel-test-logs-${{ matrix.config }} - path: bazel-testlogs - - # Run the test suite under Valgrind's Memcheck. Valgrind is only - # available on Linux/x86_64 in practice. - valgrind: - name: valgrind - runs-on: ubuntu-latest - timeout-minutes: 45 - - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Install Valgrind - run: | - sudo apt-get update - sudo apt-get install -y valgrind - - - name: Install Bazel - uses: bazel-contrib/setup-bazel@0.18.0 - with: - bazelisk-cache: true - disk-cache: ${{ github.workflow }}-valgrind - repository-cache: true - - - name: Run tests under Valgrind - run: | - bazel test //... \ - --config=valgrind \ - --verbose_failures \ - --test_output=errors - - - name: Upload Bazel test logs - uses: actions/upload-artifact@v7 - if: failure() - with: - name: bazel-test-logs-valgrind - path: bazel-testlogs diff --git a/.gitignore b/.gitignore index c5b40a6..2235016 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,2 @@ bazel-* .vscode/* - -# CMake build artifacts -build/ -CMakeCache.txt -CMakeFiles/ -cmake_install.cmake -Makefile -_deps/ -lib/ -*.a -*.o diff --git a/CMakeLists.txt b/CMakeLists.txt index a8dce7d..8741110 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,7 @@ FetchContent_MakeAvailable(abseil) FetchContent_Declare( co GIT_REPOSITORY https://github.com/dallison/co.git - GIT_TAG cf1252b2f5952d7cba83b67dd69288971c0a2b57 + GIT_TAG main # Pass architecture settings to co's CMake build CMAKE_ARGS CMAKE_OSX_ARCHITECTURES="${CMAKE_OSX_ARCHITECTURES}" diff --git a/MODULE.bazel b/MODULE.bazel index 806e12f..24d82bf 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,18 +1,24 @@ module( - name = "cpp_toolbelt", - version = "2.1.1", + name = "toolbelt", ) -bazel_dep(name = "platforms", version = "1.0.0") -bazel_dep(name = "bazel_skylib", version = "1.9.0") -bazel_dep(name = "abseil-cpp", version = "20250814.1") -bazel_dep(name = "googletest", version = "1.17.0.bcr.2") -bazel_dep(name = "coroutines", version = "3.3.1") -bazel_dep(name = "rules_cc", version = "0.2.17") +http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +bazel_dep(name = "bazel_skylib", version = "1.7.1") +bazel_dep(name = "platforms", version = "0.0.10") +bazel_dep(name = "abseil-cpp", version = "20240722.0.bcr.1", repo_name = "com_google_absl") +bazel_dep(name = "googletest", version = "1.15.2", repo_name = "com_google_googletest") + +# Coroutines +http_archive( + name = "coroutines", + integrity = "sha256-PhOYq1eE8Q8UOhzDHq2+rafTU4VTt9fZ0ZZyNE+hWb4=", + strip_prefix = "co-2.1.0", + urls = ["https://github.com/dallison/co/archive/refs/tags/2.1.0.tar.gz"], +) # For local debugging of co coroutine library. +# bazel_dep(name = "coroutines") # local_path_override( -# module_name = "coroutines", -# path = "../co", +# module_name = "coroutines", +# path = "../co", # ) - diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 574c750..6505515 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -1,5 +1,5 @@ { - "lockFileVersion": 24, + "lockFileVersion": 18, "registryFileHashes": { "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", @@ -10,23 +10,16 @@ "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", "https://bcr.bazel.build/modules/abseil-cpp/20240116.2/MODULE.bazel": "73939767a4686cd9a520d16af5ab440071ed75cec1a876bf2fcfaf1f71987a16", - "https://bcr.bazel.build/modules/abseil-cpp/20250127.1/MODULE.bazel": "c4a89e7ceb9bf1e25cf84a9f830ff6b817b72874088bf5141b314726e46a57c1", - "https://bcr.bazel.build/modules/abseil-cpp/20250512.1/MODULE.bazel": "d209fdb6f36ffaf61c509fcc81b19e81b411a999a934a032e10cd009a0226215", - "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/MODULE.bazel": "51f2312901470cdab0dbdf3b88c40cd21c62a7ed58a3de45b365ddc5b11bcab2", - "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/source.json": "cea3901d7e299da7320700abbaafe57a65d039f10d0d7ea601c4a66938ea4b0c", + "https://bcr.bazel.build/modules/abseil-cpp/20240722.0.bcr.1/MODULE.bazel": "c0aa5eaefff1121b40208397f229604c717bd2fdf214ff67586d627118e17720", + "https://bcr.bazel.build/modules/abseil-cpp/20240722.0.bcr.1/source.json": "e067fdd217bacbe74c88a975434be5df0b44315a247be180f0e20f891715210c", "https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85", - "https://bcr.bazel.build/modules/apple_support/1.22.1/MODULE.bazel": "90bd1a660590f3ceffbdf524e37483094b29352d85317060b2327fff8f3f4458", - "https://bcr.bazel.build/modules/apple_support/1.23.1/MODULE.bazel": "53763fed456a968cf919b3240427cf3a9d5481ec5466abc9d5dc51bc70087442", - "https://bcr.bazel.build/modules/apple_support/1.23.1/source.json": "d888b44312eb0ad2c21a91d026753f330caa48a25c9b2102fae75eb2b0dcfdd2", + "https://bcr.bazel.build/modules/apple_support/1.15.1/source.json": "517f2b77430084c541bc9be2db63fdcbb7102938c5f64c17ee60ffda2e5cf07b", "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", - "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b", - "https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65", - "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", "https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc", "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", @@ -42,53 +35,40 @@ "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", - "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", - "https://bcr.bazel.build/modules/bazel_skylib/1.9.0/MODULE.bazel": "72997b29dfd95c3fa0d0c48322d05590418edef451f8db8db5509c57875fb4b7", - "https://bcr.bazel.build/modules/bazel_skylib/1.9.0/source.json": "7ad77c1e8c1b84222d9b3f3cae016a76639435744c19330b0b37c0a3c9da7dc0", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953", "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", - "https://bcr.bazel.build/modules/coroutines/3.3.1/MODULE.bazel": "96746c200b0890b9a124713598fc0eb028cb7bf04a796e393573179ce5a4d34f", - "https://bcr.bazel.build/modules/coroutines/3.3.1/source.json": "c79ca39719820a3cef8fea46a5c204f0ca1ee725acd4f2799266441faf53484d", "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", "https://bcr.bazel.build/modules/googletest/1.15.2/MODULE.bazel": "6de1edc1d26cafb0ea1a6ab3f4d4192d91a312fd2d360b63adaa213cd00b2108", - "https://bcr.bazel.build/modules/googletest/1.17.0.bcr.2/MODULE.bazel": "827f54f492a3ce549c940106d73de332c2b30cebd0c20c0bc5d786aba7f116cb", - "https://bcr.bazel.build/modules/googletest/1.17.0.bcr.2/source.json": "3664514073a819992320ffbce5825e4238459df344d8b01748af2208f8d2e1eb", - "https://bcr.bazel.build/modules/googletest/1.17.0/MODULE.bazel": "dbec758171594a705933a29fcf69293d2468c49ec1f2ebca65c36f504d72df46", + "https://bcr.bazel.build/modules/googletest/1.15.2/source.json": "dbdda654dcb3a0d7a8bc5d0ac5fc7e150b58c2a986025ae5bc634bb2cb61f470", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d", "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", + "https://bcr.bazel.build/modules/platforms/0.0.11/source.json": "f7e188b79ebedebfe75e9e1d098b8845226c7992b307e28e1496f23112e8fc29", "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc", - "https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580", - "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", - "https://bcr.bazel.build/modules/protobuf/23.1/MODULE.bazel": "88b393b3eb4101d18129e5db51847cd40a5517a53e81216144a8c32dfeeca52a", - "https://bcr.bazel.build/modules/protobuf/24.4/MODULE.bazel": "7bc7ce5f2abf36b3b7b7c8218d3acdebb9426aeb35c2257c96445756f970eb12", "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", - "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92", "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", "https://bcr.bazel.build/modules/protobuf/29.0/source.json": "b857f93c796750eef95f0d61ee378f3420d00ee1dd38627b27193aa482f4f981", "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34", - "https://bcr.bazel.build/modules/pybind11_bazel/2.13.6/MODULE.bazel": "2d746fda559464b253b2b2e6073cb51643a2ac79009ca02100ebbc44b4548656", - "https://bcr.bazel.build/modules/pybind11_bazel/2.13.6/source.json": "6aa0703de8efb20cc897bbdbeb928582ee7beaf278bcd001ac253e1605bddfae", + "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/source.json": "6900fdc8a9e95866b8c0d4ad4aba4d4236317b5c1cd04c502df3f0d33afed680", "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", - "https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/MODULE.bazel": "b4963dda9b31080be1905ef085ecd7dd6cd47c05c79b9cdf83ade83ab2ab271a", "https://bcr.bazel.build/modules/re2/2024-07-02/MODULE.bazel": "0eadc4395959969297cbcf31a249ff457f2f1d456228c67719480205aa306daa", - "https://bcr.bazel.build/modules/re2/2025-08-12.bcr.1/MODULE.bazel": "e09b434b122bfb786a69179f9b325e35cb1856c3f56a7a81dd61609260ed46e1", - "https://bcr.bazel.build/modules/re2/2025-08-12.bcr.1/source.json": "a8ae7c09533bf67f9f6e5122d884d5741600b09d78dca6fc0f2f8d2ee0c2d957", + "https://bcr.bazel.build/modules/re2/2024-07-02/source.json": "547d0111a9d4f362db32196fef805abbf3676e8d6afbe44d395d87816c1130ca", "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", @@ -97,18 +77,12 @@ "https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac", "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", - "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", - "https://bcr.bazel.build/modules/rules_cc/0.1.4/MODULE.bazel": "bb03a452a7527ac25a7518fb86a946ef63df860b9657d8323a0c50f8504fb0b9", - "https://bcr.bazel.build/modules/rules_cc/0.2.0/MODULE.bazel": "b5c17f90458caae90d2ccd114c81970062946f49f355610ed89bebf954f5783c", - "https://bcr.bazel.build/modules/rules_cc/0.2.16/MODULE.bazel": "9242fa89f950c6ef7702801ab53922e99c69b02310c39fb6e62b2bd30df2a1d4", - "https://bcr.bazel.build/modules/rules_cc/0.2.17/MODULE.bazel": "1849602c86cb60da8613d2de887f9566a6d354a6df6d7009f9d04a14402f9a84", - "https://bcr.bazel.build/modules/rules_cc/0.2.17/source.json": "3832f45d145354049137c0090df04629d9c2b5493dc5c2bf46f1834040133a07", - "https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642", + "https://bcr.bazel.build/modules/rules_cc/0.1.1/source.json": "d61627377bd7dd1da4652063e368d9366fc9a73920bfa396798ad92172cf645c", "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e", @@ -117,16 +91,13 @@ "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", "https://bcr.bazel.build/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6", "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", - "https://bcr.bazel.build/modules/rules_java/7.1.0/MODULE.bazel": "30d9135a2b6561c761bd67bd4990da591e6bdc128790ce3e7afd6a3558b2fb64", "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", - "https://bcr.bazel.build/modules/rules_java/8.14.0/MODULE.bazel": "717717ed40cc69994596a45aec6ea78135ea434b8402fb91b009b9151dd65615", - "https://bcr.bazel.build/modules/rules_java/8.14.0/source.json": "8a88c4ca9e8759da53cddc88123880565c520503321e2566b4e33d0287a3d4bc", - "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", - "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", + "https://bcr.bazel.build/modules/rules_java/8.12.0/MODULE.bazel": "8e6590b961f2defdfc2811c089c75716cb2f06c8a4edeb9a8d85eaa64ee2a761", + "https://bcr.bazel.build/modules/rules_java/8.12.0/source.json": "cbd5d55d9d38d4008a7d00bee5b5a5a4b6031fcd4a56515c9accbcd42c7be2ba", "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", @@ -156,11 +127,9 @@ "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", "https://bcr.bazel.build/modules/rules_python/0.33.2/MODULE.bazel": "3e036c4ad8d804a4dad897d333d8dce200d943df4827cb849840055be8d2e937", - "https://bcr.bazel.build/modules/rules_python/0.34.0/MODULE.bazel": "1d623d026e075b78c9fde483a889cda7996f5da4f36dffb24c246ab30f06513a", "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", "https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7", - "https://bcr.bazel.build/modules/rules_python/1.5.1/MODULE.bazel": "acfe65880942d44a69129d4c5c3122d57baaf3edf58ae5a6bd4edea114906bf5", - "https://bcr.bazel.build/modules/rules_python/1.5.1/source.json": "aa903e1bcbdfa1580f2b8e2d55100b7c18bc92d779ebb507fec896c75635f7bd", + "https://bcr.bazel.build/modules/rules_python/0.40.0/source.json": "939d4bd2e3110f27bfb360292986bb79fd8dcefb874358ccd6cdaa7bda029320", "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", "https://bcr.bazel.build/modules/rules_shell/0.2.0/source.json": "7f27af3c28037d9701487c4744b5448d26537cc66cdef0d8df7ae85411f8de95", "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", @@ -168,10 +137,8 @@ "https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef", "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", "https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7", - "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", - "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", + "https://bcr.bazel.build/modules/stardoc/0.7.1/source.json": "b6500ffcd7b48cd72c29bb67bcac781e12701cc0d6d55d266a652583cfcdab01", "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", - "https://bcr.bazel.build/modules/upb/0.0.0-20230516-61a97ef/MODULE.bazel": "c0df5e35ad55e264160417fd0875932ee3c9dda63d9fccace35ac62f45e1b6f9", "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", @@ -179,9 +146,40 @@ }, "selectedYankedVersions": {}, "moduleExtensions": { + "@@apple_support+//crosstool:setup.bzl%apple_cc_configure_extension": { + "general": { + "bzlTransitiveDigest": "E970FlMbwpgJPdPUQzatKh6BMfeE0ZpWABvwshh7Tmg=", + "usagesDigest": "aYRVMk+1OupIp+5hdBlpzT36qgd6ntgSxYTzMLW5K4U=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "local_config_apple_cc_toolchains": { + "repoRuleId": "@@apple_support+//crosstool:setup.bzl%_apple_cc_autoconf_toolchains", + "attributes": {} + }, + "local_config_apple_cc": { + "repoRuleId": "@@apple_support+//crosstool:setup.bzl%_apple_cc_autoconf", + "attributes": {} + } + }, + "recordedRepoMappingEntries": [ + [ + "apple_support+", + "bazel_tools", + "bazel_tools" + ], + [ + "bazel_tools", + "rules_cc", + "rules_cc+" + ] + ] + } + }, "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { - "bzlTransitiveDigest": "rL/34P1aFDq2GqVC2zCFgQ8nTuOC6ziogocpvG50Qz8=", + "bzlTransitiveDigest": "hUTp2w+RUVdL7ma5esCXZJAFnX7vLbVfLd7FwnQI6bU=", "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -242,48 +240,6 @@ ] ] } - }, - "@@rules_python+//python/uv:uv.bzl%uv": { - "general": { - "bzlTransitiveDigest": "8vT1ddXtljNxYD0tJkksqzeKE6xqx4Ix+tXthAppjTI=", - "usagesDigest": "WYhzIw9khRBy34H1GxV5+fI1yi07O90NmCXosPUdHWQ=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "uv": { - "repoRuleId": "@@rules_python+//python/uv/private:uv_toolchains_repo.bzl%uv_toolchains_repo", - "attributes": { - "toolchain_type": "'@@rules_python+//python/uv:uv_toolchain_type'", - "toolchain_names": [ - "none" - ], - "toolchain_implementations": { - "none": "'@@rules_python+//python:none'" - }, - "toolchain_compatible_with": { - "none": [ - "@platforms//:incompatible" - ] - }, - "toolchain_target_settings": {} - } - } - }, - "recordedRepoMappingEntries": [ - [ - "rules_python+", - "bazel_tools", - "bazel_tools" - ], - [ - "rules_python+", - "platforms", - "platforms" - ] - ] - } } - }, - "facts": {} + } } diff --git a/README.md b/README.md index fda81f4..169d5dc 100644 --- a/README.md +++ b/README.md @@ -1,1029 +1,63 @@ # cpp_toolbelt +Collection of useful C++ classes and utility functions -A collection of useful C++ classes and utility functions for systems programming. +I find these useful when writing C++ code. -## Table of Contents +The classes are: -- [Installation](#installation) -- [Classes](#classes) - - [FileDescriptor](#filedescriptor) - - [Socket Classes](#socket-classes) - - [InetAddress](#inetaddress) - - [VirtualAddress](#virtualaddress) - - [SocketAddress](#socketaddress) - - [Socket](#socket) - - [UnixSocket](#unixsocket) - - [TCPSocket](#tcpsocket) - - [UDPSocket](#udpsocket) - - [VirtualStreamSocket](#virtualstreamsocket) - - [StreamSocket](#streamsocket) - - [Logger](#logger) - - [MutexLock and RWLock](#mutexlock-and-rwlock) - - [BitSet](#bitset) - - [Pipe](#pipe) - - [Table](#table) - - [PayloadBuffer](#payloadbuffer) - - [TriggerFd](#triggerfd) -- [Utility Functions](#utility-functions) - - [Now](#now) - - [Hexdump](#hexdump) - - [PrintCurrentStack](#printcurrentstack) +1. FileDescriptor: a reference counted UNIX file descriptor representing an open file +2. InetAddress: an interet address (IPv4 for now) +3. Socket: a general socket +4. UnixSocket: a UNIX Domain socket +5. TCPSocket: a TCP socket +6. UDPSocket: a UDP socket +7. Logger: a level-aware logger that prints in color on TTYs +8. MutexLock: an RAII class for handling pthread mutexes +9. BitSet: a fixed size set of bits that allows allocation. -## Installation +In addition, the following functions are provided: -### Using Bazel +1. Hexdump: dump memory in hex +2. Now: get the current nanosecond monotonic time. -Add this to your `MODULE.bazel` or `WORKSPACE` file: +The Socket classes are coroutine aware and need my +[coroutine library](https://github.com/dallison/cocpp) -```python -http_archive( - name = "toolbelt", - urls = ["https://github.com/dallison/cpp_toolbelt/archive/refs/tags/A.B.C.tar.gz"], - strip_prefix = "cpp_toolbelt-A.B.C", -) -``` - -Replace `A.B.C` with the version you want (e.g., `1.0.3`). +Enjoy! -Add a dependency to your `BUILD.bazel` targets: +# Downloading and using the toolbelt +Add this to your WORKSPACE file to download and use toolbelt: -```python -deps = [ - # ... - "@toolbelt//toolbelt", -], ``` - -### Using CMake - -```cmake -include(FetchContent) -FetchContent_Declare( - toolbelt - GIT_REPOSITORY https://github.com/dallison/cpp_toolbelt.git - GIT_TAG +http_archive( + name = "toolbelt", + urls = ["https://github.com/dallison/cpp_toolbelt/archive/refs/tags/A.B.C.tar.gz"], + strip_prefix = "cpp_toolbelt-A.B.C", ) -FetchContent_MakeAvailable(toolbelt) - -target_link_libraries(your_target PRIVATE toolbelt) -``` - -## Classes - -### FileDescriptor - -A reference-counted wrapper around UNIX file descriptors. Automatically closes the file descriptor when all references are destroyed. - -**Header:** `toolbelt/fd.h` - -#### API - -```cpp -class FileDescriptor { -public: - FileDescriptor(); - explicit FileDescriptor(int fd, bool owned = true); - FileDescriptor(const FileDescriptor &f); - FileDescriptor(FileDescriptor &&f); - - void Close(); - bool IsOpen() const; - bool IsATTY() const; - int RefCount() const; - struct pollfd GetPollFd(); - bool Valid() const; - int Fd() const; - void SetFd(int fd, bool owned = true); - void Reset(); - void Release(); - void ForceClose(); - bool IsNonBlocking() const; - absl::Status SetNonBlocking(); - absl::Status SetCloseOnExec(); - absl::StatusOr Read(void* buffer, size_t length, - const co::Coroutine* c = nullptr); - absl::StatusOr Write(const void* buffer, size_t length, - const co::Coroutine* c = nullptr); -}; -``` - -#### Example - -```cpp -#include "toolbelt/fd.h" - -// Create from an existing file descriptor -int fd = open("/path/to/file", O_RDONLY); -toolbelt::FileDescriptor file(fd); - -// Copy creates a new reference (cheap, just increments ref count) -toolbelt::FileDescriptor file2 = file; -assert(file.RefCount() == 2); - -// File descriptor is automatically closed when last reference is destroyed -file.Close(); // Still open, file2 still references it -// file2 goes out of scope -> file descriptor is closed - -// Read from file descriptor -char buffer[1024]; -auto result = file.Read(buffer, sizeof(buffer)); -if (result.ok()) { - size_t bytes_read = *result; - // Process data... -} -``` - -### Socket Classes - -The socket classes provide a high-level interface to various socket types. They are coroutine-aware and work with the [co coroutine library](https://github.com/dallison/co). - -**Header:** `toolbelt/sockets.h` - -#### InetAddress - -Represents an IPv4 internet address and port. - -```cpp -class InetAddress { -public: - InetAddress(); - InetAddress(int port); // INADDR_ANY with given port - InetAddress(const in_addr &ip, int port); - InetAddress(const std::string &hostname, int port); - InetAddress(const struct sockaddr_in &addr); - - const sockaddr_in &GetAddress() const; - socklen_t GetLength() const; - bool Valid() const; - in_addr IpAddress() const; // Host byte order - int Port() const; // Host byte order - void SetPort(int port); - std::string ToString() const; - - static InetAddress BroadcastAddress(int port); - static InetAddress AnyAddress(int port); -}; -``` - -#### Example - -```cpp -#include "toolbelt/sockets.h" - -// Create address from hostname and port -toolbelt::InetAddress addr("localhost", 8080); - -// Create address for any interface on port 8080 -auto any_addr = toolbelt::InetAddress::AnyAddress(8080); - -// Get address components -in_addr ip = addr.IpAddress(); -int port = addr.Port(); -std::string str = addr.ToString(); // "127.0.0.1:8080" ``` -#### VirtualAddress +Replacing A.B.C with the version you want (e.g. 1.0.3). -Represents a virtual socket address (VSOCK) used for communication between VMs and the host. +Add a dependency to your BUILD.bazel targets like this: -```cpp -class VirtualAddress { -public: - VirtualAddress(); - VirtualAddress(uint32_t port); - VirtualAddress(uint32_t cid, uint32_t port); - - uint32_t Cid() const; - uint32_t Port() const; - void SetPort(uint32_t port); - std::string ToString() const; - - static VirtualAddress HypervisorAddress(uint32_t port); - static VirtualAddress HostAddress(uint32_t port); - static VirtualAddress AnyAddress(uint32_t port); - #if defined(__linux__) - static VirtualAddress LocalAddress(uint32_t port); - #endif -}; ``` - -#### SocketAddress - -A variant type that can hold an `InetAddress`, `VirtualAddress`, or Unix socket path (string). - -```cpp -class SocketAddress { -public: - SocketAddress(); - SocketAddress(const InetAddress &addr); - SocketAddress(const VirtualAddress &addr); - SocketAddress(const std::string &addr); // Unix socket path - - const InetAddress &GetInetAddress() const; - const VirtualAddress &GetVirtualAddress() const; - const std::string &GetUnixAddress() const; - std::string ToString() const; - bool Valid() const; - int Type() const; // kAddressInet, kAddressVirtual, or kAddressUnix - int Port() const; -}; + deps = [ + # ... + "@toolbelt//toolbelt", + ], ``` -#### Socket -Base class for all socket types. Provides common functionality for sending and receiving data. +All the header files are in *toolbelt/*, so for example: -```cpp -class Socket { -public: - void Close(); - bool Connected() const; - absl::StatusOr Receive(char *buffer, size_t buflen, - const co::Coroutine *c = nullptr); - absl::StatusOr Send(const char *buffer, size_t length, - const co::Coroutine *c = nullptr); - absl::StatusOr ReceiveMessage(char *buffer, size_t buflen, - const co::Coroutine *c = nullptr); - absl::StatusOr> ReceiveVariableLengthMessage( - const co::Coroutine *c = nullptr); - absl::StatusOr SendMessage(char *buffer, size_t length, - const co::Coroutine *c = nullptr); - absl::Status SetNonBlocking(); - FileDescriptor GetFileDescriptor() const; - absl::Status SetCloseOnExec(); - bool IsNonBlocking() const; - bool IsBlocking() const; -}; +```c++ +#include "toolbelt/fd.h ``` -#### UnixSocket +Then you can use it as: -A Unix Domain socket for inter-process communication. - -```cpp -class UnixSocket : public Socket { -public: - UnixSocket(); - explicit UnixSocket(int fd, bool connected = false); - - absl::Status Bind(const std::string &pathname, bool listen); - absl::Status Connect(const std::string &pathname); - absl::StatusOr Accept(const co::Coroutine *c = nullptr) const; - absl::Status SendFds(const std::vector &fds, - const co::Coroutine *c = nullptr); - absl::Status ReceiveFds(std::vector &fds, - const co::Coroutine *c = nullptr); - std::string BoundAddress() const; - absl::StatusOr GetPeerName() const; - absl::StatusOr LocalAddress() const; -}; +```c++ +toolbelt::FileDescriptor fd_; ``` -#### Example - -```cpp -#include "toolbelt/sockets.h" - -// Server side -toolbelt::UnixSocket server; -server.Bind("/tmp/mysocket", true); // true = listen - -// In a coroutine or event loop -auto client = server.Accept(coroutine); -if (client.ok()) { - char buffer[1024]; - auto result = client->Receive(buffer, sizeof(buffer), coroutine); - // Process received data... -} - -// Client side -toolbelt::UnixSocket client; -client.Connect("/tmp/mysocket"); -client.Send("Hello", 5, coroutine); -``` - -#### TCPSocket - -A TCP socket for network communication. - -```cpp -class TCPSocket : public NetworkSocket { -public: - TCPSocket(); - explicit TCPSocket(int fd, bool connected = false); - - absl::Status Bind(const InetAddress &addr, bool listen); - absl::StatusOr Accept(const co::Coroutine *c = nullptr) const; - absl::StatusOr LocalAddress(int port) const; - absl::StatusOr GetPeerName() const; -}; -``` - -#### Example - -```cpp -#include "toolbelt/sockets.h" - -// Server -toolbelt::TCPSocket server; -toolbelt::InetAddress addr(8080); -server.Bind(addr, true); // Listen on port 8080 - -// Accept connections -auto client = server.Accept(coroutine); -if (client.ok()) { - char buffer[1024]; - auto result = client->Receive(buffer, sizeof(buffer), coroutine); -} - -// Client -toolbelt::TCPSocket client; -toolbelt::InetAddress server_addr("example.com", 8080); -client.Connect(server_addr); -client.Send("Hello", 5, coroutine); -``` - -#### UDPSocket - -A UDP socket for datagram communication. - -```cpp -class UDPSocket : public NetworkSocket { -public: - UDPSocket(); - explicit UDPSocket(int fd, bool connected = false); - - absl::Status Bind(const InetAddress &addr); - absl::Status JoinMulticastGroup(const InetAddress &addr); - absl::Status LeaveMulticastGroup(const InetAddress &addr); - absl::Status SendTo(const InetAddress &addr, const void *buffer, - size_t length, const co::Coroutine *c = nullptr); - absl::StatusOr Receive(void *buffer, size_t buflen, - const co::Coroutine *c = nullptr); - absl::StatusOr ReceiveFrom(InetAddress &sender, void *buffer, - size_t buflen, - const co::Coroutine *c = nullptr); - absl::Status SetBroadcast(); - absl::Status SetMulticastLoop(); -}; -``` - -#### Example - -```cpp -#include "toolbelt/sockets.h" - -// Server -toolbelt::UDPSocket socket; -toolbelt::InetAddress addr(8080); -socket.Bind(addr); - -toolbelt::InetAddress sender; -char buffer[1024]; -auto result = socket.ReceiveFrom(sender, buffer, sizeof(buffer), coroutine); -if (result.ok()) { - // Process datagram from sender -} - -// Client -toolbelt::UDPSocket socket; -toolbelt::InetAddress server("example.com", 8080); -socket.SendTo(server, "Hello", 5, coroutine); -``` - -#### VirtualStreamSocket - -A virtual stream socket for VM-to-host communication using VSOCK. - -```cpp -class VirtualStreamSocket : public Socket { -public: - VirtualStreamSocket(); - explicit VirtualStreamSocket(int fd, bool connected = false); - - absl::Status Connect(const VirtualAddress &addr); - absl::Status Bind(const VirtualAddress &addr, bool listen); - absl::StatusOr Accept( - const co::Coroutine *c = nullptr) const; - absl::StatusOr LocalAddress(uint32_t port) const; - const VirtualAddress &BoundAddress() const; - absl::StatusOr GetPeerName() const; - uint32_t Cid() const; -}; -``` - -#### StreamSocket - -A type-erased wrapper that can hold a `TCPSocket`, `VirtualStreamSocket`, or `UnixSocket`. Useful when you need to work with different socket types uniformly. - -```cpp -class StreamSocket { -public: - StreamSocket(); - - absl::Status Bind(const SocketAddress &addr, bool listen); - absl::Status Connect(const SocketAddress &addr); - absl::StatusOr Accept(const co::Coroutine *c = nullptr) const; - - TCPSocket &GetTCPSocket(); - VirtualStreamSocket &GetVirtualStreamSocket(); - UnixSocket &GetUnixSocket(); - - SocketAddress BoundAddress() const; - void Close(); - bool Connected() const; - absl::StatusOr Receive(char *buffer, size_t buflen, - const co::Coroutine *c = nullptr); - absl::StatusOr Send(const char *buffer, size_t length, - const co::Coroutine *c = nullptr); - // ... other Socket methods -}; -``` - -#### Example - -```cpp -#include "toolbelt/sockets.h" - -// Works with any socket type -toolbelt::StreamSocket socket; - -// Can bind to TCP, Unix, or VSOCK addresses -toolbelt::SocketAddress addr = toolbelt::InetAddress(8080); -// or: toolbelt::SocketAddress addr("/tmp/socket"); -// or: toolbelt::SocketAddress addr(toolbelt::VirtualAddress(1234)); - -socket.Bind(addr, true); -auto client = socket.Accept(coroutine); -``` - -### Logger - -A level-aware logger that supports colored output, multiple output modes, and themes. - -**Header:** `toolbelt/logging.h` - -#### API - -```cpp -enum class LogLevel { - kVerboseDebug, - kDebug, - kInfo, - kWarning, - kError, - kFatal, -}; - -enum class LogDisplayMode { - kPlain, - kColor, - kColumnar, -}; - -enum class LogTheme { - kDefault, - kLight, - kDark, -}; - -class Logger { -public: - Logger(); - Logger(const std::string &subsystem, bool enabled = true, - LogTheme theme = LogTheme::kDefault, - LogDisplayMode mode = LogDisplayMode::kPlain); - Logger(LogLevel min); - - void Enable(); - void Disable(); - absl::Status SetTeeFile(const std::string &filename, bool truncate = true); - void SetTeeStream(FILE *stream); - void Log(LogLevel level, const char *fmt, ...); - void VLog(LogLevel level, const char *fmt, va_list ap); - void Log(LogLevel level, uint64_t timestamp, - const std::string &source, std::string text); - void SetTheme(LogTheme theme); - void SetLogLevel(LogLevel l); - void SetLogLevel(const std::string &s); // "verbose", "debug", "info", etc. - LogLevel GetLogLevel() const; - void SetOutputStream(FILE *stream); -}; -``` - -#### Example - -```cpp -#include "toolbelt/logging.h" - -// Create logger with subsystem name -toolbelt::Logger logger("myapp", true, toolbelt::LogTheme::kDefault, - toolbelt::LogDisplayMode::kColor); - -// Set minimum log level -logger.SetLogLevel(toolbelt::LogLevel::kInfo); -// or -logger.SetLogLevel("info"); - -// Log messages -logger.Log(toolbelt::LogLevel::kInfo, "Application started"); -logger.Log(toolbelt::LogLevel::kDebug, "Debug value: %d", 42); -logger.Log(toolbelt::LogLevel::kWarning, "Warning: %s", "Something happened"); -logger.Log(toolbelt::LogLevel::kError, "Error code: %d", errno); - -// Tee output to a file -logger.SetTeeFile("/var/log/myapp.log"); - -// Disable logging temporarily -logger.Disable(); -// ... do something ... -logger.Enable(); -``` - -### MutexLock and RWLock - -RAII wrappers for pthread mutexes and read-write locks. - -**Header:** `toolbelt/mutex.h` - -#### API - -```cpp -class MutexLock { -public: - MutexLock(pthread_mutex_t *mutex); - ~MutexLock(); -}; - -class RWLock { -public: - RWLock(pthread_rwlock_t *lock, bool read); - ~RWLock(); - - void ReadLock(); - void WriteLock(); - void Unlock(); -}; -``` - -#### Example - -```cpp -#include "toolbelt/mutex.h" -#include - -pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; - -{ - toolbelt::MutexLock lock(&mutex); - // Critical section - mutex is automatically unlocked when lock goes out of scope - // Do work... -} - -pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; - -{ - toolbelt::RWLock lock(&rwlock, true); // true = read lock - // Multiple readers allowed - // Read data... -} - -{ - toolbelt::RWLock lock(&rwlock, false); // false = write lock - // Exclusive write access - // Write data... -} -``` - -### BitSet - -A fixed-size bitset that allows allocation of individual bits. - -**Header:** `toolbelt/bitset.h` - -#### API - -```cpp -template -class BitSet { -public: - BitSet(); - void Init(); - absl::StatusOr Allocate(const std::string &type); - bool IsEmpty() const; - void Set(int b); - void Clear(int b); - bool IsSet(int b) const; -}; -``` - -#### Example - -```cpp -#include "toolbelt/bitset.h" - -// Create a bitset with 256 bits -toolbelt::BitSet<256> bitset; - -// Allocate the first available bit -auto result = bitset.Allocate("resource"); -if (result.ok()) { - int bit_index = *result; - // Use the bit... - - // Check if bit is set - if (bitset.IsSet(bit_index)) { - // ... - } - - // Clear the bit when done - bitset.Clear(bit_index); -} - -// Check if all bits are clear -if (bitset.IsEmpty()) { - // All resources freed -} -``` - -### Pipe - -A pipe for inter-process or inter-coroutine communication. Supports both blocking and non-blocking I/O, and can work with coroutines. - -**Header:** `toolbelt/pipe.h` - -#### API - -```cpp -class Pipe { -public: - static absl::StatusOr Create(); - static absl::StatusOr CreateWithFlags(int flags); - static absl::StatusOr Create(int r, int w); - - Pipe(); - Pipe(int r, int w); - - absl::Status Open(int flags = 0); - FileDescriptor &ReadFd(); - FileDescriptor &WriteFd(); - void SetReadFd(int fd); - void SetWriteFd(int fd); - void Close(); - void ForceClose(); - absl::Status SetNonBlocking(bool read, bool write); - absl::StatusOr GetPipeSize(); - absl::Status SetPipeSize(size_t size); - absl::StatusOr Read(char *buffer, size_t length, - const co::Coroutine *c = nullptr); - absl::StatusOr Write(const char *buffer, size_t length, - const co::Coroutine *c = nullptr); -}; - -template -class SharedPtrPipe : public Pipe { -public: - static absl::StatusOr> Create(); - static absl::StatusOr> Create(int r, int w); - - absl::StatusOr> Read(const co::Coroutine *c = nullptr); - absl::Status Write(std::shared_ptr p, const co::Coroutine *c = nullptr); -}; -``` - -#### Example - -```cpp -#include "toolbelt/pipe.h" - -// Create a pipe -auto pipe_result = toolbelt::Pipe::Create(); -if (!pipe_result.ok()) { - // Handle error - return; -} -toolbelt::Pipe pipe = *pipe_result; - -// Set non-blocking mode -pipe.SetNonBlocking(true, true); - -// Write data (in a coroutine or thread) -char data[] = "Hello, World!"; -auto write_result = pipe.Write(data, strlen(data), coroutine); - -// Read data (in another coroutine or thread) -char buffer[1024]; -auto read_result = pipe.Read(buffer, sizeof(buffer), coroutine); -if (read_result.ok()) { - size_t bytes_read = *read_result; - // Process data... -} - -// SharedPtrPipe for sending shared_ptr objects (same process only) -auto shared_pipe = toolbelt::SharedPtrPipe::Create(); -if (shared_pipe.ok()) { - auto obj = std::make_shared(); - shared_pipe->Write(obj, coroutine); - - auto received = shared_pipe->Read(coroutine); - if (received.ok()) { - std::shared_ptr obj = *received; - // Use object... - } -} -``` - -### Table - -A table formatter for displaying tabular data with colors and sorting. - -**Header:** `toolbelt/table.h` - -#### API - -```cpp -class Table { -public: - struct Cell { - std::string data; - color::Color color; - }; - - Table(const std::vector titles, ssize_t sort_column = 0, - std::function comp = nullptr); - - void AddRow(const std::vector cells); - void AddRow(const std::vector cells, color::Color color); - void AddRowWithColors(const std::vector cells); - void AddRow(); - void SetCell(size_t col, Cell &&cell); - void Print(int width, std::ostream &os); - void Clear(); - void SortBy(size_t column, - std::function comp); - void SortBy(size_t column); - - static Cell MakeCell(std::string data, color::Color color = {...}); -}; -``` - -#### Example - -```cpp -#include "toolbelt/table.h" -#include - -// Create table with column titles -toolbelt::Table table({"Name", "Age", "City"}, 0); // Sort by first column - -// Add rows -table.AddRow({"Alice", "30", "New York"}); -table.AddRow({"Bob", "25", "London"}); -table.AddRow({"Charlie", "35", "Paris"}); - -// Add row with color -auto red = toolbelt::color::Color{.mod = toolbelt::color::kRed}; -table.AddRow({"David", "40", "Tokyo"}, red); - -// Add row with per-cell colors -std::vector cells = { - toolbelt::Table::MakeCell("Eve", toolbelt::color::Color{.mod = toolbelt::color::kGreen}), - toolbelt::Table::MakeCell("28"), - toolbelt::Table::MakeCell("Berlin") -}; -table.AddRowWithColors(cells); - -// Sort by age column (column 1) -table.SortBy(1, [](const std::string &a, const std::string &b) { - return std::stoi(a) < std::stoi(b); -}); - -// Print table -table.Print(80, std::cout); -``` - -### PayloadBuffer - -A memory buffer with built-in allocation and deallocation. Useful for message serialization and wire protocols. Supports both fixed-size and resizable buffers, with optional bitmap-based small block allocator. - -**Header:** `toolbelt/payload_buffer.h` - -#### Key Types - -```cpp -using BufferOffset = uint32_t; - -struct PayloadBuffer { - // Constructors - PayloadBuffer(uint32_t size, bool bitmap_allocator = true); - PayloadBuffer(uint32_t initial_size, Resizer r, bool bitmap_allocator = true); - - // Allocation - static void *Allocate(PayloadBuffer **buffer, uint32_t n, - bool clear = true, bool enable_small_block = true); - void Free(void *p); - static void *Realloc(PayloadBuffer **buffer, void *p, uint32_t n, - bool clear = true, bool enable_small_block = true); - - // String operations - static char *SetString(PayloadBuffer **self, const char *s, size_t len, - BufferOffset header_offset); - std::string GetString(BufferOffset header_offset) const; - std::string_view GetStringView(BufferOffset header_offset) const; - - // Vector operations - template - static void VectorPush(PayloadBuffer **self, VectorHeader *hdr, T v, - bool enable_small_block = true); - template - static void VectorReserve(PayloadBuffer **self, VectorHeader *hdr, size_t n, - bool enable_small_block = true); - template - T VectorGet(const VectorHeader *hdr, size_t index) const; - - // Address conversion - template - T *ToAddress(BufferOffset offset, size_t size = 0); - template - BufferOffset ToOffset(T *addr, size_t size = 0); - - // Utilities - size_t Size() const; - void Dump(std::ostream &os); - bool IsValidMagic() const; - bool IsMoveable() const; -}; -``` - -#### Example - -```cpp -#include "toolbelt/payload_buffer.h" - -// Create a fixed-size buffer -char buffer_memory[4096]; -toolbelt::PayloadBuffer *pb = new (buffer_memory) toolbelt::PayloadBuffer(4096); - -// Allocate memory in the buffer -void *data = toolbelt::PayloadBuffer::Allocate(&pb, 100); -// Use data... - -// Free memory -pb->Free(data); - -// Create a resizable buffer -auto resizer = [](toolbelt::PayloadBuffer **b, size_t old_size, size_t new_size) { - char *new_mem = new char[new_size]; - memcpy(new_mem, *b, old_size); - delete[] reinterpret_cast(*b); - *b = reinterpret_cast(new_mem); -}; -toolbelt::PayloadBuffer *resizable = new toolbelt::PayloadBuffer(1024, resizer); - -// Allocate string -toolbelt::BufferOffset str_offset = 100; -toolbelt::PayloadBuffer::SetString(&resizable, "Hello", 5, str_offset); -std::string str = resizable->GetString(str_offset); - -// Vector operations -toolbelt::VectorHeader vec_hdr = {0, 0}; -toolbelt::PayloadBuffer::VectorPush(&resizable, &vec_hdr, 42); -toolbelt::PayloadBuffer::VectorPush(&resizable, &vec_hdr, 100); -int value = resizable->VectorGet(&vec_hdr, 0); // Returns 42 -``` - -### TriggerFd - -A file descriptor that can be used to trigger events. Implemented as an `eventfd` on Linux or a pipe on other systems. - -**Header:** `toolbelt/triggerfd.h` - -#### API - -```cpp -class TriggerFd { -public: - TriggerFd(); - TriggerFd(const FileDescriptor &poll_fd, const FileDescriptor &trigger_fd); - - absl::Status Open(); - static absl::StatusOr Create(); - static absl::StatusOr Create(const FileDescriptor &poll_fd, - const FileDescriptor &trigger_fd); - - void Close(); - void SetPollFd(FileDescriptor fd); - void SetTriggerFd(FileDescriptor fd); - void Trigger(); - bool Clear(); // Clears trigger and returns true if it was triggered - FileDescriptor &GetPollFd(); - FileDescriptor &GetTriggerFd(); - void AddPollFd(std::vector &fds); -}; -``` - -#### Example - -```cpp -#include "toolbelt/triggerfd.h" -#include - -// Create a trigger file descriptor -auto trigger_result = toolbelt::TriggerFd::Create(); -if (!trigger_result.ok()) { - return; -} -toolbelt::TriggerFd trigger = *trigger_result; - -// Add to poll set -std::vector fds; -trigger.AddPollFd(fds); - -// In event loop -int result = poll(fds.data(), fds.size(), -1); -if (result > 0 && (fds[0].revents & POLLIN)) { - if (trigger.Clear()) { - // Trigger was set, handle event - } -} - -// Trigger from another thread/coroutine -trigger.Trigger(); -``` - -## Utility Functions - -### Now - -Get the current monotonic time in nanoseconds. - -**Header:** `toolbelt/clock.h` - -```cpp -uint64_t Now(); -``` - -#### Example - -```cpp -#include "toolbelt/clock.h" - -uint64_t start = toolbelt::Now(); -// Do work... -uint64_t end = toolbelt::Now(); -uint64_t elapsed_ns = end - start; -double elapsed_ms = elapsed_ns / 1e6; -``` - -### Hexdump - -Dump memory in hexadecimal format. - -**Header:** `toolbelt/hexdump.h` - -```cpp -void Hexdump(const void* addr, size_t length, FILE* out = stdout); -``` - -#### Example - -```cpp -#include "toolbelt/hexdump.h" - -char buffer[64] = "Hello, World!"; -toolbelt::Hexdump(buffer, sizeof(buffer)); -// Output: -// 00000000 48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21 00 00 00 |Hello, World!...| -// ... -``` - -### PrintCurrentStack - -Print the current stack trace. - -**Header:** `toolbelt/stacktrace.h` - -```cpp -void PrintCurrentStack(std::ostream &os); -``` - -#### Example - -```cpp -#include "toolbelt/stacktrace.h" -#include - -void some_function() { - // Print stack trace - toolbelt::PrintCurrentStack(std::cerr); -} -``` - -## Dependencies - -- [Abseil](https://github.com/abseil/abseil-cpp) - Status types, strings, formatting -- [co](https://github.com/dallison/co) - Coroutine library (for socket classes) - -## License - -See LICENSE file for licensing information. + diff --git a/toolbelt/BUILD.bazel b/toolbelt/BUILD.bazel index 4fad7ab..a3b03f9 100644 --- a/toolbelt/BUILD.bazel +++ b/toolbelt/BUILD.bazel @@ -1,7 +1,5 @@ package(default_visibility = ["//visibility:public"]) -load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") - cc_library( name = "toolbelt", srcs = [ @@ -14,7 +12,6 @@ cc_library( "sockets.cc", "table.cc", "triggerfd.cc", - "stacktrace.cc", ], hdrs = [ "bitset.h", @@ -29,14 +26,13 @@ cc_library( "sockets.h", "table.h", "triggerfd.h", - "stacktrace.h", ], deps = [ - "@abseil-cpp//absl/status", - "@abseil-cpp//absl/status:statusor", - "@abseil-cpp//absl/strings", - "@abseil-cpp//absl/strings:str_format", - "@abseil-cpp//absl/types:span", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/types:span", "@coroutines//:co", ], ) @@ -47,50 +43,50 @@ cc_test( srcs = ["fd_test.cc"], deps = [ ":toolbelt", - "@abseil-cpp//absl/hash:hash_testing", - "@abseil-cpp//absl/status", - "@abseil-cpp//absl/status:statusor", - "@googletest//:gtest_main", + "@com_google_absl//absl/hash:hash_testing", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_googletest//:gtest_main", ], ) cc_test( - name = "table_test", + name = "logging_test", size = "small", - srcs = ["table_test.cc"], + srcs = ["logging_test.cc"], deps = [ ":toolbelt", - "@abseil-cpp//absl/hash:hash_testing", - "@abseil-cpp//absl/status", - "@abseil-cpp//absl/status:statusor", - "@abseil-cpp//absl/strings:str_format", - "@googletest//:gtest_main", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_googletest//:gtest_main", ], ) + cc_test( - name = "stacktrace_test", + name = "table_test", size = "small", - srcs = ["stacktrace_test.cc"], + srcs = ["table_test.cc"], deps = [ ":toolbelt", - "@abseil-cpp//absl/hash:hash_testing", - "@abseil-cpp//absl/status", - "@abseil-cpp//absl/status:statusor", - "@abseil-cpp//absl/strings:str_format", - "@googletest//:gtest_main", + "@com_google_absl//absl/hash:hash_testing", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings:str_format", + "@com_google_googletest//:gtest_main", ], ) + cc_test( name = "pipe_test", - size = "medium", + size = "small", srcs = ["pipe_test.cc"], deps = [ ":toolbelt", - "@abseil-cpp//absl/hash:hash_testing", - "@abseil-cpp//absl/status", - "@abseil-cpp//absl/status:status_matchers", - "@abseil-cpp//absl/status:statusor", - "@googletest//:gtest_main", + "@com_google_absl//absl/hash:hash_testing", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:status_matchers", + "@com_google_absl//absl/status:statusor", + "@com_google_googletest//:gtest_main", ], ) @@ -100,11 +96,10 @@ cc_test( srcs = ["sockets_test.cc"], deps = [ ":toolbelt", - "@abseil-cpp//absl/hash:hash_testing", - "@abseil-cpp//absl/status", - "@abseil-cpp//absl/status:status_matchers", - "@abseil-cpp//absl/status:statusor", - "@googletest//:gtest_main", + "@com_google_absl//absl/hash:hash_testing", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_googletest//:gtest_main", ], ) @@ -115,6 +110,6 @@ cc_test( ], deps = [ ":toolbelt", - "@googletest//:gtest", + "@com_google_googletest//:gtest", ], ) diff --git a/toolbelt/bitset.h b/toolbelt/bitset.h index 9106d71..42c7318 100644 --- a/toolbelt/bitset.h +++ b/toolbelt/bitset.h @@ -71,7 +71,7 @@ template inline void BitSet::Set(int b) { } template inline bool BitSet::IsEmpty() const { - for (std::size_t i = 0; i < kNumWords; i++) { + for (int i = 0; i < kNumWords; i++) { if (bits_[i] != 0) { return false; } diff --git a/toolbelt/color.h b/toolbelt/color.h index c1ca09b..39db22c 100644 --- a/toolbelt/color.h +++ b/toolbelt/color.h @@ -48,13 +48,13 @@ struct Color { FixedColor fixed = FixedColor::kNotSet; // Your terminal might not support this. - int eight = 0; // 8-bit color for k8bit. + int eight; // 8-bit color for k8bit. // If fixed is kNotSet, these are RGB values for the color. // Your terminal may not support this. - int r = 0; - int g = 0; - int b = 0; + int r; + int g; + int b; }; // Make a 3-bit fixed color. diff --git a/toolbelt/fd.cc b/toolbelt/fd.cc index a160646..e4a8440 100644 --- a/toolbelt/fd.cc +++ b/toolbelt/fd.cc @@ -16,7 +16,7 @@ void CloseAllFds(std::function predicate) { } absl::StatusOr FileDescriptor::Read(void *buffer, size_t length, - const co::Coroutine *c) { + co::Coroutine *c) { char *buf = reinterpret_cast(buffer); size_t total = 0; while (total < length) { @@ -58,7 +58,7 @@ absl::StatusOr FileDescriptor::Read(void *buffer, size_t length, } absl::StatusOr FileDescriptor::Write(const void *buffer, size_t length, - const co::Coroutine *c) { + co::Coroutine *c) { const char *buf = reinterpret_cast(buffer); size_t total = 0; diff --git a/toolbelt/fd.h b/toolbelt/fd.h index 5760b56..d89dc68 100644 --- a/toolbelt/fd.h +++ b/toolbelt/fd.h @@ -18,7 +18,7 @@ #include #include #include -#include "co/coroutine.h" +#include "coroutine.h" namespace toolbelt { @@ -56,8 +56,7 @@ class FileDescriptor { FileDescriptor() = default; // FileDescriptor initialize with an OS fd. Takes ownership // of the fd and will close it when all references go away. - // If owned is false, the fd will not be closed when all references go away. - explicit FileDescriptor(int fd, bool owned = true) : data_(std::make_shared(fd, owned)) {} + explicit FileDescriptor(int fd) : data_(std::make_shared(fd)) {} // Copy constructor, increments reference on shared data. Very cheap. FileDescriptor(const FileDescriptor &f) : data_(f.data_) {} @@ -98,9 +97,9 @@ class FileDescriptor { // Construct and return a struct pollfd suitable for use in ::poll. struct pollfd GetPollFd() { if (data_ == nullptr) { - return {.fd = -1, .events = POLLIN, .revents = 0}; + return {.fd = -1, .events = POLLIN}; } - return {.fd = data_->fd, .events = POLLIN, .revents = 0}; + return {.fd = data_->fd, .events = POLLIN}; } bool operator==(const FileDescriptor &fd) const { @@ -120,13 +119,13 @@ class FileDescriptor { // Sets the OS fd. If it's the same as the underlying OS fd, there is // no effect (that's not another reference to it). Allocates new // shared data for the fd. - void SetFd(int fd, bool owned = true) { + void SetFd(int fd) { if (Fd() == fd) { // SetFd with same fd. This isn't another reference to the // fd. return; } - data_ = std::make_shared(fd, owned); + data_ = std::make_shared(fd); } void Reset() { Close(); } @@ -188,27 +187,23 @@ class FileDescriptor { return absl::OkStatus(); } - absl::StatusOr Read(void* buffer, size_t length, const co::Coroutine* c = nullptr); + absl::StatusOr Read(void* buffer, size_t length, co::Coroutine* c = nullptr); absl::StatusOr Write(const void* buffer, size_t length, - const co::Coroutine* c = nullptr); + co::Coroutine* c = nullptr); private: // Reference counted OS fd, shared among all FileDescriptors with the // same OS fd, provided you don't create two FileDescriptors with the // same OS fd (that would be a mistake but there's no way to stop it). struct SharedData { SharedData() = default; - SharedData(int f, bool o) : fd(f), owned(o) {} + SharedData(int f) : fd(f) {} ~SharedData() { if (fd != -1) { - if (!owned) { - return; - } ::close(fd); } } int fd = -1; // OS file descriptor. bool nonblocking = false; - bool owned = true; }; // The actual shared data. If nullptr the FileDescriptor is invalid. diff --git a/toolbelt/fd_test.cc b/toolbelt/fd_test.cc index be1cd26..79a9e01 100644 --- a/toolbelt/fd_test.cc +++ b/toolbelt/fd_test.cc @@ -173,26 +173,3 @@ TEST(FdTest, Reset) { int e = fstat(f, &st); ASSERT_EQ(-1, e); } - -TEST(FdTest, CreateUnowned) { - int f = dup(1); - { - FileDescriptor fd(f, false); - ASSERT_TRUE(fd.Valid()); - ASSERT_EQ(f, fd.Fd()); - ASSERT_EQ(1, fd.RefCount()); - } - // Fd should still be open. - ASSERT_EQ(0, fcntl(f, F_GETFD)); - - // Now take ownership of f. - { - FileDescriptor fd(f, true); - ASSERT_TRUE(fd.Valid()); - ASSERT_EQ(f, fd.Fd()); - ASSERT_EQ(1, fd.RefCount()); - } - // Will be closed. - ASSERT_EQ(-1, fcntl(f, F_GETFD)); - ASSERT_EQ(EBADF, errno); -} diff --git a/toolbelt/logging.cc b/toolbelt/logging.cc index 00fcc3e..933d76e 100644 --- a/toolbelt/logging.cc +++ b/toolbelt/logging.cc @@ -7,7 +7,6 @@ #include "clock.h" #include #include -#include namespace toolbelt { @@ -149,19 +148,10 @@ void Logger::VLog(LogLevel level, const char *fmt, va_list ap) { if (level < min_level_) { return; } -#if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-nonliteral" -#elif defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-nonliteral" -#endif size_t n = vsnprintf(buffer_, sizeof(buffer_), fmt, ap); -#if defined(__clang__) #pragma clang diagnostic pop -#elif defined(__GNUC__) -#pragma GCC diagnostic pop -#endif // Strip final \n if present. Refactoring from printf can leave // this in place. diff --git a/toolbelt/logging.h b/toolbelt/logging.h index 50b95a2..e190eb9 100644 --- a/toolbelt/logging.h +++ b/toolbelt/logging.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -77,6 +78,86 @@ class Logger { virtual void Log(LogLevel level, uint64_t timestamp, const std::string &source, std::string text); + inline void VerboseDebug(const std::string& str) { + Log(LogLevel::kVERBOSE_DEBUG, str); + } + inline void Debug(const std::string& str) { + Log(LogLevel::kDBG, str); + } + inline void Info(const std::string& str) { + Log(LogLevel::kINFO, str); + } + inline void Warn(const std::string& str) { + Log(LogLevel::kWARNING, str); + } + inline void Error(const std::string& str) { + Log(LogLevel::kERROR, str); + } + inline void Fatal(const std::string& str) { + Log(LogLevel::kFATAL, str); + } + inline void VerboseDebug(std::string&& str) { + Log(LogLevel::kVERBOSE_DEBUG, std::move(str)); + } + inline void Debug(std::string&& str) { + Log(LogLevel::kDBG, std::move(str)); + } + inline void Info(std::string&& str) { + Log(LogLevel::kINFO, std::move(str)); + } + inline void Warn(std::string&& str) { + Log(LogLevel::kWARNING, std::move(str)); + } + inline void Error(std::string&& str) { + Log(LogLevel::kERROR, std::move(str)); + } + inline void Fatal(std::string&& str) { + Log(LogLevel::kFATAL, std::move(str)); + } + + template + void VerboseDebug(const char* fmt, Ts&&... args) { + if (!_enabled || LogLevel::kVERBOSE_DEBUG < min_level_) { + return; + } + Log(LogLevel::kVERBOSE_DEBUG, fmt::format(fmt, std::forward(args)...)); + } + + template + void Debug(const char* fmt, Ts&&... args) { + if (!_enabled || LogLevel::kDBG < min_level_) { + return; + } + Log(LogLevel::DBG, fmt::format(fmt, std::forward(args)...)); + } + template + void Info(const char* fmt, Ts&&... args) { + if (!_enabled || LogLevel::kINFO < min_level_) { + return; + } + Log(LogLevel::kINFO, fmt::format(fmt, std::forward(args)...)); + } + template + void Warn(const char* fmt, Ts&&... args) { + if (!_enabled || LogLevel::kWARNING < min_level_) { + return; + } + Log(LogLevel::kWARNING, fmt::format(fmt, std::forward(args)...)); + } + template + void Error(const char* fmt, Ts&&... args) { + if (!_enabled || LogLevel::kERROR < min_level_) { + return; + } + Log(LogLevel::kERROR, fmt::format(fmt, std::forward(args)...)); + } + template + void Fatal(const char* fmt, Ts&&... args) { + if (!_enabled || LogLevel::kFATAL < min_level_) { + return; + } + Log(LogLevel::kFATAL, fmt::format(fmt, std::forward(args)...)); + } void SetTheme(LogTheme theme); // All logged messages with a level below the min level will be diff --git a/toolbelt/logging_test.cc b/toolbelt/logging_test.cc new file mode 100644 index 0000000..09b433d --- /dev/null +++ b/toolbelt/logging_test.cc @@ -0,0 +1,238 @@ +#include "logging.h" +#include + +#include + +#include +#include + +TEST(LoggingTest, Disabled) { + toolbelt::Logger logger("foobar", false); + logger.log(toolbelt::LogLevel::kINFO, "this should not be logged"); +} + +TEST(LoggingTest, Levels) { + toolbelt::Logger logger; + + struct Level { + std::string name; + toolbelt::LogLevel level; + } levels[] = { + {"verbose", toolbelt::LogLevel::kVERBOSE_DEBUG}, + {"debug", toolbelt::LogLevel::kDBG}, + {"info", toolbelt::LogLevel::kINFO}, + {"warning", toolbelt::LogLevel::kWARNING}, + {"error", toolbelt::LogLevel::kERROR}, + {"fatal", toolbelt::LogLevel::FATAL}, + }; + + for (auto& level : levels) { + logger.setLogLevel(level.name); + ASSERT_EQ(level.level, logger.getLogLevel()); + } + logger.setLogLevel(toolbelt::LogLevel::kINFO); + + logger.log(toolbelt::LogLevel::kVERBOSE_DEBUG, "verbose debug"); + logger.log(toolbelt::LogLevel::kDBG, "debug"); + logger.log(toolbelt::LogLevel::kINFO, "info"); + logger.log(toolbelt::LogLevel::kWARNING, "warning"); + logger.log(toolbelt::LogLevel::kERROR, "error"); + + EXPECT_DEATH(logger.log(toolbelt::LogLevel::FATAL, "fatal"), "fatal"); +} + +static void ReadAndCheck(FILE* fp, const char* s) { + char buf[256] = {}; + fgets(buf, sizeof(buf), fp); + ASSERT_NE(nullptr, strstr(buf, s)); +} + +TEST(LoggingTest, Output) { + toolbelt::Logger logger; + + int pipes[2]; + (void)pipe(pipes); + + FILE* logger_fp = fdopen(pipes[1], "w"); + FILE* read_fp = fdopen(pipes[0], "r"); + ASSERT_NE(nullptr, logger_fp); + ASSERT_NE(nullptr, read_fp); + + logger.setOutputStream(logger_fp); + + toolbelt::LogLevel levels[] = { + toolbelt::LogLevel::kVERBOSE_DEBUG, + toolbelt::LogLevel::kDBG, + toolbelt::LogLevel::kINFO, + toolbelt::LogLevel::kWARNING, + toolbelt::LogLevel::kERROR, + }; + + for (auto& min_level : levels) { + logger.setLogLevel(min_level); + for (auto& level : levels) { + logger.log(level, "foobar"); + fflush(logger_fp); + if (level >= min_level) { + ReadAndCheck(read_fp, "foobar"); + } + } + } + + fclose(logger_fp); + fclose(read_fp); +} + +TEST(LoggingTest, Plain) { + toolbelt::Logger defaultLogger("default", true, toolbelt::LogTheme::DEFAULT); + toolbelt::Logger lightLogger("light", true, toolbelt::LogTheme::LIGHT); + toolbelt::Logger darkLogger("dark", true, toolbelt::LogTheme::DARK); + + defaultLogger.setDisplayMode(toolbelt::LogDisplayMode::PLAIN); + lightLogger.setDisplayMode(toolbelt::LogDisplayMode::PLAIN); + darkLogger.setDisplayMode(toolbelt::LogDisplayMode::PLAIN); + + toolbelt::LogLevel levels[] = { + toolbelt::LogLevel::kVERBOSE_DEBUG, + toolbelt::LogLevel::kDBG, + toolbelt::LogLevel::kINFO, + toolbelt::LogLevel::kWARNING, + toolbelt::LogLevel::kERROR, + }; + + std::string message = "test "; + for (int i = 0; i < 100; i++) { + for (auto& level : levels) { + defaultLogger.setLogLevel(level); + lightLogger.setLogLevel(level); + darkLogger.setLogLevel(level); + + defaultLogger.log(level, message); + lightLogger.log(level, message); + darkLogger.log(level, message); + } + message += "test "; + } +} + +TEST(LoggingTest, Color) { + toolbelt::Logger defaultLogger("default", true, toolbelt::LogTheme::DEFAULT); + toolbelt::Logger lightLogger("light", true, toolbelt::LogTheme::LIGHT); + toolbelt::Logger darkLogger("dark", true, toolbelt::LogTheme::DARK); + + defaultLogger.setDisplayMode(toolbelt::LogDisplayMode::COLOR); + lightLogger.setDisplayMode(toolbelt::LogDisplayMode::COLOR); + darkLogger.setDisplayMode(toolbelt::LogDisplayMode::COLOR); + + + toolbelt::LogLevel levels[] = { + toolbelt::LogLevel::kVERBOSE_DEBUG, + toolbelt::LogLevel::kDBG, + toolbelt::LogLevel::kINFO, + toolbelt::LogLevel::kWARNING, + toolbelt::LogLevel::kERROR, + }; + + std::string message = "test "; + for (int i = 0; i < 100; i++) { + for (auto& level : levels) { + defaultLogger.setLogLevel(level); + lightLogger.setLogLevel(level); + darkLogger.setLogLevel(level); + + defaultLogger.log(level, message); + lightLogger.log(level, message); + darkLogger.log(level, message); + } + message += "test "; + } +} + +TEST(LoggingTest, Columnar) { + toolbelt::Logger defaultLogger("default", true, toolbelt::LogTheme::DEFAULT); + toolbelt::Logger lightLogger("light", true, toolbelt::LogTheme::LIGHT); + toolbelt::Logger darkLogger("dark", true, toolbelt::LogTheme::DARK); + + defaultLogger.setDisplayMode(toolbelt::LogDisplayMode::COLUMNAR, 120); + lightLogger.setDisplayMode(toolbelt::LogDisplayMode::COLUMNAR, 120); + darkLogger.setDisplayMode(toolbelt::LogDisplayMode::COLUMNAR, 120); + + toolbelt::LogLevel levels[] = { + toolbelt::LogLevel::kVERBOSE_DEBUG, + toolbelt::LogLevel::kDBG, + toolbelt::LogLevel::kINFO, + toolbelt::LogLevel::kWARNING, + toolbelt::LogLevel::kERROR, + }; + + std::string message = "test "; + for (int i = 0; i < 100; i++) { + for (auto& level : levels) { + defaultLogger.setLogLevel(level); + lightLogger.setLogLevel(level); + darkLogger.setLogLevel(level); + + defaultLogger.log(level, message); + lightLogger.log(level, message); + darkLogger.log(level, message); + } + message += "test "; + } +} + +TEST(LoggingTest, ColumnarSplit) { + toolbelt::Logger defaultLogger("default", true, toolbelt::LogTheme::DEFAULT); + defaultLogger.setDisplayMode(toolbelt::LogDisplayMode::COLUMNAR, 60); + defaultLogger.log( + toolbelt::LogLevel::kINFO, + "now is the time for all good men to come to the aid of the party\n"); + defaultLogger.log( + toolbelt::LogLevel::kINFO, "the quick brown fox\njumps over the\nlazy dog"); +} + + +TEST(LoggingTest, ColumnarLongSubsystem) { + toolbelt::Logger longLogger( + "this_is_a_very_long_subsystem_name_just_to_get_coverage_of_a_line", + true, + toolbelt::LogTheme::DEFAULT); + longLogger.setDisplayMode(toolbelt::LogDisplayMode::COLUMNAR, 60); + longLogger.log( + toolbelt::LogLevel::kINFO, + "now is the time for all good men to come to the aid of the party\n"); + longLogger.log( + toolbelt::LogLevel::kINFO, "the quick brown fox\njumps over the\nlazy dog"); +} + +TEST(LoggingTest, Tee) { + toolbelt::Logger logger("tee_test", true); + logger.setDisplayMode(toolbelt::LogDisplayMode::COLOR); + + const std::string teeFile = "/tmp/foo/bar/tee_test.log"; + auto status = logger.setTeeFile(teeFile); + ASSERT_TRUE(status.ok()) << status.toString(); + + logger.log(toolbelt::LogLevel::kINFO, "this is a test message"); + + std::ifstream teeStream(teeFile); + ASSERT_TRUE(teeStream.is_open()); + std::string line; + std::getline(teeStream, line); + ASSERT_NE(line.find("this is a test message"), std::string::npos); + teeStream.close(); + + std::string tee2 = "/tmp/bar/foo/tee_test2.log"; + // Use another tee file. This will close the previous one. + status = logger.setTeeFile(tee2); + ASSERT_TRUE(status.ok()) << status.toString(); + logger.log(toolbelt::LogLevel::kINFO, "this is another test message"); + std::ifstream teeStream2(tee2); + ASSERT_TRUE(teeStream2.is_open()); + std::getline(teeStream2, line); + ASSERT_NE(line.find("this is another test message"), std::string::npos); + teeStream2.close(); + + // Clean up + remove(teeFile.c_str()); + remove(tee2.c_str()); +} diff --git a/toolbelt/manual_socket_programs/BUILD.bazel b/toolbelt/manual_socket_programs/BUILD.bazel index f8b246c..777965e 100644 --- a/toolbelt/manual_socket_programs/BUILD.bazel +++ b/toolbelt/manual_socket_programs/BUILD.bazel @@ -1,7 +1,3 @@ -package(default_visibility = ["//visibility:public"]) - -load("@rules_cc//cc:defs.bzl", "cc_binary") - cc_binary( name = "tcp_sender", srcs = ["tcp_sender.cc"], diff --git a/toolbelt/payload_buffer.cc b/toolbelt/payload_buffer.cc index cf7a221..a45b116 100644 --- a/toolbelt/payload_buffer.cc +++ b/toolbelt/payload_buffer.cc @@ -5,7 +5,7 @@ namespace toolbelt { static constexpr struct BitmapRunInfo { int num; - uint32_t size; + int size; } bitmp_run_infos[kNumBitmapRuns] = { {kRunSize1, kBitmapRunSize1}, {kRunSize2, kBitmapRunSize2}, @@ -162,7 +162,7 @@ void PayloadBuffer::Dump(std::ostream &os) { os << " free_list: " << free_list << " " << ToAddress(free_list) << std::endl; os << " message: " << message << " " << ToAddress(message) << std::endl; - for (size_t i = 0; i < kNumBitmapRuns; i++) { + for (int i = 0; i < kNumBitmapRuns; i++) { os << " bitmaps[" << i << "]: " << bitmaps[i] << " " << ToAddress(bitmaps[i]) << std::endl; } @@ -576,23 +576,20 @@ void *PayloadBuffer::Realloc(PayloadBuffer **buffer, void *p, uint32_t n, (n & kBitmapRunSizeMask); *len_ptr = encoded_size; - if (clear && n > static_cast(decoded_length)) { + if (clear && n > decoded_length) { memset(reinterpret_cast(p) + decoded_length, 0, - n - static_cast(decoded_length)); + n - decoded_length); } return p; } // Need to free the old block and allocate a new one as the small block // index is different. - BufferOffset p_offset = (*buffer)->ToOffset(p); void *newp = Allocate(buffer, n, false, enable_small_block); if (newp == NULL) { return NULL; } - // Re-derive p since Allocate may have triggered a buffer resize. - p = (*buffer)->ToAddress(p_offset); memcpy(newp, p, decoded_length); - if (clear && n > static_cast(decoded_length)) { + if (clear && n > decoded_length) { memset(reinterpret_cast(newp) + decoded_length, 0, n - decoded_length); } @@ -632,8 +629,8 @@ void *PayloadBuffer::Realloc(PayloadBuffer **buffer, void *p, uint32_t n, int diff = n - orig_length; if (alloc_addr + orig_length == free_addr) { // There is a free block above. See if has enough space. - if (free_block->length > static_cast(diff)) { - uint32_t freelen = free_block->length - static_cast(diff); + if (free_block->length > diff) { + ssize_t freelen = free_block->length - diff; if (freelen > sizeof(FreeBlockHeader)) { (*buffer)->ExpandIntoFreeBlockAbove(free_block, n, diff, freelen, len_ptr, next_ptr, clear); @@ -645,7 +642,7 @@ void *PayloadBuffer::Realloc(PayloadBuffer **buffer, void *p, uint32_t n, if (prev != NULL) { uintptr_t prev_addr = (uintptr_t)prev; if (prev_addr + prev->length == (uintptr_t)alloc_block && - prev->length >= static_cast(diff)) { + prev->length >= diff) { // Previous free block is adjacent and has enough space in it. // Use start of new block as new address and place FreeBlockHeader // at newly free part. @@ -665,13 +662,10 @@ void *PayloadBuffer::Realloc(PayloadBuffer **buffer, void *p, uint32_t n, // one, copy the memory and free the old block. We are guaranteed that // the new block is larger than the original one since if it was smaller // we can always reuse the block. - BufferOffset p_offset = (*buffer)->ToOffset(p); void *newp = Allocate(buffer, n, false, enable_small_block); if (newp == NULL) { return NULL; } - // Re-derive p since Allocate may have triggered a buffer resize. - p = (*buffer)->ToAddress(p_offset); memcpy(newp, p, orig_length); if (clear) { memset(reinterpret_cast(newp) + orig_length, 0, n - orig_length); @@ -692,14 +686,15 @@ bool PayloadBuffer::PrimeBitmapAllocator(PayloadBuffer **self, size_t size) { return false; } (*self)->bitmaps[index] = offset; + VectorHeader *hdr = (*self)->ToAddress((*self)->bitmaps[index]); BitMapRun *run = PayloadBuffer::AllocateBitMapRun( self, bitmp_run_infos[index].size, bitmp_run_infos[index].num); if (run == nullptr) { return false; } - // Re-derive hdr since AllocateBitMapRun may have triggered a buffer resize. - VectorHeader *hdr = (*self)->ToAddress((*self)->bitmaps[index]); + // Add to the vector, this might move the vector contents but the header + // will stay where it is. (*self)->VectorPush(self, hdr, (*self)->ToOffset(run), false); return true; } @@ -737,7 +732,7 @@ BitMapRun *PayloadBuffer::AllocateBitMapRun(PayloadBuffer **self, uint32_t size, return run; } -void *BitMapRun::Allocate(PayloadBuffer **pb, int index, uint32_t, int size, +void *BitMapRun::Allocate(PayloadBuffer **pb, int index, uint32_t n, int size, int num, bool clear) { // Lazy init of vector. if ((*pb)->bitmaps[index] == 0) { @@ -747,10 +742,8 @@ void *BitMapRun::Allocate(PayloadBuffer **pb, int index, uint32_t, int size, } (*pb)->bitmaps[index] = offset; } + VectorHeader *hdr = (*pb)->ToAddress((*pb)->bitmaps[index]); for (;;) { - // Re-derive hdr each iteration since allocations below may trigger a - // buffer resize (realloc), invalidating any previous pointer. - VectorHeader *hdr = (*pb)->ToAddress((*pb)->bitmaps[index]); // Go backwards through the elements as that is most likely to find a free // bit. for (int i = hdr->num_elements - 1; i >= 0; i--) { @@ -788,9 +781,8 @@ void *BitMapRun::Allocate(PayloadBuffer **pb, int index, uint32_t, int size, if (run == nullptr) { return nullptr; } - // Re-derive hdr since AllocateBitMapRun may have triggered a buffer - // resize, invalidating the previous pointer. - hdr = (*pb)->ToAddress((*pb)->bitmaps[index]); + // Add to the vector, this might move the vector contents but the header + // will stay where it is. (*pb)->VectorPush(pb, hdr, (*pb)->ToOffset(run), false); } } diff --git a/toolbelt/payload_buffer.h b/toolbelt/payload_buffer.h index 08b1775..5c56cfa 100644 --- a/toolbelt/payload_buffer.h +++ b/toolbelt/payload_buffer.h @@ -1,7 +1,6 @@ #pragma once #include "absl/types/span.h" -#include "toolbelt/hexdump.h" #include #include #include @@ -9,6 +8,7 @@ #include #include #include +#include "toolbelt/hexdump.h" namespace toolbelt { @@ -144,7 +144,7 @@ struct PayloadBuffer { PayloadBuffer(uint32_t size, bool bitmap_allocator = true) : magic(kFixedBufferMagic | (bitmap_allocator ? kBitMapFlag : 0)), message(0), hwm(0), full_size(size), metadata(0) { - for (size_t i = 0; i < kNumBitmapRuns; i++) { + for (int i = 0; i < kNumBitmapRuns; i++) { bitmaps[i] = 0; } InitFreeList(); @@ -163,7 +163,7 @@ struct PayloadBuffer { PayloadBuffer(uint32_t initial_size, Resizer r, bool bitmap_allocator = true) : magic(kMovableBufferMagic | (bitmap_allocator ? kBitMapFlag : 0)), message(0), hwm(0), full_size(initial_size), metadata(0) { - for (size_t i = 0; i < kNumBitmapRuns; i++) { + for (int i = 0; i < kNumBitmapRuns; i++) { bitmaps[i] = 0; } InitFreeList(); @@ -228,7 +228,7 @@ struct PayloadBuffer { return (p[word] & (1 << bit)) != 0; } - static uint32_t DecodeSize(BufferOffset *addr) { + static uint32_t DecodeSize(BufferOffset* addr) { // Length is 64 bits long but we only need the bottom 32 bits of it. uint32_t *p = reinterpret_cast(addr) - 2; if ((*p & (1U << 31)) == 0) { @@ -314,11 +314,12 @@ struct PayloadBuffer { FreeBlockHeader *FreeList() { return ToAddress(free_list); } // Allocate some memory in the buffer. The buffer might move. - static void *Allocate(PayloadBuffer **buffer, uint32_t n, bool clear = true, - bool enable_small_block = true); + static void *Allocate(PayloadBuffer **buffer, uint32_t n, + bool clear = true, bool enable_small_block = true); void Free(void *p); static void *Realloc(PayloadBuffer **buffer, void *p, uint32_t n, - bool clear = true, bool enable_small_block = true); + bool clear = true, + bool enable_small_block = true); static BufferOffset AllocateBitMapRunVector(PayloadBuffer **self); static void *AllocateSmallBlock(PayloadBuffer **pb, uint32_t size, int index, bool clear = true); @@ -330,7 +331,8 @@ struct PayloadBuffer { // individually. The addresses of the allocated items are returned in a // vector. static std::vector AllocateMany(PayloadBuffer **buffer, uint32_t size, - uint32_t n, bool clear = true); + uint32_t n, + bool clear = true); bool IsValidMagic() const { uint32_t m = magic & kBitMapMask; @@ -351,12 +353,12 @@ struct PayloadBuffer { // Given the address of a block, return the size of the block. This is // in the previous 8 bytes but might be encoded if the block is a small // block. - static uint32_t DecodedSize(BufferOffset *addr) { - BufferOffset *p = addr - 2; - if (*p & (1U << 31)) { - return *p & kBitmapRunSizeMask; - } - return *p; + static uint32_t DecodedSize(BufferOffset* addr) { + BufferOffset* p = addr - 2; + if (*p & (1U << 31)) { + return *p & kBitmapRunSizeMask; + } + return *p; } template @@ -481,15 +483,11 @@ inline void PayloadBuffer::VectorPush(PayloadBuffer **self, VectorHeader *hdr, // BufferOffset data; - BufferOffset to vector contents // The vector contents is allocated in the buffer. It is preceded // by the block size (in bytes). - BufferOffset hdr_offset = (*self)->ToOffset(hdr); - uint32_t total_size = hdr->num_elements * sizeof(T); if (hdr->data == 0) { // The vector is empty, allocate it with a default size of 2. void *vecp = Allocate(self, 2 * sizeof(T), true, enable_small_block); - VectorHeader *new_hdr = (*self)->ToAddress(hdr_offset); - new_hdr->data = (*self)->ToOffset(vecp); - hdr = new_hdr; + hdr->data = (*self)->ToOffset(vecp); } else { // Vector has some values in it. Retrieve the total size from // the allocated block header (before the start of the memory) @@ -497,11 +495,9 @@ inline void PayloadBuffer::VectorPush(PayloadBuffer **self, VectorHeader *hdr, uint32_t current_size = DecodeSize(block); if (current_size == total_size) { // Need to double the size of the memory. - void *vecp = Realloc(self, block, 2 * hdr->num_elements * sizeof(T), true, - enable_small_block); - VectorHeader *new_hdr = (*self)->ToAddress(hdr_offset); - new_hdr->data = (*self)->ToOffset(vecp); - hdr = new_hdr; + void *vecp = Realloc(self, block, 2 * hdr->num_elements * sizeof(T), + true, enable_small_block); + hdr->data = (*self)->ToOffset(vecp); } } // Get address of next location in vector. @@ -516,12 +512,9 @@ template inline void PayloadBuffer::VectorReserve(PayloadBuffer **self, VectorHeader *hdr, size_t n, bool enable_small_block) { - BufferOffset hdr_offset = (*self)->ToOffset(hdr); if (hdr->data == 0) { void *vecp = Allocate(self, n * sizeof(T), false, enable_small_block); - VectorHeader* new_hdr = (*self)->ToAddress(hdr_offset); - new_hdr->data = (*self)->ToOffset(vecp); - hdr = new_hdr; + hdr->data = (*self)->ToOffset(vecp); } else { // Vector has some values in it. Retrieve the total size from // the allocated block header (before the start of the memory) @@ -531,9 +524,7 @@ inline void PayloadBuffer::VectorReserve(PayloadBuffer **self, // Need to expand the memory to the size given. void *vecp = Realloc(self, block, n * sizeof(T), false, enable_small_block); - VectorHeader* new_hdr = (*self)->ToAddress(hdr_offset); - new_hdr->data = (*self)->ToOffset(vecp); - hdr = new_hdr; + hdr->data = (*self)->ToOffset(vecp); } } } @@ -541,12 +532,9 @@ inline void PayloadBuffer::VectorReserve(PayloadBuffer **self, template inline void PayloadBuffer::VectorResize(PayloadBuffer **self, VectorHeader *hdr, size_t n) { - BufferOffset hdr_offset = (*self)->ToOffset(hdr); if (hdr->data == 0) { void *vecp = Allocate(self, n * sizeof(T)); - VectorHeader* new_hdr = (*self)->ToAddress(hdr_offset); - new_hdr->data = (*self)->ToOffset(vecp); - hdr = new_hdr; + hdr->data = (*self)->ToOffset(vecp); } else { // Vector has some values in it. Retrieve the total size from // the allocated block header (before the start of the memory) @@ -555,9 +543,7 @@ inline void PayloadBuffer::VectorResize(PayloadBuffer **self, VectorHeader *hdr, if (current_size < n * sizeof(T)) { // Need to expand the memory to the size given. void *vecp = Realloc(self, block, n * sizeof(T), 8); - VectorHeader* new_hdr = (*self)->ToAddress(hdr_offset); - new_hdr->data = (*self)->ToOffset(vecp); - hdr = new_hdr; + hdr->data = (*self)->ToOffset(vecp); } } hdr->num_elements = n; diff --git a/toolbelt/payload_buffer_test.cc b/toolbelt/payload_buffer_test.cc index cd7d9db..e33e52e 100644 --- a/toolbelt/payload_buffer_test.cc +++ b/toolbelt/payload_buffer_test.cc @@ -1,7 +1,6 @@ #include "toolbelt/clock.h" #include "toolbelt/hexdump.h" #include "toolbelt/payload_buffer.h" -#include #include #include @@ -11,7 +10,7 @@ using VectorHeader = toolbelt::VectorHeader; using Resizer = toolbelt::Resizer; TEST(BufferTest, Simple) { - char *buffer = (char *)calloc(1, 4096); + char *buffer = (char *)malloc(4096); PayloadBuffer *pb = new (buffer) PayloadBuffer(4096); pb->Dump(std::cout); toolbelt::Hexdump(pb, 64); @@ -25,7 +24,7 @@ TEST(BufferTest, Simple) { } TEST(BufferTest, TwoAllocs) { - char *buffer = (char *)calloc(1, 4096); + char *buffer = (char *)malloc(4096); PayloadBuffer *pb = new (buffer) PayloadBuffer(4096); pb->Dump(std::cout); toolbelt::Hexdump(pb, 64); @@ -45,7 +44,7 @@ TEST(BufferTest, TwoAllocs) { } TEST(BufferTest, Free) { - char *buffer = (char *)calloc(1, 4096); + char *buffer = (char *)malloc(4096); PayloadBuffer *pb = new (buffer) PayloadBuffer(4096); pb->Dump(std::cout); toolbelt::Hexdump(pb, 64); @@ -68,7 +67,7 @@ TEST(BufferTest, Free) { } TEST(BufferTest, FreeThenAlloc) { - char *buffer = (char *)calloc(1, 4096); + char *buffer = (char *)malloc(4096); PayloadBuffer *pb = new (buffer) PayloadBuffer(4096); pb->Dump(std::cout); toolbelt::Hexdump(pb, 64); @@ -95,7 +94,7 @@ TEST(BufferTest, FreeThenAlloc) { } TEST(BufferTest, SmallBlockAllocSimple) { - char *buffer = (char *)calloc(1, 4096); + char *buffer = (char *)malloc(4096); PayloadBuffer *pb = new (buffer) PayloadBuffer(4096); void *addr = PayloadBuffer::Allocate(&pb, 16); @@ -105,13 +104,10 @@ TEST(BufferTest, SmallBlockAllocSimple) { // Allocate again and make sure it's the same address. void *addr2 = PayloadBuffer::Allocate(&pb, 16); ASSERT_EQ(addr, addr2); - - pb->~PayloadBuffer(); - free(buffer); } TEST(BufferTest, SmallBlockAlloc) { - char *buffer = (char *)calloc(1, 8192); + char *buffer = (char *)malloc(8192); PayloadBuffer *pb = new (buffer) PayloadBuffer(8192); // Small block sizes are 16, 32, 64 and 128. @@ -140,13 +136,10 @@ TEST(BufferTest, SmallBlockAlloc) { } pb->Dump(std::cout); toolbelt::Hexdump(pb, pb->hwm); - - pb->~PayloadBuffer(); - free(buffer); } TEST(BufferTest, SmallBlockAllocFree) { - char *buffer = (char *)calloc(1, 8192); + char *buffer = (char *)malloc(8192); PayloadBuffer *pb = new (buffer) PayloadBuffer(8192); // Do a mix of sizes and free them. @@ -159,13 +152,13 @@ TEST(BufferTest, SmallBlockAllocFree) { blocks.push_back(addr); } // Free every 5th block. - for (size_t i = 0; i < blocks.size(); i++) { + for (int i = 0; i < blocks.size(); i++) { if (i % 5 == 0) { pb->Free(blocks[i]); } } // Now allocate every 5th block again. - for (size_t i = 0; i < blocks.size(); i++) { + for (int i = 0; i < blocks.size(); i++) { if (i % 5 == 0) { size_t size = sizes[i % sizes.size()]; void *addr = PayloadBuffer::Allocate(&pb, size); @@ -175,9 +168,6 @@ TEST(BufferTest, SmallBlockAllocFree) { } pb->Dump(std::cout); toolbelt::Hexdump(pb, pb->hwm); - - pb->~PayloadBuffer(); - free(buffer); } // This performance test compares the performance of the small block allocator @@ -195,7 +185,7 @@ TEST(BufferTest, BestCasePerformance) { constexpr int kIterations = 10000; for (int iter = 0; iter < kIterations; iter++) { - char *buffer = (char *)calloc(1, kSize); + char *buffer = (char *)malloc(kSize); PayloadBuffer *pb = new (buffer) PayloadBuffer(kSize); ASSERT_TRUE(PayloadBuffer::PrimeBitmapAllocator(&pb, 16)); @@ -240,7 +230,7 @@ TEST(BufferTest, BestCasePerformance) { // New buffer. free(buffer); - buffer = (char *)calloc(1, kSize); + buffer = (char *)malloc(kSize); pb = new (buffer) PayloadBuffer(kSize); // Now allocate by disabling the small block allocator. @@ -308,7 +298,7 @@ TEST(BufferTest, TypicalPerformance) { } for (int iter = 0; iter < kIterations; iter++) { - char *buffer = (char *)calloc(1, kSize); + char *buffer = (char *)malloc(kSize); PayloadBuffer *pb = new (buffer) PayloadBuffer(kSize); // No priming the small block allocator for this test. It probably won't @@ -324,7 +314,7 @@ TEST(BufferTest, TypicalPerformance) { small_blocks.push_back(addr); } // Free some of the blocks. - for (size_t i = prev_size; i < small_blocks.size(); i++) { + for (int i = prev_size; i < small_blocks.size(); i++) { if (i % 8 == 0) { continue; } @@ -338,7 +328,7 @@ TEST(BufferTest, TypicalPerformance) { // New buffer. free(buffer); - buffer = (char *)calloc(1, kSize); + buffer = (char *)malloc(kSize); pb = new (buffer) PayloadBuffer(kSize); // Switch off small block alloctor. @@ -347,12 +337,11 @@ TEST(BufferTest, TypicalPerformance) { for (int j = 0; j < 1000; j++) { int prev_size = int(large_blocks.size()); for (int i = 0; i < kNumBlocks; i++) { - void *addr = PayloadBuffer::Allocate(&pb, 10, false, - /*enable_small_block=*/false); + void *addr = PayloadBuffer::Allocate(&pb, 10, false, /*enable_small_block=*/false); large_blocks.push_back(addr); } // Free some of the blocks. - for (size_t i = prev_size; i < large_blocks.size(); i++) { + for (int i = prev_size; i < large_blocks.size(); i++) { if (i % 8 == 0) { continue; } @@ -374,10 +363,11 @@ TEST(BufferTest, TypicalPerformance) { TEST(BufferTest, Many) { constexpr size_t kSize = 8192; - char *buffer = (char *)calloc(1, kSize); + char *buffer = (char *)malloc(kSize); PayloadBuffer *pb = new (buffer) PayloadBuffer(kSize); - std::vector addrs = PayloadBuffer::AllocateMany(&pb, 100, 10, true); + std::vector addrs = + PayloadBuffer::AllocateMany(&pb, 100, 10, true); ASSERT_EQ(10, addrs.size()); // Print the addresses. for (auto addr : addrs) { @@ -396,7 +386,7 @@ TEST(BufferTest, Many) { } TEST(BufferTest, String) { - char *buffer = (char *)calloc(1, 4096); + char *buffer = (char *)malloc(4096); PayloadBuffer *pb = new (buffer) PayloadBuffer(4096); // Allocate space for a message containing an offset for the string. @@ -426,7 +416,7 @@ TEST(BufferTest, String) { } TEST(BufferTest, Vector) { - char *buffer = (char *)calloc(1, 4096); + char *buffer = (char *)malloc(4096); PayloadBuffer *pb = new (buffer) PayloadBuffer(4096); // Allocate space for a message containing the VectorHeader. @@ -448,7 +438,7 @@ TEST(BufferTest, Vector) { } TEST(BufferTest, VectorExpand) { - char *buffer = (char *)calloc(1, 4096); + char *buffer = (char *)malloc(4096); PayloadBuffer *pb = new (buffer) PayloadBuffer(4096); // Allocate space for a message containing the VectorHeader. @@ -475,7 +465,7 @@ TEST(BufferTest, VectorExpand) { } TEST(BufferTest, VectorExpandMore) { - char *buffer = (char *)calloc(1, 4096); + char *buffer = (char *)malloc(4096); PayloadBuffer *pb = new (buffer) PayloadBuffer(4096); // Allocate space for a message containing the VectorHeader. @@ -503,140 +493,13 @@ TEST(BufferTest, VectorExpandMore) { free(buffer); } -TEST(BufferTest, VectorPushWithResize) { - char *buffer = (char *)calloc(256, 1); - bool resized = false; - PayloadBuffer *pb = new (buffer) PayloadBuffer( - 256, [&resized, &buffer](PayloadBuffer **p, size_t, size_t new_size) { -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wclass-memaccess" -#elif defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wclass-memaccess" -#endif - *p = reinterpret_cast(realloc(*p, new_size)); -#if defined(__clang__) -#pragma clang diagnostic pop -#elif defined(__GNUC__) -#pragma GCC diagnostic pop -#endif - buffer = reinterpret_cast(*p); - resized = true; - }); - - PayloadBuffer::AllocateMainMessage(&pb, sizeof(VectorHeader)); - BufferOffset msg_offset = pb->message; - - constexpr int kCount = 200; - for (int i = 0; i < kCount; i++) { - VectorHeader *hdr = pb->ToAddress(msg_offset); - PayloadBuffer::VectorPush(&pb, hdr, i + 1); - } - ASSERT_TRUE(resized); - - VectorHeader *hdr = pb->ToAddress(msg_offset); - ASSERT_EQ(kCount, hdr->num_elements); - for (int i = 0; i < kCount; i++) { - uint32_t v = pb->VectorGet(hdr, i); - ASSERT_EQ(i + 1, v); - } - - pb->~PayloadBuffer(); - free(buffer); -} - -TEST(BufferTest, VectorReserveWithResize) { - char *buffer = (char *)calloc(256, 1); - bool resized = false; - PayloadBuffer *pb = new (buffer) PayloadBuffer( - 256, [&resized, &buffer](PayloadBuffer **p, size_t, size_t new_size) { -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wclass-memaccess" -#elif defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wclass-memaccess" -#endif - *p = reinterpret_cast(realloc(*p, new_size)); -#if defined(__clang__) -#pragma clang diagnostic pop -#elif defined(__GNUC__) -#pragma GCC diagnostic pop -#endif - buffer = reinterpret_cast(*p); - resized = true; - }); - - PayloadBuffer::AllocateMainMessage(&pb, sizeof(VectorHeader)); - BufferOffset msg_offset = pb->message; - - VectorHeader *hdr = pb->ToAddress(msg_offset); - PayloadBuffer::VectorReserve(&pb, hdr, 500); - ASSERT_TRUE(resized); - - hdr = pb->ToAddress(msg_offset); - ASSERT_NE(0u, hdr->data); - - pb->~PayloadBuffer(); - free(buffer); -} - -TEST(BufferTest, VectorResizeWithResize) { - char *buffer = (char *)calloc(256, 1); - bool resized = false; - PayloadBuffer *pb = new (buffer) PayloadBuffer( - 256, [&resized, &buffer](PayloadBuffer **p, size_t, size_t new_size) { -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wclass-memaccess" -#elif defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wclass-memaccess" -#endif - *p = reinterpret_cast(realloc(*p, new_size)); -#if defined(__clang__) -#pragma clang diagnostic pop -#elif defined(__GNUC__) -#pragma GCC diagnostic pop -#endif - buffer = reinterpret_cast(*p); - resized = true; - }); - - PayloadBuffer::AllocateMainMessage(&pb, sizeof(VectorHeader)); - BufferOffset msg_offset = pb->message; - - VectorHeader *hdr = pb->ToAddress(msg_offset); - PayloadBuffer::VectorResize(&pb, hdr, 500); - ASSERT_TRUE(resized); - - hdr = pb->ToAddress(msg_offset); - ASSERT_EQ(500u, hdr->num_elements); - - pb->~PayloadBuffer(); - free(buffer); -} - TEST(BufferTest, Resizeable) { - char *buffer = (char *)calloc(1, 512); + char *buffer = (char *)malloc(512); bool resized = false; - PayloadBuffer *pb = new (buffer) PayloadBuffer( - 256, [&resized](PayloadBuffer **p, size_t, size_t new_size) { + PayloadBuffer *pb = new (buffer) + PayloadBuffer(256, [&resized](PayloadBuffer **p, size_t old_size, size_t new_size) { std::cout << "resize for " << new_size << std::endl; -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wclass-memaccess" -#elif defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wclass-memaccess" -#endif *p = reinterpret_cast(realloc(*p, new_size)); -#if defined(__clang__) -#pragma clang diagnostic pop -#elif defined(__GNUC__) -#pragma GCC diagnostic pop -#endif resized = true; }); pb->Dump(std::cout); @@ -661,11 +524,7 @@ TEST(BufferTest, Resizeable) { toolbelt::Hexdump(pb, pb->hwm); // Don't free 'buffer' as it has already been freed by the call to realloc. - // pb was constructed via placement-new on a malloc/realloc'd buffer, so we - // must invoke the destructor explicitly and then free() the storage rather - // than calling operator delete (which would be an alloc-dealloc mismatch). - pb->~PayloadBuffer(); - free(pb); + delete pb; } int main(int argc, char **argv) { diff --git a/toolbelt/pipe.cc b/toolbelt/pipe.cc index 0cb8312..82c89cb 100644 --- a/toolbelt/pipe.cc +++ b/toolbelt/pipe.cc @@ -83,7 +83,7 @@ absl::Status Pipe::SetPipeSize(size_t size) { } absl::StatusOr Pipe::Read(char *buffer, size_t length, - const co::Coroutine *c) { + co::Coroutine *c) { size_t total = 0; ScopedRead sc(*this, c); @@ -123,7 +123,7 @@ absl::StatusOr Pipe::Read(char *buffer, size_t length, } absl::StatusOr Pipe::Write(const char *buffer, size_t length, - const co::Coroutine *c) { + co::Coroutine *c) { size_t total = 0; ScopedWrite sc(*this, c); diff --git a/toolbelt/pipe.h b/toolbelt/pipe.h index cfe4749..25996db 100644 --- a/toolbelt/pipe.h +++ b/toolbelt/pipe.h @@ -3,7 +3,7 @@ #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_format.h" -#include "co/coroutine.h" +#include "coroutine.h" #include "toolbelt/fd.h" #include @@ -66,9 +66,9 @@ class Pipe { absl::Status SetPipeSize(size_t size); virtual absl::StatusOr Read(char *buffer, size_t length, - const co::Coroutine *c = nullptr); + co::Coroutine *c = nullptr); virtual absl::StatusOr Write(const char *buffer, size_t length, - const co::Coroutine *c = nullptr); + co::Coroutine *c = nullptr); protected: // RAII classes for keeping coroutines from interleaving reads or writes on a @@ -78,7 +78,7 @@ class Pipe { // // Same applies to non-coroutine use except we block with a sleep. struct ScopedRead { - ScopedRead(Pipe &p, const co::Coroutine *c) : pipe(p) { + ScopedRead(Pipe &p, co::Coroutine *c) : pipe(p) { while (pipe.read_in_progress_) { if (c) { c->Yield(); @@ -97,7 +97,7 @@ class Pipe { }; struct ScopedWrite { - ScopedWrite(Pipe &p, const co::Coroutine *c) : pipe(p) { + ScopedWrite(Pipe &p, co::Coroutine *c) : pipe(p) { while (pipe.write_in_progress_) { if (c) { c->Yield(); @@ -150,16 +150,16 @@ template class SharedPtrPipe : public Pipe { SharedPtrPipe(int r, int w) : Pipe(r, w) {} // You can't use raw buffers with shared ptr pipes. - absl::StatusOr Read(char *, size_t , - const co::Coroutine * = nullptr) override { + absl::StatusOr Read(char *buffer, size_t length, + co::Coroutine *c = nullptr) override { return absl::InternalError("Not supported on SharedPtrPipe"); } - absl::StatusOr Write(const char *, size_t , - const co::Coroutine *c = nullptr) override { + absl::StatusOr Write(const char *buffer, size_t length, + co::Coroutine *c = nullptr) override { return absl::InternalError("Not supported on SharedPtrPipe"); } - absl::StatusOr> Read(const co::Coroutine *c = nullptr) { + absl::StatusOr> Read(co::Coroutine *c = nullptr) { char buffer[sizeof(std::shared_ptr)]; size_t length = sizeof(buffer); size_t total = 0; @@ -205,7 +205,7 @@ template class SharedPtrPipe : public Pipe { } // This makes the pipe an owner of the pointer. - absl::Status Write(std::shared_ptr p, const co::Coroutine *c = nullptr) { + absl::Status Write(std::shared_ptr p, co::Coroutine *c = nullptr) { // On entry, ref count for p = N char buffer[sizeof(std::shared_ptr)]; @@ -272,4 +272,4 @@ template class SharedPtrPipe : public Pipe { } }; -} // namespace toolbelt +} // namespace toolbelt \ No newline at end of file diff --git a/toolbelt/pipe_test.cc b/toolbelt/pipe_test.cc index 30be8a5..097160c 100644 --- a/toolbelt/pipe_test.cc +++ b/toolbelt/pipe_test.cc @@ -3,10 +3,9 @@ // See LICENSE file for licensing information. #include "absl/status/status_matchers.h" -#include "co/coroutine.h" +#include "coroutine.h" #include "pipe.h" #include -#include #define VAR(a) a##__COUNTER__ #define EVAL_AND_ASSERT_OK(expr) EVAL_AND_ASSERT_OK2(VAR(r_), expr) @@ -70,7 +69,7 @@ TEST(PipeTest, CoroutinePipeReadAndWrite) { auto r = pipe.Read(buffer, 5, c); ASSERT_OK(r); ASSERT_EQ(*r, 5); - ASSERT_EQ(std::string_view(buffer, *r), "Hello"); + ASSERT_STREQ(buffer, "Hello"); }); co::Coroutine writer(scheduler, [&pipe](co::Coroutine *c) { const char *msg = "Hello"; @@ -93,7 +92,7 @@ TEST(PipeTest, CoroutinePipeReadAndWriteNonblocking) { auto r = pipe.Read(buffer, 5, c); ASSERT_OK(r); ASSERT_EQ(*r, 5); - ASSERT_EQ(std::string_view(buffer, *r), "Hello"); + ASSERT_STREQ(buffer, "Hello"); }); co::Coroutine writer(scheduler, [&pipe](co::Coroutine *c) { const char *msg = "Hello"; @@ -159,7 +158,7 @@ TEST(PipeTest, CoroutineFullPipeReadAndWrite) { auto r = pipe.Read(buffer, kMessageSize, c); ASSERT_OK(r); ASSERT_EQ(*r, kMessageSize); - ASSERT_EQ(std::string_view(buffer, *r), "1234"); + ASSERT_STREQ(buffer, "1234"); } }); co::Coroutine writer(scheduler, [&pipe, kMessageSize](co::Coroutine *c) { @@ -175,10 +174,6 @@ TEST(PipeTest, CoroutineFullPipeReadAndWrite) { } TEST(PipeTest, CoroutineOverFullPipeReadAndWrite) { -#if defined(THREAD_SANITIZER) - GTEST_SKIP() << "TSan + many coroutine fiber switches in this stress test " - "trips a known TSan runtime issue (nested DEADLYSIGNAL)."; -#endif co::CoroutineScheduler scheduler; auto p = toolbelt::Pipe::Create(); ASSERT_OK(p); @@ -194,7 +189,7 @@ TEST(PipeTest, CoroutineOverFullPipeReadAndWrite) { auto r = pipe.Read(buffer, kMessageSize, c); ASSERT_OK(r); ASSERT_EQ(*r, kMessageSize); - ASSERT_EQ(std::string_view(buffer, *r), "1234"); + ASSERT_STREQ(buffer, "1234"); } }); co::Coroutine writer(scheduler, [&pipe, kMessageSize](co::Coroutine *c) { @@ -226,7 +221,7 @@ TEST(PipeTest, CoroutineFullPipeReadAndWriteNonblocking) { auto r = pipe.Read(buffer, kMessageSize, c); ASSERT_OK(r); ASSERT_EQ(*r, kMessageSize); - ASSERT_EQ(std::string_view(buffer, *r), "1234"); + ASSERT_STREQ(buffer, "1234"); } }); co::Coroutine writer(scheduler, [&pipe, kMessageSize](co::Coroutine *c) { @@ -242,10 +237,6 @@ TEST(PipeTest, CoroutineFullPipeReadAndWriteNonblocking) { } TEST(PipeTest, CoroutineOverFullPipeReadAndWriteNonblocking) { -#if defined(THREAD_SANITIZER) - GTEST_SKIP() << "TSan + many coroutine fiber switches in this stress test " - "trips a known TSan runtime issue (nested DEADLYSIGNAL)."; -#endif co::CoroutineScheduler scheduler; auto p = toolbelt::Pipe::Create(); ASSERT_OK(p); @@ -262,7 +253,7 @@ TEST(PipeTest, CoroutineOverFullPipeReadAndWriteNonblocking) { auto r = pipe.Read(buffer, kMessageSize, c); ASSERT_OK(r); ASSERT_EQ(*r, kMessageSize); - ASSERT_EQ(std::string_view(buffer, *r), "1234"); + ASSERT_STREQ(buffer, "1234"); } }); co::Coroutine writer(scheduler, [&pipe, kMessageSize](co::Coroutine *c) { @@ -288,12 +279,12 @@ TEST(PipeTest, CoroutinePipeReadAndMultiWrite) { auto r = pipe.Read(buffer, 5, c); ASSERT_OK(r); ASSERT_EQ(*r, 5); - ASSERT_EQ(std::string_view(buffer, *r), "12345"); + ASSERT_STREQ(buffer, "12345"); r = pipe.Read(buffer, 5, c); ASSERT_OK(r); ASSERT_EQ(*r, 5); - ASSERT_EQ(std::string_view(buffer, *r), "54321"); + ASSERT_STREQ(buffer, "54321"); }); co::Coroutine writer1(scheduler, [&pipe](co::Coroutine *c) { @@ -313,10 +304,6 @@ TEST(PipeTest, CoroutinePipeReadAndMultiWrite) { } TEST(PipeTest, CoroutineOverFullPipeReadAndWriteMultiwriter) { -#if defined(THREAD_SANITIZER) - GTEST_SKIP() << "TSan + many coroutine fiber switches in this stress test " - "trips a known TSan runtime issue (nested DEADLYSIGNAL)."; -#endif co::CoroutineScheduler scheduler; auto p = toolbelt::Pipe::Create(); ASSERT_OK(p); @@ -333,8 +320,7 @@ TEST(PipeTest, CoroutineOverFullPipeReadAndWriteMultiwriter) { ASSERT_OK(r); ASSERT_EQ(*r, kMessageSize); // Can be in either order. - std::string_view got(buffer, *r); - bool ok = got == "1234" || got == "4321"; + bool ok = (strcmp(buffer, "1234") == 0) || (strcmp(buffer, "4321") == 0); ASSERT_TRUE(ok); } }); @@ -361,10 +347,6 @@ TEST(PipeTest, CoroutineOverFullPipeReadAndWriteMultiwriter) { TEST(PipeTest, CoroutineOverFullPipeReadAndWriteMultiwriterNonblocking) { -#if defined(THREAD_SANITIZER) - GTEST_SKIP() << "TSan + many coroutine fiber switches in this stress test " - "trips a known TSan runtime issue (nested DEADLYSIGNAL)."; -#endif co::CoroutineScheduler scheduler; auto p = toolbelt::Pipe::Create(); ASSERT_OK(p); @@ -382,8 +364,7 @@ TEST(PipeTest, CoroutineOverFullPipeReadAndWriteMultiwriterNonblocking) { ASSERT_OK(r); ASSERT_EQ(*r, kMessageSize); // Can be in either order. - std::string_view got(buffer, *r); - bool ok = got == "1234" || got == "4321"; + bool ok = (strcmp(buffer, "1234") == 0) || (strcmp(buffer, "4321") == 0); ASSERT_TRUE(ok); } }); @@ -406,4 +387,4 @@ TEST(PipeTest, CoroutineOverFullPipeReadAndWriteMultiwriterNonblocking) { } }); scheduler.Run(); -} +} \ No newline at end of file diff --git a/toolbelt/sockets.cc b/toolbelt/sockets.cc index d4d9bff..f3a8d69 100644 --- a/toolbelt/sockets.cc +++ b/toolbelt/sockets.cc @@ -33,27 +33,23 @@ InetAddress InetAddress::AnyAddress(int port) { return InetAddress(port); } InetAddress::InetAddress(const in_addr &ip, int port) { valid_ = true; addr_ = { -#if defined(__APPLE__) - .sin_len = sizeof(struct sockaddr_in), +#if defined(_APPLE__) + .sin_len = sizeof(int), #endif - .sin_family = AF_INET, - .sin_port = htons(port), - .sin_addr = {.s_addr = ip.s_addr}, - .sin_zero = {0} - }; + .sin_family = AF_INET, + .sin_port = htons(port), + .sin_addr = {.s_addr = htonl(ip.s_addr)}}; } InetAddress::InetAddress(int port) { valid_ = true; addr_ = { -#if defined(__APPLE__) - .sin_len = sizeof(struct sockaddr_in), +#if defined(_APPLE__) + .sin_len = sizeof(int), #endif - .sin_family = AF_INET, - .sin_port = htons(port), - .sin_addr = {.s_addr = INADDR_ANY}, - .sin_zero = {0} - }; + .sin_family = AF_INET, + .sin_port = htons(port), + .sin_addr = {.s_addr = INADDR_ANY}}; } InetAddress::InetAddress(const std::string &hostname, int port) { @@ -72,14 +68,12 @@ InetAddress::InetAddress(const std::string &hostname, int port) { } valid_ = true; addr_ = { -#if defined(__APPLE__) - .sin_len = sizeof(struct sockaddr_in), +#if defined(_APPLE__) + .sin_len = sizeof(int), #endif - .sin_family = AF_INET, - .sin_port = htons(port), - .sin_addr = {.s_addr = ipaddr}, - .sin_zero = {0} - }; + .sin_family = AF_INET, + .sin_port = htons(port), + .sin_addr = {.s_addr = ipaddr}}; } std::string InetAddress::ToString() const { @@ -93,51 +87,25 @@ std::string InetAddress::ToString() const { VirtualAddress::VirtualAddress(uint32_t cid, uint32_t port) { valid_ = true; memset(&addr_, 0, sizeof(addr_)); -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wmissing-field-initializers" -#elif defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif addr_ = { -#if defined(__APPLE__) - .svm_len = sizeof(struct sockaddr_vm), -#endif - .svm_family = AF_VSOCK, - .svm_port = port, - .svm_cid = cid - }; -#if defined(__clang__) -#pragma clang diagnostic pop -#elif defined(__GNUC__) -#pragma GCC diagnostic pop +#if defined(_APPLE__) + .svm_len = sizeof(struct sockaddr_vm), #endif + .svm_family = AF_VSOCK, + .svm_port = port, + .svm_cid = cid}; } VirtualAddress::VirtualAddress(uint32_t port) { valid_ = true; memset(&addr_, 0, sizeof(addr_)); -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wmissing-field-initializers" -#elif defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif addr_ = { -#if defined(__APPLE__) - .svm_len = sizeof(struct sockaddr_vm), -#endif - .svm_family = AF_VSOCK, - .svm_port = port, - .svm_cid = VMADDR_CID_ANY - }; -#if defined(__clang__) -#pragma clang diagnostic pop -#elif defined(__GNUC__) -#pragma GCC diagnostic pop +#if defined(_APPLE__) + .svm_len = sizeof(struct sockaddr_vm), #endif + .svm_family = AF_VSOCK, + .svm_port = port, + .svm_cid = VMADDR_CID_ANY}; } VirtualAddress VirtualAddress::HypervisorAddress(uint32_t port) { @@ -153,7 +121,7 @@ VirtualAddress VirtualAddress::AnyAddress(uint32_t port) { return VirtualAddress(VMADDR_CID_ANY, port); } -#if defined(__linux__) && defined(VMADDR_CID_LOCAL) +#if defined(__linux__) VirtualAddress VirtualAddress::LocalAddress(uint32_t port) { return VirtualAddress(VMADDR_CID_LOCAL, port); } @@ -163,17 +131,14 @@ std::string VirtualAddress::ToString() const { return absl::StrFormat("%d:%d", addr_.svm_cid, addr_.svm_port); } -static ssize_t ReceiveFully(const co::Coroutine *c, int fd, size_t length, +static ssize_t ReceiveFully(co::Coroutine *c, int fd, size_t length, char *buffer, size_t buflen) { int offset = 0; size_t remaining = length; while (remaining > 0) { size_t readlen = std::min(remaining, buflen); if (c != nullptr) { - int f = c->Wait(fd, POLLIN); - if (f != fd) { - return -1; - } + c->Wait(fd, POLLIN); } ssize_t n = ::recv(fd, buffer + offset, readlen, 0); if (n == -1) { @@ -199,7 +164,7 @@ static ssize_t ReceiveFully(const co::Coroutine *c, int fd, size_t length, return length; } -static ssize_t SendFully(const co::Coroutine *c, int fd, const char *buffer, +static ssize_t SendFully(co::Coroutine *c, int fd, const char *buffer, size_t length, bool blocking) { size_t remaining = length; size_t offset = 0; @@ -211,10 +176,7 @@ static ssize_t SendFully(const co::Coroutine *c, int fd, const char *buffer, // Yielding before sending to a nonblocking socket will // cause a context switch between coroutines and we want // the write to the network to be as fast as possible. - int f = c->Wait(fd, POLLOUT); - if (f != fd) { - return -1; - } + c->Wait(fd, POLLOUT); } ssize_t n = ::send(fd, buffer + offset, remaining, 0); if (n == -1) { @@ -228,10 +190,7 @@ static ssize_t SendFully(const co::Coroutine *c, int fd, const char *buffer, // If we are nonblocking yield the coroutine now. When we // are resumed we can write to the socket again. if (!blocking) { - int f = c->Wait(fd, POLLOUT); - if (f != fd) { - return -1; - } + c->Wait(fd, POLLOUT); } continue; } @@ -248,7 +207,7 @@ static ssize_t SendFully(const co::Coroutine *c, int fd, const char *buffer, } absl::StatusOr Socket::Receive(char *buffer, size_t buflen, - const co::Coroutine *c) { + co::Coroutine *c) { if (!Connected()) { return absl::InternalError("Socket is not connected"); } @@ -262,7 +221,7 @@ absl::StatusOr Socket::Receive(char *buffer, size_t buflen, } absl::StatusOr Socket::Send(const char *buffer, size_t length, - const co::Coroutine *c) { + co::Coroutine *c) { if (!Connected()) { return absl::InternalError("Socket is not connected"); } @@ -276,7 +235,7 @@ absl::StatusOr Socket::Send(const char *buffer, size_t length, } absl::StatusOr Socket::ReceiveMessage(char *buffer, size_t buflen, - const co::Coroutine *c) { + co::Coroutine *c) { if (!Connected()) { return absl::InternalError("Socket is not connected"); } @@ -309,7 +268,7 @@ absl::StatusOr Socket::ReceiveMessage(char *buffer, size_t buflen, } absl::StatusOr> -Socket::ReceiveVariableLengthMessage(const co::Coroutine *c) { +Socket::ReceiveVariableLengthMessage(co::Coroutine *c) { if (!Connected()) { return absl::InternalError("Socket is not connected"); } @@ -344,7 +303,7 @@ Socket::ReceiveVariableLengthMessage(const co::Coroutine *c) { } absl::StatusOr Socket::SendMessage(char *buffer, size_t length, - const co::Coroutine *c) { + co::Coroutine *c) { if (!Connected()) { return absl::InternalError("Socket is not connected"); } @@ -372,25 +331,20 @@ static struct sockaddr_un BuildUnixSocketName(const std::string &pathname) { // On Linux we can create it in the abstract namespace which doesn't // consume a pathname. addr.sun_path[0] = '\0'; - memcpy(addr.sun_path + 1, pathname.c_str(), - std::min(pathname.size(), sizeof(addr.sun_path) - 2)); + memcpy(addr.sun_path + 1, pathname.c_str(), std::min(pathname.size(), sizeof(addr.sun_path) - 2)); #else // Portable uses the file system so it must be a valid path name. - memcpy(addr.sun_path, pathname.c_str(), - std::min(pathname.size(), sizeof(addr.sun_path) - 1)); + memcpy(addr.sun_path, pathname.c_str(), std::min(pathname.size(), sizeof(addr.sun_path) - 1)); #endif return addr; } -static std::string ExtractUnixSocketNameString(const struct sockaddr_un &addr, - socklen_t addrlen) { +static std::string ExtractUnixSocketNameString(const struct sockaddr_un &addr, socklen_t addrlen) { #if defined(__linux__) - auto addr_str_len = - strnlen(addr.sun_path + 1, addrlen - offsetof(sockaddr_un, sun_path) - 1); + auto addr_str_len = strnlen(addr.sun_path + 1, addrlen - offsetof(sockaddr_un, sun_path) - 1); return std::string(addr.sun_path + 1, addr.sun_path + addr_str_len + 1); #else - auto addr_str_len = - strnlen(addr.sun_path, addrlen - offsetof(sockaddr_un, sun_path)); + auto addr_str_len = strnlen(addr.sun_path, addrlen - offsetof(sockaddr_un, sun_path)); return std::string(addr.sun_path, addr.sun_path + addr_str_len); #endif } @@ -412,15 +366,12 @@ absl::Status UnixSocket::Bind(const std::string &pathname, bool listen) { return absl::OkStatus(); } -absl::StatusOr UnixSocket::Accept(const co::Coroutine *c) const { +absl::StatusOr UnixSocket::Accept(co::Coroutine *c) const { if (!fd_.Valid()) { return absl::InternalError("UnixSocket is not valid"); } if (c != nullptr) { - int fd = c->Wait(fd_.Fd(), POLLIN); - if (fd != fd_.Fd()) { - return absl::InternalError("Interrupted"); - } + c->Wait(fd_.Fd(), POLLIN); } struct sockaddr_un sender; socklen_t sock_len = sizeof(sender); @@ -463,7 +414,7 @@ absl::Status UnixSocket::Connect(const std::string &pathname) { } absl::Status UnixSocket::SendFds(const std::vector &fds, - const co::Coroutine *c) { + co::Coroutine *c) { if (!Connected()) { return absl::InternalError("Socket is not connected"); } @@ -487,23 +438,12 @@ absl::Status UnixSocket::SendFds(const std::vector &fds, struct iovec iov = {.iov_base = reinterpret_cast(&num_fds), .iov_len = sizeof(int32_t)}; size_t fds_size = fds_to_send * sizeof(int); -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wmissing-field-initializers" -#elif defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif struct msghdr msg = {.msg_iov = &iov, .msg_iovlen = 1, .msg_control = u.buf, .msg_controllen = static_cast(CMSG_SPACE(fds_size))}; -#if defined(__clang__) -#pragma clang diagnostic pop -#elif defined(__GNUC__) -#pragma GCC diagnostic pop -#endif + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; @@ -514,10 +454,7 @@ absl::Status UnixSocket::SendFds(const std::vector &fds, } if (c != nullptr) { - int fd = c->Wait(fd_.Fd(), POLLOUT); - if (fd != fd_.Fd()) { - return absl::InternalError("Interrupted"); - } + c->Wait(fd_.Fd(), POLLOUT); } int e = ::sendmsg(fd_.Fd(), &msg, 0); if (e == -1) { @@ -531,7 +468,7 @@ absl::Status UnixSocket::SendFds(const std::vector &fds, } absl::Status UnixSocket::ReceiveFds(std::vector &fds, - const co::Coroutine *c) { + co::Coroutine *c) { if (!Connected()) { return absl::InternalError("Socket is not connected"); } @@ -550,27 +487,13 @@ absl::Status UnixSocket::ReceiveFds(std::vector &fds, struct iovec iov = {.iov_base = reinterpret_cast(&total_fds), .iov_len = sizeof(int32_t)}; -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wmissing-field-initializers" -#elif defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif struct msghdr msg = {.msg_iov = &iov, .msg_iovlen = 1, .msg_control = u.buf, .msg_controllen = sizeof(u.buf)}; -#if defined(__clang__) -#pragma clang diagnostic pop -#elif defined(__GNUC__) -#pragma GCC diagnostic pop -#endif + if (c != nullptr) { - int fd = c->Wait(fd_.Fd(), POLLIN); - if (fd != fd_.Fd()) { - return absl::InternalError("Interrupted"); - } + c->Wait(fd_.Fd(), POLLIN); } ssize_t n = ::recvmsg(fd_.Fd(), &msg, 0); if (n == -1) { @@ -706,15 +629,12 @@ absl::Status TCPSocket::Bind(const InetAddress &addr, bool listen) { return absl::OkStatus(); } -absl::StatusOr TCPSocket::Accept(const co::Coroutine *c) const { +absl::StatusOr TCPSocket::Accept(co::Coroutine *c) const { if (!fd_.Valid()) { return absl::InternalError("Socket is not valid"); } if (c != nullptr) { - int fd = c->Wait(fd_.Fd(), POLLIN); - if (fd != fd_.Fd()) { - return absl::InternalError("Interrupted"); - } + c->Wait(fd_.Fd(), POLLIN); } struct sockaddr_in sender; socklen_t sock_len = sizeof(sender); @@ -749,7 +669,7 @@ absl::StatusOr TCPSocket::GetPeerName() const { return InetAddress(peer); } -absl::StatusOr TCPSocket::LocalAddress(int) const { +absl::StatusOr TCPSocket::LocalAddress(int port) const { if (!fd_.Valid()) { return absl::InternalError("Socket is not valid"); } @@ -799,13 +719,9 @@ absl::Status UDPSocket::Bind(const InetAddress &addr) { } absl::Status UDPSocket::JoinMulticastGroup(const InetAddress &addr) { - // Use POSIX-portable ip_mreq instead of Linux-specific ip_mreqn so this - // builds on macOS, BSDs and QNX as well. We always join via the default - // interface (INADDR_ANY); callers needing per-interface control should - // extend this API. - struct ip_mreq membership_request = {}; - membership_request.imr_multiaddr = addr.GetAddress().sin_addr; - membership_request.imr_interface.s_addr = htonl(INADDR_ANY); + ip_mreqn membership_request{.imr_multiaddr = addr.GetAddress().sin_addr, + .imr_address = {INADDR_ANY}, + .imr_ifindex = 0}; int setsockopt_ret = ::setsockopt(fd_.Fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &membership_request, sizeof(membership_request)); @@ -819,9 +735,9 @@ absl::Status UDPSocket::JoinMulticastGroup(const InetAddress &addr) { } absl::Status UDPSocket::LeaveMulticastGroup(const InetAddress &addr) { - struct ip_mreq membership_request = {}; - membership_request.imr_multiaddr = addr.GetAddress().sin_addr; - membership_request.imr_interface.s_addr = htonl(INADDR_ANY); + ip_mreqn membership_request{.imr_multiaddr = addr.GetAddress().sin_addr, + .imr_address = {INADDR_ANY}, + .imr_ifindex = 0}; int setsockopt_ret = ::setsockopt(fd_.Fd(), IPPROTO_IP, IP_DROP_MEMBERSHIP, &membership_request, sizeof(membership_request)); @@ -855,12 +771,9 @@ absl::Status UDPSocket::SetMulticastLoop() { } absl::Status UDPSocket::SendTo(const InetAddress &addr, const void *buffer, - size_t length, const co::Coroutine *c) { + size_t length, co::Coroutine *c) { if (c != nullptr) { - int fd = c->Wait(fd_.Fd(), POLLOUT); - if (fd != fd_.Fd()) { - return absl::InternalError("Interrupted"); - } + c->Wait(fd_.Fd(), POLLOUT); } ssize_t n = ::sendto(fd_.Fd(), buffer, length, 0, reinterpret_cast(&addr.GetAddress()), @@ -874,12 +787,9 @@ absl::Status UDPSocket::SendTo(const InetAddress &addr, const void *buffer, } absl::StatusOr UDPSocket::Receive(void *buffer, size_t buflen, - const co::Coroutine *c) { + co::Coroutine *c) { if (c != nullptr) { - int fd = c->Wait(fd_.Fd(), POLLIN); - if (fd != fd_.Fd()) { - return absl::InternalError("Interrupted"); - } + c->Wait(fd_.Fd(), POLLIN); } ssize_t n = recv(fd_.Fd(), buffer, buflen, 0); if (n == -1) { @@ -890,12 +800,9 @@ absl::StatusOr UDPSocket::Receive(void *buffer, size_t buflen, } absl::StatusOr UDPSocket::ReceiveFrom(InetAddress &sender, void *buffer, size_t buflen, - const co::Coroutine *c) { + co::Coroutine *c) { if (c != nullptr) { - int fd = c->Wait(fd_.Fd(), POLLIN); - if (fd != fd_.Fd()) { - return absl::InternalError("Interrupted"); - } + c->Wait(fd_.Fd(), POLLIN); } struct sockaddr_in sender_addr; socklen_t sender_addr_length = sizeof(sender_addr); @@ -907,9 +814,6 @@ absl::StatusOr UDPSocket::ReceiveFrom(InetAddress &sender, return absl::InternalError( absl::StrFormat("Unable to receive UDP datagram: %s", strerror(errno))); } -#if defined(__APPLE__) - sender_addr.sin_len = sender_addr_length; -#endif sender = {sender_addr}; return n; } @@ -950,15 +854,12 @@ absl::Status VirtualStreamSocket::Bind(const VirtualAddress &addr, } absl::StatusOr -VirtualStreamSocket::Accept(const co::Coroutine *c) const { +VirtualStreamSocket::Accept(co::Coroutine *c) const { if (!fd_.Valid()) { return absl::InternalError("Socket is not valid"); } if (c != nullptr) { - int fd = c->Wait(fd_.Fd(), POLLIN); - if (fd != fd_.Fd()) { - return absl::InternalError("Interrupted"); - } + c->Wait(fd_.Fd(), POLLIN); } struct sockaddr_vm sender; socklen_t sock_len = sizeof(sender); @@ -999,16 +900,11 @@ absl::Status VirtualStreamSocket::Connect(const VirtualAddress &addr) { absl::StatusOr VirtualStreamSocket::LocalAddress(uint32_t port) const { -#if defined(IOCTL_VM_SOCKETS_GET_LOCAL_CID) int32_t cid; int e = ioctl(fd_.Fd(), IOCTL_VM_SOCKETS_GET_LOCAL_CID, &cid); if (e == -1) { return absl::InternalError("Failed to get local CID"); } -#else - // If we cannot get the local CID, return ANY. - int32_t cid = VMADDR_CID_ANY; -#endif return VirtualAddress(cid, port); } diff --git a/toolbelt/sockets.h b/toolbelt/sockets.h index a159894..3535518 100644 --- a/toolbelt/sockets.h +++ b/toolbelt/sockets.h @@ -6,7 +6,7 @@ #define __TOOLBELT_SOCKETS_H #include "absl/status/status.h" #include "absl/status/statusor.h" -#include "co/coroutine.h" +#include "coroutine.h" #include "fd.h" #include #include @@ -17,44 +17,9 @@ #include #if defined(__linux__) - -#if __has_include() #include -#define HAS_VM_SOCKETS 1 -#else -#define HAS_VM_SOCKETS 0 -#endif - #else -#if __has_include() #include -#define HAS_VM_SOCKETS 1 -#else -#define HAS_VM_SOCKETS 0 -#endif -#endif - -// Older systems may not have the header file. -#if !HAS_VM_SOCKETS -struct sockaddr_vm { -#if defined(__APPLE__) - uint8_t svm_len; /* total length of sockaddr */ -#endif - sa_family_t svm_family; /* AF_VSOCK */ - uint32_t svm_reserved1; - uint32_t svm_port; - uint32_t svm_cid; - uint32_t svm_reserved2; -}; -#define VMADDR_CID_ANY (~0U) -#define VMADDR_CID_HOST 1 -#define VMADDR_CID_HYPERVISOR 2 -#define VMADDR_CID_LOCAL 3 -#define AF_VSOCK 40 -#endif - -#if !defined(VMADDR_CID_LOCAL) -#define VMADDR_CID_LOCAL 3 #endif #include @@ -99,8 +64,6 @@ class InetAddress { in_addr IpAddress() const { return {ntohl(addr_.sin_addr.s_addr)}; } int Port() const { return ntohs(addr_.sin_port); } - in_addr IpAddressInNetworkOrder() const { return {addr_.sin_addr.s_addr}; } - // Port is in host byte order. void SetPort(int port) { addr_.sin_port = htons(port); } @@ -116,7 +79,7 @@ class InetAddress { static InetAddress AnyAddress(int port); private: - struct sockaddr_in addr_ = {}; // In network byte order. + struct sockaddr_in addr_; // In network byte order. bool valid_ = false; }; @@ -162,7 +125,7 @@ class VirtualAddress { static VirtualAddress HypervisorAddress(uint32_t port); static VirtualAddress HostAddress(uint32_t port); static VirtualAddress AnyAddress(uint32_t port); -#if defined(__linux__) && defined(VMADDR_CID_LOCAL) +#if defined(__linux__) static VirtualAddress LocalAddress(uint32_t port); #endif @@ -241,7 +204,7 @@ class SocketAddress { return std::visit( EyeOfNewt{ [](const InetAddress &a) { - return SocketAddress(a.IpAddressInNetworkOrder(), 0); + return SocketAddress(a.IpAddress(), 0); }, [](const VirtualAddress &a) { return SocketAddress(a.Cid(), 0); }, [](const std::string &a) { return SocketAddress(a); }}, @@ -283,7 +246,7 @@ class SocketAddress { return std::visit( EyeOfNewt{[](const InetAddress &a) { return int(a.Port()); }, [](const VirtualAddress &a) { return int(a.Port()); }, - [](const std::string &) { return 0; }}, + [](const std::string &a) { return 0; }}, address_); } @@ -354,17 +317,17 @@ class Socket { // Send and receive raw buffers. absl::StatusOr Receive(char *buffer, size_t buflen, - const co::Coroutine *c = nullptr); + co::Coroutine *c = nullptr); absl::StatusOr Send(const char *buffer, size_t length, - const co::Coroutine *c = nullptr); + co::Coroutine *c = nullptr); // Send and receive length-delimited message. The length is a 4-byte // network byte order (big endian) int as the first 4 bytes and // contains the length of the message. absl::StatusOr ReceiveMessage(char *buffer, size_t buflen, - const co::Coroutine *c = nullptr); + co::Coroutine *c = nullptr); absl::StatusOr> - ReceiveVariableLengthMessage(const co::Coroutine *c = nullptr); + ReceiveVariableLengthMessage(co::Coroutine *c = nullptr); // For SendMessage, the buffer pointer must be 4 bytes beyond // the actual buffer start, which must be length+4 bytes @@ -372,7 +335,7 @@ class Socket { // at buffer-4. This is to allow us to do a single send // to the socket rather than splitting it into 2. absl::StatusOr SendMessage(char *buffer, size_t length, - const co::Coroutine *c = nullptr); + co::Coroutine *c = nullptr); absl::Status SetNonBlocking() { if (absl::Status s = fd_.SetNonBlocking(); !s.ok()) { @@ -405,12 +368,12 @@ class UnixSocket : public Socket { absl::Status Bind(const std::string &pathname, bool listen); absl::Status Connect(const std::string &pathname); - absl::StatusOr Accept(const co::Coroutine *c = nullptr) const; + absl::StatusOr Accept(co::Coroutine *c = nullptr) const; absl::Status SendFds(const std::vector &fds, - const co::Coroutine *c = nullptr); + co::Coroutine *c = nullptr); absl::Status ReceiveFds(std::vector &fds, - const co::Coroutine *c = nullptr); + co::Coroutine *c = nullptr); std::string BoundAddress() const { return bound_address_; } absl::StatusOr GetPeerName() const; @@ -455,12 +418,12 @@ class UDPSocket : public NetworkSocket { // NOTE: Read and Write may or may not work on UDP sockets. Use SendTo and // Receive for datagrams. absl::Status SendTo(const InetAddress &addr, const void *buffer, - size_t length, const co::Coroutine *c = nullptr); + size_t length, co::Coroutine *c = nullptr); absl::StatusOr Receive(void *buffer, size_t buflen, - const co::Coroutine *c = nullptr); + co::Coroutine *c = nullptr); absl::StatusOr ReceiveFrom(InetAddress &sender, void *buffer, size_t buflen, - const co::Coroutine *c = nullptr); + co::Coroutine *c = nullptr); absl::Status SetBroadcast(); absl::Status SetMulticastLoop(); }; @@ -474,7 +437,7 @@ class TCPSocket : public NetworkSocket { absl::Status Bind(const InetAddress &addr, bool listen); - absl::StatusOr Accept(const co::Coroutine *c = nullptr) const; + absl::StatusOr Accept(co::Coroutine *c = nullptr) const; absl::StatusOr LocalAddress(int port) const; @@ -491,7 +454,7 @@ class VirtualStreamSocket : public Socket { absl::Status Bind(const VirtualAddress &addr, bool listen); - absl::StatusOr Accept(const co::Coroutine *c = nullptr) const; + absl::StatusOr Accept(co::Coroutine *c = nullptr) const; absl::StatusOr LocalAddress(uint32_t port) const; const VirtualAddress &BoundAddress() const { return bound_address_; } @@ -552,7 +515,7 @@ class StreamSocket { return absl::Status(absl::StatusCode::kInternal, "Invalid socket address"); } - absl::StatusOr Accept(const co::Coroutine *c = nullptr) const { + absl::StatusOr Accept(co::Coroutine *c = nullptr) const { return std::visit( EyeOfNewt{ [&](const TCPSocket &s) mutable -> absl::StatusOr { @@ -618,7 +581,7 @@ class StreamSocket { // Send and receive raw buffers. absl::StatusOr Receive(char *buffer, size_t buflen, - const co::Coroutine *c = nullptr) { + co::Coroutine *c = nullptr) { return std::visit( EyeOfNewt{[&](TCPSocket &s) { return s.Receive(buffer, buflen, c); }, [&](VirtualStreamSocket &s) { @@ -629,7 +592,7 @@ class StreamSocket { } absl::StatusOr Send(const char *buffer, size_t length, - const co::Coroutine *c = nullptr) { + co::Coroutine *c = nullptr) { return std::visit( EyeOfNewt{ [&](TCPSocket &s) { return s.Send(buffer, length, c); }, @@ -642,7 +605,7 @@ class StreamSocket { // network byte order (big endian) int as the first 4 bytes and // contains the length of the message. absl::StatusOr ReceiveMessage(char *buffer, size_t buflen, - const co::Coroutine *c = nullptr) { + co::Coroutine *c = nullptr) { return std::visit( EyeOfNewt{ [&](TCPSocket &s) { return s.ReceiveMessage(buffer, buflen, c); }, @@ -654,7 +617,7 @@ class StreamSocket { } absl::StatusOr> - ReceiveVariableLengthMessage(const co::Coroutine *c = nullptr) { + ReceiveVariableLengthMessage(co::Coroutine *c = nullptr) { return std::visit( EyeOfNewt{ [&](TCPSocket &s) { return s.ReceiveVariableLengthMessage(c); }, @@ -671,7 +634,7 @@ class StreamSocket { // at buffer-4. This is to allow us to do a single send // to the socket rather than splitting it into 2. absl::StatusOr SendMessage(char *buffer, size_t length, - const co::Coroutine *c = nullptr) { + co::Coroutine *c = nullptr) { return std::visit( EyeOfNewt{ [&](TCPSocket &s) { return s.SendMessage(buffer, length, c); }, diff --git a/toolbelt/sockets_test.cc b/toolbelt/sockets_test.cc index 24532ae..476c89a 100644 --- a/toolbelt/sockets_test.cc +++ b/toolbelt/sockets_test.cc @@ -4,31 +4,13 @@ #include #include #include -#include -#include "absl/status/status_matchers.h" -#include "toolbelt/hexdump.h" - -#define VAR(a) a##__COUNTER__ -#define EVAL_AND_ASSERT_OK(expr) EVAL_AND_ASSERT_OK2(VAR(r_), expr) - -#define EVAL_AND_ASSERT_OK2(result, expr) \ - ({ \ - auto result = (expr); \ - if (!result.ok()) { \ - std::cerr << result.status() << std::endl; \ - } \ - ASSERT_OK(result); \ - std::move(*result); \ - }) - -#define ASSERT_OK(e) ASSERT_THAT(e, ::absl_testing::IsOk()) namespace { constexpr std::string_view TEST_DATA = "The quick brown fox jumped over the lazy dog."; const static absl::Duration LOOPBACK_TIMEOUT = absl::Milliseconds(10); // Test class to hold on to a randomly assigned unused port until destruction -// Any tests Binding to this unused port will probably need to call NetworkSocket::SetReusePort +// Any tests binding to this unused port will probably need to call NetworkSocket::SetReusePort class UnusedPort { public: UnusedPort() { @@ -49,532 +31,77 @@ class UnusedPort { }; } -TEST(SocketsTest, InetAddresses) { - toolbelt::InetAddress addr1; - ASSERT_FALSE(addr1.Valid()); - - toolbelt::InetAddress addr2 = toolbelt::InetAddress::BroadcastAddress(1234); - ASSERT_TRUE(addr2.Valid()); - ASSERT_EQ(1234, addr2.Port()); - - toolbelt::InetAddress addr3 = toolbelt::InetAddress::AnyAddress(4321); - ASSERT_EQ(4321, addr3.Port()); - - toolbelt::InetAddress local_ip = toolbelt::InetAddress("127.0.0.1", 1111); - ASSERT_EQ(1111, local_ip.Port()); - ASSERT_EQ("127.0.0.1:1111", local_ip.ToString()); - - toolbelt::InetAddress local_host = toolbelt::InetAddress("localhost", 2222); - ASSERT_EQ(2222, local_host.Port()); - ASSERT_EQ("127.0.0.1:2222", local_host.ToString()); - - toolbelt::InetAddress bad = toolbelt::InetAddress("foobardoesntexist", 2222); - ASSERT_FALSE(bad.Valid()); - - in_addr ipaddr; - ASSERT_EQ(1, inet_pton(AF_INET, "127.0.0.1", &ipaddr.s_addr)); - toolbelt::InetAddress local_in = toolbelt::InetAddress(ipaddr, 3333); - ASSERT_EQ(3333, local_in.Port()); - ASSERT_EQ("127.0.0.1:3333", local_in.ToString()); -} - -TEST(SocketsTest, UnixSocket) { - char tmp[] = "/tmp/socketsXXXXXX"; - int fd = mkstemp(tmp); - ASSERT_NE(-1, fd); - std::string socket_name = tmp; - close(fd); - - unlink(socket_name.c_str()); - co::CoroutineScheduler scheduler; - - toolbelt::UnixSocket listener; - absl::Status status = listener.Bind(socket_name, true); - std::cerr << status << std::endl; - ASSERT_TRUE(status.ok()); - - co::Coroutine incoming(scheduler, [&listener](co::Coroutine* c) { - absl::StatusOr s = listener.Accept(c); - ASSERT_TRUE(s.ok()); - auto socket = s.value(); - - char buffer[256]; - // ReceiveMessage uses the 4 bytes below the buffer for the length. - absl::StatusOr nbytes = socket.ReceiveMessage(buffer + 4, sizeof(buffer) - 4, c); - ASSERT_TRUE(nbytes.ok()); - auto n = nbytes.value(); - ASSERT_EQ(12, n); // "hello world\0" - ASSERT_EQ("hello world", std::string(buffer + 4, n - 1)); - std::vector fds; - - absl::Status s2 = socket.ReceiveFds(fds, c); - ASSERT_TRUE(s2.ok()); - ASSERT_EQ(3, fds.size()); - }); - - co::Coroutine outgoing(scheduler, [&socket_name](co::Coroutine* c) { - toolbelt::UnixSocket socket; - absl::Status s = socket.Connect(socket_name); - ASSERT_TRUE(s.ok()); - char buffer[256]; - // SendMessage uses the 4 bytes below the buffer for the length of the message. - ssize_t n = snprintf(buffer + 4, sizeof(buffer) - 4, "hello world"); - n += 1; // Include NUL at end. - absl::StatusOr nsent = socket.SendMessage(buffer + 4, n, c); - ASSERT_TRUE(nsent.ok()); - ASSERT_EQ(n + 4, nsent.value()); - - std::vector fds; - for (int i = 0; i < 3; i++) { - // We dup the file descriptors to avoid closing stdout. - fds.push_back(toolbelt::FileDescriptor(dup(i))); - } - absl::Status s2 = socket.SendFds(fds, c); - ASSERT_TRUE(s2.ok()); - }); - - scheduler.Run(); - remove(socket_name.c_str()); -} - -TEST(SocketsTest, UnixSocketErrors) { - toolbelt::UnixSocket socket; - // Socket is inValid, all will fail. - ASSERT_FALSE(socket.Accept().ok()); - ASSERT_FALSE(socket.Connect("foobar").ok()); - std::vector fds; - ASSERT_FALSE(socket.SendFds(fds).ok()); - ASSERT_FALSE(socket.ReceiveFds(fds).ok()); -} - -TEST(SocketsTest, TCPSocket) { - toolbelt::InetAddress addr("127.0.0.1", 6502); - - co::CoroutineScheduler scheduler; - - toolbelt::TCPSocket listener; - ASSERT_TRUE(listener.SetReuseAddr().ok()); - absl::Status status = listener.Bind(addr, true); - ASSERT_TRUE(status.ok()); - - co::Coroutine incoming(scheduler, [&listener](co::Coroutine* c) { - absl::StatusOr s = listener.Accept(c); - ASSERT_TRUE(s.ok()); - auto socket = s.value(); - - absl::StatusOr> b = socket.ReceiveVariableLengthMessage(c); - ASSERT_TRUE(b.ok()); - auto buf = b.value(); - ASSERT_EQ(12, buf.size()); // "hello world\0" - ASSERT_EQ("hello world", std::string(buf.data(), 11)); - }); - - co::Coroutine outgoing(scheduler, [&addr](co::Coroutine* c) { - toolbelt::TCPSocket socket; - absl::Status s = socket.Connect(addr); - ASSERT_TRUE(s.ok()); - char buffer[256]; - // SendMessage uses the 4 bytes below the buffer for the length of the message. - ssize_t n = snprintf(buffer + 4, sizeof(buffer) - 4, "hello world"); - n += 1; // Include NUL at end. - absl::StatusOr nsent = socket.SendMessage(buffer + 4, n, c); - ASSERT_TRUE(nsent.ok()); - ASSERT_EQ(n + 4, nsent.value()); - }); - - scheduler.Run(); -} - -TEST(SocketsTest, BigTCPSocketNonblocking) { - toolbelt::InetAddress addr("127.0.0.1", 6502); - - co::CoroutineScheduler scheduler; - - toolbelt::TCPSocket listener; - ASSERT_TRUE(listener.SetReuseAddr().ok()); - absl::Status status = listener.Bind(addr, true); - ASSERT_TRUE(status.ok()); - - constexpr size_t kBufferSize = 10 * 1024 * 1024; - co::Coroutine incoming(scheduler, [&listener, kBufferSize](co::Coroutine* c) { - absl::StatusOr s = listener.Accept(c); - ASSERT_TRUE(s.ok()); - auto socket = s.value(); - ASSERT_OK(socket.SetNonBlocking()); - - absl::StatusOr> b = socket.ReceiveVariableLengthMessage(c); - ASSERT_TRUE(b.ok()); - auto buf = b.value(); - ASSERT_EQ(kBufferSize, buf.size()); - for (size_t i = 0; i < kBufferSize; i++) { - if (size_t(buf[i]) != 'a' + ((i + 4) % 26)) { - std::cerr << "Mismatch at " << i << ": " << buf[i] << " != " << 'a' + (i % 26) - << "\n"; - } - ASSERT_EQ('a' + ((i + 4) % 26), buf[i]); - } - }); - - co::Coroutine outgoing(scheduler, [&addr](co::Coroutine* c) { - toolbelt::TCPSocket socket; - absl::Status s = socket.Connect(addr); - ASSERT_TRUE(s.ok()); - ASSERT_OK(socket.SetNonBlocking()); - std::vector buffer(kBufferSize + 4); - for (size_t i = 4; i < buffer.size(); i++) { - buffer[i] = 'a' + (i % 26); - } - absl::StatusOr nsent = - socket.SendMessage(buffer.data() + 4, buffer.size() - 4, c); - ASSERT_TRUE(nsent.ok()); - ASSERT_EQ(buffer.size(), nsent.value()); - }); - - scheduler.Run(); -} - -TEST(SocketsTest, BigTCPSocketBlocking) { - toolbelt::InetAddress addr("127.0.0.1", 6502); - - co::CoroutineScheduler sendScheduler, ReceiveScheduler; - - toolbelt::TCPSocket listener; - ASSERT_TRUE(listener.SetReuseAddr().ok()); - absl::Status status = listener.Bind(addr, true); - ASSERT_TRUE(status.ok()); - - constexpr size_t kBufferSize = 10 * 1024 * 1024; - co::Coroutine incoming( - sendScheduler, [&listener, kBufferSize](co::Coroutine* c) { - absl::StatusOr s = listener.Accept(c); - ASSERT_TRUE(s.ok()); - auto socket = s.value(); - - absl::StatusOr> b = socket.ReceiveVariableLengthMessage(c); - ASSERT_TRUE(b.ok()); - auto buf = b.value(); - ASSERT_EQ(kBufferSize, buf.size()); - for (size_t i = 0; i < kBufferSize; i++) { - if (size_t(buf[i]) != 'a' + ((i + 4) % 26)) { - std::cerr << "Mismatch at " << i << ": " << buf[i] - << " != " << 'a' + (i % 26) << "\n"; - } - ASSERT_EQ('a' + ((i + 4) % 26), buf[i]); - } - }); - - co::Coroutine outgoing(ReceiveScheduler, [&addr](co::Coroutine* c) { - toolbelt::TCPSocket socket; - absl::Status s = socket.Connect(addr); - ASSERT_TRUE(s.ok()); - std::vector buffer(kBufferSize + 4); - for (size_t i = 4; i < buffer.size(); i++) { - buffer[i] = 'a' + (i % 26); - } - absl::StatusOr nsent = - socket.SendMessage(buffer.data() + 4, buffer.size() - 4, c); - ASSERT_TRUE(nsent.ok()); - ASSERT_EQ(buffer.size(), nsent.value()); - }); - - std::thread sender([&sendScheduler]() { sendScheduler.Run(); }); - std::thread Receiver([&ReceiveScheduler]() { ReceiveScheduler.Run(); }); - sender.join(); - Receiver.join(); -} - -TEST(SocketsTest, TCPSocketInterrupt) { - // TODO(dave.allison): is there a way to pick an unused port? - toolbelt::InetAddress addr("127.0.0.1", 6502); - - co::CoroutineScheduler scheduler; - - toolbelt::TCPSocket listener; - ASSERT_TRUE(listener.SetReuseAddr().ok()); - absl::Status status = listener.Bind(addr, true); - ASSERT_TRUE(status.ok()); - - co::Coroutine incoming( - scheduler, - [&listener](co::Coroutine* c) { - absl::StatusOr s = listener.Accept(c); - ASSERT_FALSE(s.ok()); - }, - co::CoroutineOptions{.name = "foo", .interrupt_fd = scheduler.GetInterruptFd()}); - - co::Coroutine interrupt(scheduler, [](co::Coroutine* c) { - c->Yield(); - c->Scheduler().TriggerInterrupt(); - }); - - scheduler.Run(); -} - -TEST(SocketsTest, TCPSocket2) { - // TODO(dave.allison): is there a way to pick an unused port? - toolbelt::InetAddress addr("127.0.0.1", 6502); - - co::CoroutineScheduler scheduler; - - toolbelt::TCPSocket listener; - ASSERT_TRUE(listener.SetReuseAddr().ok()); - absl::Status status = listener.Bind(addr, true); - ASSERT_TRUE(status.ok()); - - co::Coroutine incoming(scheduler, [&listener](co::Coroutine* c) { - absl::StatusOr s = listener.Accept(c); - ASSERT_TRUE(s.ok()); - auto socket = s.value(); - - char buffer[256]; - absl::StatusOr nbytes = socket.Receive(buffer, 12, c); - ASSERT_TRUE(nbytes.ok()); - auto n = nbytes.value(); - ASSERT_EQ(12, n); // "hello world\0" - ASSERT_EQ("hello world", std::string(buffer, n - 1)); - std::vector fds; - }); - - co::Coroutine outgoing(scheduler, [&addr](co::Coroutine* c) { - toolbelt::TCPSocket socket; - absl::Status s = socket.Connect(addr); - ASSERT_TRUE(s.ok()); - char buffer[256]; - ssize_t n = snprintf(buffer, sizeof(buffer), "hello world"); - n += 1; // Include NUL at end. - absl::StatusOr nsent = socket.Send(buffer, n, c); - ASSERT_TRUE(nsent.ok()); - ASSERT_EQ(n, nsent.value()); - }); - - scheduler.Run(); -} - -TEST(SocketsTest, TCPSocket3) { - toolbelt::InetAddress addr("127.0.0.1", 0); - - co::CoroutineScheduler scheduler; - - toolbelt::TCPSocket listener; - ASSERT_TRUE(listener.SetReuseAddr().ok()); - ASSERT_TRUE(listener.SetReusePort().ok()); - absl::Status status = listener.Bind(addr, true); - ASSERT_TRUE(status.ok()); - toolbelt::InetAddress baddr = listener.BoundAddress(); - - co::Coroutine incoming(scheduler, [&listener](co::Coroutine* c) { - absl::StatusOr s = listener.Accept(c); - ASSERT_TRUE(s.ok()); - auto socket = s.value(); - - char buffer[256]; - absl::StatusOr nbytes = socket.Receive(buffer, 12, c); - ASSERT_TRUE(nbytes.ok()); - auto n = nbytes.value(); - ASSERT_EQ(12, n); // "hello world\0" - ASSERT_EQ("hello world", std::string(buffer, n - 1)); - std::vector fds; - }); - - co::Coroutine outgoing(scheduler, [&baddr](co::Coroutine* c) { - toolbelt::TCPSocket socket; - absl::Status s = socket.Connect(baddr); - ASSERT_TRUE(s.ok()); - char buffer[256]; - ssize_t n = snprintf(buffer, sizeof(buffer), "hello world"); - n += 1; // Include NUL at end. - absl::StatusOr nsent = socket.Send(buffer, n, c); - ASSERT_TRUE(nsent.ok()); - ASSERT_EQ(n, nsent.value()); - }); - - scheduler.Run(); -} - -TEST(SocketsTest, TCPSocketErrors) { - toolbelt::TCPSocket socket; - char buffer[256]; - - // Socket is not Connected. These will fail. - toolbelt::InetAddress goodAddr("localhost", 2222); - ASSERT_FALSE(socket.Connect(goodAddr).ok()); // Valid fd, but nothing is on this port. - ASSERT_FALSE(socket.Send(buffer, 1).ok()); - ASSERT_FALSE(socket.Receive(buffer, 1).ok()); - ASSERT_FALSE(socket.SendMessage(buffer, 1).ok()); - ASSERT_FALSE(socket.ReceiveMessage(buffer, 1).ok()); - - toolbelt::InetAddress badAddr = - toolbelt::InetAddress("foobardoesntexist", 2222); - ASSERT_FALSE(badAddr.Valid()); - ASSERT_FALSE(socket.Connect(badAddr).ok()); - - socket.Close(); - ASSERT_FALSE(socket.Connect(goodAddr).ok()); // InValid fd. -} - -TEST(SocketsTest, UDPSocket) { - // TODO(dave.allison): is there a way to pick an unused port? - toolbelt::InetAddress sender("127.0.0.1", 6502); - toolbelt::InetAddress Receiver("127.0.0.1", 6503); - - co::CoroutineScheduler scheduler; - - co::Coroutine incoming(scheduler, [&Receiver](co::Coroutine* c) { - toolbelt::UDPSocket socket; - absl::Status s1 = socket.Bind(Receiver); - ASSERT_TRUE(s1.ok()); - - char buffer[256]; - absl::StatusOr nbytes = socket.Receive(buffer, sizeof(buffer), c); - ASSERT_TRUE(nbytes.ok()); - auto n = nbytes.value(); - ASSERT_EQ(12, n); // "hello world\0" - ASSERT_EQ("hello world", std::string(buffer, n - 1)); - }); - - co::Coroutine outgoing(scheduler, [&sender, &Receiver](co::Coroutine* c) { - toolbelt::UDPSocket socket; - absl::Status s1 = socket.Bind(sender); - ASSERT_TRUE(s1.ok()); - - char buffer[256]; - ssize_t n = snprintf(buffer, sizeof(buffer), "hello world"); - n += 1; // Include NUL at end. - - absl::Status s2 = socket.SendTo(Receiver, buffer, n, c); - ASSERT_TRUE(s2.ok()); - }); - - scheduler.Run(); -} - -TEST(SocketsTest, UDPSocket2) { - // TODO(dave.allison): is there a way to pick an unused port? - toolbelt::InetAddress sender("127.0.0.1", 6502); - toolbelt::InetAddress receiver("127.0.0.1", 6503); - - co::CoroutineScheduler scheduler; - - co::Coroutine incoming(scheduler, [&receiver, &sender](co::Coroutine* c) { - toolbelt::UDPSocket socket; - absl::Status s1 = socket.Bind(receiver); - ASSERT_TRUE(s1.ok()); - - char buffer[256]; - toolbelt::InetAddress from; - absl::StatusOr nbytes = socket.ReceiveFrom(from, buffer, sizeof(buffer), c); - ASSERT_TRUE(nbytes.ok()); - auto n = nbytes.value(); - ASSERT_EQ(12, n); // "hello world\0" - ASSERT_EQ("hello world", std::string(buffer, n - 1)); - ASSERT_EQ(sender, from); - }); - - co::Coroutine outgoing(scheduler, [&sender, &receiver](co::Coroutine* c) { - toolbelt::UDPSocket socket; - absl::Status s1 = socket.Bind(sender); - ASSERT_TRUE(s1.ok()); - - char buffer[256]; - ssize_t n = snprintf(buffer, sizeof(buffer), "hello world"); - n += 1; // Include NUL at end. - - absl::Status s2 = socket.SendTo(receiver, buffer, n, c); - ASSERT_TRUE(s2.ok()); - }); - - scheduler.Run(); -} - -TEST(SocketsTest, UDPSocketBroadcast) { - toolbelt::UDPSocket socket; - ASSERT_TRUE(socket.SetBroadcast().ok()); -} - -TEST(SocketsTest, InValidAsString) { - toolbelt::InetAddress addr; - ASSERT_FALSE(addr.Valid()); - EXPECT_EQ(addr.ToString(), "0.0.0.0:0"); -} - - TEST(SocketsTest, UDPSocket_SendAndReceiveUnicast) { UnusedPort port; auto sender = toolbelt::UDPSocket(); - auto Receiver = toolbelt::UDPSocket(); + auto receiver = toolbelt::UDPSocket(); - ASSERT_TRUE(Receiver.SetReusePort().ok()); - ASSERT_TRUE(Receiver.Bind(toolbelt::InetAddress("localhost", port)).ok()); + ASSERT_TRUE(receiver.SetReusePort().ok()); + ASSERT_TRUE(receiver.Bind(toolbelt::InetAddress("localhost", port)).ok()); toolbelt::InetAddress sendto_address("localhost", port); ASSERT_TRUE(sender.SendTo(sendto_address, TEST_DATA.data(), TEST_DATA.size()).ok()); - std::vector Receive_buffer(TEST_DATA.size()); - ASSERT_EQ(*Receiver.Receive(Receive_buffer.data(), Receive_buffer.size()), TEST_DATA.size()); - ASSERT_EQ(std::string_view(Receive_buffer.data(), Receive_buffer.size()), TEST_DATA); + std::vector receive_buffer(TEST_DATA.size()); + ASSERT_EQ(*receiver.Receive(receive_buffer.data(), receive_buffer.size()), TEST_DATA.size()); + ASSERT_EQ(std::string_view(receive_buffer.data(), receive_buffer.size()), TEST_DATA); + + ASSERT_EQ(0, std::strcmp(receive_buffer.data(), TEST_DATA.data())); } TEST(SocketsTest, UDPSocket_SendAndReceiveBroadcast) { -#if defined(__APPLE__) - GTEST_SKIP() << "UDP broadcast to 255.255.255.255 over loopback is not " - "permitted in the GitHub Actions macOS runner network " - "sandbox; skip on macOS."; -#endif UnusedPort port; auto sender = toolbelt::UDPSocket(); - auto Receiver = toolbelt::UDPSocket(); + auto receiver = toolbelt::UDPSocket(); ASSERT_TRUE(sender.SetBroadcast().ok()); - ASSERT_TRUE(Receiver.SetReusePort().ok()); - ASSERT_TRUE(Receiver.Bind(toolbelt::InetAddress(toolbelt::InetAddress::AnyAddress(port))).ok()); + ASSERT_TRUE(receiver.SetReusePort().ok()); + ASSERT_TRUE(receiver.Bind(toolbelt::InetAddress(toolbelt::InetAddress::AnyAddress(port))).ok()); toolbelt::InetAddress sendto_address(toolbelt::InetAddress::BroadcastAddress(port)); ASSERT_TRUE(sender.SendTo(sendto_address, TEST_DATA.data(), TEST_DATA.size()).ok()); - std::vector Receive_buffer(TEST_DATA.size()); - ASSERT_EQ(*Receiver.Receive(Receive_buffer.data(), Receive_buffer.size()), TEST_DATA.size()); - ASSERT_EQ(std::string_view(Receive_buffer.data(), Receive_buffer.size()), TEST_DATA); + std::vector receive_buffer(TEST_DATA.size()); + ASSERT_EQ(*receiver.Receive(receive_buffer.data(), receive_buffer.size()), TEST_DATA.size()); + ASSERT_EQ(std::string_view(receive_buffer.data(), receive_buffer.size()), TEST_DATA); + + ASSERT_EQ(0, std::strcmp(receive_buffer.data(), TEST_DATA.data())); } TEST(SocketsTest, UDPSocket_SendAndReceiveMulticast) { -#if defined(__APPLE__) - GTEST_SKIP() << "UDP multicast over loopback is not reliably available in " - "the GitHub Actions macOS runner network sandbox; skip on " - "macOS."; -#endif UnusedPort port; std::string multicast_ip = "224.0.0.205"; toolbelt::InetAddress multicast_address(multicast_ip, port); auto sender = toolbelt::UDPSocket(); - auto Receiver = toolbelt::UDPSocket(); + auto receiver = toolbelt::UDPSocket(); ASSERT_TRUE(sender.SetMulticastLoop().ok()); - std::vector Receive_buffer(TEST_DATA.size()); - ASSERT_TRUE(Receiver.SetReusePort().ok()); - ASSERT_TRUE(Receiver.SetNonBlocking().ok()); - ASSERT_TRUE(Receiver.Bind(toolbelt::InetAddress::AnyAddress(port)).ok()); + std::vector receive_buffer(TEST_DATA.size()); + ASSERT_TRUE(receiver.SetReusePort().ok()); + ASSERT_TRUE(receiver.SetNonBlocking().ok()); + ASSERT_TRUE(receiver.Bind(toolbelt::InetAddress::AnyAddress(port)).ok()); - ASSERT_TRUE(Receiver.JoinMulticastGroup(multicast_address).ok()); + ASSERT_TRUE(receiver.JoinMulticastGroup(multicast_address).ok()); ASSERT_TRUE(sender.SendTo(multicast_address, TEST_DATA.data(), TEST_DATA.size()).ok()); absl::Time timeout = absl::Now() + LOOPBACK_TIMEOUT; while (absl::Now() < timeout) { - auto status_or_len = Receiver.Receive(Receive_buffer.data(), Receive_buffer.size()); + auto status_or_len = receiver.Receive(receive_buffer.data(), receive_buffer.size()); if (status_or_len.ok()) { ASSERT_EQ(*status_or_len, TEST_DATA.size()); - ASSERT_EQ(std::string_view(Receive_buffer.data(), Receive_buffer.size()), TEST_DATA); + ASSERT_EQ(std::string_view(receive_buffer.data(), receive_buffer.size()), TEST_DATA); break; } } - ASSERT_TRUE(Receiver.LeaveMulticastGroup(multicast_address).ok()); + ASSERT_TRUE(receiver.LeaveMulticastGroup(multicast_address).ok()); ASSERT_TRUE(sender.SendTo(multicast_address, TEST_DATA.data(), TEST_DATA.size()).ok()); timeout = absl::Now() + LOOPBACK_TIMEOUT; while (absl::Now() < timeout) { - auto status_or_len = Receiver.Receive(Receive_buffer.data(), Receive_buffer.size()); + auto status_or_len = receiver.Receive(receive_buffer.data(), receive_buffer.size()); if (status_or_len.ok()) { FAIL() << "Received " << *status_or_len << " bytes but expected nothing"; } diff --git a/toolbelt/stacktrace.cc b/toolbelt/stacktrace.cc deleted file mode 100644 index 4557e21..0000000 --- a/toolbelt/stacktrace.cc +++ /dev/null @@ -1,42 +0,0 @@ - -#include "absl/debugging/stacktrace.h" -#include "absl/base/optimization.h" -#include "absl/debugging/symbolize.h" -#include -#include - -namespace toolbelt { - -void PrintCurrentStack(std::ostream &os) { - os << "--- Stack Trace Capture (Deepest Function) ---\n"; - - constexpr int kMaxFrames = 50; - - // 1. Capture the raw stack addresses - void *stack[kMaxFrames]; - int depth = absl::GetStackTrace(stack, kMaxFrames, 0); - - // 2. Resolve addresses to human-readable symbol names - os << "Captured " << depth << " stack frames:\n"; - - // Buffer to hold the symbolized name - char symbolized_name[1024]; - - for (int i = 0; i < depth; ++i) { - // Attempt to symbolize the address - if (absl::Symbolize(stack[i], symbolized_name, sizeof(symbolized_name))) { - // Success: Print the frame index, address, and resolved symbol name - os << "#" << std::setw(2) << std::left << i << " [0x" << std::hex - << std::setw(16) << stack[i] << std::dec << "] " << symbolized_name - << "\n"; - } else { - // Failure: Symbolization failed (e.g., address not in a symbol table) - os << "#" << std::setw(2) << std::left << i << " [0x" << std::hex - << std::setw(16) << stack[i] << std::dec << "] " - << "\n"; - } - } - os << "----------------------------------------------\n"; -} - -} // namespace toolbelt diff --git a/toolbelt/stacktrace.h b/toolbelt/stacktrace.h deleted file mode 100644 index be6c332..0000000 --- a/toolbelt/stacktrace.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include - -namespace toolbelt { -void PrintCurrentStack(std::ostream &os); -} // namespace toolbelt diff --git a/toolbelt/stacktrace_test.cc b/toolbelt/stacktrace_test.cc deleted file mode 100644 index 9ed8394..0000000 --- a/toolbelt/stacktrace_test.cc +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2025 David Allison -// All Rights Reserved -// See LICENSE file for licensing information. - -#include "absl/strings/str_format.h" -#include "toolbelt/stacktrace.h" -#include - -TEST(StacktraceTest, PrintCurrentStack) { - toolbelt::PrintCurrentStack(std::cout); -} - -void foo() { - toolbelt::PrintCurrentStack(std::cout); -} - -void bar() { - foo(); -} - -void baz() { - bar(); -} - -TEST(StacktraceTest, PrintCurrentStackWithFunction) { - baz(); -} \ No newline at end of file diff --git a/toolbelt/table.cc b/toolbelt/table.cc index 3840688..7517b4f 100644 --- a/toolbelt/table.cc +++ b/toolbelt/table.cc @@ -73,7 +73,7 @@ void Table::Print(int width, std::ostream &os) { // Print titles. for (auto &col : cols_) { std::string title = col.title; - if (title.size() > static_cast(col.width)) { + if (title.size() > col.width) { title = title.substr(0, col.width - 1); } os << std::left << std::setw(col.width) << std::setfill(' ') << title; @@ -83,10 +83,10 @@ void Table::Print(int width, std::ostream &os) { os << std::setw(width) << std::setfill('-') << "" << std::endl; // Print each row. - for (int i = 0; i < num_rows_; i++) { + for (size_t i = 0; i < num_rows_; i++) { for (auto &col : cols_) { std::string data = col.cells[i].data; - if (data.size() > static_cast(col.width)) { + if (data.size() > col.width) { // Truncate if too wide. data = data.substr(0, col.width - 1); } @@ -107,7 +107,7 @@ void Table::Clear() { void Table::Render(int width) { std::vector max_widths(cols_.size()); - for (int i = 0; i < num_rows_; i++) { + for (size_t i = 0; i < num_rows_; i++) { int col_index = 0; for (auto &col : cols_) { if (col.cells[i].data.size() > max_widths[col_index]) { @@ -132,7 +132,7 @@ void Table::Render(int width) { } void Table::Sort() { - if (sort_column_ == -1ULL || sort_column_ >= cols_.size()) { + if (sort_column_ == -1 || sort_column_ >= cols_.size()) { return; } struct Index { @@ -140,8 +140,8 @@ void Table::Sort() { std::string data; }; std::vector index(num_rows_); - for (int i = 0; i < num_rows_; i++) { - index[i] = {.row = static_cast(i), .data = cols_[sort_column_].cells[i].data}; + for (size_t i = 0; i < num_rows_; i++) { + index[i] = {.row = i, .data = cols_[sort_column_].cells[i].data}; } std::sort(index.begin(), index.end(), [this](const Index &a, const Index &b) { return sorter_(a.data, b.data); diff --git a/toolbelt/table.h b/toolbelt/table.h index 75df96b..a10d8dd 100644 --- a/toolbelt/table.h +++ b/toolbelt/table.h @@ -17,7 +17,7 @@ class Table { public: struct Cell { std::string data; - color::Color color = {.mod = color::kNormal, .fixed = color::FixedColor::kNotSet}; + color::Color color; }; Table(const std::vector titles, ssize_t sort_column = 0, @@ -61,9 +61,9 @@ class Table { private: struct Column { - std::string title = ""; - int width = 0; - std::vector cells = {}; + std::string title; + int width; + std::vector cells; }; void Render(int width); @@ -71,10 +71,10 @@ class Table { void AddCell(size_t col, const Cell &cell); - std::vector cols_ = {}; + std::vector cols_; int num_rows_ = 0; - size_t sort_column_ = 0; + size_t sort_column_; std::function sorter_; }; diff --git a/toolbelt/table_test.cc b/toolbelt/table_test.cc index 1efacb5..3bbea7d 100644 --- a/toolbelt/table_test.cc +++ b/toolbelt/table_test.cc @@ -16,8 +16,7 @@ namespace color = toolbelt::color; TEST(FdTest, WithPadding) { Table table({"name", "rank", "serial", "address"}); - struct winsize win; - memset(&win, 0, sizeof(win)); + struct winsize win = {0}; ioctl(0, TIOCGWINSZ, &win); // Might fail. table.AddRow({"Dave", "Captain", "1234", "here"}); @@ -28,8 +27,7 @@ TEST(FdTest, WithPadding) { TEST(FdTest, SortDefault) { Table table({"name", "rank", "serial", "address"}); - struct winsize win; - memset(&win, 0, sizeof(win)); + struct winsize win = {0}; ioctl(0, TIOCGWINSZ, &win); // Might fail. table.AddRow({"Bob", "Major", "4321", "there"}); @@ -44,8 +42,7 @@ TEST(FdTest, SortNumber) { return atoi(a.c_str()) < atoi(b.c_str()); }); - struct winsize win; - memset(&win, 0, sizeof(win)); + struct winsize win = {0}; ioctl(0, TIOCGWINSZ, &win); // Might fail. table.AddRow({"Bob", "Major", "4321", "there"}); @@ -65,8 +62,7 @@ TEST(FdTest, TooWide) { TEST(FdTest, Colors) { Table table({"subsystem", "admin", "oper"}); - struct winsize win; - memset(&win, 0, sizeof(win)); + struct winsize win = {0}; ioctl(0, TIOCGWINSZ, &win); // Might fail. table.AddRow(); @@ -83,8 +79,7 @@ TEST(FdTest, Colors) { TEST(FdTest, RGB) { Table table({"subsystem", "admin", "oper"}); - struct winsize win; - memset(&win, 0, sizeof(win)); + struct winsize win = {0}; ioctl(0, TIOCGWINSZ, &win); // Might fail. table.AddRow(); @@ -99,8 +94,7 @@ TEST(FdTest, RGB) { TEST(FdTest, EightBit) { Table table({"subsystem", "admin", "oper"}); - struct winsize win; - memset(&win, 0, sizeof(win)); + struct winsize win = {0}; ioctl(0, TIOCGWINSZ, &win); // Might fail. table.AddRow();