commit 5a00e06cb939f01e683bd44a2d32da0b197e5a66 Author: CJ van den Berg Date: Thu Feb 8 22:23:40 2024 +0100 Initial Release diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..301af57 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,2 @@ +--- +Checks: '*,-llvmlibc-*,-readability-*,-google-readability-*,-fuchsia-*,-hicpp-exception-baseclass,-altera-*,-hicpp-braces-around-statements,-google-runtime-int,-misc-non-private-member-variables-in-classes,-cert-err58-cpp,-hicpp-no-array-decay,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cppcoreguidelines-pro-type-union-access,-hicpp-signed-bitwise,-cppcoreguidelines-avoid-magic-numbers,-misc-no-recursion,-bugprone-easily-swappable-parameters,-cppcoreguidelines-owning-memory,-cppcoreguidelines-macro-usage,-google-objc-*' diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..46ea169 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.cache/ +/zig-out/ +/zig-cache/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..b7fe79f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug tests", + "program": "${workspaceFolder}/zig-out/bin/test", + "env": { + "TRACE": "1", + }, + "cwd": "${workspaceFolder}", + "preLaunchTask": "build", + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..84a82d3 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "clangd.arguments": [ + "--enable-config" + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..5fbac66 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,52 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "type": "process", + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "dedicated", + "showReuseMessage": true, + "clear": true + }, + "command": "./zig", + "args": [ + "build", + "-freference-trace", + "--prominent-compile-errors" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": "$zig" + }, + { + "label": "test", + "type": "process", + "presentation": { + "echo": true, + "reveal": "always", + "focus": true, + "panel": "dedicated", + "showReuseMessage": true, + "clear": true + }, + "command": "./zig", + "args": [ + "build", + "test", + "-freference-trace", + "--prominent-compile-errors" + ], + "group": { + "kind": "test", + "isDefault": true + }, + "problemMatcher": "$zig" + } + ] +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..92d6792 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 CJ van den Berg + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..e49eac7 --- /dev/null +++ b/build.zig @@ -0,0 +1,125 @@ +const std = @import("std"); + +const CrossTarget = std.zig.CrossTarget; + +const cppflags = [_][]const u8{ + "-fcolor-diagnostics", + "-std=c++20", + "-Wall", + "-Wextra", + "-Werror", + "-Wno-unqualified-std-cast-call", + "-Wno-bitwise-instead-of-logical", //for notcurses + "-fno-sanitize=undefined", + "-gen-cdb-fragment-path", + ".cache/cdb", +}; + +pub fn build(b: *std.Build) void { + const enable_tracy_option = b.option(bool, "enable_tracy", "Enable tracy client library (default: no)"); + const tracy_enabled = if (enable_tracy_option) |enabled| enabled else false; + + const options = b.addOptions(); + options.addOption(bool, "enable_tracy", tracy_enabled); + + const options_mod = options.createModule(); + + const target = b.standardTargetOptions(.{ + .default_target = if (tracy_enabled) + CrossTarget.parse(.{ .arch_os_abi = "native-native-gnu" }) catch unreachable + else + CrossTarget.parse(.{ .arch_os_abi = "native-native-musl" }) catch unreachable, + }); + const optimize = b.standardOptimizeOption(.{}); + const mode = .{ .target = target, .optimize = optimize }; + + const asio_dep = b.dependency("asio", mode); + const tracy_dep = if (tracy_enabled) b.dependency("tracy", mode) else undefined; + + const lib = b.addStaticLibrary(.{ + .name = "thespian", + .target = target, + .optimize = optimize, + }); + if (tracy_enabled) { + lib.defineCMacro("TRACY_ENABLE", null); + lib.defineCMacro("TRACY_CALLSTACK", null); + } + lib.addIncludePath(.{ .path = "src" }); + lib.addIncludePath(.{ .path = "include" }); + lib.addCSourceFiles(.{ .files = &[_][]const u8{ + "src/backtrace.cpp", + "src/c/context.cpp", + "src/c/env.cpp", + "src/c/file_descriptor.cpp", + "src/c/handle.cpp", + "src/c/instance.cpp", + "src/c/metronome.cpp", + "src/c/signal.cpp", + "src/c/timeout.cpp", + "src/c/trace.cpp", + "src/cbor.cpp", + "src/executor_asio.cpp", + "src/hub.cpp", + "src/instance.cpp", + "src/trace.cpp", + }, .flags = &cppflags }); + if (tracy_enabled) { + lib.linkLibrary(tracy_dep.artifact("tracy")); + } + lib.linkLibrary(asio_dep.artifact("asio")); + lib.linkLibCpp(); + b.installArtifact(lib); + + const cbor_mod = b.addModule("cbor", .{ + .root_source_file = .{ .path = "src/cbor.zig" }, + }); + + const thespian_mod = b.addModule("thespian", .{ + .root_source_file = .{ .path = "src/thespian.zig" }, + .imports = &.{ + .{ .name = "cbor", .module = cbor_mod }, + }, + }); + thespian_mod.addIncludePath(.{ .path = "include" }); + thespian_mod.linkLibrary(lib); + + const tests = b.addTest(.{ + .root_source_file = .{ .path = "test/tests.zig" }, + .target = target, + .optimize = optimize, + }); + + tests.root_module.addImport("build_options", options_mod); + tests.root_module.addImport("cbor", cbor_mod); + tests.root_module.addImport("thespian", thespian_mod); + tests.addIncludePath(.{ .path = "test" }); + tests.addIncludePath(.{ .path = "src" }); + tests.addIncludePath(.{ .path = "include" }); + tests.addCSourceFiles(.{ .files = &[_][]const u8{ + "test/cbor_match.cpp", + "test/debug.cpp", + "test/endpoint_unx.cpp", + "test/endpoint_tcp.cpp", + "test/hub_filter.cpp", + "test/ip_tcp_client_server.cpp", + "test/ip_udp_echo.cpp", + "test/metronome_test.cpp", + "test/perf_cbor.cpp", + "test/perf_hub.cpp", + "test/perf_ring.cpp", + "test/perf_spawn.cpp", + "test/spawn_exit.cpp", + "test/tests.cpp", + "test/timeout_test.cpp", + }, .flags = &cppflags }); + tests.linkLibrary(lib); + tests.linkLibrary(asio_dep.artifact("asio")); + tests.linkLibCpp(); + b.installArtifact(tests); + + const test_run_cmd = b.addRunArtifact(tests); + + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&test_run_cmd.step); +} diff --git a/build.zig.version b/build.zig.version new file mode 100644 index 0000000..6b44f4d --- /dev/null +++ b/build.zig.version @@ -0,0 +1 @@ +0.12.0-dev.2644+42fcca49c diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..be9fe51 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,22 @@ +.{ + .name = "thespian", + .version = "0.0.1", + + .dependencies = .{ + .asio = .{ + .url = "https://github.com/kassane/asio/archive/5fc70d29f458f5b596a20bdf3d638294eef91478.tar.gz", + .hash = "1220926d7e934fcb9e4e55f25e24d17613fbe9b145798581e0ae7ea3679b37f4e44d", + }, + .tracy = .{ + .url = "https://github.com/neurocyte/zig-tracy/archive/d2113e7d778ebe7a063e95b5182ff145343aac38.tar.gz", + .hash = "122005c37f1324dcdd00600f266b64f0a66a73428550015ece51d52ae40a552608d1", + }, + }, + .paths = .{ + "include", + "src", + "test", + "build.zig", + "build.zig.zon", + }, +} diff --git a/include/cbor/c/cbor.h b/include/cbor/c/cbor.h new file mode 100644 index 0000000..cfa0eff --- /dev/null +++ b/include/cbor/c/cbor.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include // NOLINT +#include // NOLINT + +#ifdef __cplusplus +extern "C" { +#endif + +struct cbor_buffer_t { + const uint8_t *base; + size_t len; +}; +typedef struct // NOLINT + cbor_buffer_t cbor_buffer; + +typedef // NOLINT + void (*cbor_to_json_callback)(c_string_view); + +void cbor_to_json(cbor_buffer, cbor_to_json_callback); + +#ifdef __cplusplus +} +#endif diff --git a/include/cbor/cbor.hpp b/include/cbor/cbor.hpp new file mode 100644 index 0000000..6657273 --- /dev/null +++ b/include/cbor/cbor.hpp @@ -0,0 +1,463 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace cbor { + +constexpr auto buffer_reserve = 128; + +constexpr auto cbor_magic_null = 0xf6; +constexpr auto cbor_magic_true = 0xf5; +constexpr auto cbor_magic_false = 0xf4; + +constexpr auto cbor_magic_type_array = 4; +constexpr auto cbor_magic_type_map = 5; + +enum class type { + number, + bytes, + string, + array, + map, + tag, + boolean, + null, + any, + more, + unknown, +}; +const auto any = type::any; +const auto more = type::more; + +template inline auto is_more(const T & /*unused*/) -> bool { + return false; +} +template <> inline auto is_more(const type &t) -> bool { + return t == type::more; +} + +using buffer_base = std::vector; + +class buffer : public buffer_base { +private: + using iter = const_iterator; + +public: + using extractor = std::function; + +private: + static auto decode_range_header(iter &, const iter &) -> size_t; + static auto decode_array_header(iter &, const iter &) -> size_t; + static auto decode_map_header(iter &, const iter &) -> size_t; + + [[nodiscard]] static auto match_value(iter &, const iter &, const extractor &) + -> bool; + [[nodiscard]] static auto match_value(iter &, const iter &, type) -> bool; + [[nodiscard]] static auto match_value(iter &, const iter &, int64_t) -> bool; + [[nodiscard]] static auto match_value(iter &, const iter &, bool) -> bool; + [[nodiscard]] static auto match_value(iter &, const iter &, + const std::string &) -> bool; + [[nodiscard]] static auto match_value(iter &, const iter &, + const std::string_view) -> bool; + [[nodiscard]] static auto match_value(iter &b, const iter &e, const char *s) + -> bool { + return match_value(b, e, std::string(s)); + } + + [[nodiscard]] static auto match_value(iter &b, const iter &e, uint64_t v) + -> bool { + return match_value(b, e, static_cast(v)); + } + [[nodiscard]] static auto match_value(iter &b, const iter &e, int32_t v) + -> bool { + return match_value(b, e, static_cast(v)); + } + [[nodiscard]] static auto match_value(iter &b, const iter &e, uint32_t v) + -> bool { + return match_value(b, e, static_cast(v)); + } + [[nodiscard]] static auto match_value(iter &b, const iter &e, int16_t v) + -> bool { + return match_value(b, e, static_cast(v)); + } + [[nodiscard]] static auto match_value(iter &b, const iter &e, uint16_t v) + -> bool { + return match_value(b, e, static_cast(v)); + } + [[nodiscard]] static auto match_value(iter &b, const iter &e, int8_t v) + -> bool { + return match_value(b, e, static_cast(v)); + } + [[nodiscard]] static auto match_value(iter &b, const iter &e, uint8_t v) + -> bool { + return match_value(b, e, static_cast(v)); + } + + template + [[nodiscard]] static auto match_next(size_t n, iter &b, const iter &e, T &&p) + -> bool { + if (is_more(p)) { + while (n) { + if (!match_value(b, e, any)) + return false; + --n; + } + return true; + } + if (!n) + return false; + if (!match_value(b, e, p)) + return false; + return n == 1; + } + + template + [[nodiscard]] static auto match_next(size_t n, iter &b, const iter &e, T &&p, + Ts &&...parms) -> bool { + if (!n) + return false; + if (!match_value(b, e, p)) + return false; + return match_next(--n, b, e, std::forward(parms)...); + } + + auto push_typed_val(int type, uint64_t value) -> void; + + auto push_parms() -> buffer & { return *this; } + template + auto push_parms(const T &p, Ts &&...parms) -> buffer & { + push(p); + return push_parms(std::forward(parms)...); + } + + template + [[nodiscard]] static auto match_array(iter &b, const iter &e, Ts &&...parms) + -> bool { + auto n = decode_array_header(b, e); + return match_next(n, b, e, std::forward(parms)...); + } + + template + [[nodiscard]] static auto match_map(iter &b, const iter &e, Ts &&...parms) + -> bool { + auto n = decode_map_header(b, e); + n *= 2; + return match_next(n, b, e, std::forward(parms)...); + } + +public: + using buffer_base::buffer_base; + auto operator[](size_t) -> value_type & = delete; + + static const buffer null_value; + + [[nodiscard]] auto raw_cbegin() const -> iter { + return buffer_base::cbegin(); + } + [[nodiscard]] auto raw_cend() const -> iter { return buffer_base::cend(); } + + template + [[nodiscard]] auto operator()(Ts &&...parms) const -> bool { + auto b = raw_cbegin(); + auto e = raw_cend(); + return match_array(b, e, std::forward(parms)...); + } + + [[nodiscard]] auto is_null() const -> bool { return *this == null_value; } + + auto push_null() -> buffer & { + push_back(cbor_magic_null); + return *this; + } + + auto push_bool(bool v) -> buffer & { + if (v) + push_back(cbor_magic_true); + else + push_back(cbor_magic_false); + return *this; + } + + auto push_string(const std::string &) -> buffer &; + auto push_string(const std::string_view &s) -> buffer &; + + auto push_int(int64_t value) -> buffer & { + if (value < 0) { + push_typed_val(1, -(value + 1)); + } else { + push_typed_val(0, value); + } + return *this; + } + auto push(const long long &i) -> buffer & { return push_int(i); } + auto push(const int64_t &i) -> buffer & { return push_int(i); } + auto push(const int32_t &i) -> buffer & { return push_int(i); } + auto push(const int16_t &i) -> buffer & { return push_int(i); } + auto push(const int8_t &i) -> buffer & { return push_int(i); } + auto push(const bool &v) -> buffer & { return push_bool(v); } + auto push(const std::string &s) -> buffer & { return push_string(s); } + auto push(const std::string_view &s) -> buffer & { return push_string(s); } + auto push(char *s) -> buffer & { return push_string(std::string_view(s)); } + auto push(const char *s) -> buffer & { + return push_string(std::string_view(s)); + } + + auto push_uint(uint64_t value) -> buffer & { + push_typed_val(0, value); + return *this; + } + auto push(const unsigned long long &i) -> buffer & { return push_uint(i); } + auto push(const uint64_t &i) -> buffer & { return push_uint(i); } + auto push(const uint32_t &i) -> buffer & { return push_uint(i); } + auto push(const uint16_t &i) -> buffer & { return push_uint(i); } + auto push(const uint8_t &i) -> buffer & { return push_uint(i); } + + template auto push(const T &a) -> buffer & { + a.to_cbor(*this); + return *this; + } + + auto array_header(size_t sz) -> buffer & { + push_typed_val(cbor_magic_type_array, sz); + return *this; + } + + auto map_header(size_t sz) -> buffer & { + push_typed_val(cbor_magic_type_map, sz); + return *this; + } + + template auto push_array(const T &a) -> buffer & { + if (a.empty()) + push_null(); + else { + array_header(a.size()); + for (auto v : a) + push(v); + } + return *this; + } + + auto push_json(const std::string &) -> void; + + auto array() -> buffer & { return push_null(); } + + template auto array(Ts &&...parms) -> buffer & { + array_header(sizeof...(parms)); + return push_parms(std::forward(parms)...); + } + + auto map() -> buffer & { return push_null(); } + + template auto map(Ts &&...parms) -> buffer & { + constexpr size_t sz = sizeof...(parms); + static_assert(sz % 2 == 0, + "cbor maps must contain an even number of values"); + map_header(sz / 2); + return push_parms(std::forward(parms)...); + } + + auto to_cbor(buffer &b) const -> buffer & { + b.insert(b.raw_cend(), raw_cbegin(), raw_cend()); + return b; + } + + [[nodiscard]] auto to_json() const -> std::string; + auto to_json(std::ostream &) const -> void; + [[nodiscard]] auto hexdump() const -> std::string; + + class range; + + struct value_accessor { + iter b; + iter e; + + [[nodiscard]] auto type_() const -> type; + + operator type() const; // NOLINT + operator int64_t() const; // NOLINT + operator uint64_t() const; // NOLINT + operator int32_t() const; // NOLINT + operator uint32_t() const; // NOLINT + operator int16_t() const; // NOLINT + operator uint16_t() const; // NOLINT + operator int8_t() const; // NOLINT + operator uint8_t() const; // NOLINT + operator bool() const; // NOLINT + operator std::string_view() const; // NOLINT + operator buffer::range() const; // NOLINT + + struct unvisitable_type { + cbor::type t; + }; + + template auto visit(F f) const -> decltype(f(true)) { + type t{type_()}; + switch (t) { + case type::string: + return f(static_cast(*this)); + case type::number: + return f(static_cast(*this)); + case type::boolean: + return f(static_cast(*this)); + case type::array: + case type::map: + return f(static_cast(*this)); + case type::null: + case type::bytes: + case type::tag: + case type::any: + case type::more: + case type::unknown: + return f(unvisitable_type{t}); + } + return f(unvisitable_type{t}); + } + + auto to_cbor(buffer &buf) const -> buffer & { + buf.insert(buf.raw_cend(), b, e); + return buf; + } + }; + + auto push(const value_accessor::unvisitable_type & /*unused*/) -> buffer & { + return push("cbor::value_accessor::unvisitable_type"); + } + + struct value_iterator { + iter b; + iter e; + size_t n = 0; + + auto operator!=(const value_iterator &other) const -> bool { + return b != other.b; + } + auto operator++() -> void { + if (n) { + --n; + [[maybe_unused]] bool _ = match_value(b, e, type::any); + } + } + auto operator*() -> value_accessor { + if (n) + return {b, e}; + throw std::out_of_range("cbor iterator out of range"); + } + }; + + [[nodiscard]] auto cbegin() const -> value_iterator = delete; + [[nodiscard]] auto cend() const -> value_iterator = delete; + [[nodiscard]] auto begin() const -> value_iterator { + auto b = raw_cbegin(); + auto e = raw_cend(); + auto n = decode_range_header(b, e); + return {b, e, n}; + } + [[nodiscard]] auto end() const -> value_iterator { + return {raw_cend(), raw_cend()}; + } + + class range { + iter b_; + iter e_; + friend auto extract(range &) -> extractor; + [[nodiscard]] auto raw_cbegin() const -> iter { return b_; } + [[nodiscard]] auto raw_cend() const -> iter { return e_; } + + public: + [[nodiscard]] auto begin() const -> value_iterator { + auto b = raw_cbegin(); + auto e = raw_cend(); + auto n = decode_range_header(b, e); + return {b, e, n}; + } + [[nodiscard]] auto end() const -> value_iterator { + return {raw_cend(), raw_cend()}; + } + + auto is_null() -> bool { + auto b = raw_cbegin(); + return decode_range_header(b, raw_cend()) == 0; + } + + template + [[nodiscard]] auto operator()(Ts &&...parms) const -> bool { + auto b = raw_cbegin(); + return match_array(b, raw_cend(), std::forward(parms)...); + } + [[nodiscard]] auto to_json() const -> std::string; + auto to_json(std::ostream &) const -> void; + auto to_cbor(buffer &b) const -> buffer & { + b.insert(b.raw_cend(), raw_cbegin(), raw_cend()); + return b; + } + auto operator==(const range &b) const -> bool { + using std::equal; + return equal(b.raw_cbegin(), b.raw_cend(), raw_cbegin(), raw_cend()); + } + }; + friend class range; + friend struct value_accessor; + friend auto extract(range &) -> extractor; + template friend auto A(Ts &&...parms) -> buffer::extractor; + template friend auto M(Ts &&...parms) -> buffer::extractor; +}; + +template auto array(Ts &&...parms) -> buffer { + buffer b; + b.reserve(buffer_reserve); + b.array(std::forward(parms)...); + return b; +} + +template auto map(Ts &&...parms) -> buffer { + buffer b; + b.reserve(buffer_reserve); + b.map(std::forward(parms)...); + return b; +} + +auto extract(type &) -> buffer::extractor; +auto extract(int64_t &) -> buffer::extractor; +auto extract(unsigned long long &) -> buffer::extractor; +auto extract(uint64_t &) -> buffer::extractor; +auto extract(int32_t &) -> buffer::extractor; +auto extract(uint32_t &) -> buffer::extractor; +auto extract(int16_t &) -> buffer::extractor; +auto extract(uint16_t &) -> buffer::extractor; +auto extract(int8_t &) -> buffer::extractor; +auto extract(uint8_t &) -> buffer::extractor; +auto extract(bool &) -> buffer::extractor; +auto extract(std::string &) -> buffer::extractor; +auto extract(std::string_view &) -> buffer::extractor; +auto extract(buffer::range &) -> buffer::extractor; + +template auto A(Ts &&...parms) -> buffer::extractor { + return [=](buffer::iter &b, const buffer::iter &e) mutable { + return buffer::match_array(b, e, std::forward(parms)...); + }; +} + +template auto M(Ts &&...parms) -> buffer::extractor { + return [=](buffer::iter &b, const buffer::iter &e) mutable { + return buffer::match_map(b, e, std::forward(parms)...); + }; +} + +inline auto operator<<(std::ostream &s, const buffer &b) -> std::ostream & { + b.to_json(s); + return s; +} + +inline auto operator<<(std::ostream &s, const buffer::range &b) + -> std::ostream & { + b.to_json(s); + return s; +} + +} // namespace cbor diff --git a/include/cbor/cbor_in.hpp b/include/cbor/cbor_in.hpp new file mode 100644 index 0000000..a2b6bd2 --- /dev/null +++ b/include/cbor/cbor_in.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "cbor.hpp" + +#include +#include + +namespace cbor { + +template <> inline auto buffer::push(const in6_addr &a) -> buffer & { + push(std::string_view(reinterpret_cast(&a), // NOLINT + sizeof(a))); + return *this; +} + +inline auto extract(in6_addr &a) -> cbor::buffer::extractor { + return [&a](auto &b, const auto &e) { + std::string_view s; + auto ret = cbor::extract(s)(b, e); + if (ret && s.size() == sizeof(in6_addr)) { + std::memcpy(&a, s.data(), sizeof(in6_addr)); + return true; + } + return false; + }; +} + +} // namespace cbor diff --git a/include/thespian/backtrace.h b/include/thespian/backtrace.h new file mode 100644 index 0000000..49d53ad --- /dev/null +++ b/include/thespian/backtrace.h @@ -0,0 +1,13 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +void install_debugger(); +void install_backtrace(); +void install_jitdebugger(); + +#ifdef __cplusplus +} +#endif diff --git a/include/thespian/c/context.h b/include/thespian/c/context.h new file mode 100644 index 0000000..f44c7f5 --- /dev/null +++ b/include/thespian/c/context.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +// NOLINTBEGIN(modernize-use-trailing-return-type, modernize-use-using) +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*thespian_last_exit_handler)(); +typedef void (*thespian_context_destroy)(void *); + +typedef void *thespian_behaviour_state; +typedef void *thespian_exit_handler_state; +typedef thespian_result (*thespian_behaviour)(thespian_behaviour_state); +typedef void (*thespian_exit_handler)(thespian_exit_handler_state, + const char *msg, size_t len); + +struct thespian_context_t; +typedef struct thespian_context_t *thespian_context; + +thespian_context thespian_context_create(thespian_context_destroy *); +void thespian_context_run(thespian_context); +void thespian_context_on_last_exit(thespian_context, + thespian_last_exit_handler); + +int thespian_context_spawn_link(thespian_context, thespian_behaviour, + thespian_behaviour_state, thespian_exit_handler, + thespian_exit_handler_state, const char *name, + thespian_env, thespian_handle *); + +const char *thespian_get_last_error(); +void thespian_set_last_error(const char *); + +#ifdef __cplusplus +} +#endif +// NOLINTEND(modernize-use-trailing-return-type, modernize-use-using) diff --git a/include/thespian/c/env.h b/include/thespian/c/env.h new file mode 100644 index 0000000..25affe7 --- /dev/null +++ b/include/thespian/c/env.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include + +// NOLINTBEGIN(modernize-use-trailing-return-type, modernize-use-using) +#ifdef __cplusplus +extern "C" { +#endif + +struct thespian_env_t; +typedef struct + thespian_env_t *thespian_env; + +void thespian_env_enable_all_channels(thespian_env); +void thespian_env_disable_all_channels(thespian_env); +void thespian_env_enable(thespian_env, thespian_trace_channel); +void thespian_env_disable(thespian_env, thespian_trace_channel); +bool thespian_env_enabled(thespian_env, thespian_trace_channel); +void thespian_env_on_trace(thespian_env, thespian_trace_handler); +void thespian_env_trace(thespian_env, cbor_buffer); + +bool thespian_env_is(thespian_env, c_string_view key); +void thespian_env_set(thespian_env, c_string_view key, bool); +int64_t thespian_env_num(thespian_env, c_string_view key); +void thespian_env_num_set(thespian_env, c_string_view key, int64_t); +c_string_view thespian_env_str(thespian_env, c_string_view key); +void thespian_env_str_set(thespian_env, c_string_view key, + c_string_view); +thespian_handle thespian_env_proc(thespian_env, c_string_view key); +void thespian_env_proc_set(thespian_env, c_string_view key, + thespian_handle); + +thespian_env thespian_env_get(); +thespian_env thespian_env_new(); +thespian_env thespian_env_clone(thespian_env); +void thespian_env_destroy(thespian_env); + +#ifdef __cplusplus +} +#endif +// NOLINTEND(modernize-use-trailing-return-type, modernize-use-using) diff --git a/include/thespian/c/file_descriptor.h b/include/thespian/c/file_descriptor.h new file mode 100644 index 0000000..5517ac9 --- /dev/null +++ b/include/thespian/c/file_descriptor.h @@ -0,0 +1,19 @@ +#pragma once + +// NOLINTBEGIN(modernize-use-trailing-return-type) +#ifdef __cplusplus +extern "C" { +#endif + +struct thespian_file_descriptor_handle; +struct thespian_file_descriptor_handle * +thespian_file_descriptor_create(const char* tag, int fd); +int thespian_file_descriptor_wait_write(struct thespian_file_descriptor_handle *); +int thespian_file_descriptor_wait_read(struct thespian_file_descriptor_handle *); +int thespian_file_descriptor_cancel(struct thespian_file_descriptor_handle *); +void thespian_file_descriptor_destroy(struct thespian_file_descriptor_handle *); + +#ifdef __cplusplus +} +#endif +// NOLINTEND(modernize-use-trailing-return-type) diff --git a/include/thespian/c/handle.h b/include/thespian/c/handle.h new file mode 100644 index 0000000..fca8a84 --- /dev/null +++ b/include/thespian/c/handle.h @@ -0,0 +1,29 @@ +#pragma once + +#include "thespian/c/string_view.h" +#include + +// NOLINTBEGIN(modernize-*, hicpp-*) +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef cbor_buffer thespian_error; +typedef thespian_error *thespian_result; + +struct thespian_handle_t; +typedef struct thespian_handle_t *thespian_handle; + +thespian_handle thespian_handle_clone(thespian_handle); +void thespian_handle_destroy(thespian_handle); + +thespian_result thespian_handle_send_raw(thespian_handle, cbor_buffer); +thespian_result thespian_handle_send_exit(thespian_handle, c_string_view); +bool thespian_handle_is_expired(thespian_handle); + +#ifdef __cplusplus +} +#endif +// NOLINTEND(modernize-*, hicpp-*) diff --git a/include/thespian/c/instance.h b/include/thespian/c/instance.h new file mode 100644 index 0000000..3a2eb06 --- /dev/null +++ b/include/thespian/c/instance.h @@ -0,0 +1,29 @@ +#pragma once + +#include "context.h" +#include "env.h" +#include "handle.h" + +// NOLINTBEGIN(modernize-use-trailing-return-type, modernize-use-using) +#ifdef __cplusplus +extern "C" { +#endif + +typedef thespian_result (*thespian_receiver)(thespian_behaviour_state, + thespian_handle from, cbor_buffer); + +void thespian_receive(thespian_receiver, thespian_behaviour_state); + +bool thespian_get_trap(); +bool thespian_set_trap(bool); +void thespian_link(thespian_handle); + +thespian_handle thespian_self(); + +int thespian_spawn_link(thespian_behaviour, thespian_behaviour_state, + const char *name, thespian_env, thespian_handle *); + +#ifdef __cplusplus +} +#endif +// NOLINTEND(modernize-use-trailing-return-type, modernize-use-using) diff --git a/include/thespian/c/metronome.h b/include/thespian/c/metronome.h new file mode 100644 index 0000000..b440b00 --- /dev/null +++ b/include/thespian/c/metronome.h @@ -0,0 +1,20 @@ +#pragma once + +// NOLINTBEGIN(modernize-use-trailing-return-type) +#ifdef __cplusplus +extern "C" { +#endif + +struct thespian_metronome_handle; +struct thespian_metronome_handle * +thespian_metronome_create_ms(unsigned long ms); +struct thespian_metronome_handle * +thespian_metronome_create_us(unsigned long us); +int thespian_metronome_start(struct thespian_metronome_handle *); +int thespian_metronome_stop(struct thespian_metronome_handle *); +void thespian_metronome_destroy(struct thespian_metronome_handle *); + +#ifdef __cplusplus +} +#endif +// NOLINTEND(modernize-use-trailing-return-type) diff --git a/include/thespian/c/signal.h b/include/thespian/c/signal.h new file mode 100644 index 0000000..4fe8536 --- /dev/null +++ b/include/thespian/c/signal.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +// NOLINTBEGIN(modernize-use-trailing-return-type) +#ifdef __cplusplus +extern "C" { +#endif + +struct thespian_signal_handle; +struct thespian_signal_handle * +thespian_signal_create(int signum, cbor_buffer m); +int thespian_signal_cancel(struct thespian_signal_handle *); +void thespian_signal_destroy(struct thespian_signal_handle *); + +#ifdef __cplusplus +} +#endif +// NOLINTEND(modernize-use-trailing-return-type) diff --git a/include/thespian/c/string_view.h b/include/thespian/c/string_view.h new file mode 100644 index 0000000..4ec8a5f --- /dev/null +++ b/include/thespian/c/string_view.h @@ -0,0 +1,19 @@ +#pragma once + +// NOLINTBEGIN(modernize-*, hicpp-*) +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct c_string_view_t { + const char *base; + size_t len; +}; +typedef struct c_string_view_t c_string_view; + +#ifdef __cplusplus +} +#endif +// NOLINTEND(modernize-*, hicpp-*) diff --git a/include/thespian/c/timeout.h b/include/thespian/c/timeout.h new file mode 100644 index 0000000..7e18970 --- /dev/null +++ b/include/thespian/c/timeout.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +// NOLINTBEGIN(modernize-use-trailing-return-type) +#ifdef __cplusplus +extern "C" { +#endif + +struct thespian_timeout_handle; +struct thespian_timeout_handle * +thespian_timeout_create_ms(unsigned long ms, cbor_buffer m); +struct thespian_timeout_handle * +thespian_timeout_create_us(unsigned long us, cbor_buffer m); +int thespian_timeout_cancel(struct thespian_timeout_handle *); +void thespian_timeout_destroy(struct thespian_timeout_handle *); + +#ifdef __cplusplus +} +#endif +// NOLINTEND(modernize-use-trailing-return-type) diff --git a/include/thespian/c/trace.h b/include/thespian/c/trace.h new file mode 100644 index 0000000..7d73bdb --- /dev/null +++ b/include/thespian/c/trace.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +// NOLINTBEGIN(modernize-*, hicpp-*) +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int thespian_trace_channel_set; + +typedef int thespian_trace_channel; + +static const thespian_trace_channel thespian_trace_channel_send = 1; +static const thespian_trace_channel thespian_trace_channel_receive = 2; +static const thespian_trace_channel thespian_trace_channel_lifetime = 4; +static const thespian_trace_channel thespian_trace_channel_link = 8; +static const thespian_trace_channel thespian_trace_channel_execute = 16; +static const thespian_trace_channel thespian_trace_channel_udp = 32; +static const thespian_trace_channel thespian_trace_channel_tcp = 64; +static const thespian_trace_channel thespian_trace_channel_timer = 128; +static const thespian_trace_channel thespian_trace_channel_metronome = 256; +static const thespian_trace_channel thespian_trace_channel_endpoint = 512; +static const thespian_trace_channel thespian_trace_channel_signal = 1024; +static const thespian_trace_channel thespian_trace_channel_all = INT_MAX; + +typedef void (*thespian_trace_handler)(cbor_buffer); + +void thespian_on_trace(thespian_trace_handler); +void thespian_trace_to_json_file(const char *file_name); +void thespian_trace_to_cbor_file(const char *file_name); +void thespian_trace_to_mermaid_file(const char *file_name); +void thespian_trace_to_trace(thespian_trace_channel default_channel); + +#ifdef __cplusplus +} +#endif +// NOLINTEND(modernize-*, hicpp-*) diff --git a/include/thespian/context.hpp b/include/thespian/context.hpp new file mode 100644 index 0000000..d98ca31 --- /dev/null +++ b/include/thespian/context.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "env.hpp" +#include "handle.hpp" + +#include +#include + +namespace thespian { + +using behaviour = std::functionresult>; +using exit_handler = std::function; + +struct context { + using dtor = void (*)(context *); + using ref = std::unique_ptr; + using last_exit_handler = std::function; + + [[nodiscard]] static auto create() -> ref; + [[nodiscard]] static auto create(dtor *) -> context *; + void run(); + void on_last_exit(last_exit_handler); + + [[nodiscard]] auto spawn(behaviour b, std::string_view name) + -> expected; + [[nodiscard]] auto spawn(behaviour b, std::string_view name, env_t env) + -> expected; + [[nodiscard]] auto spawn_link(behaviour b, exit_handler eh, + std::string_view name) + -> expected; + [[nodiscard]] auto spawn_link(behaviour b, exit_handler eh, + std::string_view name, env_t env) + -> expected; + +protected: + context() = default; + ~context() = default; + +public: + context(const context &) = delete; + context(context &&) = delete; + void operator=(const context &) = delete; + void operator=(context &&) = delete; +}; + +} // namespace thespian diff --git a/include/thespian/debug.hpp b/include/thespian/debug.hpp new file mode 100644 index 0000000..e769878 --- /dev/null +++ b/include/thespian/debug.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "context.hpp" +#include "handle.hpp" + +#include + +using port_t = unsigned short; + +namespace thespian::debug { + +auto enable(context &) -> void; +auto disable(context &) -> void; +auto isenabled(context &) -> bool; + +namespace tcp { + +auto create(context &, port_t port, const std::string &prompt) + -> expected; + +} // namespace tcp +} // namespace thespian::debug diff --git a/include/thespian/endpoint.hpp b/include/thespian/endpoint.hpp new file mode 100644 index 0000000..2b2ca45 --- /dev/null +++ b/include/thespian/endpoint.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "handle.hpp" +#include "tcp.hpp" +#include "unx.hpp" +#include +#include + +using port_t = unsigned short; + +namespace thespian::endpoint { + +using namespace std::chrono_literals; + +namespace tcp { + +auto listen(in6_addr, port_t) -> expected; +auto connect(in6_addr, port_t, std::chrono::milliseconds retry_time = 50ms, + size_t retry_count = 5) -> expected; + +} // namespace tcp + +namespace unx { +using thespian::unx::mode; + +auto listen(std::string_view, mode = mode::abstract) -> expected; +auto connect(std::string_view, mode = mode::abstract, + std::chrono::milliseconds retry_time = 50ms, + size_t retry_count = 5) -> expected; + +} // namespace unx + +} // namespace thespian::endpoint diff --git a/include/thespian/env.hpp b/include/thespian/env.hpp new file mode 100644 index 0000000..26e6b0c --- /dev/null +++ b/include/thespian/env.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include "handle.hpp" +#include "trace.hpp" + +#include +#include +#include + +namespace thespian { + +struct env_t { + void enable_all_channels() { + trace_channels = static_cast(channel::all); + } + void disable_all_channels() { trace_channels = 0; } + void enable(channel c) { trace_channels |= static_cast(c); } + void disable(channel c) { trace_channels ^= static_cast(c); } + auto enabled(channel c) -> bool { + return trace_channels & static_cast(c); + } + + auto on_trace(trace_handler h) { + std::swap(h, trace_handler); + return h; + } + + auto trace(const cbor::buffer &msg) const { + if (trace_handler) + trace_handler(msg); + } + + auto is(std::string_view k) -> bool & { return b[std::string(k)]; } + auto num(std::string_view k) -> int64_t & { return i[std::string(k)]; } + auto str(std::string_view k) -> std::string & { return s[std::string(k)]; } + auto proc(std::string_view k) -> handle & { return h[std::string(k)]; } + [[nodiscard]] auto proc(std::string_view k) const -> const handle & { + return h.at(std::string(k)); + } + +private: + std::map s; + std::map b; + std::map i; + std::map h; + channel_set trace_channels{0}; + trace_handler trace_handler; +}; +auto env() -> env_t &; + +} // namespace thespian diff --git a/include/thespian/expected.hpp b/include/thespian/expected.hpp new file mode 100644 index 0000000..1a83c7c --- /dev/null +++ b/include/thespian/expected.hpp @@ -0,0 +1,1716 @@ +// SPDX-License-Identifier: CC0-1.0 +/// +// expected - An c++17 implementation of std::expected with extensions +// Written in 2017 by Simon Brand (simonrbrand@gmail.com, @TartanLlama) +// Modified in 2020 by Shen-Ta Hsieh (ibmibmibm.tw@gmail.com, @ibmibmibm) +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to the +// public domain worldwide. This software is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. If not, see +// . +/// + +#pragma once +#include +#include +#include +#include + +namespace std::experimental { +inline namespace fundamentals_v3 { + +template class expected; +template class unexpected; +template unexpected(E) -> unexpected; + +template class bad_expected_access; +template <> class bad_expected_access : public exception { +public: + explicit bad_expected_access() = default; +}; +template +class bad_expected_access : public bad_expected_access { +public: + explicit bad_expected_access(E e) : m_error(move(e)) {} + const char *what() const noexcept override { return "Bad expected access"; } + const E &error() const & { return m_error; } + const E &&error() const && { return move(m_error); } + E &error() & { return m_error; } + E &&error() && { return move(m_error); } + +private: + E m_error; +}; + +struct unexpect_t { + explicit constexpr unexpect_t() = default; +}; +inline constexpr unexpect_t unexpect{}; + +namespace detail { + +template using remove_cvref_t = remove_cv_t>; + +template +static inline constexpr bool + is_not_constructible_and_not_reverse_convertible_v = + !is_constructible_v && !is_constructible_v && + !is_constructible_v && + !is_constructible_v && !is_convertible_v && + !is_convertible_v && !is_convertible_v && + !is_convertible_v; + +struct no_init_t { + explicit constexpr no_init_t() = default; +}; +inline constexpr no_init_t no_init{}; + +template struct is_expected : false_type {}; +template struct is_expected> : true_type {}; +template +static inline constexpr bool is_expected_v = is_expected::value; + +} // namespace detail + +template class unexpected { + E m_val; + +public: + constexpr unexpected(const unexpected &) = default; + constexpr unexpected(unexpected &&) = default; + constexpr unexpected &operator=(const unexpected &) = default; + constexpr unexpected &operator=(unexpected &&) = default; + + template > * = nullptr, + bool NoExcept = is_nothrow_constructible_v> + constexpr explicit unexpected(in_place_t, Args &&...args) noexcept(NoExcept) + : m_val(forward(args)...) {} + template , Args...>> * = + nullptr, + bool NoExcept = + is_nothrow_constructible_v, Args...>> + constexpr explicit unexpected(in_place_t, initializer_list il, + Args &&...args) noexcept(NoExcept) + : m_val(il, forward(args)...) {} + template && + !is_same_v, in_place_t> && + !is_same_v, unexpected>> * = + nullptr, + bool NoExcept = is_nothrow_constructible_v> + constexpr explicit unexpected(Err &&e) noexcept(NoExcept) + : m_val(forward(e)) {} + + template < + class Err, + enable_if_t && + detail::is_not_constructible_and_not_reverse_convertible_v< + E, unexpected>> * = nullptr, + bool NoExcept = is_nothrow_constructible_v> + constexpr unexpected(const unexpected &rhs) noexcept(NoExcept) + : m_val(rhs.m_val) {} + template < + class Err, + enable_if_t && + detail::is_not_constructible_and_not_reverse_convertible_v< + E, unexpected>> * = nullptr, + bool NoExcept = is_nothrow_constructible_v> + constexpr unexpected(unexpected &&rhs) noexcept(NoExcept) + : m_val(move(rhs.m_val)) {} + + template > * = nullptr, + bool NoExcept = is_nothrow_assignable_v> + constexpr unexpected &operator=(const unexpected &e) noexcept(NoExcept) { + m_val = e.m_val; + return *this; + } + template > * = nullptr, + bool NoExcept = is_nothrow_assignable_v> + constexpr unexpected &operator=(unexpected &&e) noexcept(NoExcept) { + m_val = move(e.m_val); + return *this; + } + constexpr const E &value() const &noexcept { return m_val; } + constexpr E &value() &noexcept { return m_val; } + constexpr E &&value() &&noexcept { return move(m_val); } + constexpr const E &&value() const &&noexcept { return move(m_val); } + + template > * = nullptr, + bool NoExcept = is_nothrow_swappable_v> + void swap(unexpected &rhs) noexcept(NoExcept) { + using std::swap; + swap(m_val, rhs.m_val); + } + + template > * = nullptr> + void swap(unexpected &rhs) = delete; +}; +template () == declval())> +constexpr bool operator==(const unexpected &x, + const unexpected &y) noexcept(NoExcept) { + return x.value() == y.value(); +} +template () != declval())> +constexpr bool operator!=(const unexpected &x, + const unexpected &y) noexcept(NoExcept) { + return x.value() != y.value(); +} + +namespace detail { + +template struct expected_traits { + template + static inline constexpr bool enable_other_copy_constructible_v = + is_constructible_v &&is_constructible_v && + is_not_constructible_and_not_reverse_convertible_v> + &&detail::is_not_constructible_and_not_reverse_convertible_v< + unexpected, expected>; + template + static inline constexpr bool explicit_other_copy_constructible_v = + !is_convertible_v || !is_convertible_v; + + template + static inline constexpr bool enable_other_move_constructible_v = + is_constructible_v &&is_constructible_v && + is_not_constructible_and_not_reverse_convertible_v> + &&detail::is_not_constructible_and_not_reverse_convertible_v< + unexpected, expected>; + template + static inline constexpr bool explicit_other_move_constructible_v = + !is_convertible_v || !is_convertible_v; + + template + static inline constexpr bool is_nothrow_other_copy_constructible_v = + is_nothrow_constructible_v + &&is_nothrow_constructible_v; + template + static inline constexpr bool is_nothrow_other_move_constructible_v = + is_nothrow_constructible_v &&is_nothrow_constructible_v; + template + static inline constexpr bool enable_in_place_v = + is_constructible_v && !is_same_v, in_place_t> && + !is_same_v, remove_cvref_t> && + !is_same_v, remove_cvref_t>; + static inline constexpr bool enable_emplace_value_v = + is_nothrow_move_constructible_v || is_nothrow_move_constructible_v; + template + static inline constexpr bool enable_assign_value_v = + !conjunction_v, is_same>> && + is_constructible_v && is_assignable_v && + is_nothrow_move_constructible_v; +}; +template struct expected_traits { + template + static inline constexpr bool enable_other_copy_constructible_v = + is_void_v &&is_constructible_v + &&detail::is_not_constructible_and_not_reverse_convertible_v< + unexpected, expected>; + template + static inline constexpr bool explicit_other_copy_constructible_v = + !is_convertible_v; + + template + static inline constexpr bool enable_other_move_constructible_v = + is_void_v &&is_constructible_v + &&detail::is_not_constructible_and_not_reverse_convertible_v< + unexpected, expected>; + template + static inline constexpr bool explicit_other_move_constructible_v = + !is_convertible_v; + + template + static inline constexpr bool is_nothrow_other_copy_constructible_v = + is_void_v &&is_nothrow_constructible_v; + template + static inline constexpr bool is_nothrow_other_move_constructible_v = + is_void_v &&is_nothrow_constructible_v; + template static inline constexpr bool enable_in_place_v = false; + template static inline constexpr bool enable_assign_value_v = false; +}; + +template , + bool = is_trivially_destructible_v> +struct expected_storage_base { + constexpr expected_storage_base() noexcept( + is_nothrow_default_constructible_v) + : m_has_val(true), m_val() {} + constexpr expected_storage_base(no_init_t) noexcept + : m_has_val(false), m_no_init() {} + constexpr expected_storage_base(const expected_storage_base &) = default; + constexpr expected_storage_base & + operator=(const expected_storage_base &) = default; + + template > * = nullptr, + bool NoExcept = is_nothrow_constructible_v> + constexpr expected_storage_base(in_place_t, Args &&...args) noexcept(NoExcept) + : m_has_val(true), m_val(forward(args)...) {} + template , Args...>> * = + nullptr, + bool NoExcept = + is_nothrow_constructible_v, Args...>> + constexpr expected_storage_base(in_place_t, initializer_list il, + Args &&...args) noexcept(NoExcept) + : m_has_val(true), m_val(move(il), forward(args)...) {} + + template > * = nullptr, + bool NoExcept = is_nothrow_constructible_v> + constexpr expected_storage_base(unexpect_t, Args &&...args) noexcept(NoExcept) + : m_has_val(false), m_unex(in_place, forward(args)...) {} + template , Args...>> * = + nullptr, + bool NoExcept = + is_nothrow_constructible_v, Args...>> + constexpr expected_storage_base(unexpect_t, initializer_list il, + Args &&...args) noexcept(NoExcept) + : m_has_val(false), m_unex(in_place, move(il), forward(args)...) {} + + ~expected_storage_base() noexcept( + is_nothrow_destructible_v &&is_nothrow_destructible_v) { + if (m_has_val) { + destruct_value(); + } else { + destruct_error(); + } + } + +protected: + constexpr void destruct_value() noexcept(is_nothrow_destructible_v) { + m_val.~T(); + } + constexpr void destruct_error() noexcept(is_nothrow_destructible_v) { + m_unex.~unexpected(); + } + + bool m_has_val; + union { + T m_val; + unexpected m_unex; + char m_no_init; + }; +}; + +template struct expected_storage_base { + constexpr expected_storage_base() noexcept( + is_nothrow_default_constructible_v) + : m_has_val(true), m_val() {} + constexpr expected_storage_base(no_init_t) noexcept + : m_has_val(false), m_no_init() {} + constexpr expected_storage_base(const expected_storage_base &) = default; + constexpr expected_storage_base & + operator=(const expected_storage_base &) = default; + + template > * = nullptr, + bool NoExcept = is_nothrow_constructible_v> + constexpr expected_storage_base(in_place_t, Args &&...args) noexcept(NoExcept) + : m_has_val(true), m_val(forward(args)...) {} + template , Args...>> * = + nullptr, + bool NoExcept = + is_nothrow_constructible_v, Args...>> + constexpr expected_storage_base(in_place_t, initializer_list il, + Args &&...args) noexcept(NoExcept) + : m_has_val(true), m_val(move(il), forward(args)...) {} + + template > * = nullptr, + bool NoExcept = is_nothrow_constructible_v> + constexpr expected_storage_base(unexpect_t, Args &&...args) noexcept(NoExcept) + : m_has_val(false), m_unex(in_place, forward(args)...) {} + template , Args...>> * = + nullptr, + bool NoExcept = + is_nothrow_constructible_v, Args...>> + constexpr expected_storage_base(unexpect_t, initializer_list il, + Args &&...args) noexcept(NoExcept) + : m_has_val(false), m_unex(in_place, move(il), forward(args)...) {} + + ~expected_storage_base() noexcept = default; + +protected: + constexpr void destruct_value() noexcept {} + constexpr void destruct_error() noexcept {} + + bool m_has_val; + union { + T m_val; + unexpected m_unex; + char m_no_init; + }; +}; + +template struct expected_storage_base { + constexpr expected_storage_base() noexcept( + is_nothrow_default_constructible_v) + : m_has_val(true), m_val() {} + constexpr expected_storage_base(no_init_t) noexcept + : m_has_val(false), m_no_init() {} + constexpr expected_storage_base(const expected_storage_base &) = default; + constexpr expected_storage_base & + operator=(const expected_storage_base &) = default; + + template > * = nullptr, + bool NoExcept = is_nothrow_constructible_v> + constexpr expected_storage_base(in_place_t, Args &&...args) noexcept(NoExcept) + : m_has_val(true), m_val(forward(args)...) {} + template , Args...>> * = + nullptr, + bool NoExcept = + is_nothrow_constructible_v, Args...>> + constexpr expected_storage_base(in_place_t, initializer_list il, + Args &&...args) noexcept(NoExcept) + : m_has_val(true), m_val(move(il), forward(args)...) {} + + template > * = nullptr, + bool NoExcept = is_nothrow_constructible_v> + constexpr expected_storage_base(unexpect_t, Args &&...args) noexcept(NoExcept) + : m_has_val(false), m_unex(in_place, forward(args)...) {} + template , Args...>> * = + nullptr, + bool NoExcept = + is_nothrow_constructible_v, Args...>> + constexpr expected_storage_base(unexpect_t, initializer_list il, + Args &&...args) noexcept(NoExcept) + : m_has_val(false), m_unex(in_place, move(il), forward(args)...) {} + + ~expected_storage_base() noexcept(is_nothrow_destructible_v) { + if (!m_has_val) { + destruct_error(); + } + } + +protected: + constexpr void destruct_value() noexcept {} + constexpr void destruct_error() noexcept(is_nothrow_destructible_v) { + m_unex.~unexpected(); + } + + bool m_has_val; + union { + T m_val; + unexpected m_unex; + char m_no_init; + }; +}; + +template struct expected_storage_base { + constexpr expected_storage_base() noexcept( + is_nothrow_default_constructible_v) + : m_has_val(true), m_val() {} + constexpr expected_storage_base(no_init_t) noexcept + : m_has_val(false), m_no_init() {} + constexpr expected_storage_base(const expected_storage_base &) = default; + constexpr expected_storage_base & + operator=(const expected_storage_base &) = default; + + template > * = nullptr, + bool NoExcept = is_nothrow_constructible_v> + constexpr expected_storage_base(in_place_t, Args &&...args) noexcept(NoExcept) + : m_has_val(true), m_val(forward(args)...) {} + template , Args...>> * = + nullptr, + bool NoExcept = + is_nothrow_constructible_v, Args...>> + constexpr expected_storage_base(in_place_t, initializer_list il, + Args &&...args) noexcept(NoExcept) + : m_has_val(true), m_val(move(il), forward(args)...) {} + + template > * = nullptr, + bool NoExcept = is_nothrow_constructible_v> + constexpr expected_storage_base(unexpect_t, Args &&...args) noexcept(NoExcept) + : m_has_val(false), m_unex(in_place, forward(args)...) {} + template , Args...>> * = + nullptr, + bool NoExcept = + is_nothrow_constructible_v, Args...>> + constexpr expected_storage_base(unexpect_t, initializer_list il, + Args &&...args) noexcept(NoExcept) + : m_has_val(false), m_unex(in_place, move(il), forward(args)...) {} + + ~expected_storage_base() noexcept(is_nothrow_destructible_v) { + if (m_has_val) { + destruct_value(); + } + } + +protected: + constexpr void destruct_value() noexcept(is_nothrow_destructible_v) { + m_val.~T(); + } + constexpr void destruct_error() noexcept {} + + bool m_has_val; + union { + T m_val; + unexpected m_unex; + char m_no_init; + }; +}; + +template struct expected_storage_base { + constexpr expected_storage_base() noexcept : m_has_val(true), m_no_init() {} + constexpr expected_storage_base(no_init_t) noexcept + : m_has_val(false), m_no_init() {} + constexpr expected_storage_base(in_place_t) noexcept + : m_has_val(true), m_no_init() {} + constexpr expected_storage_base(const expected_storage_base &) = default; + constexpr expected_storage_base & + operator=(const expected_storage_base &) = default; + + template > * = nullptr, + bool NoExcept = is_nothrow_constructible_v> + constexpr expected_storage_base(unexpect_t, Args &&...args) noexcept(NoExcept) + : m_has_val(false), m_unex(forward(args)...) {} + template , Args...>> * = + nullptr, + bool NoExcept = + is_nothrow_constructible_v, Args...>> + constexpr expected_storage_base(unexpect_t, initializer_list il, + Args &&...args) noexcept(NoExcept) + : m_has_val(false), m_unex(move(il), forward(args)...) {} + + ~expected_storage_base() noexcept(is_nothrow_destructible_v) { + if (!m_has_val) { + destruct_error(); + } + } + +protected: + constexpr void destruct_value() noexcept {} + constexpr void destruct_error() noexcept(is_nothrow_destructible_v) { + m_unex.~unexpected(); + } + + bool m_has_val; + union { + unexpected m_unex; + char m_no_init; + }; +}; + +template struct expected_storage_base { + constexpr expected_storage_base() noexcept : m_has_val(true), m_no_init() {} + constexpr expected_storage_base(no_init_t) noexcept + : m_has_val(false), m_no_init() {} + constexpr expected_storage_base(in_place_t) noexcept + : m_has_val(true), m_no_init() {} + constexpr expected_storage_base(const expected_storage_base &) = default; + constexpr expected_storage_base & + operator=(const expected_storage_base &) = default; + + template > * = nullptr, + bool NoExcept = is_nothrow_constructible_v> + constexpr expected_storage_base(unexpect_t, Args &&...args) noexcept(NoExcept) + : m_has_val(false), m_unex(forward(args)...) {} + template , Args...>> * = + nullptr, + bool NoExcept = + is_nothrow_constructible_v, Args...>> + constexpr expected_storage_base(unexpect_t, initializer_list il, + Args &&...args) noexcept(NoExcept) + : m_has_val(false), m_unex(move(il), forward(args)...) {} + + ~expected_storage_base() noexcept = default; + +protected: + constexpr void destruct_value() noexcept {} + constexpr void destruct_error() noexcept {} + + bool m_has_val; + union { + unexpected m_unex; + char m_no_init; + }; +}; + +template +struct expected_view_base : public expected_storage_base { + using base = expected_storage_base; + using base::base; + + constexpr bool has_value() const noexcept { return base::m_has_val; } + constexpr const E &error() const &noexcept { return base::m_unex.value(); } + constexpr const E &&error() const &&noexcept { + return move(base::m_unex).value(); + } + constexpr E &error() &noexcept { return base::m_unex.value(); } + constexpr E &&error() &&noexcept { return move(base::m_unex).value(); } + + template && + (is_nothrow_constructible_v || + is_nothrow_move_constructible_v || + is_nothrow_move_constructible_v)> * = nullptr, + bool NoExcept = is_nothrow_constructible_v && + is_nothrow_move_assignable_v &&is_nothrow_destructible_v> + T &emplace(Args &&...args) noexcept(NoExcept) { + if (has_value()) { + val() = T(forward(args)...); + } else if constexpr (is_nothrow_constructible_v) { + base::destruct_error(); + construct_value(forward(args)...); + } else if constexpr (is_nothrow_move_constructible_v) { + T tmp(forward(args)...); + base::destruct_error(); + construct_value(move(tmp)); + } else { + E tmp = move(error()); + base::destruct_error(); + try { + construct_value(forward(args)...); + } catch (...) { + base::construct_error(move(tmp)); + throw; + } + } + return val(); + } + template , Args...> && + (is_nothrow_constructible_v, Args...> || + is_nothrow_move_constructible_v || + is_nothrow_move_constructible_v)> * = nullptr, + bool NoExcept = is_nothrow_constructible_v, + Args...> && + is_nothrow_move_assignable_v &&is_nothrow_destructible_v> + T &emplace(initializer_list il, Args &&...args) noexcept(NoExcept) { + if (has_value()) { + val() = T(il, forward(args)...); + } else if constexpr (is_nothrow_constructible_v) { + base::destruct_error(); + construct_value(il, forward(args)...); + } else if constexpr (is_nothrow_move_constructible_v) { + T tmp(il, forward(args)...); + base::destruct_error(); + construct_value(move(tmp)); + } else { + E tmp = move(error()); + base::destruct_error(); + try { + construct_value(il, forward(args)...); + } catch (...) { + base::construct_error(move(tmp)); + throw; + } + } + return val(); + } + +protected: + constexpr const T &val() const &noexcept { return base::m_val; } + constexpr const T &&val() const &&noexcept { return move(base::m_val); } + constexpr T &val() &noexcept { return base::m_val; } + constexpr T &&val() &&noexcept { return move(base::m_val); } + template > * = nullptr, + bool NoExcept = is_nothrow_constructible_v> + void construct_value(Args &&...args) noexcept(NoExcept) { + new (addressof(base::m_val)) T(forward(args)...); + base::m_has_val = true; + } + template > * = nullptr, + bool NoExcept = is_nothrow_constructible_v> + void construct_error(Args &&...args) noexcept(NoExcept) { + new (addressof(base::m_unex)) unexpected(forward(args)...); + base::m_has_val = false; + } +}; + +template +struct expected_view_base : public expected_storage_base { + using base = expected_storage_base; + using base::base; + + constexpr bool has_value() const noexcept { return base::m_has_val; } + constexpr const E &error() const &noexcept { return base::m_unex.value(); } + constexpr const E &&error() const &&noexcept { + return move(base::m_unex).value(); + } + constexpr E &error() &noexcept { return base::m_unex.value(); } + constexpr E &&error() &&noexcept { return move(base::m_unex).value(); } + + void emplace() noexcept(is_nothrow_destructible_v) { + if (!has_value()) { + base::destruct_error(); + } + construct_value(); + } + +protected: + constexpr void val() const noexcept {} + + void construct_value() noexcept { base::m_has_val = true; } + template > * = nullptr, + bool NoExcept = is_nothrow_constructible_v> + void construct_error(Args &&...args) noexcept(NoExcept) { + new (addressof(base::m_unex)) unexpected(forward(args)...); + base::m_has_val = false; + } +}; + +template +struct expected_operations_base : public expected_view_base { + using expected_view_base::expected_view_base; + +protected: + template + &&is_nothrow_assignable_v, U>> + void assign_value(U &&rhs) noexcept(NoExcept) { + if (!this->has_value()) { + if constexpr (is_nothrow_constructible_v) { + this->destruct_error(); + this->construct_value(forward(rhs)); + } else { + E tmp = this->error(); + this->destruct_error(); + try { + this->construct_value(forward(rhs)); + } catch (...) { + this->construct_error(move(tmp)); + throw; + } + } + } else { + this->val() = forward(rhs); + } + } + + template > + void assign_error(const unexpected &rhs) noexcept(NoExcept) { + static_assert(is_nothrow_constructible_v, + "E must nothrow copy constructible"); + static_assert(is_nothrow_assignable_v, + "E must copy assignable"); + if (this->has_value()) { + this->destruct_value(); + this->construct_error(rhs.value()); + } else { + this->error() = rhs.value(); + } + } + template > + void assign_error(unexpected &&rhs) noexcept(NoExcept) { + static_assert(is_nothrow_constructible_v, + "E must nothrow move constructible"); + static_assert(is_nothrow_assignable_v, "E must move assignable"); + if (this->has_value()) { + this->destruct_value(); + this->construct_error(move(rhs).value()); + } else { + this->error() = move(rhs).value(); + } + } + + void assign(const expected_operations_base &rhs) noexcept( + is_nothrow_copy_assignable_v &&is_nothrow_copy_assignable_v + &&is_nothrow_copy_assignable_v &&is_nothrow_destructible_v && + (disjunction_v, is_nothrow_copy_constructible>)) { + static_assert(is_nothrow_move_constructible_v, + "E must nothrow move constructible"); + static_assert(disjunction_v, is_move_constructible>, + "T must move constructible"); + if (!this->has_value() && rhs.has_value()) { + if constexpr (is_void_v) { + this->destruct_error(); + this->construct_value(); + } else if constexpr (is_nothrow_copy_constructible_v) { + this->destruct_error(); + this->construct_value(rhs.val()); + } else if constexpr (is_nothrow_move_constructible_v) { + T tmp = rhs.val(); + this->destruct_error(); + this->construct_value(move(tmp)); + } else { + E tmp = this->error(); + this->destruct_error(); + try { + this->construct_value(rhs.val()); + } catch (...) { + this->construct_error(move(tmp)); + throw; + } + } + } else if (this->has_value() && !rhs.has_value()) { + if constexpr (is_void_v) { + this->construct_error(rhs.error()); + } else if constexpr (is_nothrow_copy_constructible_v) { + this->destruct_value(); + this->construct_error(rhs.error()); + } else if constexpr (is_nothrow_move_constructible_v) { + E tmp = rhs.error(); + this->destruct_value(); + this->construct_error(move(tmp)); + } else { + T tmp = this->val(); + this->destruct_value(); + try { + this->construct_error(rhs.error()); + } catch (...) { + this->construct_value(move(tmp)); + throw; + } + } + } else { + if constexpr (is_void_v) { + if (!this->has_value()) { + this->error() = rhs.error(); + } + } else { + if (this->has_value()) { + this->val() = rhs.val(); + } else { + this->error() = rhs.error(); + } + } + } + } + + void assign(expected_operations_base &&rhs) noexcept( + is_nothrow_destructible_v &&is_nothrow_destructible_v + &&is_nothrow_move_constructible_v + &&is_nothrow_move_assignable_v) { + static_assert(is_nothrow_move_constructible_v, + "E must nothrow move constructible"); + static_assert(disjunction_v, is_move_constructible>, + "T must move constructible"); + if (!this->has_value() && rhs.has_value()) { + if constexpr (is_void_v) { + this->destruct_error(); + this->construct_value(); + } else if constexpr (is_nothrow_move_constructible_v) { + this->destruct_error(); + this->construct_value(move(rhs).val()); + } else { + E tmp = move(this->error()); + this->destruct_error(); + try { + this->construct_value(move(rhs).val()); + } catch (...) { + this->construct_error(move(tmp)); + throw; + } + } + } else if (this->has_value() && !rhs.has_value()) { + if constexpr (is_void_v) { + this->construct_error(move(rhs).error()); + } else if constexpr (is_nothrow_move_constructible_v) { + this->destruct_value(); + this->construct_error(move(rhs).error()); + } else { + T tmp = move(this->val()); + this->destruct_value(); + try { + this->construct_error(move(rhs).error()); + } catch (...) { + this->construct_value(move(tmp)); + throw; + } + } + } else { + if (this->has_value()) { + if constexpr (!is_void_v) { + this->val() = move(rhs).val(); + } + } else { + this->error() = move(rhs).error(); + } + } + } +}; + +template , + disjunction, is_trivially_copy_constructible>>> +struct expected_copy_base : public expected_operations_base { + using expected_operations_base::expected_operations_base; +}; + +template +struct expected_copy_base : public expected_operations_base { + using expected_operations_base::expected_operations_base; + + constexpr expected_copy_base() = default; + constexpr expected_copy_base(const expected_copy_base &rhs) noexcept( + is_nothrow_copy_constructible_v) + : expected_operations_base(no_init) { + if (rhs.has_value()) { + if constexpr (is_void_v) { + this->construct_value(); + } else { + this->construct_value(rhs.val()); + } + } else { + this->construct_error(rhs.error()); + } + } + constexpr expected_copy_base(expected_copy_base &&rhs) = default; + constexpr expected_copy_base & + operator=(const expected_copy_base &rhs) = default; + constexpr expected_copy_base &operator=(expected_copy_base &&rhs) = default; +}; + +template , + disjunction, is_trivially_move_constructible>>> +struct expected_move_base : public expected_copy_base { + using expected_copy_base::expected_copy_base; +}; + +template +struct expected_move_base : public expected_copy_base { + using expected_copy_base::expected_copy_base; + + constexpr expected_move_base() = default; + constexpr expected_move_base(expected_move_base &&rhs) noexcept( + is_nothrow_move_constructible_v) + : expected_copy_base(no_init) { + if (rhs.has_value()) { + if constexpr (is_void_v) { + this->construct_value(); + } else { + this->construct_value(move(rhs).val()); + } + } else { + this->construct_error(move(rhs).error()); + } + } + constexpr expected_move_base(const expected_move_base &rhs) = default; + constexpr expected_move_base & + operator=(const expected_move_base &rhs) = default; + constexpr expected_move_base &operator=(expected_move_base &&rhs) = default; +}; + +template < + class T, class E, + bool = conjunction_v< + disjunction, conjunction, + is_trivially_copy_constructible, + is_trivially_destructible>>, + is_trivially_copy_assignable, is_trivially_copy_constructible, + is_trivially_destructible>> +struct expected_copy_assign_base : expected_move_base { + using expected_move_base::expected_move_base; +}; + +template +struct expected_copy_assign_base : expected_move_base { + using expected_move_base::expected_move_base; + + constexpr expected_copy_assign_base() = default; + constexpr expected_copy_assign_base(const expected_copy_assign_base &rhs) = + default; + constexpr expected_copy_assign_base(expected_copy_assign_base &&rhs) = + default; + constexpr expected_copy_assign_base & + operator=(const expected_copy_assign_base &rhs) noexcept( + is_nothrow_copy_constructible_v &&is_nothrow_copy_assignable_v) { + this->assign(rhs); + return *this; + } + constexpr expected_copy_assign_base & + operator=(expected_copy_assign_base &&rhs) = default; +}; + +template < + class T, class E, + bool = conjunction_v< + disjunction, conjunction, + is_trivially_move_constructible, + is_trivially_move_assignable>>, + is_trivially_destructible, is_trivially_move_constructible, + is_trivially_move_assignable>> +struct expected_move_assign_base : expected_copy_assign_base { + using expected_copy_assign_base::expected_copy_assign_base; +}; + +template +struct expected_move_assign_base + : expected_copy_assign_base { + using expected_copy_assign_base::expected_copy_assign_base; + + constexpr expected_move_assign_base() = default; + constexpr expected_move_assign_base(const expected_move_assign_base &rhs) = + default; + constexpr expected_move_assign_base(expected_move_assign_base &&rhs) = + default; + constexpr expected_move_assign_base & + operator=(const expected_move_assign_base &rhs) = default; + constexpr expected_move_assign_base & + operator=(expected_move_assign_base &&rhs) noexcept( + is_nothrow_destructible_v &&is_nothrow_destructible_v + &&is_nothrow_move_constructible_v + &&is_nothrow_move_assignable_v) { + this->assign(move(rhs)); + return *this; + } +}; + +template ::value_type, + enable_if_t> * = nullptr, + class Ret = decltype(invoke(declval(), *declval()))> +constexpr auto expected_and_then_impl(Exp &&exp, F &&f) { + static_assert(is_expected_v, "F must return an expected"); + + if (exp.has_value()) { + return invoke(forward(f), *forward(exp)); + } + return Ret(unexpect, forward(exp).error()); +} + +template ::value_type, + enable_if_t> * = nullptr, + class Ret = decltype(invoke(declval()))> +constexpr auto expected_and_then_impl(Exp &&exp, F &&f) { + static_assert(is_expected_v, "F must return an expected"); + + if (exp.has_value()) { + return invoke(forward(f)); + } + return Ret(unexpect, forward(exp).error()); +} + +template (), declval().error())), + enable_if_t> * = nullptr> +constexpr auto expected_or_else_impl(Exp &&exp, F &&f) { + static_assert(is_expected_v, "F must return an expected"); + if (exp.has_value()) { + return forward(exp); + } + return invoke(forward(f), forward(exp).error()); +} + +template (), declval().error())), + enable_if_t> * = nullptr> +constexpr auto expected_or_else_impl(Exp &&exp, F &&f) { + if (!exp.has_value()) { + invoke(forward(f), forward(exp).error()); + } + return forward(exp); +} + +template ::value_type, + class E = typename decay_t::error_type, + enable_if_t> * = nullptr, + class Ret = decltype(invoke(declval(), *declval())), + enable_if_t> * = nullptr, + class Result = expected, E>> +constexpr Result expected_map_impl(Exp &&exp, F &&f) { + if (exp.has_value()) { + return Result(invoke(forward(f), *forward(exp))); + } + return Result(unexpect, forward(exp).error()); +} + +template ::value_type, + class E = typename decay_t::error_type, + enable_if_t> * = nullptr, + class Ret = decltype(invoke(declval(), *declval())), + enable_if_t> * = nullptr, + class Result = expected> +constexpr Result expected_map_impl(Exp &&exp, F &&f) { + if (exp.has_value()) { + invoke(forward(f), *forward(exp)); + return Result(); + } + return Result(unexpect, forward(exp).error()); +} + +template ::value_type, + class E = typename decay_t::error_type, + enable_if_t> * = nullptr, + class Ret = decltype(invoke(declval())), + enable_if_t> * = nullptr, + class Result = expected, E>> +constexpr Result expected_map_impl(Exp &&exp, F &&f) { + if (exp.has_value()) { + return Result(invoke(forward(f))); + } + return Result(unexpect, forward(exp).error()); +} + +template ::value_type, + class E = typename decay_t::error_type, + enable_if_t> * = nullptr, + class Ret = decltype(invoke(declval())), + enable_if_t> * = nullptr, + class Result = expected> +constexpr Result expected_map_impl(Exp &&exp, F &&f) { + if (exp.has_value()) { + invoke(forward(f)); + return Result(); + } + return Result(unexpect, forward(exp).error()); +} + +template ::value_type, + enable_if_t> * = nullptr, + class Ret = decltype(invoke(declval(), declval().error())), + enable_if_t> * = nullptr, + class Result = expected>> +constexpr Result expected_map_error_impl(Exp &&exp, F &&f) { + if (exp.has_value()) { + return Result(*forward(exp)); + } + return Result(unexpect, invoke(forward(f), forward(exp).error())); +} + +template ::value_type, + enable_if_t> * = nullptr, + class Ret = decltype(invoke(declval(), declval().error())), + enable_if_t> * = nullptr, + class Result = expected> +constexpr Result expected_map_error_impl(Exp &&exp, F &&f) { + if (exp.has_value()) { + return Result(*forward(exp)); + } + invoke(forward(f), forward(exp).error()); + return Result(unexpect); +} + +template ::value_type, + enable_if_t> * = nullptr, + class Ret = decltype(invoke(declval(), declval().error())), + enable_if_t> * = nullptr, + class Result = expected>> +constexpr Result expected_map_error_impl(Exp &&exp, F &&f) { + if (exp.has_value()) { + return Result(); + } + return Result(unexpect, invoke(forward(f), forward(exp).error())); +} + +template ::value_type, + enable_if_t> * = nullptr, + class Ret = decltype(invoke(declval(), declval().error())), + enable_if_t> * = nullptr, + class Result = expected> +constexpr Result expected_map_error_impl(Exp &&exp, F &&f) { + if (exp.has_value()) { + return Result(); + } + invoke(forward(f), forward(exp).error()); + return Result(unexpect); +} + +template || is_void_v> +struct expected_default_ctor_base { + constexpr expected_default_ctor_base() noexcept = default; + constexpr expected_default_ctor_base( + expected_default_ctor_base const &) noexcept = default; + constexpr expected_default_ctor_base(expected_default_ctor_base &&) noexcept = + default; + constexpr expected_default_ctor_base & + operator=(expected_default_ctor_base const &) noexcept = default; + constexpr expected_default_ctor_base & + operator=(expected_default_ctor_base &&) noexcept = default; + + constexpr explicit expected_default_ctor_base(in_place_t) {} +}; + +template struct expected_default_ctor_base { + constexpr expected_default_ctor_base() noexcept = delete; + constexpr expected_default_ctor_base( + expected_default_ctor_base const &) noexcept = default; + constexpr expected_default_ctor_base(expected_default_ctor_base &&) noexcept = + default; + constexpr expected_default_ctor_base & + operator=(expected_default_ctor_base const &) noexcept = default; + constexpr expected_default_ctor_base & + operator=(expected_default_ctor_base &&) noexcept = default; + + constexpr explicit expected_default_ctor_base(in_place_t) {} +}; + +template || + is_copy_constructible_v)&&is_copy_constructible_v, + bool = (is_void_v || + is_move_constructible_v)&&is_move_constructible_v> +struct expected_delete_ctor_base { + constexpr expected_delete_ctor_base() = default; + constexpr expected_delete_ctor_base(const expected_delete_ctor_base &) = + default; + constexpr expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = + default; + constexpr expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + constexpr expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +template +struct expected_delete_ctor_base { + constexpr expected_delete_ctor_base() = default; + constexpr expected_delete_ctor_base(const expected_delete_ctor_base &) = + default; + constexpr expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = + delete; + constexpr expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + constexpr expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +template +struct expected_delete_ctor_base { + constexpr expected_delete_ctor_base() = default; + constexpr expected_delete_ctor_base(const expected_delete_ctor_base &) = + delete; + constexpr expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = + default; + constexpr expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + constexpr expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +template +struct expected_delete_ctor_base { + constexpr expected_delete_ctor_base() = default; + constexpr expected_delete_ctor_base(const expected_delete_ctor_base &) = + delete; + constexpr expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = + delete; + constexpr expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + constexpr expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +template < + class T, class E, + bool = (is_void_v || + (is_copy_assignable_v && is_copy_constructible_v && + (is_nothrow_move_constructible_v || + is_nothrow_move_constructible_v))) && + is_copy_assignable_v &&is_copy_constructible_v, + bool = (is_void_v || + (is_move_assignable_v && is_move_constructible_v)) && + is_nothrow_move_assignable_v &&is_nothrow_move_constructible_v> +struct expected_delete_assign_base { + constexpr expected_delete_assign_base() = default; + constexpr expected_delete_assign_base(const expected_delete_assign_base &) = + default; + constexpr expected_delete_assign_base( + expected_delete_assign_base &&) noexcept = default; + constexpr expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = default; + constexpr expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = default; +}; + +template +struct expected_delete_assign_base { + constexpr expected_delete_assign_base() = default; + constexpr expected_delete_assign_base(const expected_delete_assign_base &) = + default; + constexpr expected_delete_assign_base( + expected_delete_assign_base &&) noexcept = default; + constexpr expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = default; + constexpr expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = delete; +}; + +template +struct expected_delete_assign_base { + constexpr expected_delete_assign_base() = default; + constexpr expected_delete_assign_base(const expected_delete_assign_base &) = + default; + constexpr expected_delete_assign_base( + expected_delete_assign_base &&) noexcept = default; + constexpr expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = delete; + constexpr expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = default; +}; + +template +struct expected_delete_assign_base { + constexpr expected_delete_assign_base() = default; + constexpr expected_delete_assign_base(const expected_delete_assign_base &) = + default; + constexpr expected_delete_assign_base( + expected_delete_assign_base &&) noexcept = default; + constexpr expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = delete; + constexpr expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = delete; +}; + +} // namespace detail + +template +class expected : public detail::expected_move_assign_base, + private detail::expected_delete_ctor_base, + private detail::expected_delete_assign_base, + private detail::expected_default_ctor_base { + using traits = detail::expected_traits; + using impl_base = detail::expected_move_assign_base; + using ctor_base = detail::expected_default_ctor_base; + using lvalue_reference_type = add_lvalue_reference_t; + using rvalue_reference_type = add_rvalue_reference_t; + using const_lvalue_reference_type = add_lvalue_reference_t>; + using const_rvalue_reference_type = add_rvalue_reference_t>; + +public: + using value_type = T; + using error_type = E; + using unexpected_type = unexpected; + template using rebind = expected; + + // 4.1, constructors + using impl_base::impl_base; + constexpr expected() = default; + constexpr expected(const expected &) = default; + constexpr expected(expected &&) = default; + + template < + class U, class G, + enable_if_t && + !traits::template explicit_other_copy_constructible_v> + * = nullptr> + constexpr expected(const expected &rhs) noexcept( + traits::template is_nothrow_other_copy_constructible_v) + : impl_base(detail::no_init), ctor_base(in_place) { + if (rhs.has_value()) { + this->construct_value(*rhs); + } else { + this->construct_error(rhs.error()); + } + } + template < + class U, class G, + enable_if_t && + traits::template explicit_other_copy_constructible_v> + * = nullptr> + constexpr explicit expected(const expected &rhs) noexcept( + traits::template is_nothrow_other_copy_constructible_v) + : impl_base(detail::no_init), ctor_base(in_place) { + if (rhs.has_value()) { + this->construct_value(*rhs); + } else { + this->construct_error(rhs.error()); + } + } + template < + class U, class G, + enable_if_t && + !traits::template explicit_other_move_constructible_v> + * = nullptr> + constexpr expected(expected &&rhs) noexcept( + traits::template is_nothrow_other_move_constructible_v) + : impl_base(detail::no_init), ctor_base(in_place) { + if (rhs.has_value()) { + this->construct_value(*move(rhs)); + } else { + this->construct_error(move(rhs).error()); + } + } + template < + class U, class G, + enable_if_t && + traits::template explicit_other_move_constructible_v> + * = nullptr> + constexpr explicit expected(expected &&rhs) noexcept( + traits::template is_nothrow_other_move_constructible_v) + : impl_base(detail::no_init), ctor_base(in_place) { + if (rhs.has_value()) { + this->construct_value(*move(rhs)); + } else { + this->construct_error(move(rhs).error()); + } + } + + template && + is_convertible_v> * = nullptr, + bool NoExcept = is_nothrow_constructible_v> + constexpr expected(U &&v) noexcept(NoExcept) + : expected(in_place, forward(v)) {} + template && + !is_convertible_v> * = nullptr, + bool NoExcept = is_nothrow_constructible_v> + constexpr explicit expected(U &&v) noexcept(NoExcept) + : expected(in_place, forward(v)) {} + + template > * = nullptr> + constexpr expected(const unexpected &e) : expected(unexpect, e.value()) {} + template > * = nullptr> + constexpr expected(unexpected &&e) : expected(unexpect, move(e.value())) {} + + template > * = nullptr, + bool NoExcept = is_nothrow_constructible_v> + constexpr explicit expected(in_place_t, Args &&...args) noexcept(NoExcept) + : impl_base(in_place, forward(args)...), ctor_base(in_place) {} + template , Args...>> * = + nullptr, + bool NoExcept = + is_nothrow_constructible_v, Args...>> + constexpr explicit expected(in_place_t, initializer_list il, + Args &&...args) noexcept(NoExcept) + : impl_base(in_place, move(il), forward(args)...), + ctor_base(in_place) {} + + template > * = nullptr, + bool NoExcept = is_nothrow_constructible_v> + constexpr explicit expected(unexpect_t, Args &&...args) noexcept(NoExcept) + : impl_base(unexpect, forward(args)...), ctor_base(in_place) {} + template , Args...>> * = + nullptr, + bool NoExcept = + is_nothrow_constructible_v, Args...>> + constexpr explicit expected(unexpect_t, initializer_list il, + Args &&...args) noexcept(NoExcept) + : impl_base(unexpect, move(il), forward(args)...), + ctor_base(in_place) {} + + // 4.2, destructor + ~expected() = default; + + // 4.3, assignment + constexpr expected &operator=(const expected &rhs) = default; + constexpr expected &operator=(expected &&rhs) = default; + + template > * = nullptr, + bool NoExcept = is_nothrow_constructible_v + &&is_nothrow_assignable_v> + constexpr expected &operator=(U &&v) noexcept(NoExcept) { + impl_base::assign_value(forward(v)); + return *this; + } + template + constexpr expected & + operator=(const unexpected &e) noexcept(is_nothrow_destructible_v) { + impl_base::assign_error(e); + return *this; + } + template + constexpr expected & + operator=(unexpected &&e) noexcept(is_nothrow_destructible_v) { + impl_base::assign_error(move(e)); + return *this; + } + + // 4.4, modifiers + using impl_base::emplace; + + // 4.5, swap + template < + class U = T, class G = E, + enable_if_t<(is_void_v || (is_swappable_v && + (is_nothrow_move_constructible_v || + is_nothrow_move_constructible_v))) && + is_swappable_v> * = nullptr, + bool NoExcept = is_nothrow_swappable_v &&is_nothrow_swappable_v + &&is_nothrow_move_constructible_v + &&is_nothrow_move_constructible_v> + void swap(expected &rhs) noexcept(NoExcept) { + if (this->has_value()) { + if (rhs.has_value()) { + if constexpr (!is_void_v) { + using std::swap; + swap(this->val(), rhs.val()); + } + } else { + rhs.swap(*this); + } + } else { + if (rhs.has_value()) { + if constexpr (is_void_v) { + this->construct_error(move(rhs).error()); + rhs.destruct_error(); + rhs.construct_value(); + } else if constexpr (is_nothrow_move_constructible_v) { + E tmp = move(rhs).error(); + rhs.destruct_error(); + if constexpr (is_nothrow_move_constructible_v) { + rhs.construct_value(move(*this).val()); + } else { + try { + rhs.construct_value(move(*this).val()); + } catch (...) { + rhs.construct_error(move(tmp)); + throw; + } + } + this->destruct_value(); + this->construct_error(move(tmp)); + } else { + static_assert(is_nothrow_move_constructible_v); + T tmp = move(*this).val(); + this->destruct_value(); + try { + this->construct_error(move(rhs).error()); + } catch (...) { + this->construct_value(move(tmp)); + throw; + } + rhs.destruct_error(); + rhs.construct_value(move(tmp)); + } + } else { + using std::swap; + swap(this->error(), rhs.error()); + } + } + } + template < + class U = T, class G = E, + enable_if_t<(!is_void_v && (!is_swappable_v || + (!is_nothrow_move_constructible_v && + !is_nothrow_move_constructible_v))) || + !is_swappable_v> * = nullptr> + void swap(expected &rhs) = delete; + + // 4.6, observers + constexpr const T *operator->() const { return addressof(impl_base::val()); } + constexpr T *operator->() { return addressof(impl_base::val()); } + constexpr const_lvalue_reference_type operator*() const & { + return impl_base::val(); + } + constexpr lvalue_reference_type operator*() & { return impl_base::val(); }; + constexpr const_rvalue_reference_type operator*() const && { + return move(impl_base::val()); + }; + constexpr rvalue_reference_type operator*() && { + return move(impl_base::val()); + }; + constexpr explicit operator bool() const noexcept { return has_value(); } + using impl_base::error; + using impl_base::has_value; + constexpr const_lvalue_reference_type value() const & { + if (!has_value()) { + throw bad_expected_access(error()); + } + return impl_base::val(); + } + constexpr const_rvalue_reference_type value() const && { + if (!has_value()) { + throw bad_expected_access(move(error())); + } + return move(impl_base::val()); + } + constexpr lvalue_reference_type value() & { + if (!has_value()) { + throw bad_expected_access(error()); + } + return impl_base::val(); + } + constexpr rvalue_reference_type value() && { + if (!has_value()) { + throw bad_expected_access(move(error())); + } + return move(impl_base::val()); + } + + template constexpr T value_or(U &&v) const &noexcept { + static_assert(!is_copy_constructible_v || is_convertible_v, + "T must be copy-constructible and convertible to from U"); + return bool(*this) ? **this : static_cast(forward(v)); + } + template constexpr T value_or(U &&v) &&noexcept { + static_assert(!is_move_constructible_v || is_convertible_v, + "T must be move-constructible and convertible to from U"); + return bool(*this) ? move(**this) : static_cast(forward(v)); + } + + // extensions + template constexpr auto and_then(F &&f) & { + return detail::expected_and_then_impl(*this, forward(f)); + } + template constexpr auto and_then(F &&f) && { + return detail::expected_and_then_impl(move(*this), forward(f)); + } + template constexpr auto and_then(F &&f) const & { + return detail::expected_and_then_impl(*this, forward(f)); + } + template constexpr auto and_then(F &&f) const && { + return detail::expected_and_then_impl(move(*this), forward(f)); + } + + template constexpr auto or_else(F &&f) & { + return detail::expected_or_else_impl(*this, forward(f)); + } + template constexpr auto or_else(F &&f) && { + return detail::expected_or_else_impl(move(*this), forward(f)); + } + template constexpr auto or_else(F &&f) const & { + return detail::expected_or_else_impl(*this, forward(f)); + } + template constexpr auto or_else(F &&f) const && { + return detail::expected_or_else_impl(move(*this), forward(f)); + } + + template constexpr auto map(F &&f) & { + return detail::expected_map_impl(*this, forward(f)); + } + template constexpr auto map(F &&f) && { + return detail::expected_map_impl(move(*this), forward(f)); + } + template constexpr auto map(F &&f) const & { + return detail::expected_map_impl(*this, forward(f)); + } + template constexpr auto map(F &&f) const && { + return detail::expected_map_impl(move(*this), forward(f)); + } + + template constexpr auto map_error(F &&f) & { + return detail::expected_map_error_impl(*this, forward(f)); + } + template constexpr auto map_error(F &&f) && { + return detail::expected_map_error_impl(move(*this), forward(f)); + } + template constexpr auto map_error(F &&f) const & { + return detail::expected_map_error_impl(*this, forward(f)); + } + template constexpr auto map_error(F &&f) const && { + return detail::expected_map_error_impl(move(*this), forward(f)); + } +}; + +// 4.7, Expected equality operators +template +constexpr bool operator==(const expected &x, + const expected &y) { + if (bool(x) != bool(y)) { + return false; + } + if (!bool(x)) { + return x.error() == y.error(); + } + if constexpr (is_void_v && is_void_v) { + return true; + } else { + return *x == *y; + } +} +template +constexpr bool operator!=(const expected &x, + const expected &y) { + if (bool(x) != bool(y)) { + return true; + } + if (!bool(x)) { + return x.error() != y.error(); + } + if constexpr (is_void_v && is_void_v) { + return true; + } else { + return *x != *y; + } +} + +// 4.8, Comparison with T +template +constexpr enable_if_t && !is_void_v, bool> +operator==(const expected &x, const T2 &v) { + return bool(x) ? *x == v : false; +} +template +constexpr enable_if_t && !is_void_v, bool> +operator==(const T2 &v, const expected &x) { + return bool(x) ? *x == v : false; +} +template +constexpr enable_if_t && !is_void_v, bool> +operator!=(const expected &x, const T2 &v) { + return bool(x) ? *x != v : false; +} +template +constexpr enable_if_t && !is_void_v, bool> +operator!=(const T2 &v, const expected &x) { + return bool(x) ? *x != v : false; +} + +// 4.9, Comparison with unexpected +template +constexpr bool operator==(const expected &x, const unexpected &e) { + return bool(x) ? false : x.error() == e.value(); +} +template +constexpr bool operator==(const unexpected &e, const expected &x) { + return bool(x) ? false : x.error() == e.value(); +} +template +constexpr bool operator!=(const expected &x, const unexpected &e) { + return bool(x) ? true : x.error() != e.value(); +} +template +constexpr bool operator!=(const unexpected &e, const expected &x) { + return bool(x) ? true : x.error() != e.value(); +} + +// 4.10, Specialized algorithms +template < + class T1, class E1, + enable_if_t<(is_void_v || (is_swappable_v && + (is_nothrow_move_constructible_v || + is_nothrow_move_constructible_v))) && + is_swappable_v> * = nullptr, + bool NoExcept = is_nothrow_swappable_v &&is_nothrow_swappable_v + &&is_nothrow_move_constructible_v + &&is_nothrow_move_constructible_v> +void swap(expected &x, expected &y) noexcept(NoExcept) { + x.swap(y); +} +template < + class T1, class E1, + enable_if_t<(!is_void_v && (!is_swappable_v || + (!is_nothrow_move_constructible_v && + !is_nothrow_move_constructible_v))) || + !is_swappable_v> * = nullptr> +void swap(expected &x, expected &y) = delete; + +template > * = nullptr, + bool NoExcept = is_nothrow_swappable_v> +void swap(unexpected &x, unexpected &y) noexcept(NoExcept) { + x.swap(y); +} +template > * = nullptr> +void swap(unexpected &x, unexpected &y) = delete; + +} // namespace fundamentals_v3 +} // namespace std::experimental diff --git a/include/thespian/file_descriptor.hpp b/include/thespian/file_descriptor.hpp new file mode 100644 index 0000000..c94c42e --- /dev/null +++ b/include/thespian/file_descriptor.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include + +namespace thespian { + +struct file_descriptor_impl; +using file_descriptor_dtor = void (*)(file_descriptor_impl *); +using file_descriptor_ref = + std::unique_ptr; + +struct file_descriptor { + static auto create(std::string_view tag, int fd) -> file_descriptor; + auto wait_write() -> void; + auto wait_read() -> void; + auto cancel() -> void; + static void wait_write(file_descriptor_impl *); + static void wait_read(file_descriptor_impl *); + static void cancel(file_descriptor_impl *); + static void destroy(file_descriptor_impl *); + + //->("fd", tag, "write_ready") + //->("fd", tag, "write_error", int err, string message) + //->("fd", tag, "read_ready") + //->("fd", tag, "read_error", int err, string message) + + file_descriptor_ref ref; +}; + +} // namespace thespian diff --git a/include/thespian/handle.hpp b/include/thespian/handle.hpp new file mode 100644 index 0000000..c86defb --- /dev/null +++ b/include/thespian/handle.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include + +#include "expected.hpp" +#include +#include + +namespace thespian { + +template +using expected = std::experimental::expected; +using error = cbor::buffer; + +using result = expected; +using to_error = std::experimental::unexpected; + +[[nodiscard]] inline auto ok() -> result { return result{}; } +template [[nodiscard]] auto ok(T v) { return result{v}; } +template [[nodiscard]] auto to_result(T ret) -> result { + if (ret) + return ok(); + return to_error(ret.error()); +} + +struct instance; +using ref = std::weak_ptr; + +struct handle { + [[nodiscard]] auto send_raw(cbor::buffer) const -> result; + template [[nodiscard]] auto send(Ts &&...parms) const { + return send_raw(cbor::array(std::forward(parms)...)); + } + template + auto exit(const std::string &error, Ts &&...details) const { + return send_raw(cbor::array("exit", error, std::forward(details)...)); + } + [[nodiscard]] inline auto expired() const { return ref_.expired(); } + +private: + ref ref_; + friend auto handle_ref(handle &) -> ref &; + friend auto handle_ref(const handle &) -> const ref &; + friend auto operator==(const handle &, const handle &) -> bool; +}; + +auto operator==(const handle &, const handle &) -> bool; + +} // namespace thespian diff --git a/include/thespian/hub.hpp b/include/thespian/hub.hpp new file mode 100644 index 0000000..99c7e92 --- /dev/null +++ b/include/thespian/hub.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "handle.hpp" +#include +#include + +namespace thespian { + +struct hub : private handle { + using filter = std::function; + + hub() = default; + auto expired() -> bool { return handle::expired(); } + + template auto broadcast(Ts &&...parms) const { + return send("broadcast", cbor::array(std::forward(parms)...)); + } + [[nodiscard]] auto subscribe() const -> result; + [[nodiscard]] auto subscribe(filter) const -> result; + [[nodiscard]] auto listen(std::string_view unix_socket_descriptor) const + -> result; + [[nodiscard]] auto shutdown() const { return send("shutdown"); } + + [[nodiscard]] static auto create(std::string_view name) + -> expected; + + struct pipe { + pipe(); + pipe(const pipe &) = delete; + pipe(pipe &&) = delete; + auto operator=(const pipe &) -> pipe & = delete; + auto operator=(pipe &&) -> pipe & = delete; + virtual ~pipe(); + }; + +private: + hub(const handle &, std::shared_ptr); + std::shared_ptr pipe_; + friend auto operator==(const handle &, const hub &) -> bool; + friend auto operator==(const hub &, const handle &) -> bool; +}; + +auto operator==(const handle &, const hub &) -> bool; +auto operator==(const hub &, const handle &) -> bool; + +} // namespace thespian diff --git a/include/thespian/instance.hpp b/include/thespian/instance.hpp new file mode 100644 index 0000000..e9aa731 --- /dev/null +++ b/include/thespian/instance.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include "context.hpp" +#include "env.hpp" +#include "handle.hpp" +#include "trace.hpp" + +#include +#include +#include +#include + +namespace thespian { + +using receiver = std::function; +using sync_receiver = std::function; + +[[nodiscard]] auto send_raw(cbor::buffer) -> result; +template [[nodiscard]] auto send(Ts &&...parms) -> result { + return send_raw(cbor::array(std::forward(parms)...)); +} +auto receive(receiver) -> void; +auto receive_sync(sync_receiver) -> void; + +auto trap() -> bool; +auto trap(bool) -> bool; +auto link(const handle &) -> void; + +auto self() -> handle; +auto self_ref() -> handle&; + +[[nodiscard]] auto spawn(behaviour, std::string_view name) + -> expected; +[[nodiscard]] auto spawn_link(behaviour, std::string_view name) + -> expected; + +[[nodiscard]] auto spawn(behaviour, std::string_view name, env_t) + -> expected; +[[nodiscard]] auto spawn_link(behaviour, std::string_view name, env_t) + -> expected; + +[[nodiscard]] auto ok() -> result; +[[nodiscard]] auto exit() -> result; +[[nodiscard]] auto exit(std::string_view e) -> result; +[[nodiscard]] auto exit(std::string_view e, std::string_view m) -> result; +[[nodiscard]] auto exit(std::string_view e, int err) -> result; +[[nodiscard]] auto exit(std::string_view e, size_t n) -> result; + +[[nodiscard]] auto unexpected(const cbor::buffer & /*b*/) -> result; + +} // namespace thespian diff --git a/include/thespian/metronome.hpp b/include/thespian/metronome.hpp new file mode 100644 index 0000000..8aee37a --- /dev/null +++ b/include/thespian/metronome.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +namespace thespian { + +struct metronome_impl; +using metronome_dtor = void (*)(metronome_impl *); +using metronome_ref = std::unique_ptr; + +struct metronome { + auto start() -> void; + auto stop() -> void; + + metronome_ref ref; +}; + +[[nodiscard]] auto create_metronome(std::chrono::microseconds us) -> metronome; +void start_metronome(metronome_impl *); +void stop_metronome(metronome_impl *); +void destroy_metronome(metronome_impl *); + +} // namespace thespian diff --git a/include/thespian/signal.hpp b/include/thespian/signal.hpp new file mode 100644 index 0000000..2eb30f3 --- /dev/null +++ b/include/thespian/signal.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include +#include +#include + +namespace thespian { + +struct signal_impl; +using signal_dtor = void (*)(signal_impl *); +using signal_ref = std::unique_ptr; + +struct signal { + auto cancel() -> void; + + signal_ref ref; +}; + +[[nodiscard]] auto create_signal(int signum, cbor::buffer m) -> signal; +void cancel_signal(signal_impl *); +void destroy_signal(signal_impl *); + +} // namespace thespian diff --git a/include/thespian/socket.hpp b/include/thespian/socket.hpp new file mode 100644 index 0000000..f1a8ca7 --- /dev/null +++ b/include/thespian/socket.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include + +namespace thespian { + +struct socket_impl; +using socket_dtor = void (*)(socket_impl *); +using socket_ref = std::unique_ptr; + +struct socket { + static auto create(std::string_view tag, int fd) -> socket; + auto write(std::string_view) -> void; + auto write(const std::vector &) -> void; + auto read() -> void; + auto close() -> void; + + //->("socket", tag, "write_complete", int written) + //->("socket", tag, "write_error", int err, string message) + //->("socket", tag, "read_complete", string buf) + //->("socket", tag, "read_error", int err, string message) + //->("socket", tag, "closed") + + socket_ref ref; +}; + +} // namespace thespian diff --git a/include/thespian/tcp.hpp b/include/thespian/tcp.hpp new file mode 100644 index 0000000..a996692 --- /dev/null +++ b/include/thespian/tcp.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include +#include + +using port_t = unsigned short; + +namespace thespian::tcp { + +struct acceptor_impl; +using acceptor_dtor = void (*)(acceptor_impl *); +using acceptor_ref = std::unique_ptr; + +struct acceptor { + static auto create(std::string_view tag) -> acceptor; + auto listen(in6_addr ip, port_t port) -> port_t; + auto close() -> void; + + //->("acceptor", tag, "accept", int fd, in6_addr ip, port_t port) + //->("acceptor", tag, "error", int err, string message) + //->("acceptor", tag, "closed") + + acceptor_ref ref; +}; + +struct connector_impl; +using connector_dtor = void (*)(connector_impl *); +using connector_ref = std::unique_ptr; + +struct connector { + static auto create(std::string_view tag) -> connector; + auto connect(in6_addr ip, port_t port) -> void; + auto connect(in6_addr ip, port_t port, in6_addr lip) -> void; + auto connect(in6_addr ip, port_t port, port_t lport) -> void; + auto connect(in6_addr ip, port_t port, in6_addr lip, port_t lport) -> void; + auto cancel() -> void; + + //->("connector", tag, "connected", int fd) + //->("connector", tag, "error", int err, string message) + //->("connector", tag, "cancelled") + + connector_ref ref; +}; + +} // namespace thespian::tcp diff --git a/include/thespian/timeout.hpp b/include/thespian/timeout.hpp new file mode 100644 index 0000000..9622fba --- /dev/null +++ b/include/thespian/timeout.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include +#include +#include + +namespace thespian { + +struct timeout_impl; +using timeout_dtor = void (*)(timeout_impl *); +using timeout_ref = std::unique_ptr; + +struct timeout { + auto cancel() -> void; + + timeout_ref ref; +}; + +[[nodiscard]] auto create_timeout(std::chrono::microseconds us, cbor::buffer m) + -> timeout; + +[[nodiscard]] auto never() -> timeout; +void cancel_timeout(timeout_impl *); +void destroy_timeout(timeout_impl *); + +} // namespace thespian diff --git a/include/thespian/trace.hpp b/include/thespian/trace.hpp new file mode 100644 index 0000000..bc4497e --- /dev/null +++ b/include/thespian/trace.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include "thespian/handle.hpp" +#include + +#include +#include +#include +#include + +namespace thespian { + +using channel_set = int; +enum class channel { + send = 1, + receive = 2, + lifetime = 4, + link = 8, + execute = 16, + udp = 32, + tcp = 64, + timer = 128, + metronome = 256, + endpoint = 512, + signal = 1024, + all = std::numeric_limits::max(), +}; + +using trace_handler = std::function; + +auto on_trace(trace_handler) -> trace_handler; +auto trace_to_json_file(const std::string &file_name) -> void; +auto trace_to_cbor_file(const std::string &file_name) -> void; +auto trace_to_mermaid_file(const std::string &file_name) -> void; +auto trace_to_trace(int default_channel) -> void; + +} // namespace thespian diff --git a/include/thespian/udp.hpp b/include/thespian/udp.hpp new file mode 100644 index 0000000..f8d73d7 --- /dev/null +++ b/include/thespian/udp.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include +#include +#include + +using port_t = unsigned short; + +namespace thespian { + +struct udp_impl; +using udp_dtor = void (*)(udp_impl *); +using udp_ref = std::unique_ptr; + +struct udp { + static auto create(std::string tag) -> udp; + + auto open(const in6_addr &, port_t port) -> port_t; + [[nodiscard]] auto sendto(std::string_view, in6_addr ip, port_t port) + -> size_t; + auto close() -> void; + + //->("udp", tag, "open_error", int err, string message); + //->("udp", tag, "read_error", int err, string message); + //->("udp", tag, "receive", string data, in6_addr remote_ip, int port); + //->("udp", tag, "closed"); + + udp_ref ref; +}; + +} // namespace thespian diff --git a/include/thespian/unx.hpp b/include/thespian/unx.hpp new file mode 100644 index 0000000..949746c --- /dev/null +++ b/include/thespian/unx.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include + +namespace thespian::unx { + +enum class mode { file, abstract }; + +struct acceptor_impl; +using acceptor_dtor = void (*)(acceptor_impl *); +using acceptor_ref = std::unique_ptr; + +struct acceptor { + static auto create(std::string_view tag) -> acceptor; + auto listen(std::string_view, mode) -> void; + auto close() -> void; + + //->("acceptor", tag, "accept", int fd) + //->("acceptor", tag, "error", int err, string message) + //->("acceptor",tag, "closed") + + acceptor_ref ref; +}; + +struct connector_impl; +using connector_dtor = void (*)(connector_impl *); +using connector_ref = std::unique_ptr; + +struct connector { + static auto create(std::string_view tag) -> connector; + auto connect(std::string_view, mode) -> void; + auto cancel() -> void; + + //->("connector", tag, "connected", int fd) + //->("connector", tag, "error", int err, string message) + //->("connector", tag, "cancelled") + + connector_ref ref; +}; + +} // namespace thespian::unx diff --git a/src/backtrace.cpp b/src/backtrace.cpp new file mode 100644 index 0000000..0da84a6 --- /dev/null +++ b/src/backtrace.cpp @@ -0,0 +1,102 @@ +#include +#include +#include +#include +#include +#include +#include + +static void msg(const char *msg, const char *arg) { + if (write(STDERR_FILENO, msg, strlen(msg)) != 0) { + } + if (write(STDERR_FILENO, arg, strlen(arg)) != 0) { + } + if (write(STDERR_FILENO, "\n", 1) != 0) { + } +} + +static char binpath[512]; // NOLINT +static char pid_s[11]; // NOLINT + +static void get_pid_binpath() { + pid_t pid = getpid(); + sprintf(pid_s, "%d", pid); // NOLINT + size_t ret = readlink("/proc/self/exe", binpath, 511); + if (ret < 0) + return; + binpath[ret] = 0; // NOLINT +} + +static const auto lldb{"/usr/bin/lldb"}; +static const auto default_debugger{"/usr/bin/gdbserver"}; + +static auto get_debugger() -> const char * { + const char *debugger = secure_getenv("JITDEBUG"); + if (not debugger) + return default_debugger; + if (strcmp(debugger, "on") == 0) + return default_debugger; + if (strcmp(debugger, "1") == 0) + return default_debugger; + if (strcmp(debugger, "true") == 0) + return default_debugger; + return debugger; +} +const char *const debugger = get_debugger(); + +void start_debugger(const char * dbg, const char **argv) { +#if defined(PR_SET_PTRACER) + prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0); // NOLINT +#endif + int child_pid = fork(); + if (!child_pid) { + dup2(2, 1); // redirect output to stderr + msg("debugging with ", dbg); + execv(dbg, const_cast(argv)); // NOLINT + _exit(1); + } else { + int stat(0); + waitpid(child_pid, &stat, 0); + } +} + +extern "C" void sighdl_debugger(int no, siginfo_t * /*sigi*/, void * /*uco*/) { + get_pid_binpath(); + const char *argv[] = {// NOLINT + debugger, "--attach", "[::1]:7777", pid_s, nullptr}; + start_debugger(debugger, argv); + (void)raise(no); +} + +extern "C" void sighdl_backtrace(int no, siginfo_t * /*sigi*/, void * /*uco*/) { + get_pid_binpath(); + const char *argv[] = {// NOLINT + lldb, "--batch", "-p", pid_s, + "--one-line", "bt", binpath, nullptr}; + start_debugger(lldb, argv); + (void)raise(no); +} + +static void install_crash_handler(void (*hdlr)(int, siginfo_t *, void *)) { + struct sigaction action {}; + sigemptyset(&action.sa_mask); + action.sa_flags = SA_SIGINFO | SA_RESETHAND; +#ifdef SA_FULLDUMP + action.sa_flags |= SA_FULLDUMP; +#endif + action.sa_sigaction = hdlr; + sigaction(SIGBUS, &action, nullptr); + sigaction(SIGSEGV, &action, nullptr); + sigaction(SIGABRT, &action, nullptr); + sigaction(SIGTRAP, &action, nullptr); + sigaction(SIGFPE, &action, nullptr); +} + +extern "C" void install_debugger() { install_crash_handler(sighdl_debugger); } +extern "C" void install_backtrace() { install_crash_handler(sighdl_backtrace); } +extern "C" void install_jitdebugger() { + if (secure_getenv("JITDEBUG")) + install_debugger(); + else + install_backtrace(); +} \ No newline at end of file diff --git a/src/c/context.cpp b/src/c/context.cpp new file mode 100644 index 0000000..82760b4 --- /dev/null +++ b/src/c/context.cpp @@ -0,0 +1,66 @@ +#include "thespian/env.hpp" +#include <__type_traits/is_swappable.h> +#include +#include +#include +#include + +using std::string_view; +using thespian::context; + +thread_local const char *last_error{}; // NOLINT + +extern "C" { + +auto thespian_get_last_error() -> const char * { return last_error; } +void thespian_set_last_error(const char *msg) { last_error = msg; } + +thespian_context +thespian_context_create(thespian_context_destroy *d) { // NOLINT + return reinterpret_cast( // NOLINT + context::create(reinterpret_cast(d))); // NOLINT +} + +void thespian_context_run(thespian_context ctx) { + reinterpret_cast(ctx)->run(); // NOLINT +} + +void thespian_context_on_last_exit(thespian_context ctx, + thespian_last_exit_handler h) { + reinterpret_cast(ctx)->on_last_exit(h); // NOLINT +} + +auto thespian_context_spawn_link(thespian_context ctx_, thespian_behaviour b, + thespian_behaviour_state s, + thespian_exit_handler eh, + thespian_exit_handler_state es, + const char *name, thespian_env env, + thespian_handle *handle) -> int { + auto *ctx = reinterpret_cast(ctx_); // NOLINT + thespian::env_t empty_env_{}; + thespian::env_t &env_ = + env ? *reinterpret_cast(env) : empty_env_; // NOLINT + + auto ret = ctx->spawn_link( + [b, s]() -> thespian::result { + auto *ret = b(s); + if (ret) { + auto err = cbor::buffer(); + const uint8_t *data = ret->base; // NOLINT + std::copy(data, data + ret->len, back_inserter(err)); // NOLINT + return thespian::to_error(err); + } + return thespian::ok(); + }, + [eh, es](auto e) { + if (eh) + eh(es, e.data(), e.size()); + }, + string_view(name), std::move(env_)); + if (!ret) + return -1; + *handle = reinterpret_cast( // NOLINT + new thespian::handle{ret.value()}); + return 0; +} +} diff --git a/src/c/env.cpp b/src/c/env.cpp new file mode 100644 index 0000000..a34013e --- /dev/null +++ b/src/c/env.cpp @@ -0,0 +1,92 @@ +#include +#include +#include +#include +#include + +#include + +namespace { + +auto to_env(thespian_env e) -> thespian::env_t & { + assert(e); + return *reinterpret_cast(e); // NOLINT +} + +auto destroy(thespian_env e) -> void { + delete reinterpret_cast(e); // NOLINT +} + +} // namespace + +extern "C" { + +void thespian_env_enable_all_channels(thespian_env e) { + to_env(e).enable_all_channels(); +} + +void thespian_env_disable_all_channels(thespian_env e) { + to_env(e).disable_all_channels(); +} +void thespian_env_enable(thespian_env e, thespian_trace_channel c) { + to_env(e).enable(static_cast(c)); +} +void thespian_env_disable(thespian_env e, thespian_trace_channel c) { + to_env(e).disable(static_cast(c)); +} +auto thespian_env_enabled(thespian_env e, thespian_trace_channel c) -> bool { + return to_env(e).enabled(static_cast(c)); +} +void thespian_env_on_trace(thespian_env e, thespian_trace_handler h) { + to_env(e).on_trace([h](const cbor::buffer &m) { h({m.data(), m.size()}); }); +} +void thespian_env_trace(thespian_env e, cbor_buffer m) { + cbor::buffer buf; + const uint8_t *data = m.base; + std::copy(data, data + m.len, back_inserter(buf)); // NOLINT + to_env(e).trace(buf); +} + +auto thespian_env_is(thespian_env e, c_string_view key) -> bool { + return to_env(e).is({key.base, key.len}); +} +void thespian_env_set(thespian_env e, c_string_view key, bool value) { + to_env(e).is({key.base, key.len}) = value; +} +auto thespian_env_num(thespian_env e, c_string_view key) -> int64_t { + return to_env(e).num({key.base, key.len}); +} +void thespian_env_num_set(thespian_env e, c_string_view key, int64_t value) { + to_env(e).num({key.base, key.len}) = value; +} +auto thespian_env_str(thespian_env e, c_string_view key) -> c_string_view { + auto &ret = to_env(e).str({key.base, key.len}); + return {ret.data(), ret.size()}; +} +void thespian_env_str_set(thespian_env e, c_string_view key, + c_string_view value) { + to_env(e).str({key.base, key.len}) = std::string_view{value.base, value.len}; +} +auto thespian_env_proc(thespian_env e, c_string_view key) -> thespian_handle { + auto &ret = to_env(e).proc({key.base, key.len}); + return reinterpret_cast(&ret); // NOLINT +} +void thespian_env_proc_set(thespian_env e, c_string_view key, + thespian_handle value) { + thespian::handle *h{reinterpret_cast( // NOLINT + value)}; + to_env(e).proc({key.base, key.len}) = *h; +} +auto thespian_env_clone(thespian_env e) -> thespian_env { + return reinterpret_cast( // NOLINT + new thespian::env_t{to_env(e)}); +} +void thespian_env_destroy(thespian_env e) { destroy(e); } + +auto thespian_env_get() -> thespian_env { + return reinterpret_cast(&thespian::env()); // NOLINT +} +auto thespian_env_new() -> thespian_env { + return reinterpret_cast(new thespian::env_t); // NOLINT +} +} diff --git a/src/c/file_descriptor.cpp b/src/c/file_descriptor.cpp new file mode 100644 index 0000000..bc3716a --- /dev/null +++ b/src/c/file_descriptor.cpp @@ -0,0 +1,76 @@ +#include +#include +#include + +using thespian::file_descriptor; +using thespian::file_descriptor_impl; +using thespian::file_descriptor_ref; + +// NOLINTBEGIN(*-reinterpret-cast, *-use-trailing-*) +extern "C" { + +auto thespian_file_descriptor_create(const char *tag, int fd) + -> thespian_file_descriptor_handle * { + try { + auto *p = file_descriptor::create(tag, fd).ref.release(); + return reinterpret_cast(p); + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + return nullptr; + } catch (...) { + thespian_set_last_error("unknown thespian_file_descriptor_create error"); + return nullptr; + } +} + +int thespian_file_descriptor_wait_write(thespian_file_descriptor_handle *p) { + try { + file_descriptor::wait_write(reinterpret_cast(p)); + return 0; + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + return -1; + } catch (...) { + thespian_set_last_error( + "unknown thespian_file_descriptor_wait_write error"); + return -1; + } +} + +int thespian_file_descriptor_wait_read(thespian_file_descriptor_handle *p) { + try { + file_descriptor::wait_read(reinterpret_cast(p)); + return 0; + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + return -1; + } catch (...) { + thespian_set_last_error("unknown thespian_file_descriptor_wait_read error"); + return -1; + } +} + +int thespian_file_descriptor_cancel(thespian_file_descriptor_handle *p) { + try { + file_descriptor::cancel(reinterpret_cast(p)); + return 0; + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + return -1; + } catch (...) { + thespian_set_last_error("unknown thespian_file_descriptor_wait_read error"); + return -1; + } +} + +void thespian_file_descriptor_destroy(thespian_file_descriptor_handle *p) { + try { + file_descriptor::destroy(reinterpret_cast(p)); + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + } catch (...) { + thespian_set_last_error("unknown thespian_file_descriptor_destroy error"); + } +} +} +// NOLINTEND(*-reinterpret-cast, *-use-trailing-*) diff --git a/src/c/handle.cpp b/src/c/handle.cpp new file mode 100644 index 0000000..8b4d233 --- /dev/null +++ b/src/c/handle.cpp @@ -0,0 +1,70 @@ +#include +#include +#include + +namespace { + +static thread_local cbor::buffer + message_buffer; // NOLINT(*-avoid-non-const-global-variables) +static thread_local thespian_error + error_buffer; // NOLINT(*-avoid-non-const-global-variables) + +auto to_thespian_result(thespian::result r) -> thespian_result { + if (r) { + return nullptr; + } + message_buffer = r.error(); + error_buffer.base = message_buffer.data(); + error_buffer.len = message_buffer.size(); + return &error_buffer; +} + +} // namespace + +extern "C" { + +auto thespian_handle_clone(thespian_handle h) -> thespian_handle { + thespian::handle *h_{ + reinterpret_cast( // NOLINT(*-reinterpret-cast) + h)}; + if (!h_) + return nullptr; + return reinterpret_cast( // NOLINT(*-reinterpret-cast) + new thespian::handle{*h_}); +} + +void thespian_handle_destroy(thespian_handle h) { + thespian::handle *h_{ + reinterpret_cast( // NOLINT(*-reinterpret-cast) + h)}; + delete h_; +} + +auto thespian_handle_send_raw(thespian_handle h, cbor_buffer m) + -> thespian_result { + thespian::handle *h_{ + reinterpret_cast( // NOLINT(*-reinterpret-cast) + h)}; + cbor::buffer buf; + const uint8_t *data = m.base; + std::copy(data, data + m.len, // NOLINT(*-pointer-arithmetic) + back_inserter(buf)); + return to_thespian_result(h_->send_raw(move(buf))); +} + +auto thespian_handle_send_exit(thespian_handle h, c_string_view err) + -> thespian_result { + thespian::handle *h_{ + reinterpret_cast( // NOLINT(*-reinterpret-cast) + h)}; + return to_thespian_result(h_->exit(std::string(err.base, err.len))); +} + +auto thespian_handle_is_expired(thespian_handle h) -> bool { + thespian::handle *h_{ + reinterpret_cast( // NOLINT(*-reinterpret-cast) + h)}; + return h_->expired(); +} + +} diff --git a/src/c/instance.cpp b/src/c/instance.cpp new file mode 100644 index 0000000..6c79d60 --- /dev/null +++ b/src/c/instance.cpp @@ -0,0 +1,65 @@ +#include "cbor/c/cbor.h" +#include "thespian/c/handle.h" +#include +#include + +using std::string_view; + +extern "C" { + +void thespian_receive(thespian_receiver r, thespian_behaviour_state s) { + thespian::receive([r, s](auto from, cbor::buffer msg) -> thespian::result { + thespian_handle from_handle = reinterpret_cast( // NOLINT + &from); + auto *ret = r(s, from_handle, {msg.data(), msg.size()}); + if (ret) { + auto err = cbor::buffer(); + const uint8_t *data = ret->base; + std::copy(data, data + ret->len, back_inserter(err)); // NOLINT + return thespian::to_error(err); + } + return thespian::ok(); + }); +} + +auto thespian_get_trap() -> bool { return thespian::trap(); } +auto thespian_set_trap(bool on) -> bool { return thespian::trap(on); } + +void thespian_link(thespian_handle h) { + thespian::handle *h_{ + reinterpret_cast( // NOLINT(*-reinterpret-cast) + h)}; + thespian::link(*h_); +} + +auto thespian_self() -> thespian_handle { + auto &self = thespian::self_ref(); + return reinterpret_cast(&self); // NOLINT(*-reinterpret-cast) +} + +auto thespian_spawn_link(thespian_behaviour b, thespian_behaviour_state s, + const char *name, thespian_env env, + thespian_handle *handle) -> int { + thespian::env_t empty_env_{}; + thespian::env_t env_ = + env ? *reinterpret_cast(env) : empty_env_; // NOLINT + + auto ret = spawn_link( + [b, s]() -> thespian::result { + auto *ret = b(s); + if (ret) { + auto err = cbor::buffer(); + const uint8_t *data = ret->base; // NOLINT + std::copy(data, data + ret->len, back_inserter(err)); // NOLINT + return thespian::to_error(err); + } + return thespian::ok(); + }, + string_view(name), std::move(env_)); + if (!ret) + return -1; + *handle = reinterpret_cast( // NOLINT + new thespian::handle{ret.value()}); + return 0; +} +} diff --git a/src/c/metronome.cpp b/src/c/metronome.cpp new file mode 100644 index 0000000..be37566 --- /dev/null +++ b/src/c/metronome.cpp @@ -0,0 +1,76 @@ +#include +#include +#include + +#include + +using std::chrono::microseconds; +using std::chrono::milliseconds; +using thespian::destroy_metronome; +using thespian::metronome_impl; +using thespian::metronome_ref; +using thespian::start_metronome; +using thespian::stop_metronome; + +extern "C" { + +auto thespian_metronome_create_ms(unsigned long ms) + -> thespian_metronome_handle * { + try { + auto *handle = thespian::create_metronome(milliseconds(ms)).ref.release(); + return reinterpret_cast(handle); // NOLINT + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + return nullptr; + } catch (...) { + thespian_set_last_error("unknown thespian_metronome_create_ms error"); + return nullptr; + } +} +auto thespian_metronome_create_us(unsigned long us) + -> thespian_metronome_handle * { + try { + auto *handle = thespian::create_metronome(microseconds(us)).ref.release(); + return reinterpret_cast(handle); // NOLINT + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + return nullptr; + } catch (...) { + thespian_set_last_error("unknown thespian_metronome_create_us error"); + return nullptr; + } +} +auto thespian_metronome_start(thespian_metronome_handle *handle) -> int { + try { + start_metronome(reinterpret_cast(handle)); // NOLINT + return 0; + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + return -1; + } catch (...) { + thespian_set_last_error("unknown thespian_metronome_start error"); + return -1; + } +} +auto thespian_metronome_stop(thespian_metronome_handle *handle) -> int { + try { + stop_metronome(reinterpret_cast(handle)); // NOLINT + return 0; + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + return -1; + } catch (...) { + thespian_set_last_error("unknown thespian_metronome_stop error"); + return -1; + } +} +void thespian_metronome_destroy(thespian_metronome_handle *handle) { + try { + destroy_metronome(reinterpret_cast(handle)); // NOLINT + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + } catch (...) { + thespian_set_last_error("unknown thespian_metronome_destroy error"); + } +} +} diff --git a/src/c/signal.cpp b/src/c/signal.cpp new file mode 100644 index 0000000..04fbb44 --- /dev/null +++ b/src/c/signal.cpp @@ -0,0 +1,50 @@ +#include +#include +#include + +using thespian::cancel_signal; +using thespian::destroy_signal; +using thespian::signal_impl; +using thespian::signal_ref; + +extern "C" { + +auto thespian_signal_create(int signum, cbor_buffer m) + -> thespian_signal_handle * { + try { + cbor::buffer buf; + const uint8_t *data = m.base; + std::copy(data, data + m.len, // NOLINT(*-pointer-arithmetic) + back_inserter(buf)); + auto *handle = thespian::create_signal(signum, move(buf)).ref.release(); + return reinterpret_cast(handle); // NOLINT + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + return nullptr; + } catch (...) { + thespian_set_last_error("unknown thespian_signal_create error"); + return nullptr; + } +} +auto thespian_signal_cancel(thespian_signal_handle *handle) -> int { + try { + cancel_signal(reinterpret_cast(handle)); // NOLINT + return 0; + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + return -1; + } catch (...) { + thespian_set_last_error("unknown thespian_signal_start error"); + return -1; + } +} +void thespian_signal_destroy(thespian_signal_handle *handle) { + try { + destroy_signal(reinterpret_cast(handle)); // NOLINT + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + } catch (...) { + thespian_set_last_error("unknown thespian_signal_destroy error"); + } +} +} diff --git a/src/c/timeout.cpp b/src/c/timeout.cpp new file mode 100644 index 0000000..a518deb --- /dev/null +++ b/src/c/timeout.cpp @@ -0,0 +1,73 @@ +#include +#include +#include + +#include + +using std::chrono::microseconds; +using std::chrono::milliseconds; +using thespian::cancel_timeout; +using thespian::destroy_timeout; +using thespian::timeout_impl; +using thespian::timeout_ref; + +extern "C" { + +auto thespian_timeout_create_ms(unsigned long ms, cbor_buffer m) + -> thespian_timeout_handle * { + try { + cbor::buffer buf; + const uint8_t *data = m.base; + std::copy(data, data + m.len, // NOLINT(*-pointer-arithmetic) + back_inserter(buf)); + auto *handle = + thespian::create_timeout(milliseconds(ms), move(buf)).ref.release(); + return reinterpret_cast(handle); // NOLINT + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + return nullptr; + } catch (...) { + thespian_set_last_error("unknown thespian_timeout_create_ms error"); + return nullptr; + } +} +auto thespian_timeout_create_us(unsigned long us, cbor_buffer m) + -> thespian_timeout_handle * { + try { + cbor::buffer buf; + const uint8_t *data = m.base; + std::copy(data, data + m.len, // NOLINT(*-pointer-arithmetic) + back_inserter(buf)); + auto *handle = + thespian::create_timeout(microseconds(us), move(buf)).ref.release(); + return reinterpret_cast(handle); // NOLINT + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + return nullptr; + } catch (...) { + thespian_set_last_error("unknown thespian_timeout_create_us error"); + return nullptr; + } +} +auto thespian_timeout_cancel(thespian_timeout_handle *handle) -> int { + try { + cancel_timeout(reinterpret_cast(handle)); // NOLINT + return 0; + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + return -1; + } catch (...) { + thespian_set_last_error("unknown thespian_timeout_start error"); + return -1; + } +} +void thespian_timeout_destroy(thespian_timeout_handle *handle) { + try { + destroy_timeout(reinterpret_cast(handle)); // NOLINT + } catch (const std::exception &e) { + thespian_set_last_error(e.what()); + } catch (...) { + thespian_set_last_error("unknown thespian_timeout_destroy error"); + } +} +} diff --git a/src/c/trace.cpp b/src/c/trace.cpp new file mode 100644 index 0000000..f541a7e --- /dev/null +++ b/src/c/trace.cpp @@ -0,0 +1,25 @@ +#include +#include + +extern "C" { + +void thespian_on_trace(thespian_trace_handler h) { + thespian::on_trace([h](const cbor::buffer &m) { h({m.data(), m.size()}); }); +} + +void thespian_trace_to_json_file(const char *file_name) { + thespian::trace_to_json_file(file_name); +} + +void thespian_trace_to_cbor_file(const char *file_name) { + thespian::trace_to_cbor_file(file_name); +} + +void thespian_trace_to_mermaid_file(const char *file_name) { + thespian::trace_to_mermaid_file(file_name); +} + +void thespian_trace_to_trace(thespian_trace_channel default_channel) { + thespian::trace_to_trace(default_channel); +} +} diff --git a/src/cbor.cpp b/src/cbor.cpp new file mode 100644 index 0000000..a5e0167 --- /dev/null +++ b/src/cbor.cpp @@ -0,0 +1,926 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using std::back_inserter; +using std::copy; +using std::domain_error; +using std::hex; +using std::make_tuple; +using std::numeric_limits; +using std::ostream; +using std::setfill; +using std::setw; +using std::string; +using std::string_view; +using std::stringstream; +using std::tuple; + +namespace cbor { + +const buffer buffer::null_value = buffer{0xF6}; + +auto buffer::push_typed_val(int type, uint64_t value) -> void { + type <<= 5; + if (value < 24ULL) { + push_back(type | value); + } else if (value < 256ULL) { + push_back(type | 24); + push_back(value); + } else if (value < 65536ULL) { + push_back(type | 25); + push_back(value >> 8); + push_back(value); + } else if (value < 4294967296ULL) { + push_back(type | 26); + push_back(value >> 24); + push_back(value >> 16); + push_back(value >> 8); + push_back(value); + } else { + push_back(type | 27); + push_back(value >> 56); + push_back(value >> 48); + push_back(value >> 40); + push_back(value >> 32); + push_back(value >> 24); + push_back(value >> 16); + push_back(value >> 8); + push_back(value); + } +} + +auto buffer::push_string(const string &s) -> buffer & { + push_typed_val(3, s.size()); + copy(s.begin(), s.end(), back_inserter(*this)); + return *this; +} + +auto buffer::push_string(const string_view &s) -> buffer & { + push_typed_val(3, s.size()); + copy(s.begin(), s.end(), back_inserter(*this)); + return *this; +} + +using json_iter = string::const_iterator; + +static auto match_wsp_char(json_iter &b, const json_iter &e) -> bool { + if (b == e) + return false; + char c = *b; + switch (c) { + case ' ': + case '\t': + case '\r': + case '\n': + ++b; + return true; + default: + return false; + } +} + +static auto skip_wsp(json_iter &b, const json_iter &e) -> void { + while (match_wsp_char(b, e)) + ; +} + +static auto match_char(int c, json_iter &b, const json_iter &e) -> bool { + if (b == e || *b != c) + return false; + ++b; + return true; +} + +// NOLINTBEGIN(*-pointer-arithmetic) +static auto match_literal(const char *str, json_iter &b, const json_iter &e) + -> bool { + json_iter i = b; + while (*str && match_char(*str, i, e)) + ++str; + if (!*str) { + b = i; + return true; + } + return false; +} + +constexpr auto int_str_max = 3 * sizeof(uint64_t) + 1; + +static auto push_json_number(buffer &buf, json_iter &b, const json_iter &e) + -> bool { + const bool neg = match_char('-', b, e); + std::array s{}; + char *p = s.data(); + char *se = s.data() + int_str_max; + if (b == e) + return false; + char c = *b; + if (c >= '0' && c <= '9') { + ++b; + *p = c; + ++p; + if (p == se) + return false; + } else + return false; + while (true) { + if (b == e) + break; + c = *b; + if (c >= '0' && c <= '9') { + ++b; + *p = c; + ++p; + if (p == se) + return false; + } else { + break; + } + } + *p = 0; + char *ep = s.data(); + int64_t i = strtoll(s.data(), &ep, 10); + if (ep != p) + return false; + if (neg) + i = -i; + buf.push_int(i); + return true; +} +// NOLINTEND(*-pointer-arithmetic) + +static auto push_json_qstring_slow(buffer &buf, json_iter &b, + const json_iter &e) -> bool { + if (!match_char('"', b, e)) + return false; + string s; + while (true) { + if (b == e) + return false; + char c = *b; + ++b; + if (c == '\\') { + if (b == e) + return false; + c = *b; + ++b; + switch (c) { + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case '\\': + c = '\\'; + break; + default: + break; + } + } else if (c == '"') { + buf.push_string(s); + return true; + } + s.push_back(c); + } +} + +static auto scan_json_qstring(json_iter &b, json_iter &e, bool &is_escaped) + -> bool { + if (!match_char('"', b, e)) + return false; + json_iter i = b; + is_escaped = false; + while (true) { + if (i == e) + return false; + char c = *i; + if (c == '\\') { + is_escaped = true; + return true; + } + if (c == '"') { + e = i; + return true; + } + ++i; + } +} + +static auto push_json_qstring(buffer &buf, json_iter &b, const json_iter &e) + -> bool { + json_iter sb = b; + json_iter se = e; + bool is_escaped{false}; + if (!scan_json_qstring(sb, se, is_escaped)) + return false; + if (is_escaped) + return push_json_qstring_slow(buf, b, e); + b = se; + ++b; + buf.push_string(string_view(&*sb, (&*se - &*sb))); + return true; +} + +static auto push_json_keyword(buffer &buf, json_iter &b, const json_iter &e) + -> bool { + if (match_literal("false", b, e)) + buf.push_bool(false); + else if (match_literal("true", b, e)) + buf.push_bool(true); + else if (match_literal("null", b, e)) + buf.push_null(); + else + return false; + return true; +} + +static auto push_json_value(buffer &buf, json_iter &b, const json_iter &e) + -> bool; + +static auto push_json_array(buffer &buf, json_iter &b, const json_iter &e) + -> bool { + if (!match_char('[', b, e)) + return false; + skip_wsp(b, e); + buffer inner; + size_t count{0}; + if (!push_json_value(inner, b, e)) + return false; + ++count; + while (true) { + skip_wsp(b, e); + if (b == e) + return false; + char c = *b; + switch (c) { + case ',': + ++b; + skip_wsp(b, e); + continue; + case ']': + ++b; + inner.to_cbor(buf.array_header(count)); + return true; + default: + if (!push_json_value(inner, b, e)) + return false; + ++count; + } + } +} + +static auto push_json_map(buffer &buf, json_iter &b, const json_iter &e) + -> bool { + if (!match_char('{', b, e)) + return false; + skip_wsp(b, e); + buffer inner; + size_t count{0}; + if (!push_json_value(inner, b, e)) + return false; + ++count; + while (true) { + skip_wsp(b, e); + if (b == e) + return false; + char c = *b; + switch (c) { + case ':': + case ',': + ++b; + skip_wsp(b, e); + continue; + case '}': + ++b; + if (count % 2) + return false; + inner.to_cbor(buf.map_header(count / 2)); + return true; + default: + if (!push_json_value(inner, b, e)) + return false; + ++count; + } + } +} + +static auto push_json_value(buffer &buf, json_iter &b, const json_iter &e) + -> bool { + skip_wsp(b, e); + if (b == e) + return false; + char c = *b; + if ((c >= '0' && c <= '9') || c == '-') { // integer + return push_json_number(buf, b, e); + } + if ((c >= 'A' && c <= 'Z') || (c >= 'z' && c <= 'z')) { // keyword + return push_json_keyword(buf, b, e); + } + switch (c) { + case '"': // string + return push_json_qstring(buf, b, e); + case '[': // array + return push_json_array(buf, b, e); + case '{': // map + return push_json_map(buf, b, e); + default: + break; + } + + return false; +} + +auto buffer::push_json(const string &s) -> void { + json_iter b = s.cbegin(); + if (!push_json_value(*this, b, s.cend())) { + stringstream ss; + ss << "invalid json value at pos " << std::distance(s.cbegin(), b) + << " in: " << s; + throw domain_error{ss.str()}; + } +} + +using iter = buffer::const_iterator; + +static auto decode_int_length_recurse(size_t length, iter &b, const iter &e, + int64_t i) -> int64_t { + if (b == e) + throw domain_error{"cbor message too short"}; + i |= *b; + ++b; + if (length == 1) + return i; + i <<= 8; + return decode_int_length_recurse(length - 1, b, e, i); +}; + +static auto decode_int_length(size_t length, iter &b, const iter &e) + -> int64_t { + return decode_int_length_recurse(length, b, e, 0); +} + +static auto decode_pint(uint8_t type, iter &b, const iter &e) -> uint64_t { + if (type < 24) + return type; + switch (type) { + case 24: // 1 byte + return decode_int_length(1, b, e); + case 25: // 2 byte + return decode_int_length(2, b, e); + case 26: // 4 byte + return decode_int_length(4, b, e); + case 27: // 8 byte + return decode_int_length(8, b, e); + default: + throw domain_error{"cbor invalid integer type"}; + } +} + +static auto decode_nint(uint8_t type, iter &b, const iter &e) -> int64_t { + return -(decode_pint(type, b, e) + 1); // NOLINT(*-narrowing-conversions) +} + +static auto decode_string(uint8_t type, iter &b, const iter &e) -> string_view { + auto len = decode_pint(type, b, e); + const uint8_t *s = &*b; + const auto sz = len; + while (len) { + if (b == e) + throw domain_error{"cbor message too short"}; + ++b; + --len; + } + return { + reinterpret_cast< // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + const char *>(s), + sz}; +} + +static auto decode_bytes(uint8_t type, iter &b, const iter &e) -> string_view { + return decode_string(type, b, e); +} + +static auto decode_type(iter &b, const iter &e) + -> tuple { + if (b == e) + throw domain_error{"cbor message too short"}; + uint8_t type = *b; + ++b; + return make_tuple(uint8_t(type >> 5), uint8_t(type & 31), type); +} + +auto buffer::decode_range_header(iter &b, const iter &e) -> size_t { + const auto [major, minor, type] = decode_type(b, e); + if (type == 0xf6) + return 0; + auto sz = decode_pint(minor, b, e); + switch (major) { + case 4: // array + return sz; + case 5: // map + return sz * 2; + default: + throw domain_error{"cbor unexpected type (expected array or map)"}; + } +} + +auto buffer::decode_array_header(iter &b, const iter &e) -> size_t { + const auto [major, minor, type] = decode_type(b, e); + if (type == 0xf6) + return 0; + if (major != 4) + throw domain_error{"cbor unexpected type (expected array)"}; + return decode_pint(minor, b, e); +} + +auto buffer::decode_map_header(iter &b, const iter &e) -> size_t { + const auto [major, minor, type] = decode_type(b, e); + if (type == 0xf6) + return 0; + if (major != 5) + throw domain_error{"cbor unexpected type (expected map)"}; + return decode_pint(minor, b, e); +} + +static auto skip_string(uint8_t type, iter &b, const iter &e) -> void { + auto len = decode_pint(type, b, e); + while (len) { + if (b == e) + throw domain_error{"cbor message too short"}; + ++b; + --len; + } +} + +static auto skip_bytes(uint8_t type, iter &b, const iter &e) -> void { + return skip_string(type, b, e); +} + +static auto skip_array(uint8_t type, iter &b, const iter &e) -> void; +static auto skip_map(uint8_t type, iter &b, const iter &e) -> void; + +static auto skip_value_type(uint8_t major, uint8_t minor, iter &b, + const iter &e) -> void { + switch (major) { + case 0: // positive integer + decode_pint(minor, b, e); + break; + case 1: // negative integer + decode_nint(minor, b, e); + break; + case 2: // bytes + skip_bytes(minor, b, e); + break; + case 3: // string + skip_string(minor, b, e); + break; + case 4: // array + skip_array(minor, b, e); + break; + case 5: // map + skip_map(minor, b, e); + break; + case 6: // tag + throw domain_error{"cbor unsupported type tag"}; + case 7: // special + break; + default: + throw domain_error{"cbor unsupported type unknown"}; + } +} +static auto skip_value(iter &b, const iter &e) -> void { + const auto [major, minor, type] = decode_type(b, e); + skip_value_type(major, minor, b, e); +} + +static auto skip_array(uint8_t type, iter &b, const iter &e) -> void { + auto len = decode_pint(type, b, e); + while (len) { + skip_value(b, e); + --len; + } +} + +static auto skip_map(uint8_t type, iter &b, const iter &e) -> void { + auto len = decode_pint(type, b, e); + len = len * 2; + while (len) { + skip_value(b, e); + --len; + } +} + +static auto match_type(iter &b, const iter &e, type &v) -> bool { + const auto [major, minor, type] = decode_type(b, e); + skip_value_type(major, minor, b, e); + switch (major) { + case 0: // positive integer + case 1: // negative integer + v = type::number; + break; + case 2: // bytes + v = type::bytes; + break; + case 3: // string + v = type::string; + break; + case 4: // array + v = type::array; + break; + case 5: // map + v = type::map; + break; + case 7: // special + if (type == 0xf6) + v = type::null; + else if (type == 0xf4 || type == 0xf5) + v = type::boolean; + break; + default: + return false; + } + return true; +} + +auto buffer::match_value(iter &b, const iter &e, type t) -> bool { + type v{type::any}; + if (match_type(b, e, v)) { + if (t == type::any) + return true; + return t == v; + } + return false; +} + +static auto match_uint(iter &b, const iter &e, uint64_t &val) -> bool { + const auto [major, minor, type] = decode_type(b, e); + if (major != 0) + return false; + val = decode_pint(minor, b, e); + return true; +} + +static auto match_int(iter &b, const iter &e, int64_t &val) -> bool { + const auto [major, minor, type] = decode_type(b, e); + switch (major) { + case 0: // positive integer + val = decode_pint(minor, b, e); // NOLINT(*-narrowing-conversions) + if (val < 0) + return false; + break; + case 1: // negative integer + val = decode_nint(minor, b, e); + break; + default: + return false; + } + return true; +} + +auto buffer::match_value(iter &b, const iter &e, int64_t lit) -> bool { + int64_t val{0}; + if (match_int(b, e, val)) + return val == lit; + return false; +} + +static auto match_bool(iter &b, const iter &e, bool &v) -> bool { + const auto [major, minor, type] = decode_type(b, e); + if (major == 7) { // special + if (type == 0xf4) { + v = false; + return true; + } + if (type == 0xf5) { + v = true; + return true; + } + } + return false; +} + +auto buffer::match_value(iter &b, const iter &e, bool lit) -> bool { + bool val{}; + if (match_bool(b, e, val)) + return val == lit; + return false; +} + +static auto match_string(iter &b, const iter &e, string_view &val) -> bool { + const auto [major, minor, type] = decode_type(b, e); + switch (major) { + case 2: // bytes + val = decode_bytes(minor, b, e); + break; + case 3: // string + val = decode_string(minor, b, e); + break; + default: + return false; + } + return true; +} + +auto buffer::match_value(iter &b, const iter &e, const string_view lit) + -> bool { + string_view val; + if (match_string(b, e, val)) + return val == lit; + return false; +} + +auto buffer::match_value(iter &b, const iter &e, const string &lit) -> bool { + string_view val; + if (match_string(b, e, val)) + return val == lit; + return false; +} + +auto extract(type &t) -> buffer::extractor { + return [&t](iter &b, const iter &e) { return match_type(b, e, t); }; +} + +template static auto extract_int(T &i) -> buffer::extractor { + return [&i](iter &b, const iter &e) { + int64_t i_{}; + if (match_int(b, e, i_)) { + if (i_ < int64_t(numeric_limits::min()) or + i_ > int64_t(numeric_limits::max())) + return false; + i = i_; + return true; + } + return false; + }; +} +auto extract(int64_t &i) -> buffer::extractor { return extract_int(i); } +auto extract(int32_t &i) -> buffer::extractor { return extract_int(i); } +auto extract(int16_t &i) -> buffer::extractor { return extract_int(i); } +auto extract(int8_t &i) -> buffer::extractor { return extract_int(i); } + +auto extract(uint64_t &i) -> buffer::extractor { + return [&i](iter &b, const iter &e) { return match_uint(b, e, i); }; +} +template static auto extract_uint(T &i) -> buffer::extractor { + return [&i](iter &b, const iter &e) { + uint64_t i_{}; + if (match_uint(b, e, i_)) { + if (i_ > uint64_t(numeric_limits::max())) + return false; + i = i_; + return true; + } + return false; + }; +} +auto extract(unsigned long long &i) -> buffer::extractor { + return extract_uint(i); +} +auto extract(uint32_t &i) -> buffer::extractor { return extract_uint(i); } +auto extract(uint16_t &i) -> buffer::extractor { return extract_uint(i); } +auto extract(uint8_t &i) -> buffer::extractor { return extract_uint(i); } + +auto extract(bool &val) -> buffer::extractor { + return [&val](iter &b, const iter &e) { return match_bool(b, e, val); }; +} + +auto extract(std::string &s) -> buffer::extractor { + return [&s](iter &b, const iter &e) { + string_view val; + if (match_string(b, e, val)) { + s.assign(val.data(), val.size()); + return true; + } + return false; + }; +} + +auto extract(string_view &s) -> buffer::extractor { + return [&s](iter &b, const iter &e) { return match_string(b, e, s); }; +} + +auto extract(buffer::range &r) -> buffer::extractor { + return [&r](iter &b, const iter &e) { + auto r_b = b; + type v{type::any}; + if (match_type(b, e, v) && + (v == type::null || v == type::array || v == type::map)) { + r.b_ = r_b; + r.e_ = b; + return true; + } + return false; + }; +} + +auto buffer::match_value(iter &b, const iter &e, const extractor &ex) -> bool { + return ex(b, e); +} + +static auto tohex(ostream &os, uint8_t v) -> ostream & { + return os << hex << setfill('0') << setw(2) << static_cast(v); +} + +static auto to_json(ostream &os, string_view s) -> ostream & { + os << '"'; + for (auto c : s) { + switch (c) { + case '\b': + os << "\\b"; + break; + case '\f': + os << "\\f"; + break; + case '\n': + os << "\\n"; + break; + case '\r': + os << "\\r"; + break; + case '\t': + os << "\\t"; + break; + case '\\': + os << "\\\\"; + break; + case '"': + os << "\\\""; + break; + default: + if (c >= 0 && c <= 0x1f) { + os << "\\u00"; + tohex(os, c); + } else { + os << c; + } + } + } + os << '"'; + return os; +} + +static auto to_json_stream(ostream &ss, iter &b, const iter &e) -> void { + const auto [major, minor, type] = decode_type(b, e); + switch (major) { + case 0: // positive integer + { + const auto i = decode_pint(minor, b, e); + ss << i; + } break; + case 1: // negative integer + { + const auto i = decode_nint(minor, b, e); + ss << i; + } break; + case 2: // bytes + { + const string_view v = decode_bytes(minor, b, e); + to_json(ss, v); + } break; + case 3: // string + { + const string_view v = decode_string(minor, b, e); + to_json(ss, v); + } break; + case 4: // array + { + auto i = decode_pint(minor, b, e); + ss << '['; + while (i) { + to_json_stream(ss, b, e); + --i; + if (i) + ss << ','; + } + ss << ']'; + } break; + case 5: // map + { + auto i = decode_pint(minor, b, e); + ss << '{'; + while (i) { + to_json_stream(ss, b, e); + ss << ':'; + to_json_stream(ss, b, e); + --i; + if (i) + ss << ','; + } + ss << '}'; + } break; + case 6: // tag + throw domain_error{"cbor unsupported type tag"}; + case 7: // special + { + if (type == 0xf4) + ss << "false"; + else if (type == 0xf5) + ss << "true"; + else if (type == 0xf6) + ss << "null"; + } break; + default: + throw domain_error{"cbor unsupported type unknown"}; + } +} + +static auto to_json_(const iter &b_, const iter &e) -> string { + stringstream ss; + iter b = b_; + try { + to_json_stream(ss, b, e); + } catch (const domain_error &e) { + throw domain_error{string("cbor to json failed: ") + e.what() + + "\nafter:\n" + ss.str()}; + } + return ss.str(); +} + +auto buffer::to_json() const -> string { + return to_json_(raw_cbegin(), raw_cend()); +} +auto buffer::range::to_json() const -> string { + return to_json_(raw_cbegin(), raw_cend()); +} +auto buffer::to_json(ostream &os) const -> void { + auto b = raw_cbegin(); + return to_json_stream(os, b, raw_cend()); +} +auto buffer::range::to_json(ostream &os) const -> void { + auto b = raw_cbegin(); + return to_json_stream(os, b, raw_cend()); +} + +extern "C" void cbor_to_json(cbor_buffer buf, cbor_to_json_callback cb) { + auto cbor = cbor::buffer(); + const uint8_t *data = buf.base; + std::copy(data, data + buf.len, back_inserter(cbor)); // NOLINT(*-pointer-arithmetic) + auto json = cbor.to_json(); + cb({json.data(), json.size()}); +} + +auto buffer::hexdump() const -> string { + stringstream ss; + ss << size() << ':'; + for (auto c : static_cast(*this)) { + ss << ' '; + tohex(ss, c); + } + return ss.str(); +} + +auto buffer::value_accessor::type_() const -> type { + type t{}; + iter b_ = b; + if (not match_value(b_, e, extract(t))) + return type::unknown; + return t; +} + +template static auto get(iter b, const iter &e) -> T { + T val; + extract(val)(b, e); + return val; +} +buffer::value_accessor::operator type() const { return get(b, e); } +buffer::value_accessor::operator int64_t() const { return get(b, e); } +buffer::value_accessor::operator uint64_t() const { return get(b, e); } +buffer::value_accessor::operator int32_t() const { + return get(b, e); // NOLINT(*-narrowing-conversions) +} +buffer::value_accessor::operator uint32_t() const { return get(b, e); } +buffer::value_accessor::operator int16_t() const { + return get(b, e); // NOLINT(*-narrowing-conversions) +} +buffer::value_accessor::operator uint16_t() const { return get(b, e); } +buffer::value_accessor::operator int8_t() const { + return get(b, e); // NOLINT(*-narrowing-conversions) +} +buffer::value_accessor::operator uint8_t() const { return get(b, e); } +buffer::value_accessor::operator bool() const { return get(b, e); } +buffer::value_accessor::operator string_view() const { + return get(b, e); +} +buffer::value_accessor::operator buffer::range() const { + return get(b, e); +} + +} // namespace cbor diff --git a/src/cbor.zig b/src/cbor.zig new file mode 100644 index 0000000..8008533 --- /dev/null +++ b/src/cbor.zig @@ -0,0 +1,914 @@ +const std = @import("std"); + +const eql = std.mem.eql; +const bufPrint = std.fmt.bufPrint; +const fixedBufferStream = std.io.fixedBufferStream; +const maxInt = std.math.maxInt; +const minInt = std.math.minInt; +const json = std.json; +const fba = std.heap.FixedBufferAllocator; + +pub const CborError = error{ + CborIntegerTooLarge, + CborIntegerTooSmall, + CborInvalidType, + CborTooShort, + OutOfMemory, +}; + +pub const CborJsonError = error{ + BufferUnderrun, + CborIntegerTooLarge, + CborIntegerTooSmall, + CborInvalidType, + CborTooShort, + CborUnsupportedType, + NoSpaceLeft, + OutOfMemory, + Overflow, + SyntaxError, + UnexpectedEndOfInput, +}; + +const cbor_magic_null: u8 = 0xf6; +const cbor_magic_true: u8 = 0xf5; +const cbor_magic_false: u8 = 0xf4; + +const cbor_magic_type_array: u8 = 4; +const cbor_magic_type_map: u8 = 5; + +const value_type = enum(u8) { + number, + bytes, + string, + array, + map, + tag, + boolean, + null, + any, + more, + unknown, +}; +pub const number = value_type.number; +pub const bytes = value_type.bytes; +pub const string = value_type.string; +pub const array = value_type.array; +pub const map = value_type.map; +pub const tag = value_type.tag; +pub const boolean = value_type.boolean; +pub const null_ = value_type.null; +pub const any = value_type.any; +pub const more = value_type.more; + +const null_value_buf = [_]u8{0xF6}; +pub const null_value: []const u8 = &null_value_buf; + +pub fn isNull(val: []const u8) bool { + return eql(u8, val, null_value); +} + +fn isAny(value: anytype) bool { + return if (comptime @TypeOf(value) == value_type) value == value_type.any else false; +} + +fn isMore(value: anytype) bool { + return if (comptime @TypeOf(value) == value_type) value == value_type.more else false; +} + +fn write(writer: anytype, value: u8) @TypeOf(writer).Error!void { + _ = try writer.write(&[_]u8{value}); +} + +fn writeTypedVal(writer: anytype, type_: u8, value: u64) @TypeOf(writer).Error!void { + const t: u8 = type_ << 5; + if (value < 24) { + try write(writer, t | @as(u8, @truncate(value))); + } else if (value < 256) { + try write(writer, t | 24); + try write(writer, @as(u8, @truncate(value))); + } else if (value < 65536) { + try write(writer, t | 25); + try write(writer, @as(u8, @truncate(value >> 8))); + try write(writer, @as(u8, @truncate(value))); + } else if (value < 4294967296) { + try write(writer, t | 26); + try write(writer, @as(u8, @truncate(value >> 24))); + try write(writer, @as(u8, @truncate(value >> 16))); + try write(writer, @as(u8, @truncate(value >> 8))); + try write(writer, @as(u8, @truncate(value))); + } else { + try write(writer, t | 27); + try write(writer, @as(u8, @truncate(value >> 56))); + try write(writer, @as(u8, @truncate(value >> 48))); + try write(writer, @as(u8, @truncate(value >> 40))); + try write(writer, @as(u8, @truncate(value >> 32))); + try write(writer, @as(u8, @truncate(value >> 24))); + try write(writer, @as(u8, @truncate(value >> 16))); + try write(writer, @as(u8, @truncate(value >> 8))); + try write(writer, @as(u8, @truncate(value))); + } +} + +pub fn writeArrayHeader(writer: anytype, sz: usize) @TypeOf(writer).Error!void { + return writeTypedVal(writer, cbor_magic_type_array, sz); +} + +pub fn writeMapHeader(writer: anytype, sz: usize) @TypeOf(writer).Error!void { + return writeTypedVal(writer, cbor_magic_type_map, sz); +} + +pub fn writeArray(writer: anytype, args: anytype) @TypeOf(writer).Error!void { + const args_type_info = @typeInfo(@TypeOf(args)); + if (args_type_info != .Struct) @compileError("expected tuple or struct argument"); + const fields_info = args_type_info.Struct.fields; + try writeArrayHeader(writer, fields_info.len); + inline for (fields_info) |field_info| + try writeValue(writer, @field(args, field_info.name)); +} + +fn writeI64(writer: anytype, value: i64) @TypeOf(writer).Error!void { + return if (value < 0) + writeTypedVal(writer, 1, @as(u64, @bitCast(-(value + 1)))) + else + writeTypedVal(writer, 0, @as(u64, @bitCast(value))); +} + +fn writeU64(writer: anytype, value: u64) @TypeOf(writer).Error!void { + return writeTypedVal(writer, 0, value); +} + +fn writeString(writer: anytype, s: []const u8) @TypeOf(writer).Error!void { + try writeTypedVal(writer, 3, s.len); + _ = try writer.write(s); +} + +fn writeBool(writer: anytype, value: bool) @TypeOf(writer).Error!void { + return write(writer, if (value) cbor_magic_true else cbor_magic_false); +} + +fn writeNull(writer: anytype) @TypeOf(writer).Error!void { + return write(writer, cbor_magic_null); +} + +fn writeErrorset(writer: anytype, err: anyerror) @TypeOf(writer).Error!void { + var buf: [256]u8 = undefined; + const errmsg = try bufPrint(&buf, "error.{s}", .{@errorName(err)}); + return writeString(writer, errmsg); +} + +pub fn writeValue(writer: anytype, value: anytype) @TypeOf(writer).Error!void { + const T = @TypeOf(value); + switch (@typeInfo(T)) { + .Int, .ComptimeInt => return if (T == u64) writeU64(writer, value) else writeI64(writer, @intCast(value)), + .Bool => return writeBool(writer, value), + .Optional => return if (value) |v| writeValue(writer, v) else writeNull(writer), + .ErrorUnion => return if (value) |v| writeValue(writer, v) else |err| writeValue(writer, err), + .ErrorSet => return writeErrorset(writer, value), + .Union => |info| { + if (info.tag_type) |TagType| { + comptime var v = void; + inline for (info.fields) |u_field| { + if (value == @field(TagType, u_field.name)) + v = @field(value, u_field.name); + } + try writeArray(writer, .{ + @typeName(T), + @tagName(@as(TagType, value)), + v, + }); + } else { + try writeArray(writer, .{@typeName(T)}); + } + }, + .Struct => |info| { + if (info.is_tuple) { + if (info.fields.len == 0) return writeNull(writer); + try writeArrayHeader(writer, info.fields.len); + inline for (info.fields) |f| + try writeValue(writer, @field(value, f.name)); + } else { + if (info.fields.len == 0) return writeNull(writer); + try writeMapHeader(writer, info.fields.len); + inline for (info.fields) |f| { + try writeString(writer, f.name); + try writeValue(writer, @field(value, f.name)); + } + } + }, + .Pointer => |ptr_info| switch (ptr_info.size) { + .One => return writeValue(writer, value.*), + .Many, .C => @compileError("cannot write type '" ++ @typeName(T) ++ "' to cbor stream"), + .Slice => { + if (ptr_info.child == u8) return writeString(writer, value); + if (value.len == 0) return writeNull(writer); + try writeArrayHeader(writer, value.len); + for (value) |elem| + try writeValue(writer, elem); + }, + }, + .Array => |info| { + if (info.child == u8) return writeString(writer, &value); + if (value.len == 0) return writeNull(writer); + try writeArrayHeader(writer, value.len); + for (value) |elem| + try writeValue(writer, elem); + }, + .Vector => |info| { + try writeArrayHeader(writer, info.len); + var i: usize = 0; + while (i < info.len) : (i += 1) { + try writeValue(writer, value[i]); + } + }, + .Null => try writeNull(writer), + else => @compileError("cannot write type '" ++ @typeName(T) ++ "' to cbor stream"), + } +} + +pub fn fmt(buf: []u8, value: anytype) []const u8 { + var stream = fixedBufferStream(buf); + writeValue(stream.writer(), value) catch unreachable; + return stream.getWritten(); +} + +const CborType = struct { type: u8, minor: u5, major: u3 }; + +pub fn decodeType(iter: *[]const u8) error{CborTooShort}!CborType { + if (iter.len < 1) + return error.CborTooShort; + const type_: u8 = iter.*[0]; + const bits: packed struct { minor: u5, major: u3 } = @bitCast(type_); + iter.* = iter.*[1..]; + return .{ .type = type_, .minor = bits.minor, .major = bits.major }; +} + +fn decodeUIntLengthRecurse(iter: *[]const u8, length: usize, acc: u64) !u64 { + if (iter.len < 1) + return error.CborTooShort; + const v: u8 = iter.*[0]; + iter.* = iter.*[1..]; + var i = acc | v; + if (length == 1) + return i; + i <<= 8; + // return @call(.always_tail, decodeUIntLengthRecurse, .{ iter, length - 1, i }); FIXME: @call(.always_tail) seems broken as of 0.11.0-dev.2964+e9cbdb2cf + return decodeUIntLengthRecurse(iter, length - 1, i); +} + +fn decodeUIntLength(iter: *[]const u8, length: usize) !u64 { + return decodeUIntLengthRecurse(iter, length, 0); +} + +fn decodePInt(iter: *[]const u8, minor: u5) !u64 { + if (minor < 24) return minor; + return switch (minor) { + 24 => decodeUIntLength(iter, 1), // 1 byte + 25 => decodeUIntLength(iter, 2), // 2 byte + 26 => decodeUIntLength(iter, 4), // 4 byte + 27 => decodeUIntLength(iter, 8), // 8 byte + else => error.CborInvalidType, + }; +} + +fn decodeNInt(iter: *[]const u8, minor: u5) CborError!i64 { + return -@as(i64, @intCast(try decodePInt(iter, minor) + 1)); +} + +pub fn decodeMapHeader(iter: *[]const u8) CborError!usize { + const t = try decodeType(iter); + if (t.type == cbor_magic_null) + return 0; + if (t.major != 5) + return error.CborInvalidType; + return decodePInt(iter, t.minor); +} + +pub fn decodeArrayHeader(iter: *[]const u8) CborError!usize { + const t = try decodeType(iter); + if (t.type == cbor_magic_null) + return 0; + if (t.major != 4) + return error.CborInvalidType; + return decodePInt(iter, t.minor); +} + +fn decodeString(iter_: *[]const u8, minor: u5) CborError![]const u8 { + var iter = iter_.*; + const len = try decodePInt(&iter, minor); + if (iter.len < len) + return error.CborTooShort; + const s = iter[0..len]; + iter = iter[len..]; + iter_.* = iter; + return s; +} + +fn decodeBytes(iter: *[]const u8, minor: u5) CborError![]const u8 { + return decodeString(iter, minor); +} + +fn decodeJsonArray(iter_: *[]const u8, minor: u5, arr: *json.Array) CborError!bool { + var iter = iter_.*; + var n = try decodePInt(&iter, minor); + while (n > 0) { + const value = try arr.addOne(); + if (!try matchJsonValue(&iter, value, arr.allocator)) + return false; + n -= 1; + } + iter_.* = iter; + return true; +} + +fn decodeJsonObject(iter_: *[]const u8, minor: u5, obj: *json.ObjectMap) CborError!bool { + var iter = iter_.*; + var n = try decodePInt(&iter, minor); + while (n > 0) { + var key: []u8 = undefined; + var value: json.Value = .null; + + if (!try matchString(&iter, &key)) + return false; + if (!try matchJsonValue(&iter, &value, obj.allocator)) + return false; + + _ = try obj.getOrPutValue(key, value); + n -= 1; + } + iter_.* = iter; + return true; +} + +pub fn matchInt(comptime T: type, iter_: *[]const u8, val: *T) CborError!bool { + var iter = iter_.*; + const t = try decodeType(&iter); + val.* = switch (t.major) { + 0 => blk: { // positive integer + const v = try decodePInt(&iter, t.minor); + if (v > maxInt(T)) + return error.CborIntegerTooLarge; + break :blk @intCast(v); + }, + 1 => blk: { // negative integer + const v = try decodeNInt(&iter, t.minor); + if (v < minInt(T)) + return error.CborIntegerTooSmall; + break :blk @intCast(v); + }, + + else => return false, + }; + iter_.* = iter; + return true; +} + +pub fn matchIntValue(comptime T: type, iter: *[]const u8, val: T) CborError!bool { + var v: T = 0; + return if (try matchInt(T, iter, &v)) v == val else false; +} + +pub fn matchBool(iter_: *[]const u8, v: *bool) CborError!bool { + var iter = iter_.*; + const t = try decodeType(&iter); + if (t.major == 7) { // special + if (t.type == cbor_magic_false) { + v.* = false; + iter_.* = iter; + return true; + } + if (t.type == cbor_magic_true) { + v.* = true; + iter_.* = iter; + return true; + } + } + return false; +} + +fn matchBoolValue(iter: *[]const u8, val: bool) CborError!bool { + var v: bool = false; + return if (try matchBool(iter, &v)) v == val else false; +} + +fn skipString(iter: *[]const u8, minor: u5) CborError!void { + const len = try decodePInt(iter, minor); + if (iter.len < len) + return error.CborTooShort; + iter.* = iter.*[len..]; +} + +fn skipBytes(iter: *[]const u8, minor: u5) CborError!void { + return skipString(iter, minor); +} + +fn skipArray(iter: *[]const u8, minor: u5) CborError!void { + var len = try decodePInt(iter, minor); + while (len > 0) { + try skipValue(iter); + len -= 1; + } +} + +fn skipMap(iter: *[]const u8, minor: u5) CborError!void { + var len = try decodePInt(iter, minor); + len *= 2; + while (len > 0) { + try skipValue(iter); + len -= 1; + } +} + +pub fn skipValue(iter: *[]const u8) CborError!void { + const t = try decodeType(iter); + try skipValueType(iter, t.major, t.minor); +} + +fn skipValueType(iter: *[]const u8, major: u3, minor: u5) CborError!void { + switch (major) { + 0 => { // positive integer + _ = try decodePInt(iter, minor); + }, + 1 => { // negative integer + _ = try decodeNInt(iter, minor); + }, + 2 => { // bytes + try skipBytes(iter, minor); + }, + 3 => { // string + try skipString(iter, minor); + }, + 4 => { // array + try skipArray(iter, minor); + }, + 5 => { // map + try skipMap(iter, minor); + }, + 6 => { // tag + return error.CborInvalidType; + }, + 7 => { // special + return; + }, + } +} + +fn matchType(iter_: *[]const u8, v: *value_type) CborError!bool { + var iter = iter_.*; + const t = try decodeType(&iter); + try skipValueType(&iter, t.major, t.minor); + switch (t.major) { + 0, 1 => v.* = value_type.number, // positive integer or negative integer + 2 => v.* = value_type.bytes, // bytes + 3 => v.* = value_type.string, // string + 4 => v.* = value_type.array, // array + 5 => v.* = value_type.map, // map + 7 => { // special + if (t.type == cbor_magic_null) { + v.* = value_type.null; + } else { + if (t.type == cbor_magic_false or t.type == cbor_magic_true) { + v.* = value_type.boolean; + } else { + return false; + } + } + }, + else => return false, + } + iter_.* = iter; + return true; +} + +fn matchValueType(iter: *[]const u8, t: value_type) CborError!bool { + var v: value_type = value_type.unknown; + return if (try matchType(iter, &v)) (t == value_type.any or t == v) else false; +} + +pub fn matchString(iter_: *[]const u8, val: *[]const u8) CborError!bool { + var iter = iter_.*; + const t = try decodeType(&iter); + val.* = switch (t.major) { + 2 => try decodeBytes(&iter, t.minor), // bytes + 3 => try decodeString(&iter, t.minor), // string + else => return false, + }; + iter_.* = iter; + return true; +} + +fn matchStringValue(iter: *[]const u8, lit: []const u8) CborError!bool { + var val: []const u8 = undefined; + return if (try matchString(iter, &val)) eql(u8, val, lit) else false; +} + +fn matchError(comptime T: type) noreturn { + @compileError("cannot match type '" ++ @typeName(T) ++ "' to cbor stream"); +} + +pub fn matchValue(iter: *[]const u8, value: anytype) CborError!bool { + if (@TypeOf(value) == value_type) + return matchValueType(iter, value); + const T = comptime @TypeOf(value); + if (comptime isExtractor(T)) + return value.extract(iter); + return switch (comptime @typeInfo(T)) { + .Int => return matchIntValue(T, iter, value), + .ComptimeInt => return matchIntValue(i64, iter, value), + .Bool => matchBoolValue(iter, value), + .Pointer => |info| switch (info.size) { + .One => matchValue(iter, value.*), + .Many, .C => matchError(T), + .Slice => if (info.child == u8) matchStringValue(iter, value) else matchArray(iter, value, info), + }, + .Struct => |info| if (info.is_tuple) + matchArray(iter, value, info) + else + matchError(T), + .Array => |info| if (info.child == u8) matchStringValue(iter, &value) else matchArray(iter, value, info), + else => @compileError("cannot match value type '" ++ @typeName(T) ++ "' to cbor stream"), + }; +} + +fn matchJsonValue(iter_: *[]const u8, v: *json.Value, a: std.mem.Allocator) CborError!bool { + var iter = iter_.*; + const t = try decodeType(&iter); + const ret = switch (t.major) { + 0 => ret: { // positive integer + v.* = json.Value{ .integer = @intCast(try decodePInt(&iter, t.minor)) }; + break :ret true; + }, + 1 => ret: { // negative integer + v.* = json.Value{ .integer = try decodeNInt(&iter, t.minor) }; + break :ret true; + }, + 2 => ret: { // bytes + break :ret false; + }, + 3 => ret: { // string + v.* = json.Value{ .string = try decodeString(&iter, t.minor) }; + break :ret true; + }, + 4 => ret: { // array + v.* = json.Value{ .array = json.Array.init(a) }; + break :ret try decodeJsonArray(&iter, t.minor, &v.array); + }, + 5 => ret: { // map + v.* = json.Value{ .object = json.ObjectMap.init(a) }; + break :ret try decodeJsonObject(&iter, t.minor, &v.object); + }, + 6 => ret: { // tag + break :ret false; + }, + 7 => ret: { // special + switch (t.type) { + cbor_magic_false => { + v.* = json.Value{ .bool = false }; + break :ret true; + }, + cbor_magic_true => { + v.* = json.Value{ .bool = true }; + break :ret true; + }, + cbor_magic_null => { + v.* = json.Value{ .null = {} }; + break :ret true; + }, + else => break :ret false, + } + }, + }; + if (ret) iter_.* = iter; + return ret; +} + +fn matchArrayMore(iter_: *[]const u8, n_: u64) CborError!bool { + var iter = iter_.*; + var n = n_; + while (n > 0) { + if (!try matchValue(&iter, value_type.any)) + return false; + n -= 1; + } + iter_.* = iter; + return true; +} + +fn matchArray(iter_: *[]const u8, arr: anytype, info: anytype) CborError!bool { + var iter = iter_.*; + var n = try decodeArrayHeader(&iter); + inline for (info.fields) |f| { + const value = @field(arr, f.name); + if (isMore(value)) + break; + } else if (info.fields.len != n) + return false; + inline for (info.fields) |f| { + const value = @field(arr, f.name); + if (isMore(value)) + return matchArrayMore(&iter, n); + if (n == 0) return false; + const matched = try matchValue(&iter, @field(arr, f.name)); + if (!matched) return false; + n -= 1; + } + if (n == 0) iter_.* = iter; + return n == 0; +} + +fn matchJsonObject(iter_: *[]const u8, obj: *json.ObjectMap) !bool { + var iter = iter_.*; + const t = try decodeType(&iter); + if (t.type == cbor_magic_null) + return true; + if (t.major != 5) + return error.CborInvalidType; + const ret = try decodeJsonObject(&iter, t.minor, obj); + if (ret) iter_.* = iter; + return ret; +} + +pub fn match(buf: []const u8, pattern: anytype) CborError!bool { + var iter: []const u8 = buf; + return matchValue(&iter, pattern); +} + +fn extractError(comptime T: type) noreturn { + @compileError("cannot extract type '" ++ @typeName(T) ++ "' from cbor stream"); +} + +fn hasExtractorTag(info: anytype) bool { + if (info.is_tuple) return false; + inline for (info.decls) |decl| { + if (comptime eql(u8, decl.name, "EXTRACTOR_TAG")) + return true; + } + return false; +} + +fn isExtractor(comptime T: type) bool { + return comptime switch (@typeInfo(T)) { + .Struct => |info| hasExtractorTag(info), + else => false, + }; +} + +const JsonValueExtractor = struct { + dest: *T, + const Self = @This(); + pub const EXTRACTOR_TAG = struct {}; + const T = json.Value; + + pub fn init(dest: *T) Self { + return .{ .dest = dest }; + } + + pub fn extract(self: Self, iter: *[]const u8) CborError!bool { + var null_heap_: [0]u8 = undefined; + var heap = fba.init(&null_heap_); + return matchJsonValue(iter, self.dest, heap.allocator()); + } +}; + +const JsonObjectExtractor = struct { + dest: *T, + const Self = @This(); + pub const EXTRACTOR_TAG = struct {}; + const T = json.ObjectMap; + + pub fn init(dest: *T) Self { + return .{ .dest = dest }; + } + + pub fn extract(self: Self, iter: *[]const u8) CborError!bool { + return matchJsonObject(iter, self.dest); + } +}; + +fn Extractor(comptime T: type) type { + if (T == json.Value) + return JsonValueExtractor; + if (T == json.ObjectMap) + return JsonObjectExtractor; + return struct { + dest: *T, + const Self = @This(); + pub const EXTRACTOR_TAG = struct {}; + + pub fn init(dest: *T) Self { + return .{ .dest = dest }; + } + + pub fn extract(self: Self, iter: *[]const u8) CborError!bool { + switch (comptime @typeInfo(T)) { + .Int, .ComptimeInt => return matchInt(T, iter, self.dest), + .Bool => return matchBool(iter, self.dest), + .Pointer => |ptr_info| switch (ptr_info.size) { + .Slice => { + if (ptr_info.child == u8) return matchString(iter, self.dest) else extractError(T); + }, + else => extractError(T), + }, + else => extractError(T), + } + } + }; +} + +fn ExtractorType(comptime T: type) type { + const T_type_info = @typeInfo(T); + if (T_type_info != .Pointer) @compileError("extract requires a pointer argument"); + return Extractor(T_type_info.Pointer.child); +} + +pub fn extract(dest: anytype) ExtractorType(@TypeOf(dest)) { + comptime { + if (!isExtractor(ExtractorType(@TypeOf(dest)))) + @compileError("isExtractor self check failed for " ++ @typeName(ExtractorType(@TypeOf(dest)))); + } + return ExtractorType(@TypeOf(dest)).init(dest); +} + +const CborExtractor = struct { + dest: *[]const u8, + const Self = @This(); + pub const EXTRACTOR_TAG = struct {}; + + pub fn init(dest: *[]const u8) Self { + return .{ .dest = dest }; + } + + pub fn extract(self: Self, iter: *[]const u8) CborError!bool { + const b = iter.*; + try skipValue(iter); + self.dest.* = b[0..(b.len - iter.len)]; + return true; + } +}; + +pub fn extract_cbor(dest: *[]const u8) CborExtractor { + return CborExtractor.init(dest); +} + +pub fn JsonStream(comptime T: type) type { + return struct { + const Writer = T.Writer; + const JsonWriter = json.WriteStream(Writer, .{ .checked_to_fixed_depth = 256 }); + + fn jsonWriteArray(w: *JsonWriter, iter: *[]const u8, minor: u5) !void { + var count = try decodePInt(iter, minor); + try w.beginArray(); + while (count > 0) : (count -= 1) { + try jsonWriteValue(w, iter); + } + try w.endArray(); + } + + fn jsonWriteMap(w: *JsonWriter, iter: *[]const u8, minor: u5) !void { + var count = try decodePInt(iter, minor); + try w.beginObject(); + while (count > 0) : (count -= 1) { + const t = try decodeType(iter); + if (t.major != 3) return error.CborInvalidType; + try w.objectField(try decodeString(iter, t.minor)); + try jsonWriteValue(w, iter); + } + try w.endObject(); + } + + pub fn jsonWriteValue(w: *JsonWriter, iter: *[]const u8) (CborJsonError || Writer.Error)!void { + const t = try decodeType(iter); + if (t.type == cbor_magic_false) + return w.write(false); + if (t.type == cbor_magic_true) + return w.write(true); + if (t.type == cbor_magic_null) + return w.write(null); + return switch (t.major) { + 0 => w.write(try decodePInt(iter, t.minor)), // positive integer + 1 => w.write(try decodeNInt(iter, t.minor)), // negative integer + 2 => error.CborUnsupportedType, // bytes + 3 => w.write(try decodeString(iter, t.minor)), // string + 4 => jsonWriteArray(w, iter, t.minor), // array + 5 => jsonWriteMap(w, iter, t.minor), // map + else => error.CborInvalidType, + }; + } + }; +} + +pub fn toJson(cbor_buf: []const u8, json_buf: []u8) CborJsonError![]const u8 { + var fbs = fixedBufferStream(json_buf); + var s = json.writeStream(fbs.writer(), .{}); + var iter: []const u8 = cbor_buf; + try JsonStream(@TypeOf(fbs)).jsonWriteValue(&s, &iter); + return fbs.getWritten(); +} + +pub fn toJsonPretty(cbor_buf: []const u8, json_buf: []u8) CborJsonError![]const u8 { + var fbs = fixedBufferStream(json_buf); + var s = json.writeStream(fbs.writer(), .{ .whitespace = .indent_1 }); + var iter: []const u8 = cbor_buf; + try JsonStream(@TypeOf(fbs)).jsonWriteValue(&s, &iter); + return fbs.getWritten(); +} + +fn writeJsonValue(writer: anytype, value: json.Value) !void { + try switch (value) { + .array => |_| unreachable, + .object => |_| unreachable, + .null => writeNull(writer), + .float => |_| error.CborUnsupportedType, + inline else => |v| writeValue(writer, v), + }; +} + +fn jsonScanUntil(writer: anytype, scanner: *json.Scanner, end_token: anytype) CborJsonError!usize { + var partial = try std.BoundedArray(u8, 4096).init(0); + var count: usize = 0; + + var token = try scanner.next(); + while (token != end_token) : (token = try scanner.next()) { + count += 1; + switch (token) { + .object_begin => try writeJsonObject(writer, scanner), + .array_begin => try writeJsonArray(writer, scanner), + + .true => try writeBool(writer, true), + .false => try writeBool(writer, false), + .null => try writeNull(writer), + + .number => |v| { + try partial.appendSlice(v); + try writeJsonValue(writer, json.Value.parseFromNumberSlice(partial.slice())); + try partial.resize(0); + }, + .partial_number => |v| { + try partial.appendSlice(v); + count -= 1; + }, + + .string => |v| { + try partial.appendSlice(v); + try writeString(writer, partial.slice()); + try partial.resize(0); + }, + .partial_string => |v| { + try partial.appendSlice(v); + count -= 1; + }, + .partial_string_escaped_1 => |v| { + try partial.appendSlice(&v); + count -= 1; + }, + .partial_string_escaped_2 => |v| { + try partial.appendSlice(&v); + count -= 1; + }, + .partial_string_escaped_3 => |v| { + try partial.appendSlice(&v); + count -= 1; + }, + .partial_string_escaped_4 => |v| { + try partial.appendSlice(&v); + count -= 1; + }, + + else => return error.SyntaxError, + } + } + return count; +} + +pub const local_heap_size = 4096 * 16; + +fn writeJsonArray(writer_: anytype, scanner: *json.Scanner) CborJsonError!void { + var buf: [local_heap_size]u8 = undefined; + var stream = fixedBufferStream(&buf); + const writer = stream.writer(); + const count = try jsonScanUntil(writer, scanner, .array_end); + try writeArrayHeader(writer_, count); + try writer_.writeAll(stream.getWritten()); +} + +fn writeJsonObject(writer_: anytype, scanner: *json.Scanner) CborJsonError!void { + var buf: [local_heap_size]u8 = undefined; + var stream = fixedBufferStream(&buf); + const writer = stream.writer(); + const count = try jsonScanUntil(writer, scanner, .object_end); + try writeMapHeader(writer_, count / 2); + try writer_.writeAll(stream.getWritten()); +} + +pub fn fromJson(json_buf: []const u8, cbor_buf: []u8) ![]const u8 { + var local_heap_: [local_heap_size]u8 = undefined; + var heap = fba.init(&local_heap_); + var stream = fixedBufferStream(cbor_buf); + const writer = stream.writer(); + + var scanner = json.Scanner.initCompleteInput(heap.allocator(), json_buf); + defer scanner.deinit(); + + _ = try jsonScanUntil(writer, &scanner, .end_of_document); + return stream.getWritten(); +} diff --git a/src/executor.hpp b/src/executor.hpp new file mode 100644 index 0000000..3237933 --- /dev/null +++ b/src/executor.hpp @@ -0,0 +1,207 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +using port_t = unsigned short; + +namespace thespian::executor { + +struct strand; + +struct context_impl; +using context_ref = std::shared_ptr; + +struct context { + static auto create() -> context; + auto create_strand() -> strand; + void run(); + auto pending_tasks() -> size_t; + auto pending_posts() -> size_t; + context_ref ref; +}; + +struct strand_impl; +using strand_ref = std::shared_ptr; + +struct strand { + void post(std::function); + strand_ref ref; +}; + +struct signal_impl; +using signal_dtor = void (*)(signal_impl *); +using signal_ref = std::unique_ptr; + +struct signal { + using handler = std::function; + + explicit signal(context &); + explicit signal(strand &); + void fires_on(int signum); + void on_fired(handler); + void cancel(); + signal_ref ref; +}; + +struct timer_impl; +using timer_dtor = void (*)(timer_impl *); +using timer_ref = std::unique_ptr; + +struct timer { + using handler = std::function; + + explicit timer(context &); + explicit timer(strand &); + void expires_at(std::chrono::time_point); + void expires_after(std::chrono::microseconds); + void on_expired(handler); + void cancel(); + timer_ref ref; +}; + +auto to_string(const in6_addr &ip) -> std::string; + +constexpr auto receive_buffer_size{4L * 1024L}; + +struct endpoint { + in6_addr ip; + port_t port; +}; + +namespace udp { + +struct socket_impl; +using socket_dtor = void (*)(socket_impl *); +using socket_ref = std::unique_ptr; + +struct socket { + using handler = + std::function; + + explicit socket(strand &); + auto bind(const in6_addr &ip, port_t port) -> std::error_code; + [[nodiscard]] auto send_to(std::string_view data, in6_addr ip, + port_t port) const -> size_t; + void receive(handler); + void close(); + [[nodiscard]] auto local_endpoint() const -> const endpoint &; + [[nodiscard]] auto remote_endpoint() const -> const endpoint &; + std::array receive_buffer{}; + socket_ref ref; +}; + +} // namespace udp + +namespace tcp { + +struct socket_impl; +using socket_dtor = void (*)(socket_impl *); +using socket_ref = std::unique_ptr; + +struct socket { + using connect_handler = std::function; + using read_handler = + std::function; + using write_handler = + std::function; + + explicit socket(strand &); + explicit socket(strand &, int fd); + auto bind(const in6_addr &ip, port_t port) -> std::error_code; + void connect(const in6_addr &ip, port_t port, connect_handler); + void write(const std::vector &data, write_handler); + void read(read_handler); + void close(); + static void close(int fd); + auto release() -> int; + [[nodiscard]] auto local_endpoint() const -> const endpoint &; + std::array read_buffer{}; + socket_ref ref; +}; + +struct acceptor_impl; +using acceptor_dtor = void (*)(acceptor_impl *); +using acceptor_ref = std::unique_ptr; + +struct acceptor { + using handler = std::function; + + explicit acceptor(strand &); + auto bind(const in6_addr &, port_t) -> std::error_code; + auto listen() -> std::error_code; + void accept(handler); + void close(); + [[nodiscard]] auto local_endpoint() const -> endpoint; + acceptor_ref ref; +}; + +} // namespace tcp + +namespace unx { + +struct socket_impl; +using socket_dtor = void (*)(socket_impl *); +using socket_ref = std::unique_ptr; + +struct socket { + using connect_handler = std::function; + using read_handler = + std::function; + using write_handler = + std::function; + + explicit socket(strand &); + explicit socket(strand &, int fd); + auto bind(std::string_view path) -> std::error_code; + void connect(std::string_view path, connect_handler); + void write(const std::vector &data, write_handler); + void read(read_handler); + void close(); + auto release() -> int; + std::array read_buffer{}; + socket_ref ref; +}; + +struct acceptor_impl; +using acceptor_dtor = void (*)(acceptor_impl *); +using acceptor_ref = std::unique_ptr; + +struct acceptor { + using handler = std::function; + + explicit acceptor(strand &); + auto bind(std::string_view path) -> std::error_code; + auto listen() -> std::error_code; + void accept(handler); + void close(); + acceptor_ref ref; +}; + +} // namespace unx + +namespace file_descriptor { + +struct watcher_impl; +using watcher_dtor = void (*)(watcher_impl *); +using watcher_ref = std::unique_ptr; + +struct watcher { + using handler = std::function; + + explicit watcher(strand &, int fd); + void wait_read(handler); + void wait_write(handler); + void cancel(); + watcher_ref ref; +}; + +} // namespace file_descriptor + +} // namespace thespian::executor diff --git a/src/executor_asio.cpp b/src/executor_asio.cpp new file mode 100644 index 0000000..77fbc24 --- /dev/null +++ b/src/executor_asio.cpp @@ -0,0 +1,645 @@ +#include "asio/error_code.hpp" +#include "asio/posix/descriptor_base.hpp" +#include "executor.hpp" +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using asio::bind_executor; +using asio::buffer; +using asio::io_context; +using asio::string_view; +using asio::system_timer; +using asio::thread; +using asio::posix::stream_descriptor; +using std::atomic; +using std::error_code; +using std::function; +using std::make_shared; +using std::make_unique; +using std::max; +using std::memory_order_relaxed; +using std::min; +using std::move; +using std::shared_ptr; +using std::string; +using std::unique_ptr; +using std::vector; +using std::weak_ptr; +using std::chrono::microseconds; +using std::chrono::system_clock; +using std::chrono::time_point; + +namespace thespian::executor { + +const char *MAX_THREAD_STR = getenv("MAX_THREAD"); // NOLINT +const auto MAX_THREAD = + static_cast(atoi(MAX_THREAD_STR ? MAX_THREAD_STR : "64")); // NOLINT + +const auto threads = min(sysconf(_SC_NPROCESSORS_ONLN), MAX_THREAD); + +struct context_impl { + context_impl() : asio{make_unique(threads)} {} + unique_ptr asio; + atomic pending; + atomic posts; +}; + +auto context::create() -> context { + if (::signal(SIGPIPE, SIG_IGN) == SIG_ERR) // NOLINT + abort(); + return {context_ref(new context_impl(), [](context_impl *p) { delete p; })}; +} + +struct strand_impl { + explicit strand_impl(const context_ref &ctx) + : ctx{ctx}, strand_{*ctx->asio} {} + void post(function f) { + ctx->posts.fetch_add(1); + strand_.post([&posts = ctx->posts, f = move(f)]() { + posts.fetch_sub(1); + f(); + }); + } + context_ref ctx; + io_context::strand strand_; +}; + +auto context::create_strand() -> strand { + return {make_shared(ref)}; +} + +void strand::post(function f) { ref->post(move(f)); } + +auto context::run() -> void { + const auto spawn_threads = max(threads - 1, 0L); + vector running; + for (auto i = 0; i < spawn_threads; ++i) { + auto *t = new thread([ctx = ref]() { ctx->asio->run(); }); + running.push_back(t); + } + ref->asio->run(); + for (auto &t : running) { + t->join(); + delete t; + } +} + +auto context::pending_tasks() -> size_t { return ref->pending.load(); } +auto context::pending_posts() -> size_t { return ref->posts.load(); } + +} // namespace thespian::executor + +namespace { + +static auto to_address(const in6_addr &ip) -> const asio::ip::address_v6 { + asio::ip::address_v6::bytes_type bytes; + memcpy(bytes.data(), ip.s6_addr, sizeof(ip.s6_addr)); + return asio::ip::address_v6{bytes}; +} + +static auto to_in6_addr(const asio::ip::address_v6 &address) -> in6_addr { + auto bytes = address.to_bytes(); + in6_addr ip{}; + memcpy(ip.s6_addr, bytes.data(), sizeof(ip.s6_addr)); + return ip; +} + +static auto to_in6_addr(const asio::ip::udp::endpoint &ep) -> in6_addr { + return to_in6_addr(ep.address().to_v6()); +} + +static auto to_in6_addr(const asio::ip::tcp::endpoint &ep) -> in6_addr { + return to_in6_addr(ep.address().to_v6()); +} + +static auto to_endpoint_tcp(const in6_addr &ip, port_t port) + -> const asio::ip::tcp::endpoint { + return {to_address(ip), port}; +} + +static auto to_endpoint_udp(const in6_addr &ip, port_t port) + -> const asio::ip::udp::endpoint { + return {to_address(ip), port}; +} + +} // namespace + +namespace thespian::executor { + +auto to_string(const in6_addr &ip) -> string { + return to_address(ip).to_string(); +} + +struct signal_impl { + explicit signal_impl(const context_ref &ctx) + : ctx{ctx}, signals_{*ctx->asio} {} + explicit signal_impl(strand &strand) + : ctx{strand.ref->ctx}, signals_{*ctx->asio} {} + void fires_on(int signum) { signals_.add(signum); } + void on_fired(signal::handler f) { + ctx->pending.fetch_add(1, memory_order_relaxed); + weak_ptr weak_token{token_}; + signals_.async_wait([c_ = ctx, f = move(f), t = move(weak_token)]( + const error_code &ec, int signum) { + c_->pending.fetch_sub(1, memory_order_relaxed); + if (auto p = t.lock()) { + f(ec, signum); + } + }); + } + void cancel() { signals_.cancel(); } + context_ref ctx; + asio::signal_set signals_; + shared_ptr token_{make_shared(true)}; +}; + +signal::signal(context &ctx) + : ref{signal_ref(new signal_impl(ctx.ref), + [](signal_impl *p) { delete p; })} {} + +signal::signal(strand &ref) + : ref{signal_ref(new signal_impl(ref), [](signal_impl *p) { delete p; })} {} + +void signal::fires_on(int signum) { ref->fires_on(signum); } +void signal::on_fired(handler f) { ref->on_fired(move(f)); } +void signal::cancel() { ref->cancel(); } + +struct timer_impl { + explicit timer_impl(const context_ref &ctx) : ctx{ctx}, timer_{*ctx->asio} {} + explicit timer_impl(strand &strand) + : ctx{strand.ref->ctx}, timer_{*ctx->asio} {} + void expires_at(time_point t) { timer_.expires_at(t); } + void expires_after(microseconds us) { timer_.expires_after(us); } + void on_expired(timer::handler f) { + ctx->pending.fetch_add(1, memory_order_relaxed); + weak_ptr weak_token{token_}; + timer_.async_wait( + [c_ = ctx, f = move(f), t = move(weak_token)](const error_code &ec) { + c_->pending.fetch_sub(1, memory_order_relaxed); + if (auto p = t.lock()) { + f(ec); + } + }); + } + void cancel() { timer_.cancel(); } + context_ref ctx; + system_timer timer_; + bool pending_{}; + shared_ptr token_{make_shared(true)}; +}; + +timer::timer(context &ctx) + : ref{timer_ref(new timer_impl(ctx.ref), [](timer_impl *p) { delete p; })} { +} + +timer::timer(strand &ref) + : ref{timer_ref(new timer_impl(ref), [](timer_impl *p) { delete p; })} {} + +void timer::expires_at(time_point t) { ref->expires_at(t); } +void timer::expires_after(microseconds us) { ref->expires_after(us); } +void timer::on_expired(handler f) { ref->on_expired(move(f)); } +void timer::cancel() { ref->cancel(); } + +namespace udp { + +struct socket_impl { + explicit socket_impl(strand &strand) + : ctx{strand.ref->ctx}, strand_{strand.ref->strand_}, + socket_{*ctx->asio, ::asio::ip::udp::v6()} {} + auto bind(const in6_addr &ip, port_t port) -> error_code { + error_code ec; + socket_.bind(to_endpoint_udp(ip, port), ec); + return ec; + } + [[nodiscard]] auto send_to(string_view data, in6_addr ip, port_t port) + -> size_t { + return socket_.send_to(buffer(data.data(), data.size()), + to_endpoint_udp(ip, port)); + } + void receive(socket &s, socket::handler h) { + ctx->pending.fetch_add(1, memory_order_relaxed); + weak_ptr weak_token{token_}; + socket_.async_receive_from( + buffer(s.receive_buffer), asio_remote_endpoint_, + bind_executor(strand_, + [c = ctx, ep = &remote_endpoint_, + aep = &asio_remote_endpoint_, t = move(weak_token), + h = move(h)](const error_code &ec, size_t received) { + c->pending.fetch_sub(1, memory_order_relaxed); + if (auto p = t.lock()) { + ep->ip = to_in6_addr(*aep); + ep->port = aep->port(); + h(ec, received); + } + })); + } + void close() { socket_.close(); } + auto local_endpoint() -> const endpoint & { + auto ep = socket_.local_endpoint(); + local_endpoint_.ip = to_in6_addr(ep); + local_endpoint_.port = ep.port(); + return local_endpoint_; + } + auto remote_endpoint() -> const endpoint & { return remote_endpoint_; } + context_ref ctx; + io_context::strand strand_; + asio::ip::udp::socket socket_; + asio::ip::udp::endpoint asio_remote_endpoint_; + executor::endpoint local_endpoint_{}; + executor::endpoint remote_endpoint_{}; + shared_ptr token_{make_shared(true)}; +}; + +socket::socket(strand &strand) + : ref{socket_ref(new socket_impl(strand), + [](socket_impl *p) { delete p; })} {} + +auto socket::bind(const in6_addr &ip, port_t port) -> error_code { + return ref->bind(ip, port); +} + +auto socket::send_to(string_view data, in6_addr ip, port_t port) const + -> size_t { + return ref->send_to(data, ip, port); +} + +void socket::receive(handler h) { ref->receive(*this, move(h)); } + +void socket::close() {} + +auto socket::local_endpoint() const -> const endpoint & { + return ref->local_endpoint(); +} + +auto socket::remote_endpoint() const -> const endpoint & { + return ref->remote_endpoint(); +} + +} // namespace udp + +namespace tcp { + +struct socket_impl { + explicit socket_impl(strand &strand) + : ctx{strand.ref->ctx}, strand_{strand.ref->strand_}, + socket_{*ctx->asio, asio::ip::tcp::v6()} {} + explicit socket_impl(strand &strand, int fd) + : ctx{strand.ref->ctx}, strand_{strand.ref->strand_}, + socket_{*ctx->asio, asio::ip::tcp::v6(), fd} {} + auto bind(const in6_addr &ip, port_t port) -> error_code { + error_code ec; + socket_.bind(to_endpoint_tcp(ip, port), ec); + return ec; + } + void connect(const in6_addr &ip, port_t port, socket::connect_handler h) { + ctx->pending.fetch_add(1, memory_order_relaxed); + weak_ptr weak_token{token_}; + socket_.async_connect( + to_endpoint_tcp(ip, port), + bind_executor(strand_, [c = ctx, h = move(h), + t = move(weak_token)](const error_code &ec) { + c->pending.fetch_sub(1, memory_order_relaxed); + if (auto p = t.lock()) + h(ec); + })); + } + void write(const vector &data, socket::write_handler h) { + ctx->pending.fetch_add(1, memory_order_relaxed); + weak_ptr weak_token{token_}; + socket_.async_write_some( + buffer(data.data(), data.size()), + bind_executor(strand_, [c = ctx, h = move(h), t = move(weak_token)]( + const error_code &ec, size_t written) { + c->pending.fetch_sub(1, memory_order_relaxed); + if (auto p = t.lock()) + h(ec, written); + })); + } + void read(socket &s, socket::read_handler h) { + ctx->pending.fetch_add(1, memory_order_relaxed); + weak_ptr weak_token{token_}; + socket_.async_read_some( + buffer(s.read_buffer.data(), s.read_buffer.size()), + bind_executor(strand_, [c = ctx, h = move(h), t = move(weak_token)]( + const error_code &ec, size_t read) { + c->pending.fetch_sub(1, memory_order_relaxed); + if (auto p = t.lock()) + h(ec, read); + })); + } + void close() { socket_.close(); } + auto release() -> int { return socket_.release(); } + auto local_endpoint() -> const endpoint & { + auto ep = socket_.local_endpoint(); + local_endpoint_.ip = to_in6_addr(ep); + local_endpoint_.port = ep.port(); + return local_endpoint_; + } + context_ref ctx; + io_context::strand strand_; + asio::ip::tcp::socket socket_; + asio::ip::tcp::endpoint remote_endpoint_; + executor::endpoint local_endpoint_{}; + shared_ptr token_{make_shared(true)}; +}; + +socket::socket(strand &strand) + : ref{socket_ref(new socket_impl(strand), + [](socket_impl *p) { delete p; })} {} + +socket::socket(strand &strand, int fd) + : ref{socket_ref(new socket_impl(strand, fd), + [](socket_impl *p) { delete p; })} {} + +auto socket::bind(const in6_addr &ip, port_t port) -> error_code { + return ref->bind(ip, port); +} + +void socket::connect(const in6_addr &ip, port_t port, connect_handler h) { + ref->connect(ip, port, move(h)); +} + +void socket::write(const vector &data, write_handler h) { + ref->write(data, move(h)); +} +void socket::read(read_handler h) { ref->read(*this, move(h)); } + +void socket::close() { ref->close(); } +void socket::close(int fd) { ::close(fd); } +auto socket::release() -> int { return ref->release(); } + +auto socket::local_endpoint() const -> const endpoint & { + return ref->local_endpoint(); +} + +struct acceptor_impl { + explicit acceptor_impl(strand &strand) + : ctx{strand.ref->ctx}, strand_{strand.ref->strand_}, + acceptor_{*ctx->asio, asio::ip::tcp::v6()}, socket_{*ctx->asio} {} + auto bind(const in6_addr &ip, port_t port) -> error_code { + error_code ec; + acceptor_.bind(to_endpoint_tcp(ip, port), ec); + return ec; + } + auto listen() -> error_code { + error_code ec; + acceptor_.listen(asio::socket_base::max_listen_connections, ec); + return ec; + } + void accept(acceptor::handler h) { + ctx->pending.fetch_add(1, memory_order_relaxed); + weak_ptr weak_token{token_}; + acceptor_.async_accept( + socket_, + bind_executor(strand_, [c = ctx, s = &socket_, h = move(h), + t = move(weak_token)](const error_code &ec) { + c->pending.fetch_sub(1, memory_order_relaxed); + if (auto p = t.lock()) + h(ec ? 0 : s->release(), ec); + })); + } + void close() { acceptor_.close(); } + auto local_endpoint() -> const endpoint & { + auto ep = acceptor_.local_endpoint(); + local_endpoint_.ip = to_in6_addr(ep); + local_endpoint_.port = ep.port(); + return local_endpoint_; + } + context_ref ctx; + io_context::strand strand_; + asio::ip::tcp::acceptor acceptor_; + asio::ip::tcp::socket socket_; + executor::endpoint local_endpoint_{}; + shared_ptr token_{make_shared(true)}; +}; + +acceptor::acceptor(strand &strand) + : ref{acceptor_ref(new acceptor_impl(strand), + [](acceptor_impl *p) { delete p; })} {} + +auto acceptor::bind(const in6_addr &ip, port_t port) -> error_code { + return ref->bind(ip, port); +} + +auto acceptor::listen() -> error_code { return ref->listen(); } + +void acceptor::accept(acceptor::handler h) { ref->accept(move(h)); } + +void acceptor::close() { ref->close(); } + +[[nodiscard]] auto acceptor::local_endpoint() const -> endpoint { + return ref->local_endpoint(); +} + +} // namespace tcp + +namespace unx { + +struct socket_impl { + explicit socket_impl(strand &strand) + : ctx{strand.ref->ctx}, strand_{strand.ref->strand_}, socket_{ + *ctx->asio} {} + explicit socket_impl(strand &strand, int fd) + : ctx{strand.ref->ctx}, strand_{strand.ref->strand_}, socket_{*ctx->asio, + fd} {} + auto bind(string_view path) -> error_code { + error_code ec; + socket_.bind(path, ec); + return ec; + } + void connect(string_view path, socket::connect_handler h) { + ctx->pending.fetch_add(1, memory_order_relaxed); + weak_ptr weak_token{token_}; + socket_.async_connect( + path, + bind_executor(strand_, [c = ctx, h = move(h), + t = move(weak_token)](const error_code &ec) { + c->pending.fetch_sub(1, memory_order_relaxed); + if (auto p = t.lock()) + h(ec); + })); + } + void write(const vector &data, socket::write_handler h) { + ctx->pending.fetch_add(1, memory_order_relaxed); + weak_ptr weak_token{token_}; + socket_.async_write_some( + buffer(data.data(), data.size()), + bind_executor(strand_, [c = ctx, h = move(h), t = move(weak_token)]( + const error_code &ec, size_t written) { + c->pending.fetch_sub(1, memory_order_relaxed); + if (auto p = t.lock()) + h(ec, written); + })); + } + void read(socket &s, socket::read_handler h) { + ctx->pending.fetch_add(1, memory_order_relaxed); + weak_ptr weak_token{token_}; + socket_.async_read_some( + buffer(s.read_buffer.data(), s.read_buffer.size()), + bind_executor(strand_, [c = ctx, h = move(h), t = move(weak_token)]( + const error_code &ec, size_t read) { + c->pending.fetch_sub(1, memory_order_relaxed); + if (auto p = t.lock()) + h(ec, read); + })); + } + void close() { socket_.close(); } + auto release() -> int { return socket_.release(); } + context_ref ctx; + io_context::strand strand_; + asio::local::stream_protocol::socket socket_; + shared_ptr token_{make_shared(true)}; +}; + +socket::socket(strand &strand) + : ref{socket_ref(new socket_impl(strand), + [](socket_impl *p) { delete p; })} {} + +socket::socket(strand &strand, int fd) + : ref{socket_ref(new socket_impl(strand, fd), + [](socket_impl *p) { delete p; })} {} + +auto socket::bind(string_view path) -> error_code { return ref->bind(path); } + +void socket::connect(string_view path, connect_handler h) { + ref->connect(path, move(h)); +} + +void socket::write(const vector &data, write_handler h) { + ref->write(data, move(h)); +} +void socket::read(read_handler h) { ref->read(*this, move(h)); } + +void socket::close() { ref->close(); } +auto socket::release() -> int { return ref->release(); } + +struct acceptor_impl { + explicit acceptor_impl(strand &strand) + : ctx{strand.ref->ctx}, strand_{strand.ref->strand_}, + acceptor_{*ctx->asio, asio::local::stream_protocol()}, + socket_{*ctx->asio} {} + auto bind(string_view path) -> error_code { + error_code ec; + acceptor_.bind(path, ec); + return ec; + } + auto listen() -> error_code { + error_code ec; + acceptor_.listen(asio::socket_base::max_listen_connections, ec); + return ec; + } + void accept(acceptor::handler h) { + ctx->pending.fetch_add(1, memory_order_relaxed); + weak_ptr weak_token{token_}; + acceptor_.async_accept( + socket_, + bind_executor(strand_, [c = ctx, s = &socket_, h = move(h), + t = move(weak_token)](const error_code &ec) { + c->pending.fetch_sub(1, memory_order_relaxed); + if (auto p = t.lock()) + h(ec ? 0 : s->release(), ec); + })); + } + void close() { acceptor_.close(); } + context_ref ctx; + io_context::strand strand_; + asio::local::stream_protocol::acceptor acceptor_; + asio::local::stream_protocol::socket socket_; + shared_ptr token_{make_shared(true)}; +}; + +acceptor::acceptor(strand &strand) + : ref{acceptor_ref(new acceptor_impl(strand), + [](acceptor_impl *p) { delete p; })} {} + +auto acceptor::bind(string_view path) -> error_code { return ref->bind(path); } + +auto acceptor::listen() -> error_code { return ref->listen(); } + +void acceptor::accept(acceptor::handler h) { ref->accept(move(h)); } + +void acceptor::close() { ref->close(); } + +} // namespace unx + +namespace file_descriptor { + +struct watcher_impl { + explicit watcher_impl(strand &strand, int fd) + : ctx{strand.ref->ctx}, strand_{strand.ref->strand_}, fd_{*ctx->asio, + fd} {} + + void wait_read(watcher::handler h) { + if (!read_in_progress_) { + read_in_progress_ = true; + ctx->pending.fetch_add(1, memory_order_relaxed); + weak_ptr weak_token{token_}; + fd_.async_wait( + asio::posix::descriptor_base::wait_type::wait_read, + bind_executor(strand_, [c = ctx, b = &read_in_progress_, h = move(h), + t = move(weak_token)](const error_code &ec) { + c->pending.fetch_sub(1, memory_order_relaxed); + if (auto p = t.lock()) { + *b = false; + h(ec); + } + })); + } + } + + void wait_write(watcher::handler h) { + if (!write_in_progress_) { + write_in_progress_ = true; + ctx->pending.fetch_add(1, memory_order_relaxed); + weak_ptr weak_token{token_}; + fd_.async_wait( + asio::posix::descriptor_base::wait_type::wait_write, + bind_executor(strand_, [c = ctx, b = &write_in_progress_, h = move(h), + t = move(weak_token)](const error_code &ec) { + c->pending.fetch_sub(1, memory_order_relaxed); + if (auto p = t.lock()) { + *b = false; + h(ec); + } + })); + } + } + + void cancel() { fd_.cancel(); } + + context_ref ctx; + io_context::strand strand_; + stream_descriptor fd_; + shared_ptr token_{make_shared(true)}; + bool read_in_progress_{false}; + bool write_in_progress_{false}; +}; + +watcher::watcher(strand &strand, int fd) + : ref{watcher_ref(new watcher_impl(strand, fd), + [](watcher_impl *p) { delete p; })} {} + +void watcher::wait_read(watcher::handler h) { ref->wait_read(move(h)); } +void watcher::wait_write(watcher::handler h) { ref->wait_write(move(h)); } +void watcher::cancel() { ref->cancel(); } + +} // namespace file_descriptor + +} // namespace thespian::executor diff --git a/src/hub.cpp b/src/hub.cpp new file mode 100644 index 0000000..6ee3eba --- /dev/null +++ b/src/hub.cpp @@ -0,0 +1,157 @@ +#include +#include +#include +#include + +#include +#include +#include +#include + +using cbor::buffer; +using cbor::extract; +using cbor::more; +using cbor::type; +using std::deque; +using std::lock_guard; +using std::make_shared; +using std::move; +using std::mutex; +using std::pair; +using std::set; +using std::shared_ptr; +using std::string; +using std::string_view; +using std::vector; + +namespace thespian { + +using filter = hub::filter; + +hub::pipe::pipe() = default; +hub::pipe::~pipe() = default; + +struct subscribe_queue : public hub::pipe { + [[nodiscard]] auto push(const handle &from, filter f) { + lock_guard lock(m_); + q_.push_back(move(f)); + return from.send("subscribe_filtered"); + } + auto pop() -> filter { + lock_guard lock(m_); + filter f = move(q_.front()); + q_.pop_front(); + return f; + } + +private: + deque q_; + mutex m_; +}; + +struct hub_impl { + hub_impl(const hub_impl &) = delete; + hub_impl(hub_impl &&) = delete; + auto operator=(const hub_impl &) -> hub_impl & = delete; + auto operator=(hub_impl &&) -> hub_impl & = delete; + + explicit hub_impl(shared_ptr q) : q_(move(q)) {} + ~hub_impl() = default; + + auto receive(handle from, buffer msg) { + if (msg("broadcast", type::array)) { + msg.erase(msg.raw_cbegin(), msg.raw_cbegin() + 11); + for (auto const &s : subscribers_) { + result ret = ok(); + if (s.second) { + if (s.second(msg)) { + ret = s.first.send_raw(msg); + } + } else { + ret = s.first.send_raw(msg); + } + // if (not ret) + // FIXME: remove dead subscriptions from subscribers_ + } + return ok(); + } + + if (msg("subscribe")) { + subscribers_.emplace_back(move(from), filter{}); + return ok(); + } + + if (msg("subscribe_filtered")) { + subscribers_.emplace_back(move(from), q_->pop()); + return ok(); + } + + buffer::range r; + if (msg("subscribe_messages", extract(r))) { + set msgs; + for (const auto val : r) + msgs.insert(string(val)); + subscribers_.emplace_back(move(from), [msgs](auto m) { + string tag; + return m(extract(tag), more) && (msgs.find(tag) != msgs.end()); + }); + return ok(); + } + + string_view desc; + if (msg("listen", extract(desc))) { + thespian::endpoint::unx::listen(desc); + } + + if (msg("shutdown")) + return exit("shutdown"); + + return unexpected(msg); + } + + using subscriber_list_t = vector>; + subscriber_list_t subscribers_; + shared_ptr q_; +}; + +hub::hub(const handle &h, shared_ptr p) : handle(h), pipe_(move(p)) {} + +auto hub::subscribe() const -> result { + link(*this); + return send("subscribe"); +} + +auto hub::subscribe(filter f) const -> result { + link(*this); + return dynamic_cast(*pipe_).push( + static_cast(*this), move(f)); +} + +auto hub::listen(string_view desc) const -> result { + return send("listen", desc); +} + +auto hub::create(string_view name) -> expected { + auto q = make_shared(); + auto ret = spawn_link( + [=]() { + receive([p{make_shared(q)}](auto from, auto m) { + return p->receive(move(from), move(m)); + }); + return ok(); + }, + name); + if (not ret) + return to_error(ret.error()); + return hub{ret.value(), q}; +} + +auto operator==(const handle &a, const hub &b) -> bool { + return a == static_cast(b); +} + +auto operator==(const hub &a, const handle &b) -> bool { + return static_cast(a) == b; +} + +} // namespace thespian diff --git a/src/instance.cpp b/src/instance.cpp new file mode 100644 index 0000000..1061e19 --- /dev/null +++ b/src/instance.cpp @@ -0,0 +1,1988 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "executor.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef TRACY_ENABLE +#include +#endif + +using cbor::any; +using cbor::array; +using cbor::buffer; +using cbor::extract; +using cbor::more; +using cbor::type; + +using std::atomic; +using std::domain_error; +using std::enable_shared_from_this; +using std::error_code; +using std::forward; +using std::forward_list; +using std::lock_guard; +using std::make_pair; +using std::make_shared; +using std::map; +using std::memory_order_relaxed; +using std::move; +using std::mutex; +using std::pair; +using std::shared_ptr; +using std::string; +using std::string_view; +using std::stringstream; +using std::swap; +using std::vector; +using std::chrono::microseconds; +using std::chrono::milliseconds; +using std::chrono::system_clock; + +using clk = std::chrono::system_clock; +using namespace std::chrono_literals; + +namespace thespian { + +struct context_impl : context { + explicit context_impl(executor::context executor) + : executor{move(executor)} {} + atomic active; + executor::context executor; + last_exit_handler last_exit{}; + + map registry; + mutex registry_mux; + atomic debug_enabled{0}; + + auto active_add() { return active.fetch_add(1, memory_order_relaxed); } + auto active_sub() { return active.fetch_sub(1, memory_order_relaxed); } + auto active_load() { return active.load(); } + + void on_last_exit(last_exit_handler h) { last_exit = move(h); } + + static void destroy(context *p) { + delete reinterpret_cast(p); // NOLINT + } +}; +thread_local instance *current_instance{}; // NOLINT + +auto impl(context &ctx) -> context_impl & { + return *reinterpret_cast(&ctx); // NOLINT +} + +auto context::create() -> ref { + return {new context_impl{executor::context::create()}, context_impl::destroy}; +} + +auto context::create(dtor *d) -> context * { + *d = context_impl::destroy; + return new context_impl{executor::context::create()}; +} + +void context::run() { impl(*this).executor.run(); } + +void context::on_last_exit(last_exit_handler h) { + impl(*this).last_exit = move(h); +} + +namespace debug { + +void register_instance(context_impl &ctx, const handle &h); +void unregister_instance(context_impl &ctx, const string &name); +auto get_name(context_impl &ctx, const string &name) -> handle; +auto get_names(context_impl &ctx) -> buffer; + +} // namespace debug + +namespace { + +auto is_exit_msg(const buffer &m) -> bool { return m("exit", any, more); } +const auto link_msg{array("link")}; +auto is_link_msg(const buffer &m) -> bool { return m == link_msg; } +constexpr auto context_error = "call out of context"; + +} // namespace + +auto handle_ref(handle &h) -> ref & { return h.ref_; } +auto handle_ref(const handle &h) -> const ref & { return h.ref_; } +auto make_handle(ref &&r) -> handle { + handle h{}; + handle_ref(h) = move(r); + return h; +} +auto ref_m(const instance *p) -> buffer; +auto ref_m(const ref &r) -> buffer; + +template auto exit_message(string_view e, Ts &&...parms) { + return buffer(array("exit", e, forward(parms)...)); +} + +[[nodiscard]] auto exit() -> result { return to_error(exit_message("normal")); } + +template +[[nodiscard]] auto exit(std::string_view e, Ts &&...parms) -> result { + return to_error(exit_message(e, std::forward(parms)...)); +} + +[[nodiscard]] auto exit(std::string_view e) -> result { + return to_error(exit_message(e)); +} + +[[nodiscard]] auto exit(std::string_view e, std::string_view m) -> result { + return to_error(exit_message(e, m)); +} + +[[nodiscard]] auto exit(std::string_view e, int err) -> result { + return to_error(exit_message(e, err)); +} + +[[nodiscard]] auto exit(std::string_view e, size_t n) -> result { + return to_error(exit_message(e, n)); +} + +[[nodiscard]] auto unexpected(const cbor::buffer &b) -> result { + return exit("UNEXPECTED_MESSAGE:" + b.to_json()); +} + +auto deadsend(const buffer &m) -> result; +const auto exit_normal_msg = array("exit", "normal"); +const auto exit_noreceive_msg = array("exit", "noreceive"); +const auto exit_nosyncreceive_msg = array("exit", "nosyncreceive"); + +struct msg_task { + explicit msg_task() = default; + msg_task(const msg_task &) = default; + msg_task(msg_task &&) = default; + auto operator=(const msg_task &) -> msg_task & = default; + auto operator=(msg_task &&) -> msg_task & = default; + virtual ~msg_task() = default; + virtual void operator()() = 0; +}; + +template struct msg_task_T : msg_task { + explicit msg_task_T(F f) : f_(move(f)) {} + void operator()() override { f_(); } + +private: + F f_; +}; + +struct instance : std::enable_shared_from_this { + instance(const instance &) = delete; + instance(const instance &&) = delete; + auto operator=(const instance &) -> instance & = delete; + auto operator=(instance &&) -> instance & = delete; + + instance(context_impl &ctx, behaviour b, exit_handler eh, string_view name, + env_t env) + : ctx(ctx), strand_(ctx.executor.create_strand()), name_{name}, + env_(move(env)) { + if (eh) + exit_handlers_.emplace_front(eh); + receiver_ = [this, b{move(b)}](auto, auto) { + this->do_trace(channel::lifetime, "init"); + receiver b_; + b_.swap(receiver_); + return b(); + }; + ctx.active_add(); + } + ~instance() { + auto prev = ctx.active_sub(); + if (debug::isenabled(ctx)) { + do_trace_links(channel::link); + if (is_enabled(channel::lifetime)) + do_trace_raw(ref_m(this), "living", debug::get_names(ctx)); + } + do_trace(channel::lifetime, "destroyed", prev - 1); + if (prev == 1) { + do_trace(channel::lifetime, "last_exit"); + if (ctx.last_exit) + ctx.last_exit(); + } + } + + static auto spawn(context_impl &ctx, behaviour b, exit_handler eh, + string_view name, const ref &link, env_t env) + -> expected { + auto pimpl = make_shared(ctx, move(b), move(eh), name, move(env)); + pimpl->lifetime_ = pimpl; + handle_ref(pimpl->self_ref_) = pimpl->lifetime_; + pimpl->do_trace(channel::lifetime, "spawn"); + auto h = make_handle(pimpl); + if (!link.expired()) { + auto ret = pimpl->queue(link, link_msg); + if (not ret) + return to_error(ret.error()); + } + auto ret = pimpl->schedule(); + if (not ret) + return to_error(ret.error()); + debug::register_instance(ctx, h); + return h; + } + + auto name() const -> const string & { return name_; } + + auto is_enabled(channel c) -> bool { return env_.enabled(c); } + + template void do_trace_raw(Ts &&...parms) { + auto a = ctx.active_load(); + long use_count{0}; + if (auto p = weak_from_this().lock()) + use_count = p.use_count() - 1; + env_.trace(array(ctx.executor.pending_tasks(), ctx.executor.pending_posts(), + a, use_count, forward(parms)...)); + } + + template + void do_trace_to(channel c, Tag &&tag, const ref &to, Ts &&...parms) { + if (is_enabled(c)) + do_trace_raw(ref_m(this), forward(tag), ref_m(to), + forward(parms)...); + } + + template void do_trace(channel c, Ts &&...parms) { + if (is_enabled(c)) + do_trace_raw(ref_m(this), forward(parms)...); + } + + template void do_trace_links(channel c) { + if (is_enabled(c)) { + int count{0}; + buffer c; + for (auto &l : links_) { + if (auto h = handle_ref(l).lock()) { + ++count; + c.push(h->name_); + } + } + buffer b; + b.array_header(count); + b.push(c); + do_trace_raw(ref_m(this), "links", b); + } + } + + auto get_strand() -> executor::strand & { return strand_; } + + void private_call() { + if (!current_instance || current_instance != this) + throw domain_error{context_error}; + } + + auto trace_enabled(channel c) { return env_.enabled(c); } + + template void submit_msg_task(F f) { + strand_.post(msg_task_T(f)); + } + + [[nodiscard]] auto dispatch_sync_raw(buffer m) -> result { + if (in_shutdown) + return deadsend(m); + ref from{}; + if (current_instance) + from = current_instance->lifetime_; + run(from, move(m)); + return ok(); + } + template + [[nodiscard]] auto dispatch_sync(Ts &&...parms) -> result { + return dispatch_sync_raw(array(std::forward(parms)...)); + } + + [[nodiscard]] auto queue(const ref &from, buffer m) -> result { + if ((not m.is_null()) and is_enabled(channel::send)) + do_trace_raw(ref_m(from), "send", ref_m(this), m); + if (in_shutdown) + return deadsend(m); + + auto run_m = [this, from{from}, m{move(m)}, lifelock{lifetime_}]() { + run(from, m); + }; + submit_msg_task(move(run_m)); + return ok(); + } + [[nodiscard]] auto schedule() -> result { + return queue(ref{}, buffer::null_value); + } + + void handle_exit(const buffer &m) { + in_shutdown = true; + for (const auto &h : links_) + auto _ = h.send_raw(m); + do_trace(channel::lifetime, "exit", m); + if (!exit_handlers_.empty()) { + forward_list exit_handlers; + exit_handlers.swap(exit_handlers_); + string bad_err; + string_view err; + if (!m(type::string, extract(err), type::more)) { + bad_err = "bad_exit_message: " + m.to_json(); + err = bad_err; + } + for (auto &eh : exit_handlers) + eh(err); + } + exited_receiver_.swap(receiver_); + exited_sync_receiver_.swap(sync_receiver_); + lifetime_.reset(); + } + + void run(ref from, buffer msg) { +#ifdef TRACY_ENABLE + ZoneScopedN("actor"); +#endif + if (is_link_msg(msg)) { + do_trace(channel::execute, "run"); + do_trace_to(channel::link, "link", from); + links_.emplace_front(make_handle(move(from))); + if (debug::isenabled(ctx)) + do_trace_links(channel::link); + do_trace(channel::execute, "sleep"); + return; + } + if (in_shutdown) + return; + do_trace(channel::execute, "run"); + result ret; + auto lifelock{lifetime_}; + try { + if (current_instance && current_instance != this) + throw domain_error{context_error}; + current_instance = this; + if (!trap_exit_ && is_exit_msg(msg)) { + if (msg != exit_normal_msg) + handle_exit(msg); + } else { + if (!msg.is_null()) + do_trace_to(channel::receive, "receive", from, msg); + ret = receiver_(make_handle(move(from)), move(msg)); + if (ret && current_instance && !receiver_) + handle_exit(exit_noreceive_msg); + } + if (not ret) + handle_exit(ret.error()); + } catch (std::exception &e) { + if (!current_instance) + throw; + handle_exit(exit_message(string("UNHANDLED_EXCEPTION: ") + e.what())); + } catch (...) { + if (!current_instance) + throw; + handle_exit(exit_message("UNHANDLED_EXCEPTION")); + } + if (current_instance) { + if (!in_shutdown) + do_trace(channel::execute, "sleep"); + current_instance = nullptr; + } + receiver cleanup_receiver_; + sync_receiver cleanup_sync_receiver_; + cleanup_receiver_.swap(exited_receiver_); + cleanup_sync_receiver_.swap(exited_sync_receiver_); + } + + void receive(receiver &&b) { + private_call(); + do_trace(channel::receive, "receive", "set"); + receiver_ = move(b); + } + + void receive_sync(sync_receiver &&b) { + private_call(); + do_trace(channel::receive, "receive_sync", "set"); + sync_receiver_ = move(b); + } + + [[nodiscard]] auto send_raw(buffer m) -> result { + ref from{}; + if (current_instance) + from = current_instance->lifetime_; + return queue(from, move(m)); + } + template [[nodiscard]] auto send(Ts &&...parms) -> result { + return send_raw(array(std::forward(parms)...)); + } + + auto spawn_link(behaviour b, string_view name, env_t env) + -> expected { + auto ret = instance::spawn(ctx, move(b), exit_handler{}, name, lifetime_, + move(env)); + if (not ret) + return ret; + handle h = ret.value(); + do_trace_to(channel::link, "link", handle_ref(h)); + links_.emplace_front(h); + return ret; + } + + auto spawn_link(behaviour b, string_view name) -> expected { + return spawn_link(move(b), name, env_); + } + + auto trap() const -> bool { return trap_exit_; } + auto trap(bool trap) -> bool { + swap(trap, trap_exit_); + return trap; + } + + auto link(const handle &h) -> result { + auto ret = h.send_raw(link_msg); + if (not ret) + return ret; + do_trace_to(channel::link, "link", handle_ref(h)); + links_.emplace_front(h); + return ok(); + } + + auto is_in_shutdown() -> bool { return in_shutdown; } + + context_impl &ctx; + executor::strand strand_; + string name_; + receiver receiver_; + receiver exited_receiver_; + sync_receiver sync_receiver_; + sync_receiver exited_sync_receiver_; + forward_list exit_handlers_; + shared_ptr lifetime_; + handle self_ref_; + pair mailbox_; + bool trap_exit_{false}; + bool in_shutdown{false}; + forward_list links_; + env_t env_; +}; + +auto deadsend(const buffer &m) -> result { + if (current_instance and !current_instance->is_in_shutdown() and + !m("exit", type::more)) { + return to_error(exit_message("DEADSEND", m)); + } + return ok(); +} + +[[nodiscard]] auto handle::send_raw(buffer m) const -> result { + if (auto p = ref_.lock()) { + return p->send_raw(move(m)); + } + return deadsend(m); +} + +auto operator==(const handle &a, const handle &b) -> bool { + return a.ref_.lock() == b.ref_.lock(); +} + +auto ref_m(const void *p, string name) -> buffer { + stringstream ss; + ss << p; + return array("PID", ss.str(), move(name)); +} + +auto ref_m(const instance *p) -> buffer { return ref_m(p, p->name()); } + +auto ref_m(const ref &r) -> buffer { + if (auto p = r.lock()) + return ref_m(p.get(), p->name()); + return ref_m(nullptr, "none"); +} + +auto private_call() -> instance & { + if (!current_instance) + throw domain_error{context_error}; + return *current_instance; +} + +auto private_call_noexcept() noexcept -> instance * { + if (!current_instance) + return nullptr; + return current_instance; +} + +auto trace_enabled(channel c) { return private_call().env_.enabled(c); } + +auto spawn(behaviour b, string_view name) -> expected { + auto &p = private_call(); + return instance::spawn(p.ctx, move(b), exit_handler{}, name, thespian::ref{}, + p.env_); +} + +auto spawn(behaviour b, string_view name, env_t env) + -> expected { + auto &p = private_call(); + return instance::spawn(p.ctx, move(b), exit_handler{}, name, thespian::ref{}, + move(env)); +} + +auto context::spawn(behaviour b, string_view name, env_t env) + -> expected { + return instance::spawn(impl(*this), move(b), exit_handler{}, name, + thespian::ref{}, move(env)); +} + +auto context::spawn(behaviour b, string_view name) -> expected { + return instance::spawn(impl(*this), move(b), exit_handler{}, name, + thespian::ref{}, env_t{}); +} + +auto context::spawn_link(behaviour b, exit_handler eh, string_view name, + env_t env) -> expected { + if (!eh) + throw domain_error{"exit_handler required"}; + return instance::spawn(impl(*this), move(b), move(eh), name, thespian::ref{}, + move(env)); +} + +auto context::spawn_link(behaviour b, exit_handler eh, string_view name) + -> expected { + return spawn_link(move(b), move(eh), name, env_t{}); +} + +auto env() -> env_t & { return private_call().env_; } +auto self() -> handle { return make_handle(private_call().lifetime_); } +auto self_ref() -> handle & { return private_call().self_ref_; } +auto spawn_link(behaviour b, string_view name) -> expected { + return private_call().spawn_link(move(b), name); +} +auto spawn_link(behaviour b, string_view name, env_t env) + -> expected { + return private_call().spawn_link(move(b), name, move(env)); +} +auto send_raw(buffer m) -> result { return private_call().send_raw(move(m)); } +void receive(receiver r) { private_call().receive(move(r)); } +void receive_sync(sync_receiver r) { private_call().receive_sync(move(r)); } +auto trap() -> bool { return private_call().trap(); } +auto trap(bool t) -> bool { return private_call().trap(t); } +void link(const handle &h) { private_call().link(h); } + +auto on_trace(trace_handler h) -> trace_handler { + return private_call().env_.on_trace(move(h)); +} + +auto get_strand(const handle &h) -> executor::strand * { + if (auto p = handle_ref(h).lock()) + return &p->strand_; + return nullptr; +} + +struct signal_impl { + signal_impl(const signal_impl &) = delete; + signal_impl(signal_impl &&) = delete; + auto operator=(const signal_impl &) -> signal_impl & = delete; + auto operator=(signal_impl &&) -> signal_impl & = delete; + + auto is_trace_enabled() { return owner_.env_.enabled(channel::signal); } + + signal_impl(int signum, buffer m) + : owner_(private_call()), signal_(owner_.ctx.executor) { + + if (is_trace_enabled()) + owner_.env_.trace(array("signal", "set", m, signum)); + + signal_.fires_on(signum); + signal_.on_fired([this, m(move(m)), lifelock{owner_.lifetime_}]( + const error_code &error, int signum) { + if (is_trace_enabled()) + owner_.env_.trace(array("signal", "fired", m, signum, error.value(), + error.message())); + if (!error) + auto _ = owner_.send_raw(m); + else + auto _ = owner_.send_raw( + exit_message("signal_error", error.value(), error.message())); + }); + } + ~signal_impl() { cancel(); } + void cancel() { signal_.cancel(); } + + instance &owner_; + executor::signal signal_; +}; +void signal::cancel() { + if (ref) + ref->cancel(); +} + +auto create_signal(int signum, buffer m) -> signal { + return {signal_ref(new signal_impl(signum, move(m)), + [](signal_impl *p) { delete p; })}; +} +void cancel_signal(signal_impl *p) { p->cancel(); } +void destroy_signal(signal_impl *p) { delete p; } + +struct metronome_impl { + metronome_impl(const metronome_impl &) = delete; + metronome_impl(metronome_impl &&) = delete; + auto operator=(const metronome_impl &) -> metronome_impl & = delete; + auto operator=(metronome_impl &&) -> metronome_impl & = delete; + + explicit metronome_impl(microseconds us) + : owner_(private_call()), strand_(owner_.get_strand()), us_(us), + timer_(strand_), is_trace_enabled_{trace_enabled(channel::metronome)}, + env_{private_call().env_} {} + ~metronome_impl() { stop(); } + + void tick(const error_code &error) { + if (error) + return; // aborted + if (not running_) + return; + last_tick_ = last_tick_ + us_; + if (is_trace_enabled_) + env_.trace(array("metronome", "tick", counter, us_.count())); + result ret; + if (!error) + ret = owner_.send("tick", counter); + else + ret = owner_.send_raw( + exit_message("metronome_error", error.message(), error.value())); + counter++; + if (ret) + schedule_tick(); + else + stop(); + } + + void schedule_tick() { + timer_.expires_at(last_tick_ + us_); + timer_.on_expired([this, lifelock{owner_.lifetime_}]( + const error_code &error) { this->tick(error); }); + } + + void start() { + if (running_) + return; + running_ = true; + last_tick_ = clk::now(); + if (is_trace_enabled_) + env_.trace(array("metronome", "start", us_.count())); + schedule_tick(); + } + + void stop() { + timer_.cancel(); + running_ = false; + } + + instance &owner_; + executor::strand strand_; + microseconds us_; + executor::timer timer_; + system_clock::time_point last_tick_; + bool running_{false}; + bool is_trace_enabled_{false}; + const env_t &env_; + size_t counter{0}; +}; +void metronome::start() { ref->start(); } +void metronome::stop() { ref->stop(); } + +auto create_metronome(microseconds us) -> metronome { + return {metronome_ref(new metronome_impl(us), destroy_metronome)}; +} +void start_metronome(metronome_impl *p) { p->start(); } +void stop_metronome(metronome_impl *p) { p->stop(); } +void destroy_metronome(metronome_impl *p) { delete p; } + +struct timeout_impl { + timeout_impl(const timeout_impl &) = delete; + timeout_impl(timeout_impl &&) = delete; + auto operator=(const timeout_impl &) -> timeout_impl & = delete; + auto operator=(timeout_impl &&) -> timeout_impl & = delete; + + auto is_trace_enabled() { return owner_.env_.enabled(channel::timer); } + + timeout_impl(microseconds us, buffer m) + : owner_(private_call()), timer_(owner_.ctx.executor) { + auto start = clk::now().time_since_epoch().count(); + + if (is_trace_enabled()) + owner_.env_.trace(array("timeout", "set", m, us.count())); + + timer_.expires_after(us); + timer_.on_expired([this, start, m(move(m)), + lifelock{owner_.lifetime_}](const error_code &error) { + if (is_trace_enabled()) + owner_.env_.trace(array("timeout", "expired", m, + clk::now().time_since_epoch().count() - start, + error.value(), error.message())); + if (!error) + auto _ = owner_.send_raw(m); + else + auto _ = owner_.send_raw( + exit_message("timeout_error", error.value(), error.message())); + }); + } + ~timeout_impl() { cancel(); } + void cancel() { timer_.cancel(); } + + instance &owner_; + executor::timer timer_; +}; +void timeout::cancel() { + if (ref) + ref->cancel(); +} + +auto create_timeout(microseconds us, buffer m) -> timeout { + return {timeout_ref(new timeout_impl(us, move(m)), destroy_timeout)}; +} +void cancel_timeout(timeout_impl *p) { p->cancel(); } +void destroy_timeout(timeout_impl *p) { delete p; } +auto never() -> timeout { return {timeout_ref(nullptr, destroy_timeout)}; } + +struct udp_impl { + udp_impl(const udp_impl &) = delete; + udp_impl(udp_impl &&) = delete; + auto operator=(const udp_impl &) -> udp_impl & = delete; + auto operator=(udp_impl &&) -> udp_impl & = delete; + + explicit udp_impl(string tag) + : owner_(private_call()), strand_(owner_.get_strand()), socket_(strand_), + tag_(move(tag)), is_trace_enabled_{owner_.env_.enabled(channel::udp)} {} + ~udp_impl() = default; + + auto open(in6_addr ip, port_t port) -> port_t { + if (is_trace_enabled_) + owner_.env_.trace(array("udp", tag_, "open", port)); + if (auto ec = socket_.bind(ip, port)) { + auto _ = owner_.send("udp", tag_, "open_error", ec.value(), ec.message()); + } else { + open_ = true; + do_receive(); + } + return socket_.local_endpoint().port; + } + + void do_receive() { + socket_.receive([this, lifelock{owner_.lifetime_}](error_code error, + std::size_t received) { + this->receive(error, received); + }); + } + + void receive(error_code ec, std::size_t received) { + if (!open_) + return; + if (received == 0) + return; + if (ec) { + if (is_trace_enabled_) + owner_.env_.trace(array("udp", tag_, "receive_error", received, + ec.value(), ec.message())); + auto _ = + owner_.send("udp", tag_, "receive_error", ec.value(), ec.message()); + } else { + string_view data(socket_.receive_buffer.data(), received); + if (is_trace_enabled_) + owner_.env_.trace(array("udp", tag_, "receive", received, data)); + auto _ = owner_.send("udp", tag_, "receive", data, + socket_.remote_endpoint().ip, + socket_.remote_endpoint().port); + do_receive(); + } + } + + [[nodiscard]] auto sendto(string_view data, in6_addr ip, port_t port) + -> size_t { + if (is_trace_enabled_) + owner_.env_.trace( + array("udp", tag_, "sendto", data, executor::to_string(ip), port)); + return socket_.send_to(data, ip, port); + } + + void close() { + if (open_) { + socket_.close(); + open_ = false; + } + auto _ = owner_.send("udp", tag_, "closed"); + } + + instance &owner_; + executor::strand strand_; + executor::udp::socket socket_; + string tag_; + bool open_{false}; + bool is_trace_enabled_{false}; +}; + +auto udp::open(const in6_addr &ip, port_t port) -> port_t { + return ref->open(ip, port); +} +auto udp::sendto(string_view data, in6_addr ip, port_t port) -> size_t { + return ref->sendto(data, ip, port); +} +void udp::close() { ref->close(); } + +auto udp::create(string tag) -> udp { + return {udp_ref(new udp_impl(move(tag)), [](udp_impl *p) { delete p; })}; +} + +struct file_descriptor_impl { + file_descriptor_impl(const file_descriptor_impl &) = delete; + file_descriptor_impl(file_descriptor_impl &&) = delete; + auto operator=(const file_descriptor_impl &) + -> file_descriptor_impl & = delete; + auto operator=(file_descriptor_impl &&) -> file_descriptor_impl & = delete; + + file_descriptor_impl(string_view tag, int fd) + : owner_(private_call()), strand_(owner_.get_strand()), + file_descriptor_(strand_, fd), tag_(tag) {} + + ~file_descriptor_impl() = default; + + void wait_write() { + private_call(); + file_descriptor_.wait_write( + [this, liffile_descriptor_elock{owner_.lifetime_}](error_code ec) { + if (ec) { + auto _ = owner_.send("fd", tag_, "write_error", ec.value(), + ec.message()); + } else { + auto _ = owner_.send("fd", tag_, "write_ready"); + } + }); + } + + void wait_read() { + private_call(); + file_descriptor_.wait_read( + [this, liffile_descriptor_elock{owner_.lifetime_}](error_code ec) { + if (ec) { + auto _ = + owner_.send("fd", tag_, "read_error", ec.value(), ec.message()); + } else { + auto _ = owner_.send("fd", tag_, "read_ready"); + } + }); + } + + void cancel() { + private_call(); + file_descriptor_.cancel(); + } + + instance &owner_; + executor::strand strand_; + executor::file_descriptor::watcher file_descriptor_; + string tag_; +}; + +auto file_descriptor::create(string_view tag, int fd) -> file_descriptor { + return {file_descriptor_ref(new file_descriptor_impl(tag, fd), + [](file_descriptor_impl *p) { delete p; })}; +} + +void file_descriptor::wait_write() { ref->wait_write(); } +void file_descriptor::wait_read() { ref->wait_read(); } +void file_descriptor::cancel() { ref->cancel(); } + +void file_descriptor::wait_write(file_descriptor_impl *p) { p->wait_write(); } +void file_descriptor::wait_read(file_descriptor_impl *p) { p->wait_read(); } +void file_descriptor::cancel(file_descriptor_impl *p) { p->cancel(); } +void file_descriptor::destroy(file_descriptor_impl *p) { delete p; } + +struct socket_impl { + socket_impl(const socket_impl &) = delete; + socket_impl(socket_impl &&) = delete; + auto operator=(const socket_impl &) -> socket_impl & = delete; + auto operator=(socket_impl &&) -> socket_impl & = delete; + + socket_impl(string_view tag, int fd) + : owner_(private_call()), strand_(owner_.get_strand()), fd_(fd), + socket_(strand_, fd), tag_(tag), open_(true) {} + + virtual ~socket_impl() { socket_.close(); }; + + void write_complete(std::size_t length) { + auto ret = owner_.dispatch_sync("socket", tag_, "write_complete", length); + if (not ret) + auto _ = close_internal(); + write_pending_ = false; + if (length < write_buf_.size()) { + auto e = write_buf_.begin(); + for (size_t x{0}; x < length; ++x) + e++; + write_buf_.erase(write_buf_.begin(), e); + write_internal(); + return; + } + write_buf_.clear(); + if (not write_q_.empty()) { + write_buf_.swap(write_q_); + write_internal(); + } + } + + void write_internal() { + write_pending_ = true; + socket_.write(write_buf_, [this, lifelock{owner_.lifetime_}]( + error_code ec, std::size_t length) { + if (ec) { + auto _ = owner_.dispatch_sync("socket", tag_, "write_error", ec.value(), + ec.message()); + _ = close_internal(); + } else { + write_complete(length); + } + }); + } + + void write(string_view buf) { + private_call(); + if (write_pending_) { + write_q_.insert(write_q_.end(), buf.begin(), buf.end()); + return; + } + write_buf_.insert(write_buf_.begin(), buf.begin(), buf.end()); + write_internal(); + } + + void write(const vector &buf) { + private_call(); + if (write_pending_) { + write_q_.insert(write_q_.end(), buf.begin(), buf.end()); + return; + } + write_buf_.insert(write_buf_.begin(), buf.begin(), buf.end()); + write_internal(); + } + + void read() { + private_call(); + socket_.read([this, lifelock{owner_.lifetime_}](error_code ec, + std::size_t length) { + if (!open_) + return; + if (ec) { + if (length == 0 and ec.value() == 2) { // EOF + auto ret = owner_.dispatch_sync("socket", tag_, "read_complete", + string_view{}); + if (not ret) + auto _ = close_internal(); + } else if (ec.value() == 125) { // ECANCELLED + auto _ = close_internal(); + } else { + auto _ = owner_.dispatch_sync("socket", tag_, "read_error", + ec.value(), ec.message()); + } + } else { + string_view buf{}; + if (length > 0) + buf = string_view(socket_.read_buffer.data(), length); + auto ret = owner_.dispatch_sync("socket", tag_, "read_complete", buf); + if (not ret) + auto _ = close_internal(); + } + }); + } + + [[nodiscard]] auto close_internal() -> result { + if (open_) { + open_ = false; + socket_.close(); + } + return owner_.send("socket", tag_, "closed"); + } + + auto close() -> result { + private_call(); + return close_internal(); + } + + instance &owner_; + executor::strand strand_; + int fd_; + executor::tcp::socket socket_; + string tag_; + bool open_{false}; + vector write_buf_; + vector write_q_; + bool write_pending_{false}; +}; + +auto socket::create(string_view tag, int fd) -> socket { + return { + socket_ref(new socket_impl(tag, fd), [](socket_impl *p) { delete p; })}; +} + +void socket::write(string_view data) { ref->write(data); } +void socket::write(const vector &data) { ref->write(data); } +void socket::read() { ref->read(); } +void socket::close() { ref->close(); } + +namespace tcp { + +struct acceptor_impl { + explicit acceptor_impl(string_view tag) + : owner_(private_call()), strand_(owner_.get_strand()), + acceptor_(strand_), tag_(tag) {} + + auto listen(const in6_addr &ip, port_t port) -> port_t { + private_call(); + if (auto ec = acceptor_.bind(ip, port)) { + auto _ = owner_.send("acceptor", tag_, "error", ec.value(), ec.message()); + return 0; + } + if (auto ec = acceptor_.listen()) { + auto _ = owner_.send("acceptor", tag_, "error", ec.value(), ec.message()); + return 0; + } + start_accept(); + open_ = true; + return acceptor_.local_endpoint().port; + } + + void accept(int fd, error_code ec) { + if (!open_) + return; + if (ec) { + auto _ = owner_.dispatch_sync("acceptor", tag_, "error", ec.value(), + ec.message()); + return; + } + auto ret = owner_.dispatch_sync("acceptor", tag_, "accept", fd); + if (not ret) + return close(); + start_accept(); + } + + void start_accept() { + acceptor_.accept([this](int fd, error_code ec) -> void { accept(fd, ec); }); + } + + void close() { + private_call(); + acceptor_.close(); + auto _ = owner_.send("acceptor", tag_, "closed"); + open_ = false; + } + + instance &owner_; + executor::strand strand_; + executor::tcp::acceptor acceptor_; + string tag_; + bool open_{false}; +}; + +auto acceptor::create(string_view tag) -> acceptor { + return { + acceptor_ref(new acceptor_impl(tag), [](acceptor_impl *p) { delete p; })}; +} + +auto acceptor::listen(in6_addr ip, port_t port) -> port_t { + return ref->listen(ip, port); +} +void acceptor::close() { ref->close(); } + +struct connector_impl { + explicit connector_impl(string_view tag) + : owner_(private_call()), strand_(owner_.get_strand()), socket_(strand_), + tag_(tag) {} + + void connect(in6_addr ip, port_t port, in6_addr lip, port_t lport) { + private_call(); + open_ = true; + if (auto ec = socket_.bind(lip, lport)) { + auto _ = + owner_.send("connector", tag_, "error", ec.value(), ec.message()); + return; + } + socket_.connect(ip, port, [this](error_code ec) -> void { + if (!open_) + return; + if (ec) { + auto _ = owner_.dispatch_sync("connector", tag_, "error", ec.value(), + ec.message()); + return; + } + auto fd = socket_.release(); + auto ret = owner_.dispatch_sync("connector", tag_, "connected", fd); + if (not ret) { + cancel(); + executor::tcp::socket socket_cleanup(strand_, fd); + socket_cleanup.close(); + } + }); + } + + void cancel() { + private_call(); + socket_.close(); + open_ = false; + auto _ = owner_.send("connector", tag_, "cancelled"); + } + + instance &owner_; + executor::strand strand_; + executor::tcp::socket socket_; + string tag_; + bool open_{false}; +}; + +auto connector::create(string_view tag) -> connector { + return {connector_ref(new connector_impl(tag), + [](connector_impl *p) { delete p; })}; +} + +void connector::connect(in6_addr ip, port_t port) { + ref->connect(ip, port, in6addr_any, 0); +} + +void connector::connect(in6_addr ip, port_t port, in6_addr lip) { + ref->connect(ip, port, lip, 0); +} + +void connector::connect(in6_addr ip, port_t port, port_t lport) { + ref->connect(ip, port, in6addr_any, lport); +} +void connector::connect(in6_addr ip, port_t port, in6_addr lip, port_t lport) { + ref->connect(ip, port, lip, lport); +} + +void connector::cancel() { ref->cancel(); } + +} // namespace tcp + +namespace unx { + +auto unx_mode_string(mode m) -> const char * { + switch (m) { + case mode::file: + return "file"; + case mode::abstract: + return "abstract"; + } + return "unknown"; +} + +auto unx_mode_path(mode m, string_view path) -> string { + switch (m) { + case mode::file: + return string(path); + case mode::abstract: + string abspath = "#"; + abspath.append(path); + abspath[0] = '\0'; + return abspath; + } + return "unknown"; +} + +struct acceptor_impl { + explicit acceptor_impl(string_view tag) + : owner_(private_call()), strand_(owner_.get_strand()), + acceptor_(strand_), tag_(tag) {} + + void listen(string_view origpath, mode m) { + private_call(); + string path = unx_mode_path(m, origpath); + if (auto ec = acceptor_.bind(path)) { + auto _ = owner_.send("acceptor", tag_, "error", ec.value(), ec.message()); + return; + } + if (auto ec = acceptor_.listen()) { + auto _ = owner_.send("acceptor", tag_, "error", ec.value(), ec.message()); + return; + } + start_accept(); + open_ = true; + } + + void accept(int fd, error_code ec) { + if (!open_) + return; + if (ec) { + auto _ = owner_.dispatch_sync("acceptor", tag_, "error", ec.value(), + ec.message()); + return; + } + auto ret = owner_.dispatch_sync("acceptor", tag_, "accept", fd); + if (not ret) + return close(); + start_accept(); + } + + void start_accept() { + acceptor_.accept([this](int fd, error_code ec) -> void { accept(fd, ec); }); + } + + void close() { + private_call(); + acceptor_.close(); + auto _ = owner_.send("acceptor", tag_, "closed"); + open_ = false; + } + + instance &owner_; + executor::strand strand_; + executor::unx::acceptor acceptor_; + string tag_; + bool open_{false}; +}; + +auto acceptor::create(string_view tag) -> acceptor { + return { + acceptor_ref(new acceptor_impl(tag), [](acceptor_impl *p) { delete p; })}; +} + +void acceptor::listen(string_view path, mode m) { ref->listen(path, m); } +void acceptor::close() { ref->close(); } + +struct connector_impl { + explicit connector_impl(string_view tag) + : owner_(private_call()), strand_(owner_.get_strand()), socket_(strand_), + tag_(tag) {} + + void connect(string_view origpath, mode m) { + private_call(); + open_ = true; + string path = unx_mode_path(m, origpath); + socket_.connect(path, [this](error_code ec) -> void { + if (!open_) + return; + if (ec) { + auto _ = owner_.dispatch_sync("connector", tag_, "error", ec.value(), + ec.message()); + return; + } + auto fd = socket_.release(); + auto ret = owner_.dispatch_sync("connector", tag_, "connected", fd); + if (not ret) { + cancel(); + executor::unx::socket socket_cleanup(strand_, fd); + socket_cleanup.close(); + } + }); + } + + void cancel() { + private_call(); + socket_.close(); + open_ = false; + auto _ = owner_.send("connector", tag_, "cancelled"); + } + + instance &owner_; + executor::strand strand_; + executor::unx::socket socket_; + string tag_; + bool open_{false}; +}; + +auto connector::create(string_view tag) -> connector { + return {connector_ref(new connector_impl(tag), + [](connector_impl *p) { delete p; })}; +} + +void connector::connect(string_view path, mode m) { ref->connect(path, m); } + +void connector::cancel() { ref->cancel(); } + +} // namespace unx + +namespace endpoint { + +struct connection { + socket s; + handle owner; + vector read_buf; + bool is_trace_enabled_{false}; + const env_t &env_; + string_view tag; + + bool write_pending{false}; + bool close_on_write_complete{false}; + + template auto trace(Ts &&...parms) { + if (is_trace_enabled_) { + stringstream ss; + ss << this; + env_.trace(array(ref_m(private_call_noexcept()), tag, ss.str(), + forward(parms)...)); + } + } + + connection(int fd, handle o, const char *tag) + : s{socket::create(tag, fd)}, owner{move(o)}, + is_trace_enabled_{trace_enabled(channel::endpoint)}, + env_{private_call().env_}, tag{tag} { + read(); + } + + void read() { + trace("read"); + s.read(); + } + + void write(const buffer &buf) { + write_pending = true; + trace("write", buf); + s.write(buf); + } + + void close() { + close_on_write_complete = true; + if (!write_pending) { + trace("close"); + s.close(); + } + } + + auto receive(const handle & /*from*/, const buffer &m) -> result { + string_view data; + int written = 0; + int err = 0; + string_view err_msg{}; + if (m("socket", tag, "read_error", extract(err), extract(err_msg))) { + owner.exit("read_error", err_msg); + close(); + } else if (m("socket", tag, "read_complete", extract(data))) { + if (data.empty()) { + close(); + } else { + read_buf.insert(read_buf.end(), data.begin(), data.end()); + while (not read_buf.empty()) { + try { + auto msg_e = read_buf.cbegin(); + auto b = read_buf.cbegin(); + buffer::range msg; + if (!extract(msg)(msg_e, read_buf.cend())) { + owner.exit("framing_error"); + close(); + } + auto ret = owner.send_raw(buffer(b, msg_e)); + read_buf.erase(b, msg_e); + if (not ret) + close(); + } catch (const domain_error &e) { + break; + } + } + read(); + } + } else if (m("socket", tag, "write_error", extract(err), + extract(err_msg))) { + owner.exit("write_error", err_msg); + close(); + } else if (m("socket", tag, "write_complete", extract(written))) { + write_pending = false; + if (close_on_write_complete) { + close(); + } + } else if (m("socket", tag, "closed")) { + return exit("closed"); + } else if (is_exit_msg(m)) { + write(m); + close(); + } else { + write(m); + } + return ok(); + } + + static auto start(int fd, const handle &owner, const char *tag) + -> expected { + return spawn( + [fd, owner, tag]() { + link(owner); + trap(true); + ::thespian::receive( + [p{make_shared(fd, owner, tag)}](auto from, auto m) { + return p->receive(move(from), move(m)); + }); + return ok(); + }, + tag); + } + + static void attach(int fd, handle owner, const char *tag) { + private_call().name_ = tag; + ::thespian::receive( + [p{make_shared(fd, owner, tag)}](auto from, auto m) { + return p->receive(move(from), move(m)); + }); + } +}; + +namespace tcp { + +struct connector { + static constexpr string_view tag{"endpoint::tcp::connector"}; + ::thespian::tcp::connector c; + handle owner; + size_t connection_count{}; + in6_addr ip; + port_t port; + milliseconds retry_time{}; + size_t retry_count{}; + timeout retry_timeout{never()}; + + explicit connector(in6_addr ip_, port_t port_, handle o, + milliseconds retry_time, size_t retry_count) + : c{::thespian::tcp::connector::create(tag)}, owner{move(o)}, ip{ip_}, + port{port_}, retry_time(retry_time), retry_count(retry_count) { + connection_count++; + c.connect(ip, port); + } + + auto receive(const handle & /*from*/, const buffer &m) -> result { + int fd = 0; + int ec{}; + string data; + + if (m("connector", tag, "connected", extract(fd))) { + auto ret = owner.send("connected"); + if (not ret) + return ret; + connection::attach(fd, owner, "endpoint::tcp::active"); + } else if (m("connector", tag, "cancelled")) { + return exit("cancelled"); + } else if (m("connector", tag, "error", extract(ec), extract(data))) { + if (connection_count > retry_count) { + return exit("connect_error", data); + } + connection_count++; + retry_timeout = + create_timeout(retry_time, array("connector", tag, "retry")); + retry_time *= 2; + } else if (m("connector", tag, "retry")) { + c.connect(ip, port); + } else if (is_exit_msg(m)) { + c.cancel(); + } else { + return unexpected(m); + } + return ok(); + } + + static auto connect(in6_addr ip, port_t port, milliseconds retry_time, + size_t retry_count) -> expected { + handle owner = self(); + return spawn_link( + [=]() { + trap(true); + ::thespian::receive( + [p{make_shared(ip, port, owner, retry_time, + retry_count)}](auto from, auto m) { + return p->receive(move(from), move(m)); + }); + return ok(); + }, + tag); + } +}; + +struct acceptor { + static constexpr string_view tag{"endpoint::tcp::acceptor"}; + acceptor(const acceptor &) = delete; + acceptor(acceptor &&) = delete; + auto operator=(const acceptor &) -> acceptor & = delete; + auto operator=(acceptor &&) -> acceptor & = delete; + + ::thespian::tcp::acceptor a; + handle owner; + port_t listen_port; + + acceptor(in6_addr ip, port_t port, handle o) + : a{::thespian::tcp::acceptor::create(tag)}, owner{move(o)}, + listen_port{a.listen(ip, port)} {} + ~acceptor() = default; + + auto receive(const handle &from, const buffer &m) -> result { + int fd = 0; + string err; + + if (m("acceptor", tag, "accept", extract(fd))) { + connection::start(fd, owner, "endpoint::tcp::passive"); + } else if (m("acceptor", tag, "closed")) { + return exit("closed"); + } else if (m("acceptor", tag, "error", extract(err))) { + return exit(err); + } else if (m("ping")) { + return from.send("pong"); + } else if (m("get", "port")) { + return from.send("port", listen_port); + } else if (is_exit_msg(m)) { + a.close(); + } else { + return unexpected(m); + } + return ok(); + } + + static auto start(in6_addr ip, port_t port) -> expected { + handle owner = self(); + return spawn_link( + [=]() { + trap(true); + ::thespian::receive( + [p{make_shared(ip, port, owner)}](auto from, auto m) { + return p->receive(move(from), move(m)); + }); + return ok(); + }, + tag); + } +}; + +auto listen(in6_addr ip, port_t port) -> expected { + return acceptor::start(ip, port); +} +auto connect(in6_addr ip, port_t port, milliseconds retry_time, + size_t retry_count) -> expected { + return connector::connect(ip, port, retry_time, retry_count); +} + +} // namespace tcp + +namespace unx { + +struct connector { + static constexpr string_view tag{"endpoint::unx::connector"}; + ::thespian::unx::connector c; + handle owner; + size_t connection_count{}; + string path_; + mode mode_; + milliseconds retry_time{}; + size_t retry_count{}; + timeout retry_timeout{never()}; + + explicit connector(string_view path, mode m, handle o, + milliseconds retry_time, size_t retry_count) + : c{::thespian::unx::connector::create(tag)}, owner{move(o)}, path_{path}, + mode_{m}, retry_time(retry_time), retry_count(retry_count) { + connection_count++; + c.connect(path_, mode_); + } + + auto receive(const handle & /*from*/, const buffer &m) -> result { + int fd = 0; + int ec{0}; + string data; + + if (m("connector", tag, "connected", extract(fd))) { + retry_timeout.cancel(); + auto ret = owner.send("connected"); + if (not ret) { + executor::tcp::socket::close(fd); + return ret; + } + connection::attach(fd, owner, "endpoint::unx::active"); + } else if (m("connector", tag, "cancelled")) { + return exit("cancelled"); + } else if (m("connector", tag, "error", extract(ec), extract(data))) { + if (connection_count > retry_count) { + return exit("connect_error", data); + } + connection_count++; + retry_timeout = + create_timeout(retry_time, array("connector", tag, "retry")); + retry_time *= 2; + } else if (m("connector", tag, "retry")) { + c.connect(path_, mode_); + } else if (is_exit_msg(m)) { + c.cancel(); + } else { + return unexpected(m); + } + return ok(); + } + + static auto connect(string_view path, mode m_, milliseconds retry_time, + size_t retry_count) -> expected { + handle owner = self(); + return spawn_link( + [path{string(path)}, m_, owner, retry_time, retry_count]() { + trap(true); + ::thespian::receive( + [p{make_shared(path, m_, owner, retry_time, + retry_count)}](auto from, auto m) { + return p->receive(move(from), move(m)); + }); + return ok(); + }, + tag); + } +}; + +struct acceptor { + static constexpr string_view tag{"endpoint::unx::acceptor"}; + acceptor(const acceptor &) = delete; + acceptor(acceptor &&) = delete; + auto operator=(const acceptor &) -> acceptor & = delete; + auto operator=(acceptor &&) -> acceptor & = delete; + + ::thespian::unx::acceptor a; + handle owner; + string listen_path; + + acceptor(string_view path, mode m, handle o) + : a{::thespian::unx::acceptor::create(tag)}, owner{move(o)}, listen_path{ + path} { + a.listen(path, m); + } + ~acceptor() = default; + + auto receive(const handle &from, const buffer &m) -> result { + int fd = 0; + string err; + + if (m("acceptor", tag, "accept", extract(fd))) { + connection::start(fd, owner, "endpoint::unx::passive"); + } else if (m("acceptor", tag, "closed")) { + return exit("closed"); + } else if (m("acceptor", tag, "error", extract(err))) { + return exit(err); + } else if (m("ping")) { + return from.send("pong"); + } else if (m("get", "path")) { + return from.send("path", listen_path); + } else if (is_exit_msg(m)) { + a.close(); + } else { + return unexpected(m); + } + return ok(); + } + + static auto start(string_view path, mode m_) -> expected { + handle owner = self(); + return spawn_link( + [path{string(path)}, m_, owner]() { + trap(true); + ::thespian::receive( + [p{make_shared(path, m_, owner)}](auto from, auto m) { + return p->receive(move(from), move(m)); + }); + return ok(); + }, + tag); + } +}; + +auto listen(string_view path, mode m) -> expected { + return acceptor::start(path, m); +} +auto connect(string_view path, mode m, milliseconds retry_time, + size_t retry_count) -> expected { + return connector::connect(path, m, retry_time, retry_count); +} + +} // namespace unx +} // namespace endpoint + +namespace debug { + +void enable(context_impl &ctx) { + ctx.debug_enabled.fetch_add(1, memory_order_relaxed); +} +void enable(context &ctx) { enable(impl(ctx)); } +auto isenabled(context_impl &ctx) -> bool { + return ctx.debug_enabled.load(memory_order_relaxed) > 0; +} +auto isenabled(context &ctx) -> bool { return isenabled(impl(ctx)); } +void disable(context_impl &ctx) { + auto prev = ctx.debug_enabled.fetch_sub(1, memory_order_relaxed); + if (prev == 1) { + lock_guard lock(ctx.registry_mux); + ctx.registry.clear(); + } +} +void disable(context &ctx) { disable(impl(ctx)); } + +void register_instance(context_impl &ctx, const handle &h) { + if (not isenabled(ctx)) + return; + auto &r = handle_ref(h); + if (auto p = r.lock()) { + auto &name = p->name(); + if (name.empty()) + return; + // if (trace) + // trace(array("register", name, ref_m(r))); + lock_guard lock(ctx.registry_mux); + ctx.registry[name] = h; + } +} + +void unregister_instance(context_impl &ctx, const string &name) { + if (not isenabled(ctx)) + return; + lock_guard lock(ctx.registry_mux); + // if (trace) + // trace(array("unregister", name, ref_m(handle_ref(registry[name])))); + ctx.registry.erase(name); +} + +auto get_name(context_impl &ctx, const string &name) -> handle { + lock_guard lock(ctx.registry_mux); + auto r = ctx.registry.find(name); + return r != ctx.registry.end() ? r->second : handle{}; +} + +auto get_names(context_impl &ctx) -> buffer { + vector names; + { + lock_guard lock(ctx.registry_mux); + for (const auto &a : ctx.registry) + if (not a.second.expired()) + names.push_back(a.first); + } + buffer b; + b.array_header(names.size()); + for (const auto &n : names) { + b.push(n); + } + return b; +} + +namespace tcp { + +struct connection { + static constexpr string_view tag{"debug_tcp_connection"}; + context_impl &ctx; + socket s; + string prompt; + string prev_buf; + bool close_on_write_complete{false}; + + connection(context_impl &ctx, int fd, string _prompt) + : ctx{ctx}, s{socket::create(tag, fd)}, prompt(move(_prompt)) { + s.write(prompt); + s.read(); + } + + auto getword(string str, const string &delim = " ") -> pair { + string::size_type pos{}; + pos = str.find_first_of(delim); + if (pos == string::npos) + return make_pair(str, ""); + string word(str.data(), pos); + str.erase(0, pos + 1); + return make_pair(word, str); + } + + auto dispatch(string buf) -> bool { + if (buf.empty()) { + vector expired_names; + { + lock_guard lock(ctx.registry_mux); + bool first{true}; + for (const auto &a : ctx.registry) { + if (not a.second.expired()) { + if (first) + first = false; + else + s.write(" "); + s.write(a.first); + } else { + expired_names.push_back(a.first); + } + } + } + s.write("\n"); + for (const auto &a : expired_names) + debug::unregister_instance(ctx, a); + } else if (buf == "bye") { + s.write("bye\n"); + return false; + } else { + auto x = getword(buf); + buf = x.second; + handle a = get_name(ctx, x.first); + if (a.expired()) { + s.write("error: "); + s.write(x.first); + s.write(" not found\n"); + } else { + try { + buffer m; + m.push_json(buf); + auto ret = a.send_raw(m); + if (not ret) { + s.write("error: "); + s.write(ret.error().to_json()); + s.write("\n"); + } + } catch (const domain_error &e) { + s.write("error: "); + s.write(e.what()); + s.write("\n"); + } + } + } + s.write(prompt); + return true; + } + + auto receive(handle from, const buffer &m) -> result { + string buf; + int written = 0; + int err = 0; + string_view msg; + + if (m("dispatch", extract(buf))) { + if (not dispatch(buf)) + close_on_write_complete = true; + return ok(); + } + if (m("socket", tag, "read_complete", extract(buf))) { + if (buf.empty()) { + s.close(); + return ok(); + } + buf.swap(prev_buf); + buf.append(prev_buf); + string::size_type pos{}; + while (not buf.empty() and pos != string::npos) { + pos = buf.find_first_of('\n'); + if (pos != string::npos) { + auto ret = self().send("dispatch", string(buf.data(), pos)); + if (not ret) + return ret; + buf.erase(0, pos + 1); + } + } + prev_buf = buf; + s.read(); + return ok(); + } + if (m("socket", tag, "write_complete", extract(written))) { + if (close_on_write_complete) + s.close(); + return ok(); + } + if (m("socket", tag, "closed")) { + return exit("closed"); + } + if (m("socket", tag, "read_error", extract(err), extract(msg))) { + return exit("read_error", msg); + } + if (m("socket", tag, "write_error", extract(err), extract(msg))) { + return exit("write_error", msg); + } + string name; + if (auto p = handle_ref(from).lock()) + name = p->name(); + else + name = "expired"; + s.write(name); + s.write(" "); + s.write(m.to_json()); + s.write("\n"); + return ok(); + } + + static auto start(context_impl &ctx, int fd, const string &prompt) + -> expected { + return spawn( + [&ctx, fd, prompt]() { + ::thespian::receive( + [p{make_shared(ctx, fd, prompt)}](auto from, auto m) { + return p->receive(move(from), move(m)); + }); + return ok(); + }, + tag); + } +}; + +struct acceptor { + static constexpr string_view tag{"debug_acceptor_tcp"}; + acceptor(const acceptor &) = delete; + acceptor(acceptor &&) = delete; + auto operator=(const acceptor &) -> acceptor & = delete; + auto operator=(acceptor &&) -> acceptor & = delete; + + context_impl &ctx; + ::thespian::tcp::acceptor a; + handle s; + string prompt; + + acceptor(context_impl &ctx, port_t port, string _prompt) + : ctx{ctx}, a{::thespian::tcp::acceptor::create(tag)}, + prompt(move(_prompt)) { + a.listen(in6addr_loopback, port); + } + ~acceptor() = default; + + auto receive(const handle &from, const buffer &m) -> result { + int fd{}; + string err; + + if (m("acceptor", tag, "accept", extract(fd))) { + auto ret = connection::start(ctx, fd, prompt); + if (ret) + s = ret.value(); + else + return to_error(ret.error()); + } else if (m("socket", connection::tag, "closed")) { + ; + } else if (m("acceptor", tag, "closed")) { + return exit("closed"); + } else if (m("acceptor", tag, "error", extract(err))) { + return exit(err); + } else if (m("ping")) { + return from.send("pong"); + } else { + a.close(); + } + return ok(); + } + + static auto start(context_impl &ctx, port_t port, const string &prompt) + -> expected { + return spawn( + [&ctx, port, prompt]() { + ::thespian::receive( + [p{make_shared(ctx, port, prompt)}](auto from, auto m) { + return p->receive(move(from), move(m)); + }); + return ok(); + }, + tag); + } +}; + +auto create(context &ctx, port_t port, const string &prompt) + -> expected { + return acceptor::start(impl(ctx), port, prompt); +} + +} // namespace tcp +} // namespace debug +} // namespace thespian diff --git a/src/subprocess.zig b/src/subprocess.zig new file mode 100644 index 0000000..fd5fed2 --- /dev/null +++ b/src/subprocess.zig @@ -0,0 +1,233 @@ +const std = @import("std"); +const cbor = @import("cbor"); +const tp = @import("thespian.zig"); + +pid: ?tp.pid, +stdin_behavior: std.ChildProcess.StdIo, + +const Self = @This(); +pub const max_chunk_size = 4096 - 32; +pub const Writer = std.io.Writer(*Self, error{Exit}, write); +pub const BufferedWriter = std.io.BufferedWriter(max_chunk_size, Writer); + +pub fn init(a: std.mem.Allocator, argv: tp.message, tag: [:0]const u8, stdin_behavior: std.ChildProcess.StdIo) !Self { + return .{ + .pid = try Proc.create(a, argv, tag, stdin_behavior), + .stdin_behavior = stdin_behavior, + }; +} + +pub fn deinit(self: *Self) void { + if (self.pid) |pid| { + pid.deinit(); + self.pid = null; + } +} + +pub fn write(self: *Self, bytes: []const u8) error{Exit}!usize { + try self.send(bytes); + return bytes.len; +} + +pub fn send(self: *const Self, bytes_: []const u8) tp.result { + if (self.stdin_behavior != .Pipe) return tp.exit("cannot send to closed stdin"); + const pid = if (self.pid) |pid| pid else return tp.exit_error(error.Closed); + var bytes = bytes_; + while (bytes.len > 0) + bytes = loop: { + if (bytes.len > max_chunk_size) { + try pid.send(.{ "stdin", bytes[0..max_chunk_size] }); + break :loop bytes[max_chunk_size..]; + } else { + try pid.send(.{ "stdin", bytes }); + break :loop &[_]u8{}; + } + }; +} + +pub fn close(self: *Self) tp.result { + defer self.deinit(); + if (self.stdin_behavior == .Pipe) + if (self.pid) |pid| try pid.send(.{"stdin_close"}); +} + +pub fn writer(self: *Self) Writer { + return .{ .context = self }; +} + +pub fn bufferedWriter(self: *Self) BufferedWriter { + return .{ .unbuffered_writer = self.writer() }; +} + +const Proc = struct { + a: std.mem.Allocator, + receiver: Receiver, + args: std.heap.ArenaAllocator, + parent: tp.pid, + child: std.ChildProcess, + tag: [:0]const u8, + stdin_buffer: std.ArrayList(u8), + fd_stdin: ?tp.file_descriptor = null, + fd_stdout: ?tp.file_descriptor = null, + fd_stderr: ?tp.file_descriptor = null, + write_pending: bool = false, + stdin_close_pending: bool = false, + + const Receiver = tp.Receiver(*Proc); + + fn create(a: std.mem.Allocator, argv: tp.message, tag: [:0]const u8, stdin_behavior: std.ChildProcess.StdIo) !tp.pid { + const self: *Proc = try a.create(Proc); + + var args = std.heap.ArenaAllocator.init(a); + const args_a = args.allocator(); + var iter = argv.buf; + var len = cbor.decodeArrayHeader(&iter) catch return error.InvalidArgument; + var argv_ = try args_a.alloc([]const u8, len); + var arg: []const u8 = undefined; + var i: usize = 0; + while (len > 0) : (len -= 1) { + if (!(cbor.matchString(&iter, &arg) catch return error.InvalidArgument)) + return error.InvalidArgument; + argv_[i] = try args_a.dupe(u8, arg); + i += 1; + } + + var child = std.ChildProcess.init(argv_, a); + child.stdin_behavior = stdin_behavior; + child.stdout_behavior = .Pipe; + child.stderr_behavior = .Pipe; + + self.* = .{ + .a = a, + .receiver = Receiver.init(receive, self), + .args = args, + .parent = tp.self_pid().clone(), + .child = child, + .tag = try a.dupeZ(u8, tag), + .stdin_buffer = std.ArrayList(u8).init(a), + }; + return tp.spawn_link(a, self, Proc.start, tag); + } + + fn deinit(self: *Proc) void { + self.args.deinit(); + if (self.fd_stdin) |fd| fd.deinit(); + if (self.fd_stdout) |fd| fd.deinit(); + if (self.fd_stderr) |fd| fd.deinit(); + self.stdin_buffer.deinit(); + self.parent.deinit(); + self.a.free(self.tag); + } + + fn start(self: *Proc) tp.result { + errdefer self.deinit(); + + self.child.spawn() catch |e| { + try self.parent.send(.{ self.tag, "term", e, 1 }); + return tp.exit_normal(); + }; + _ = self.args.reset(.free_all); + + if (self.child.stdin_behavior == .Pipe) + self.fd_stdin = tp.file_descriptor.init("stdin", self.child.stdin.?.handle) catch |e| return tp.exit_error(e); + self.fd_stdout = tp.file_descriptor.init("stdout", self.child.stdout.?.handle) catch |e| return tp.exit_error(e); + self.fd_stderr = tp.file_descriptor.init("stderr", self.child.stderr.?.handle) catch |e| return tp.exit_error(e); + if (self.fd_stdout) |fd_stdout| fd_stdout.wait_read() catch |e| return tp.exit_error(e); + if (self.fd_stderr) |fd_stderr| fd_stderr.wait_read() catch |e| return tp.exit_error(e); + + tp.receive(&self.receiver); + } + + fn receive(self: *Proc, _: tp.pid_ref, m: tp.message) tp.result { + errdefer self.deinit(); + var bytes: []u8 = ""; + var err: i64 = 0; + var err_msg: []u8 = ""; + if (try m.match(.{ "fd", "stdout", "read_ready" })) { + try self.dispatch_stdout(); + if (self.fd_stdout) |fd_stdout| fd_stdout.wait_read() catch |e| return tp.exit_error(e); + } else if (try m.match(.{ "fd", "stderr", "read_ready" })) { + try self.dispatch_stderr(); + if (self.fd_stderr) |fd_stderr| fd_stderr.wait_read() catch |e| return tp.exit_error(e); + } else if (try m.match(.{ "fd", "stdin", "write_ready" })) { + if (self.stdin_buffer.items.len > 0) { + if (self.child.stdin) |stdin| { + const written = stdin.write(self.stdin_buffer.items) catch |e| return tp.exit_error(e); + self.write_pending = false; + defer { + if (self.stdin_close_pending and !self.write_pending) + self.stdin_close(); + } + if (written == self.stdin_buffer.items.len) { + self.stdin_buffer.clearRetainingCapacity(); + } else { + std.mem.copyForwards(u8, self.stdin_buffer.items, self.stdin_buffer.items[written..]); + self.stdin_buffer.items.len = self.stdin_buffer.items.len - written; + if (self.fd_stdin) |fd_stdin| { + fd_stdin.wait_write() catch |e| return tp.exit_error(e); + self.write_pending = true; + } + } + } + } + } else if (try m.match(.{ "stdin", tp.extract(&bytes) })) { + if (self.fd_stdin) |fd_stdin| { + self.stdin_buffer.appendSlice(bytes) catch |e| return tp.exit_error(e); + fd_stdin.wait_write() catch |e| return tp.exit_error(e); + self.write_pending = true; + } + } else if (try m.match(.{"stdin_close"})) { + if (self.write_pending) { + self.stdin_close_pending = true; + } else { + self.stdin_close(); + } + } else if (try m.match(.{"stdout_close"})) { + if (self.child.stdout) |*fd| { + fd.close(); + self.child.stdout = null; + } + } else if (try m.match(.{"stderr_close"})) { + if (self.child.stderr) |*fd| { + fd.close(); + self.child.stderr = null; + } + } else if (try m.match(.{ "fd", tp.any, "read_error", tp.extract(&err), tp.extract(&err_msg) })) { + return tp.exit(err_msg); + } + } + + fn stdin_close(self: *Proc) void { + if (self.child.stdin) |*fd| { + fd.close(); + self.child.stdin = null; + } + } + + fn dispatch_stdout(self: *Proc) tp.result { + var buffer: [max_chunk_size]u8 = undefined; + const bytes = self.child.stdout.?.read(&buffer) catch |e| return tp.exit_error(e); + if (bytes == 0) + return self.handle_terminate(); + try self.parent.send(.{ self.tag, "stdout", buffer[0..bytes] }); + } + + fn dispatch_stderr(self: *Proc) tp.result { + var buffer: [max_chunk_size]u8 = undefined; + const bytes = self.child.stderr.?.read(&buffer) catch |e| return tp.exit_error(e); + if (bytes == 0) + return; + try self.parent.send(.{ self.tag, "stderr", buffer[0..bytes] }); + } + + fn handle_terminate(self: *Proc) tp.result { + const term = self.child.wait() catch |e| return tp.exit_error(e); + (switch (term) { + .Exited => |val| self.parent.send(.{ self.tag, "term", "exited", val }), + .Signal => |val| self.parent.send(.{ self.tag, "term", "signal", val }), + .Stopped => |val| self.parent.send(.{ self.tag, "term", "stop", val }), + .Unknown => |val| self.parent.send(.{ self.tag, "term", "unknown", val }), + }) catch {}; + return tp.exit_normal(); + } +}; diff --git a/src/thespian.zig b/src/thespian.zig new file mode 100644 index 0000000..70b2352 --- /dev/null +++ b/src/thespian.zig @@ -0,0 +1,716 @@ +const std = @import("std"); +const c = @cImport({ + @cInclude("thespian/c/file_descriptor.h"); + @cInclude("thespian/c/instance.h"); + @cInclude("thespian/c/metronome.h"); + @cInclude("thespian/c/timeout.h"); + @cInclude("thespian/c/signal.h"); + @cInclude("thespian/backtrace.h"); +}); +const cbor = @import("cbor"); +pub const subprocess = @import("subprocess.zig"); + +pub const install_debugger = c.install_debugger; +pub const max_message_size = 8 * 4096; +threadlocal var message_buffer: [max_message_size]u8 = undefined; +threadlocal var error_message_buffer: [256]u8 = undefined; +threadlocal var error_buffer_tl: c.thespian_error = .{ + .base = null, + .len = 0, +}; + +pub const result = error{Exit}!void; +pub const extract = cbor.extract; +pub const extract_cbor = cbor.extract_cbor; + +pub const number = cbor.number; +pub const bytes = cbor.bytes; +pub const string = cbor.string; +pub const array = cbor.array; +pub const map = cbor.map; +pub const tag = cbor.tag; +pub const boolean = cbor.boolean; +pub const null_ = cbor.null_; +pub const any = cbor.any; +pub const more = cbor.more; + +pub const Ownership = enum { + Wrapped, + Owned, +}; + +fn Pid(comptime own: Ownership) type { + return struct { + h: c.thespian_handle, + + const ownership = own; + const Self = @This(); + + pub fn clone(self: Self) Pid(.Owned) { + return .{ .h = c.thespian_handle_clone(self.h) }; + } + + pub fn ref(self: Self) Pid(.Wrapped) { + return .{ .h = self.h }; + } + + pub fn deinit(self: *const Self) void { + comptime if (ownership == Ownership.Wrapped) + @compileError("cannot call deinit on Pid(.Wrapped)"); + c.thespian_handle_destroy(self.h); + } + + pub fn send_raw(self: Self, m: message) result { + const ret = c.thespian_handle_send_raw(self.h, m.to(c.cbor_buffer)); + if (ret) |err| + return set_error(err.*); + } + + pub fn send(self: Self, m: anytype) result { + return self.send_raw(message.fmt(m)); + } + + pub fn call(self: Self, request: anytype) !message { + return CallContext.call(self.ref(), message.fmt(request)); + } + + pub fn link(self: Self) result { + return c.thespian_link(self.h); + } + + pub fn expired(self: Self) bool { + return c.thespian_handle_is_expired(self.h); + } + + pub fn wait_expired(self: Self, timeout_ns: isize) !void { + var max_sleep: isize = timeout_ns; + while (!self.expired()) { + if (max_sleep <= 0) return error.Timeout; + std.time.sleep(100); + max_sleep -= 100; + } + } + }; +} +pub const pid = Pid(.Owned); +pub const pid_ref = Pid(.Wrapped); +fn wrap_handle(h: c.thespian_handle) pid_ref { + return .{ .h = h }; +} +fn wrap_pid(h: c.thespian_handle) pid { + return .{ .h = h }; +} + +pub const message = struct { + buf: buffer_type = "", + + const Self = @This(); + pub const buffer_type: type = []const u8; + pub const c_buffer_type = c.cbor_buffer; + + pub fn fmt(value: anytype) Self { + return fmtbuf(&message_buffer, value) catch unreachable; + } + + pub fn fmtbuf(buf: []u8, value: anytype) !Self { + const f = comptime switch (@typeInfo(@TypeOf(value))) { + .Struct => |info| if (info.is_tuple) + fmtbufInternal + else + @compileError("thespian.message template should be a tuple: " ++ @typeName(@TypeOf(value))), + else => fmtbufInternalScalar, + }; + return f(buf, value); + } + + fn fmtbufInternalScalar(buf: []u8, value: anytype) !Self { + return fmtbufInternal(buf, .{value}); + } + + fn fmtbufInternal(buf: []u8, value: anytype) !Self { + var stream = std.io.fixedBufferStream(buf); + try cbor.writeValue(stream.writer(), value); + return .{ .buf = stream.getWritten() }; + } + + pub fn len(self: Self) usize { + return self.buf.len; + } + + pub fn from(span: anytype) Self { + return .{ .buf = span.base[0..span.len] }; + } + + pub fn to(self: *const Self, comptime T: type) T { + return T{ .base = self.buf.ptr, .len = self.buf.len }; + } + + pub fn clone(self: *const Self, a: std.mem.Allocator) !Self { + return .{ .buf = try a.dupe(u8, self.buf) }; + } + + pub fn to_json(self: *const Self, buffer: []u8) cbor.CborJsonError![]const u8 { + return cbor.toJson(self.buf, buffer); + } + + pub const json_string_view = c.c_string_view; + const json_callback = *const fn (json_string_view) callconv(.C) void; + + pub fn to_json_cb(self: *const Self, callback: json_callback) void { + c.cbor_to_json(self.to(c_buffer_type), callback); + } + pub fn match(self: Self, m: anytype) error{Exit}!bool { + return if (cbor.match(self.buf, m)) |ret| ret else |e| exit_error(e); + } +}; + +pub fn exit_message(e: anytype) message { + return message.fmtbuf(&error_message_buffer, .{ "exit", e }) catch unreachable; +} + +pub fn exit_normal() result { + return set_error_msg(exit_message("normal")); +} + +pub fn exit(e: []const u8) error{Exit} { + return set_error_msg(exit_message(e)); +} + +pub fn exit_fmt(comptime fmt: anytype, args: anytype) result { + var buf: [1024]u8 = undefined; + const msg = std.fmt.bufPrint(&buf, fmt, args) catch "FMTERROR"; + return set_error_msg(exit_message(msg)); +} + +pub fn exit_error(e: anyerror) error{Exit} { + return switch (e) { + error.Exit => error.Exit, + else => set_error_msg(exit_message(e)), + }; +} + +pub fn unexpected(b: message) error{Exit} { + const txt = "UNEXPECTED_MESSAGE: "; + var buf: [max_message_size]u8 = undefined; + @memcpy(buf[0..txt.len], txt); + const json = b.to_json(buf[txt.len..]) catch |e| return exit_error(e); + return set_error_msg(message.fmt(.{ "exit", buf[0..(txt.len + json.len)] })); +} + +pub fn error_message() []const u8 { + if (error_buffer_tl.base) |base| + return base[0..error_buffer_tl.len]; + set_error_msg(exit_message("NOERROR")) catch {}; + return error_message(); +} + +pub fn error_text() []const u8 { + const msg_: message = .{ .buf = error_message() }; + var msg__: []const u8 = undefined; + return if (msg_.match(.{ "exit", extract(&msg__) }) catch false) + msg__ + else + &[_]u8{}; +} + +pub fn self_pid() pid_ref { + return wrap_handle(c.thespian_self()); +} + +pub const trace_channel = c.thespian_trace_channel; +pub const channel = struct { + pub const send = c.thespian_trace_channel_send; + pub const receive = c.thespian_trace_channel_receive; + pub const lifetime = c.thespian_trace_channel_lifetime; + pub const link = c.thespian_trace_channel_link; + pub const execute = c.thespian_trace_channel_execute; + pub const udp = c.thespian_trace_channel_udp; + pub const tcp = c.thespian_trace_channel_tcp; + pub const timer = c.thespian_trace_channel_timer; + pub const metronome = c.thespian_trace_channel_metronome; + pub const endpoint = c.thespian_trace_channel_endpoint; + pub const signal = c.thespian_trace_channel_signal; + pub const event: c.thespian_trace_channel = 2048; + pub const widget: c.thespian_trace_channel = 4096; + pub const input: c.thespian_trace_channel = 8192; + pub const all = c.thespian_trace_channel_all; +}; + +pub const env = struct { + env: *c.thespian_env_t, + owned: bool = false, + + const Self = @This(); + + pub fn get() Self { + return .{ .env = c.thespian_env_get() orelse unreachable, .owned = false }; + } + + pub fn init() Self { + return .{ .env = c.thespian_env_new() orelse unreachable, .owned = true }; + } + + pub fn clone(self: *const Self) Self { + return .{ .env = c.thespian_env_clone(self.env) orelse unreachable, .owned = true }; + } + + pub fn deinit(self: Self) void { + if (self.owned) c.thespian_env_destroy(self.env); + } + + pub fn enable_all_channels(self: *const Self) void { + c.thespian_env_enable_all_channels(self.env); + } + + pub fn disable_all_channels(self: *const Self) void { + c.thespian_env_disable_all_channels(self.env); + } + + pub fn enable(self: *const Self, chan: c.thespian_trace_channel) void { + c.thespian_env_enable(self.env, chan); + } + + pub fn disable(self: *const Self, chan: c.thespian_trace_channel) void { + c.thespian_env_disable(self.env, chan); + } + + pub fn enabled(self: *const Self, chan: c.thespian_trace_channel) bool { + return c.thespian_env_enabled(self.env, chan); + } + + pub const trace_handler = *const fn (c.cbor_buffer) callconv(.C) void; + + pub fn on_trace(self: *const Self, h: trace_handler) void { + c.thespian_env_on_trace(self.env, h); + } + + pub fn trace(self: *const Self, m: c.cbor_buffer) void { + c.thespian_env_trace(self.env, m); + } + + pub fn is(self: *const Self, key: []const u8) bool { + return c.thespian_env_is(self.env, .{ .base = key.ptr, .len = key.len }); + } + pub fn set(self: *const Self, key: []const u8, value: bool) void { + c.thespian_env_set(self.env, .{ .base = key.ptr, .len = key.len }, value); + } + + pub fn num(self: *const Self, key: []const u8) i64 { + return c.thespian_env_num(self.env, .{ .base = key.ptr, .len = key.len }); + } + pub fn num_set(self: *const Self, key: []const u8, value: i64) void { + c.thespian_env_num_set(self.env, .{ .base = key.ptr, .len = key.len }, value); + } + + pub fn str(self: *const Self, key: []const u8) []const u8 { + const ret = c.thespian_env_str(self.env, .{ .base = key.ptr, .len = key.len }); + return ret.base[0..ret.len]; + } + pub fn str_set(self: *const Self, key: []const u8, value: []const u8) void { + c.thespian_env_str(self.env, .{ .base = key.ptr, .len = key.len }, .{ .base = value.ptr, .len = value.len }); + } + + pub fn proc(self: *const Self, key: []const u8) pid_ref { + return wrap_handle(c.thespian_env_proc(self.env, .{ .base = key.ptr, .len = key.len })); + } + pub fn proc_set(self: *const Self, key: []const u8, value: pid_ref) void { + c.thespian_env_proc_set(self.env, .{ .base = key.ptr, .len = key.len }, value.h); + } +}; + +pub fn trace(chan: trace_channel, value: anytype) void { + if (env.get().enabled(chan)) { + if (@TypeOf(value) == message) { + env.get().trace(value.to(message.c_buffer_type)); + } else { + var trace_buffer: [512]u8 = undefined; + const m = message.fmtbuf(&trace_buffer, value); + env.get().trace(m.to(message.c_buffer_type)); + } + } +} + +pub const context = struct { + a: std.mem.Allocator, + context: c.thespian_context, + context_destroy: *const fn (?*anyopaque) callconv(.C) void, + + const Self = @This(); + + pub fn init(a: std.mem.Allocator) !Self { + var ctx_destroy: c.thespian_context_destroy = null; + const ctx = c.thespian_context_create(&ctx_destroy) orelse return error.ThespianContextCreateFailed; + return .{ + .a = a, + .context = ctx, + .context_destroy = ctx_destroy orelse return error.ThespianContextCreateFailed, + }; + } + + pub fn run(self: *Self) void { + c.thespian_context_run(self.context); + } + + pub fn spawn_link( + self: *const Self, + data: anytype, + f: Behaviour(@TypeOf(data)).FunT, + name: [:0]const u8, + eh: anytype, + env_: ?*const env, + ) !pid { + const Tclosure = Behaviour(@TypeOf(data)); + var handle_: c.thespian_handle = null; + try neg_to_error(c.thespian_context_spawn_link( + self.context, + Tclosure.run, + try Tclosure.create(self.a, f, data), + exitHandlerRun(@TypeOf(eh)), + eh, + name, + if (env_) |env__| env__.env else null, + &handle_, + ), error.ThespianSpawnFailed); + return .{ .h = handle_ }; + } + + pub fn deinit(self: *Self) void { + self.context_destroy(self.context); + } +}; + +fn exitHandlerRun(comptime T: type) c.thespian_exit_handler { + if (T == @TypeOf(null)) return null; + return switch (@typeInfo(T)) { + .Optional => |info| switch (@typeInfo(info.child)) { + .Pointer => |ptr_info| switch (ptr_info.size) { + .One => ptr_info.child.run, + else => @compileError("expected single item pointer, found: '" ++ @typeName(T) ++ "'"), + }, + }, + .Pointer => |ptr_info| switch (ptr_info.size) { + .One => ptr_info.child.run, + else => @compileError("expected single item pointer, found: '" ++ @typeName(T) ++ "'"), + }, + else => @compileError("expected optional pointer or pointer type, found: '" ++ @typeName(T) ++ "'"), + }; +} + +pub fn receive(r: anytype) void { + const T = switch (@typeInfo(@TypeOf(r))) { + .Pointer => |ptr_info| switch (ptr_info.size) { + .One => ptr_info.child, + else => @compileError("invalid receiver type"), + }, + else => @compileError("invalid receiver type"), + }; + c.thespian_receive(T.run, r); +} + +pub fn Receiver(comptime T: type) type { + return struct { + f: FunT, + data: T, + const FunT: type = *const fn (T, from: pid_ref, m: message) result; + const Self = @This(); + pub fn init(f: FunT, data: T) Self { + return .{ .f = f, .data = data }; + } + pub fn run(ostate: c.thespian_behaviour_state, from: c.thespian_handle, m: c.cbor_buffer) callconv(.C) c.thespian_result { + const state: *Self = @ptrCast(@alignCast(ostate orelse unreachable)); + reset_error(); + return to_result(state.f(state.data, wrap_handle(from), message.from(m))); + } + }; +} + +pub fn get_trap() bool { + return c.thespian_get_trap(); +} + +pub fn set_trap(on: bool) bool { + return c.thespian_set_trap(on); +} + +pub fn spawn_link( + a: std.mem.Allocator, + data: anytype, + f: Behaviour(@TypeOf(data)).FunT, + name: [:0]const u8, +) !pid { + return spawn_link_env(a, data, f, name, env.get()); +} + +pub fn spawn_link_env( + a: std.mem.Allocator, + data: anytype, + f: Behaviour(@TypeOf(data)).FunT, + name: [:0]const u8, + env_: ?env, +) !pid { + const Tclosure = Behaviour(@TypeOf(data)); + var handle_: c.thespian_handle = null; + try neg_to_error(c.thespian_spawn_link( + Tclosure.run, + try Tclosure.create(a, f, data), + name, + if (env_) |env__| env__.env else null, + &handle_, + ), error.ThespianSpawnFailed); + return wrap_pid(handle_); +} + +pub fn neg_to_error(errcode: c_int, errortype: anyerror) !void { + if (errcode < 0) return errortype; +} + +pub fn nonzero_to_error(errcode: c_int, errortype: anyerror) !void { + if (errcode != 0) return errortype; +} + +pub fn wrap(comptime f: anytype, errortype: anyerror) fn (std.meta.ArgsTuple(@TypeOf(f))) @TypeOf(errortype)!void { + const Local = struct { + pub fn wrapped(args: std.meta.ArgsTuple(@TypeOf(f))) @TypeOf(errortype)!void { + return nonzero_to_error(@call(.auto, f, args), errortype); + } + }; + return Local.wrapped; +} + +fn Behaviour(comptime T: type) type { + return struct { + a: std.mem.Allocator, + f: FunT, + data: T, + const FunT: type = *const fn (T) result; + const Self = @This(); + + pub fn create(a: std.mem.Allocator, f: FunT, data: T) !*Self { + const self: *Self = try a.create(Self); + self.* = .{ .a = a, .f = f, .data = data }; + return self; + } + + pub fn destroy(self: *Self) void { + self.a.destroy(self); + } + + pub fn run(state: c.thespian_behaviour_state) callconv(.C) c.thespian_result { + const self: *Self = @ptrCast(@alignCast(state orelse unreachable)); + defer self.destroy(); + reset_error(); + return to_result(self.f(self.data)); + } + }; +} + +fn to_result(ret: result) c.thespian_result { + if (ret) |_| { + return null; + } else |_| { + const msg = error_message(); + if (!(cbor.match(msg, .{ "exit", "normal" }) catch false)) { + if (env.get().is("dump-stack-trace")) { + const trace_ = @errorReturnTrace(); + if (trace_) |t| std.debug.dumpStackTrace(t.*); + } + } + return &error_buffer_tl; + } +} + +fn set_error_msg(m: message) error{Exit} { + return set_error(m.to(c.thespian_error)); +} + +fn set_error(e: c.thespian_error) error{Exit} { + error_buffer_tl = e; + return error.Exit; +} + +pub fn reset_error() void { + error_buffer_tl = .{ .base = null, .len = 0 }; +} + +pub fn make_exit_handler(data: anytype, f: ExitHandler(@TypeOf(data)).FunT) ExitHandler(@TypeOf(data)) { + return ExitHandler(@TypeOf(data)).init(f, data); +} + +fn ExitHandler(comptime T: type) type { + return struct { + a: ?std.mem.Allocator = null, + f: FunT, + data: T, + const FunT: type = *const fn (T, []const u8) void; + const Self = @This(); + + pub fn init(f: FunT, data: T) Self { + return .{ .f = f, .data = data }; + } + + pub fn create(a: std.mem.Allocator, f: FunT, data: T) !*Self { + const self: *Self = try a.create(Self); + self.* = .{ .a = a, .f = f, .data = data }; + return self; + } + + pub fn destroy(self: *Self) void { + if (self.a) |a_| a_.destroy(self); + } + + pub fn run(state: c.thespian_exit_handler_state, msg: [*c]const u8, len: usize) callconv(.C) void { + const self: *Self = @ptrCast(@alignCast(state orelse unreachable)); + defer self.destroy(); + self.f(self.data, msg[0..len]); + } + }; +} + +pub const signal = struct { + handle: *c.struct_thespian_signal_handle, + + const Self = @This(); + + pub fn init(signum: c_int, m: message) !Self { + return .{ .handle = c.thespian_signal_create(signum, m.to(c.cbor_buffer)) orelse return error.ThespianSignalInitFailed }; + } + + pub fn cancel(self: *const Self) !void { + return neg_to_error(c.thespian_signal_cancel(self.handle), error.ThespianSignalCancelFailed); + } + + pub fn deinit(self: *const Self) void { + c.thespian_signal_destroy(self.handle); + } +}; + +pub const metronome = struct { + handle: *c.struct_thespian_metronome_handle, + + const Self = @This(); + + pub fn init(tick_time_us: u64) !Self { + return .{ .handle = c.thespian_metronome_create_us(tick_time_us) orelse return error.ThespianMetronomeInitFailed }; + } + + pub fn init_ms(tick_time_us: u64) !Self { + return .{ .handle = c.thespian_metronome_create_ms(tick_time_us) orelse return error.ThespianMetronomeInitFailed }; + } + + pub fn start(self: *const Self) !void { + return neg_to_error(c.thespian_metronome_start(self.handle), error.ThespianMetronomeStartFailed); + } + + pub fn stop(self: *const Self) !void { + return neg_to_error(c.thespian_metronome_stop(self.handle), error.ThespianMetronomeStopFailed); + } + + pub fn deinit(self: *const Self) void { + c.thespian_metronome_destroy(self.handle); + } +}; + +pub const timeout = struct { + handle: *c.struct_thespian_timeout_handle, + + const Self = @This(); + + pub fn init(tick_time_us: u64, m: message) !Self { + return .{ .handle = c.thespian_timeout_create_us(tick_time_us, m.to(c.cbor_buffer)) orelse return error.ThespianTimeoutInitFailed }; + } + + pub fn init_ms(tick_time_us: u64, m: message) !Self { + return .{ .handle = c.thespian_timeout_create_ms(tick_time_us, m.to(c.cbor_buffer)) orelse return error.ThespianTimeoutInitFailed }; + } + + pub fn start(self: *const Self) !void { + return neg_to_error(c.thespian_timeout_start(self.handle), error.ThespianTimeoutStartFailed); + } + + pub fn stop(self: *const Self) !void { + return neg_to_error(c.thespian_timeout_stop(self.handle), error.ThespianTimeoutStopFailed); + } + + pub fn deinit(self: *const Self) void { + c.thespian_timeout_destroy(self.handle); + } +}; + +pub const file_descriptor = struct { + handle: *c.struct_thespian_file_descriptor_handle, + + const Self = @This(); + + pub fn init(tag_: []const u8, fd: i32) !Self { + return .{ .handle = c.thespian_file_descriptor_create(tag_.ptr, fd) orelse return error.ThespianFileDescriptorInitFailed }; + } + + pub fn wait_write(self: *const Self) !void { + return neg_to_error(c.thespian_file_descriptor_wait_write(self.handle), error.ThespianFileDescriptorWaitWriteFailed); + } + + pub fn wait_read(self: *const Self) !void { + return neg_to_error(c.thespian_file_descriptor_wait_read(self.handle), error.ThespianFileDescriptorWaitReadFailed); + } + + pub fn cancel(self: *const Self) !void { + return neg_to_error(c.thespian_file_descriptor_cancel(self.handle), error.ThespianFileDescriptorCancelFailed); + } + + pub fn deinit(self: *const Self) void { + c.thespian_file_descriptor_destroy(self.handle); + } +}; + +const CallContext = struct { + receiver: ReceiverT, + to: pid_ref, + request: message, + response: ?message, + a: std.mem.Allocator, + + const Self = @This(); + const ReceiverT = Receiver(*Self); + + pub fn call(to: pid_ref, request: message) !message { + var heap: [32 + 1024]u8 = undefined; + var fba = std.heap.FixedBufferAllocator.init(&heap); + const a = fba.allocator(); + + var self: Self = undefined; + const rec = ReceiverT.init(receive_, &self); + + self = .{ + .receiver = rec, + .to = to, + .request = request, + .response = null, + .a = a, + }; + + const proc = try spawn_link(a, &self, start, @typeName(Self)); + while (!proc.expired()) { + std.time.sleep(50); + } + if (self.response) |resp| { + const m = message_buffer[0..resp.buf.len]; + @memcpy(m, resp.buf); + return .{ .buf = m }; + } else { + return .{}; + } + } + + fn start(self: *Self) result { + _ = set_trap(true); + try self.to.link(); + try self.to.send_raw(self.request); + receive(&self.receiver); + } + + fn receive_(self: *Self, from: pid_ref, m: message) result { + _ = from; + self.response = m.clone(self.a) catch |e| return exit_error(e); + try exit_normal(); + } +}; diff --git a/src/trace.cpp b/src/trace.cpp new file mode 100644 index 0000000..bc187db --- /dev/null +++ b/src/trace.cpp @@ -0,0 +1,166 @@ +#include + +#include +#include +#include +#include +#include + +using cbor::any; +using cbor::array; +using cbor::buffer; +using cbor::extract; +using cbor::more; +using std::atomic; +using std::fstream; +using std::ios_base; +using std::lock_guard; +using std::make_shared; +using std::ostream; +using std::recursive_mutex; +using std::string; +using std::string_view; +using std::stringstream; + +namespace thespian { +constexpr auto trace_file_flags = + ios_base::binary | ios_base::out | ios_base::ate; +const auto trace_buf_size = 64U * 1024U; + +template struct trace_file { + trace_file(const trace_file &) = delete; + trace_file(const trace_file &&) = delete; + auto operator=(const trace_file &) -> trace_file & = delete; + auto operator=(trace_file &&) -> trace_file & = delete; + + struct node { + Q data{}; + node *next{}; + }; + atomic head{nullptr}; + recursive_mutex m; + fstream s; + char buf[trace_buf_size]{}; // NOLINT + + explicit trace_file(const string &file_name) + : s(file_name, trace_file_flags) { + s.rdbuf()->pubsetbuf(buf, trace_buf_size); + } + ~trace_file() { + while (do_work()) + ; + } + + auto reverse(node *p) -> node * { + node *prev{nullptr}; + node *next{nullptr}; + while (p) { + next = p->next; + p->next = prev; + prev = p; + p = next; + } + return prev; + } + auto push(Q data) -> void { + auto p = new node{move(data), head}; + while (!head.compare_exchange_weak(p->next, p)) + ; + while (do_work()) + ; + } + auto do_work() -> bool { + if (!m.try_lock()) + return false; + lock_guard lock(m); + m.unlock(); + auto p = head.load(); + while (p && !head.compare_exchange_weak(p, nullptr)) { + ; + } + if (!p) + return false; + p = reverse(p); + while (p) { + static_cast(this)->on_trace(move(p->data)); + auto p_ = p; + p = p->next; + delete p_; + } + return true; + } +}; + +auto to_mermaid(ostream &s, const buffer &m) -> void { + string_view from; + string_view typ; + if (not m(A(any, any, extract(from)), extract(typ), more)) + return; + if (from.empty()) + from = "UNKNOWN"; + if (typ == "spawn") { + s << " Note right of " << from << ": SPAWN\n"; + } else if (typ == "receive") { + // ignore + } else if (typ == "run") { + s << " activate " << from << '\n'; + } else if (typ == "sleep") { + s << " deactivate " << from << '\n'; + } else if (typ == "send") { + string_view to; + buffer::range msg; + if (m(any, any, A(any, any, extract(to)), extract(msg))) { + s << " " << from << "->>" << to << ": send "; + msg.to_json(s); + s << '\n'; + } + } else if (typ == "link") { + string_view to; + if (m(any, any, A(any, any, extract(to)))) + s << " " << from << "-->>" << to << ": create link\n"; + } else if (typ == "exit") { + string_view msg; + if (m(any, any, A(any, extract(msg), more))) + s << " Note right of " << from << ": EXIT " << msg << '\n'; + } +} + +static const auto cbor_cr = array("\n"); + +auto trace_to_cbor_file(const string &file_name) -> void { + struct cbor_file : trace_file { + using trace_file::trace_file; + auto on_trace(const buffer &msg) -> void { + s.write(reinterpret_cast(msg.data()), msg.size()); // NOLINT + s.write(reinterpret_cast(cbor_cr.data()), // NOLINT + cbor_cr.size()); // NOLINT + } + }; + auto f = make_shared(file_name); + on_trace([f](auto m) { f->push(move(m)); }); +} + +auto trace_to_json_file(const string &file_name) -> void { + struct json_file : trace_file { + using trace_file::trace_file; + auto on_trace(const buffer &msg) -> void { + msg.to_json(s); + s << '\n'; + } + }; + auto f = make_shared(file_name); + on_trace([f](auto m) { f->push(move(m)); }); +} + +auto trace_to_mermaid_file(const string &file_name) -> void { + struct mermaid_file : trace_file { + explicit mermaid_file(const string &_file_name) : trace_file(_file_name) { + s << "sequenceDiagram\n"; + } + auto on_trace(const buffer &msg) -> void { to_mermaid(s, msg); } + }; + auto f = make_shared(file_name); + on_trace([f](auto m) { f->push(move(m)); }); +} + +} // namespace thespian diff --git a/test/cbor_match.cpp b/test/cbor_match.cpp new file mode 100644 index 0000000..c6a0289 --- /dev/null +++ b/test/cbor_match.cpp @@ -0,0 +1,315 @@ +#include "tests.hpp" + +#include +#include + +#include +#include + +using cbor::A; +using cbor::array; +using cbor::buffer; +using cbor::extract; +using cbor::M; +using cbor::map; +using cbor::type; +using std::move; +using std::string; +using std::string_view; +using std::stringstream; +using thespian::context; +using thespian::env; +using thespian::env_t; +using thespian::link; +using thespian::ok; +using thespian::result; + +namespace { + +auto test() -> result { + auto verbose = env().is("verbose"); + auto log = env().proc("log"); + result _; + + buffer m = array("five", 5, "four", 4, array("three", 3)); + + if (verbose) { + _ = log.send("message:", m.to_json()); + _ = log.send("buffer:", m.hexdump()); + } + + check(m.hexdump() == + "21: 85 64 66 69 76 65 05 64 66 6f 75 72 04 82 65 74 68 72 65 65 03"); + + check(m.to_json() == R"(["five",5,"four",4,["three",3]])"); + + check(m(type::string, type::number, type::string, type::number, type::array)); + check(!m(type::string, type::number, type::string, type::number)); + check(!m(type::number, type::string, type::number, type::array, type::any)); + check(m(type::any, type::number, type::string, type::number, type::any)); + + check(m("five", type::number, type::string, 4, type::any)); + check(m("five", 5, "four", 4, type::array)); + check(m("five", 5, type::more)); + check(m("five", 5, "four", 4, type::array, type::more)); + check(!m("five", 5, "four", 4, type::array, type::any)); + + check(array().is_null()); + check(!array(1).is_null()); + { + buffer b; + b.array_header(5) + .push("five") + .push(5) + .push("four") + .push(4) + .array_header(2) + .push("three") + .push(3); + check(m == b); + } + { + buffer b = array(false, true, 5, "five"); + check(b == array(false, true, 5, "five")); + check(b(false, true, 5, "five")); + check(!b(true, false, 5, "five")); + bool t = false; + check(b(extract(t), true, 5, "five")); + check(!t); + check(b(false, extract(t), 5, "five")); + check(t); + } + { + buffer::range r; + check( + m(type::string, type::number, type::string, type::number, extract(r))); + check(r(type::string, type::number)); + check(r("three", 3)); + } + { + int64_t i{}; + check(m(type::string, type::number, type::string, type::number, + A(type::string, extract(i)))); + check(i == 3); + } + { + buffer ref = array(array(124), array("blip")); + check(ref(A(type::more), A(type::more))); + } + { + int i{0}; + auto dump = [&](string_view val) { + ++i; + if (verbose) + _ = log.send("value", i, ":", val); + }; + buffer a = array("five", "four", "three", "two", "one"); + for (const auto val : a) + dump(val); + check(i == 5); + } + { + int i{0}; + auto dump = [&](auto val) { + ++i; + if (verbose) + _ = log.send("value", i, ":", val); + }; + for (const auto val : m) + val.visit(dump); + check(i == 5); + + buffer::range r; + check(m(type::any, type::any, type::any, type::any, extract(r))); + for (const auto val : r) + val.visit(dump); + check(i == 7); + } + { + buffer a = array(array(2, 3, 4, 5), array(6, 7, 8, 9)); + check(a(A(2, 3, 4, 5), A(6, 7, 8, 9))); + + buffer::range a1; + buffer::range a2; + check(a(extract(a1), extract(a2))); + + int i{0}; + auto dump = [&](uint32_t val) { + ++i; + if (verbose) + _ = log.send("value", i, ":", val); + }; + for (const auto val : a1) + dump(val); + } + { + buffer a = array(array(2, 3, 4, 5), array(6, 7, 8, 9)); + buffer::range a1; + buffer::range a2; + check(a(extract(a1), extract(a2))); + + buffer b = array(a1); + check(b(A(2, 3, 4, 5))); + + buffer b2; + b2.push(a2); + check(b2(6, 7, 8, 9)); + } + { + buffer a = array(1, 2, map(3, array(3))); + + if (verbose) { + _ = log.send("map buffer:", a.hexdump()); + _ = log.send("map json:", a.to_json()); + } + check(a(1, 2, M(3, A(3)))); + buffer::range a1; + check(a(1, 2, extract(a1))); + if (verbose) + _ = log.send("map range json:", a1.to_json()); + check(a1.to_json() == "{3:[3]}"); + } + { + buffer a; + a.push_json(R"(["five", 5, "four", 4, ["three", 3]])"); + if (verbose) { + _ = log.send("buffer:", a.hexdump()); + _ = log.send("message:", a.to_json()); + } + check(a.hexdump() == "21: 85 64 66 69 76 65 05 64 66 6f 75 72 04 82 " + "65 74 68 72 65 65 03"); + } + { + buffer a; + a.push_json(R"({"five": 5, "four": 4, "three": [{3:3}]})"); + string json = a.to_json(); + if (verbose) { + _ = log.send("buffer:", a.hexdump()); + _ = log.send("json:", json); + } + check(a.hexdump() == "23: a3 64 66 69 76 65 05 64 66 6f 75 72 04 65 " + "74 68 72 65 65 81 a1 03 03"); + check(json == R"({"five":5,"four":4,"three":[{3:3}]})"); + } + { + buffer a = array("string\r\n", "\tstring\t", "\r\nstring"); + string json = a.to_json(); + if (verbose) { + _ = log.send("escaped buffer:", a.hexdump()); + _ = log.send("escaped json:", json); + } + check(a.hexdump() == "28: 83 68 73 74 72 69 6e 67 0d 0a 68 09 73 74 " + "72 69 6e 67 09 68 0d 0a 73 74 72 69 6e 67"); + check(json == R"(["string\r\n","\tstring\t","\r\nstring"])"); + } + { + buffer a; + a.push_json(R"(["string\r\n", "\tstring\t", "\r\nstring"])"); + string json = a.to_json(); + if (verbose) { + _ = log.send("escaped2 buffer:", a.hexdump()); + _ = log.send("escaped2 json:", json); + } + check(a.hexdump() == "28: 83 68 73 74 72 69 6e 67 0d 0a 68 09 73 74 " + "72 69 6e 67 09 68 0d 0a 73 74 72 69 6e 67"); + check(json == R"(["string\r\n","\tstring\t","\r\nstring"])"); + } + { + buffer a = array(array()); + buffer::range r; + check(a(extract(r))); + int i{0}; + auto dump = [&](uint32_t /*val*/) { ++i; }; + for (const auto v1 : r) + dump(v1); + check(i == 0); + check(r.is_null()); + check(r.to_json() == "null"); + } + { + int64_t i{-1}; + uint64_t sz{0}; + buffer a = array(i, sz); + check(a(extract(i), extract(sz))); + check(i == -1); + check(sz == 0); + } + { + int64_t i{-1}; + uint64_t sz{0}; + buffer a = array(i); + check(a(extract(i))); + check(not a(extract(sz))); + } + { + int32_t i{-1}; + uint32_t sz{0}; + buffer a = array(i); + check(a(extract(i))); + check(not a(extract(sz))); + } + { + int16_t i{-1}; + uint16_t sz{0}; + buffer a = array(i); + check(a(extract(i))); + check(not a(extract(sz))); + } + { + int8_t i{-1}; + uint8_t sz{0}; + buffer a = array(i); + check(a(extract(i))); + check(not a(extract(sz))); + } + { + uint16_t ui{1000}; + uint8_t sz{0}; + buffer a = array(ui); + check(a(extract(ui))); + check(not a(extract(sz))); + } + { + uint64_t ui{18446744073709551615ULL}; + int64_t i{0}; + buffer a = array(ui); + check(a(extract(ui))); + check(not a(extract(i))); + } + { + buffer a = array(array(1, 2)); + buffer b = array(array(1, 2)); + buffer::range ra; + check(a(extract(ra))); + buffer::range rb; + check(b(extract(rb))); + check(ra == rb); + } + { + buffer a = array(array(1)); + buffer b = array(array(1, 2)); + buffer::range ra; + check(a(extract(ra))); + buffer::range rb; + check(b(extract(rb))); + check(not(ra == rb)); + } + if (verbose) + _ = log.send("done"); + return ok(); +} + +} // namespace + +auto cbor_match(context &ctx, bool &result, env_t env_) -> ::result { + return to_result(ctx.spawn_link( + [=]() { + link(env().proc("log")); + return test(); + }, + [&](auto s) { + check(s == "noreceive"); + result = true; + }, + "cbor_match", move(env_))); +}; diff --git a/test/debug.cpp b/test/debug.cpp new file mode 100644 index 0000000..0a1399c --- /dev/null +++ b/test/debug.cpp @@ -0,0 +1,196 @@ +#include "tests.hpp" + +#include "thespian/handle.hpp" +#include +#include +#include +#include +#include + +#include +#include +#include + +using cbor::buffer; +using cbor::extract; +using std::make_shared; +using std::make_unique; +using std::move; +using std::string; +using std::string_view; +using std::stringstream; +using std::unique_ptr; +using thespian::context; +using thespian::env; +using thespian::env_t; +using thespian::error; +using thespian::exit; +using thespian::expected; +using thespian::handle; +using thespian::link; +using thespian::ok; +using thespian::receive; +using thespian::result; +using thespian::self; +using thespian::socket; +using thespian::spawn_link; +using thespian::trap; +using thespian::unexpected; +using thespian::tcp::connector; + +namespace { + +struct debuggee { + auto receive(const handle &from, const buffer &m) { + if (m("ping")) { + return from.send("pong"); + } + if (m("shutdown")) + return exit("debuggee_shutdown"); + return unexpected(m); + return ok(); + } + + static auto start() -> expected { + return spawn_link( + [=]() { + ::receive([p{make_shared()}](auto from, auto m) { + return p->receive(move(from), move(m)); + }); + return ok(); + }, + "debuggee"); + } +}; + +struct controller { + static constexpr string_view tag{"debug_test_controller"}; + handle debug_tcp; + handle debuggee_; + connector c; + unique_ptr s; + size_t pong_count{}; + bool success_{false}; + string prev_buf; + + explicit controller(handle d, handle debuggee) + : debug_tcp{std::move(d)}, debuggee_{std::move(debuggee)}, + c{connector::create(tag)} { + trap(true); + } + + auto receive(const handle & /*from*/, const buffer &m) { + int fd{}; + string buf; + int written{}; + int err{}; + string_view err_msg{}; + + if (m("pong")) { + pong_count++; + if (pong_count == 2) + c.connect(in6addr_loopback, 4242); + } else if (m("connector", tag, "connected", extract(fd))) { + s = make_unique(socket::create(tag, fd)); + s->read(); + s->write("\n"); + s->write("debuggee [\"ping\"]\n"); + } else if (m("socket", tag, "read_error", extract(err), extract(err_msg))) { + return exit("read_error", err_msg); + } else if (m("socket", tag, "read_complete", extract(buf))) { + if (buf.empty()) { + s->close(); + return ok(); + } + s->read(); + buf.swap(prev_buf); + buf.append(prev_buf); + string::size_type pos{}; + while (not buf.empty() and pos != string::npos) { + pos = buf.find_first_of('\n'); + if (pos != string::npos) { + auto ret = self().send("dispatch", string(buf.data(), pos)); + if (not ret) + return ret; + buf.erase(0, pos + 1); + } + } + prev_buf = buf; + } else if (m("dispatch", extract(buf))) { + if (buf == "debuggee [\"pong\"]") { + s->write("debuggee [\"shutdown\"]\n"); + } + } else if (m("socket", tag, "write_error", extract(err), + extract(err_msg))) { + return exit("write_error", err_msg); + } else if (m("socket", tag, "write_complete", extract(written))) { + ; + } else if (m("socket", tag, "closed")) { + if (success_) + return exit("success"); + auto ret = debuggee_.send("shutdown"); + if (not ret) + return ret; + return exit("closed"); + } else if (m("exit", "debuggee_shutdown")) { + success_ = true; + s->close(); + } else if (m("connector", tag, "cancelled")) + return exit("cancelled"); + else if (m("connector", tag, "error", extract(buf))) + return exit("connect_error", buf); + + else + return unexpected(m); + return ok(); + } +}; + +} // namespace + +auto debug(context &ctx, bool &result, env_t env_) -> ::result { + thespian::debug::enable(ctx); + return to_result(ctx.spawn_link( + [&ctx]() { + link(env().proc("log")); + auto ret = thespian::debug::tcp::create(ctx, 4242, ""); + if (not ret) + return to_result(ret); + auto debug_tcp = ret.value(); + ret = spawn_link( + [&ctx, debug_tcp]() { + trap(true); + ::receive([&ctx, debug_tcp](auto, auto /*m*/) -> ::result { + auto ret = debug_tcp.send("shutdown"); + if (not ret) + return ret; + thespian::debug::disable(ctx); + return exit(); + }); + return ok(); + }, + "controller_guard"); + if (not ret) + return to_result(ret); + auto ret2 = debug_tcp.send("ping"); + if (not ret2) + return ret2; + ret = debuggee::start(); + if (not ret) + return to_result(ret); + auto debuggee = ret.value(); + ret2 = debuggee.send("ping"); + if (not ret2) + return ret2; + receive([p{make_shared(debug_tcp, debuggee)}](auto from, + auto m) { + return p->receive(move(from), move(m)); + }); + return ok(); + }, + [&](auto s) { + if (s == "success") + result = true; + }, + "debug", move(env_))); +} diff --git a/test/endpoint_tcp.cpp b/test/endpoint_tcp.cpp new file mode 100644 index 0000000..7cdb52e --- /dev/null +++ b/test/endpoint_tcp.cpp @@ -0,0 +1,93 @@ +#include "tests.hpp" + +#include "cbor/cbor.hpp" +#include "thespian/env.hpp" +#include "thespian/handle.hpp" +#include +#include +#include +#include + +#include +#include +#include + +using cbor::any; +using cbor::buffer; +using cbor::extract; +using std::make_shared; +using std::move; +using std::string; +using std::string_view; +using std::stringstream; +using thespian::context; +using thespian::env; +using thespian::env_t; +using thespian::exit; +using thespian::handle; +using thespian::link; +using thespian::ok; +using thespian::receive; +using thespian::result; +using thespian::unexpected; +using thespian::endpoint::tcp::listen; + +namespace { + +struct controller { + handle ep_listen; + handle ep_connect; + size_t ping_count{}; + size_t pong_count{}; + bool success_{false}; + + explicit controller(handle ep_l) : ep_listen{move(ep_l)} {} + + auto receive(const handle &from, const buffer &m) { + port_t port{0}; + if (m("port", extract(port))) { + ep_connect = + thespian::endpoint::tcp::connect(in6addr_loopback, port).value(); + return ok(); + } + if (m("connected")) { + ping_count++; + return ep_connect.send("ping", ping_count); + } + if (m("ping", any)) { + pong_count++; + return from.send("pong", pong_count); + } + if (m("pong", any)) { + if (ping_count > 10) { + return exit(ping_count == pong_count ? "success" : "closed"); + } + ping_count++; + return from.send("ping", ping_count); + } + return unexpected(m); + } +}; // namespace + +} // namespace + +auto endpoint_tcp(context &ctx, bool &result, env_t env_) -> ::result { + return to_result(ctx.spawn_link( + [=]() { + link(env().proc("log")); + handle ep_listen = listen(in6addr_loopback, 0).value(); + auto ret = ep_listen.send("get", "port"); + if (not ret) + return ret; + + receive([p{make_shared(ep_listen)}](auto from, auto m) { + return p->receive(move(from), move(m)); + }); + return ok(); + }, + [&](auto s) { + if (s == "success") + result = true; + }, + "endpoint_tcp", move(env_))); +} diff --git a/test/endpoint_unx.cpp b/test/endpoint_unx.cpp new file mode 100644 index 0000000..88233d2 --- /dev/null +++ b/test/endpoint_unx.cpp @@ -0,0 +1,104 @@ +#include "tests.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using cbor::array; +using cbor::buffer; +using cbor::extract; +using std::make_shared; +using std::move; +using std::string; +using std::string_view; +using std::stringstream; +using thespian::context; +using thespian::create_timeout; +using thespian::env; +using thespian::env_t; +using thespian::exit; +using thespian::handle; +using thespian::link; +using thespian::ok; +using thespian::receive; +using thespian::result; +using thespian::timeout; +using thespian::unexpected; +using thespian::endpoint::unx::connect; +using thespian::endpoint::unx::listen; +using thespian::unx::mode; + +using namespace std::chrono_literals; + +namespace { + +struct controller { + handle ep_listen; + handle ep_connect; + size_t ping_count{}; + size_t pong_count{}; + bool success_{false}; + timeout t{create_timeout(100ms, array("connect"))}; + + explicit controller(handle ep_l) : ep_listen{move(ep_l)} {} + + auto receive(const handle &from, const buffer &m) { + string_view path; + if (m("connect")) { + auto ret = ep_listen.send("get", "path"); + if (not ret) + return ret; + return ok(); + } + if (m("path", extract(path))) { + ep_connect = connect(path).value(); + return ok(); + } + if (m("connected")) { + ping_count++; + return ep_connect.send("ping"); + } + if (m("ping")) { + pong_count++; + return from.send("pong"); + } + if (m("pong")) { + if (ping_count > 10) { + return exit(ping_count == pong_count ? "success" : "closed"); + } + ping_count++; + return ep_connect.send("ping"); + } + return unexpected(m); + } +}; // namespace + +} // namespace + +auto endpoint_unx(context &ctx, bool &result, env_t env_) -> ::result { + stringstream ss; + ss << "/net/vdbonline/thespian/endpoint_t_" << getpid(); + string path = ss.str(); + return to_result(ctx.spawn_link( + [path]() { + link(env().proc("log")); + handle ep_listen = listen(path).value(); + receive([p{make_shared(ep_listen)}](auto from, auto m) { + return p->receive(move(from), move(m)); + }); + return ok(); + }, + [&](auto s) { + if (s == "success") + result = true; + }, + "endpoint_unx", move(env_))); +} diff --git a/test/hub_filter.cpp b/test/hub_filter.cpp new file mode 100644 index 0000000..a68431d --- /dev/null +++ b/test/hub_filter.cpp @@ -0,0 +1,127 @@ +#include "tests.hpp" + +#include "thespian/env.hpp" +#include "thespian/handle.hpp" +#include +#include + +#include + +using cbor::extract; +using cbor::type; +using std::map; +using std::move; +using std::string; +using std::string_view; +using std::stringstream; +using thespian::context; +using thespian::env_t; +using thespian::handle; +using thespian::hub; +using thespian::ok; +using thespian::receive; +using thespian::result; +using thespian::self; +using thespian::spawn_link; + +namespace { + +auto sub_plain(const hub &h, const handle &controller, string name) -> result { + auto ret = h.subscribe(); + if (not ret) + return ret; + ret = controller.send("ready", name); + if (not ret) + return ret; + receive([=](auto /*from*/, auto m) { + string_view cmd; + check(m("parmA", "parmB", "parmC", extract(cmd))); + check(cmd == "continue" || cmd == "done"); + if (cmd == "done") { + auto ret = controller.send(cmd, name); + if (not ret) + return ret; + } + return ok(); + }); + return ok(); +} + +auto sub_filtered(const hub &h, const handle &controller, string name) + -> result { + auto ret = h.subscribe( + [](auto m) { return m(type::any, type::any, type::any, "done"); }); + if (not ret) + return ret; + ret = controller.send("ready", name); + if (not ret) + return ret; + receive([=](auto /*from*/, auto m) { + check(m("parmA", "parmB", "parmC", "done")); + auto ret = controller.send("done", name); + if (not ret) + return ret; + return ok(); + }); + return ok(); +} + +mapresult> + subscription_defs // NOLINT + = { + {"sub_plain", sub_plain}, + {"sub_filtered", sub_filtered}, +}; +using submap = map; + +auto controller() -> result { + thespian::link(thespian::env().proc("log")); + auto h = hub::create("hub").value(); + handle c = self(); + auto ret = h.subscribe(); + if (not ret) + return ret; + + submap subs; + for (const auto &s : subscription_defs) { + subs.insert({s.first, spawn_link( + [=]() { + s.second(h, c, s.first); + return ok(); + }, + s.first) + .value()}); + } + submap not_ready = subs; + submap done = subs; + + receive([=](auto, auto m) mutable { + string name; + if (m("ready", extract(name))) { + not_ready.erase(name); + if (not_ready.empty()) { + h.broadcast("parmA", "parmB", "parmC", "continue"); + h.broadcast("parmA", "parmB", "parmC", "continue"); + h.broadcast("parmA", "parmB", "parmC", "done"); + } + } else if (m("done", extract(name))) { + done.erase(name); + if (done.empty()) + return h.shutdown(); + } + return ok(); + }); + return ok(); +} + +} // namespace + +auto hub_filter(context &ctx, bool &result, env_t env) -> ::result { + return to_result(ctx.spawn_link( + controller, + [&](auto s) { + if (s == "shutdown") + result = true; + }, + "hub_filter", move(env))); +} diff --git a/test/ip_tcp_client_server.cpp b/test/ip_tcp_client_server.cpp new file mode 100644 index 0000000..3ba3c28 --- /dev/null +++ b/test/ip_tcp_client_server.cpp @@ -0,0 +1,225 @@ +#include "tests.hpp" + +#include "thespian/handle.hpp" +#include +#include +#include +#include +#include + +#include + +using cbor::buffer; +using cbor::extract; +using std::make_shared; +using std::move; +using std::string; +using std::string_view; +using std::stringstream; +using thespian::context; +using thespian::env; +using thespian::env_t; +using thespian::exit; +using thespian::handle; +using thespian::link; +using thespian::ok; +using thespian::result; +using thespian::self; +using thespian::socket; +using thespian::spawn_link; +using thespian::unexpected; +using thespian::tcp::acceptor; +using thespian::tcp::connector; + +namespace { + +struct client_connection { + thespian::socket socket; + handle connector; + + explicit client_connection(int fd, handle connector) + : socket{socket::create("client_connection", fd)}, + connector(move(connector)) { + socket.read(); + } + + auto receive(const buffer &m) { + int written{}; + int err{}; + string_view buf{}; + + if (m("socket", "client_connection", "read_complete", extract(buf))) { + check(buf == "ping"); + check(err == 0); + socket.write("pong"); + } else if (m("socket", "client_connection", "write_complete", + extract(written))) { + check(written == 4); + check(err == 0); + // socket.close(); // let server close + } else if (m("socket", "client_connection", "closed")) { + auto ret = connector.send("client_connection", "done"); + if (not ret) + return ret; + return exit("success"); + } else { + return unexpected(m); + } + return ok(); + } + + [[nodiscard]] static auto init(int fd, const handle &connector) { + return spawn_link( + [fd, connector]() { + ::thespian::receive( + [p{make_shared(fd, connector)}]( + auto /*from*/, auto m) { return p->receive(move(m)); }); + return ok(); + }, + "client_connection"); + } +}; + +struct client { + connector connector; + handle server; + port_t server_port; + + explicit client(handle server, port_t server_port) + : connector{connector::create("client")}, server(move(server)), + server_port(server_port) { + connector.connect(in6addr_loopback, server_port); + } + + auto receive(const buffer &m) -> thespian::result { + int fd{}; + if (m("connector", "client", "connected", extract(fd))) { + return to_result(client_connection::init(fd, self())); + } + if (m("connector_connection", "done")) { + return server.send("client", "done"); + } + return unexpected(m); + } + + static auto init(handle server, port_t server_port) -> result { + ::thespian::receive( + [p{make_shared(server, server_port)}](auto /*from*/, auto m) { + return p->receive(move(m)); + }); + return ok(); + } +}; + +struct server_connection { + thespian::socket socket; + handle server; + + explicit server_connection(int fd, handle server) + : socket{socket::create("server_connection", fd)}, server(move(server)) { + socket.write("ping"); + } + + auto receive(const buffer &m) { + int written{}; + int err{}; + string_view buf{}; + + if (m("socket", "server_connection", "write_complete", extract(written))) { + check(written == 4); + check(err == 0); + socket.read(); + } else if (m("socket", "server_connection", "read_complete", + extract(buf))) { + check(buf == "pong"); + check(err == 0); + socket.close(); + } else if (m("socket", "server_connection", "closed")) { + auto ret = server.send("server_connection", "done"); + if (not ret) + return ret; + return exit("success"); + } else { + return unexpected(m); + } + return ok(); + } + + [[nodiscard]] static auto init(int fd, const handle &server) { + return spawn_link( + [fd, server]() { + ::thespian::receive( + [p{make_shared(fd, server)}]( + auto /*from*/, auto m) { return p->receive(move(m)); }); + return ok(); + }, + "server_connection"); + } +}; + +struct server { + acceptor acceptor; + bool client_done{false}; + bool server_connection_done{false}; + bool acceptor_closed{false}; + port_t server_port; + + explicit server() + : acceptor{acceptor::create("server")}, + server_port(acceptor.listen(in6addr_loopback, 0)) {} + + auto receive(const buffer &m) { + int fd{}; + if (m("acceptor", "server", "accept", extract(fd))) { + auto ret = server_connection::init(fd, self()); + if (not ret) + return to_result(ret); + acceptor.close(); + } else if (m("acceptor", "server", "closed")) { + acceptor_closed = true; + } else if (m("client", "done")) { + client_done = true; + } else if (m("server_connection", "done")) { + server_connection_done = true; + } else { + return unexpected(m); + } + if (acceptor_closed and client_done and server_connection_done) + return exit("success"); + return ok(); + } + + [[nodiscard]] static auto init() { + link(env().proc("log")); + auto log = env().proc("log"); + auto _ = log.send("server starting"); + auto p{make_shared()}; + _ = log.send("server listening on port", p->server_port); + auto ret = spawn_link( + [server{self()}, server_port{p->server_port}]() { + return client::init(server, server_port); + }, + "ip_tcp_client"); + if (not ret) + return to_result(ret); + thespian::receive( + [p](auto /*from*/, auto m) { return p->receive(move(m)); }); + return ok(); + } +}; + +} // namespace + +auto ip_tcp_client_server(context &ctx, bool &result, env_t env) -> ::result { + thespian::debug::enable(ctx); + return to_result(ctx.spawn_link( + server::init, + [&](auto s) { + if (s == "success") { + result = true; + } else { + auto _ = env.proc("log").send("failed:", s); + } + }, + "ip_tcp_client_server", move(env))); +} diff --git a/test/ip_udp_echo.cpp b/test/ip_udp_echo.cpp new file mode 100644 index 0000000..fb0c1bc --- /dev/null +++ b/test/ip_udp_echo.cpp @@ -0,0 +1,122 @@ +#include "tests.hpp" + +#include "thespian/env.hpp" +#include "thespian/handle.hpp" +#include +#include +#include + +#include + +using cbor::array; +using cbor::buffer; +using cbor::extract; +using cbor::more; +using std::make_shared; +using std::move; +using std::string; +using std::string_view; +using std::stringstream; +using thespian::context; +using thespian::create_timeout; +using thespian::env; +using thespian::env_t; +using thespian::exit; +using thespian::link; +using thespian::ok; +using thespian::receive; +using thespian::result; +using thespian::udp; +using thespian::unexpected; + +using namespace std::chrono_literals; + +namespace { + +struct controller { + port_t server_port{0}; + port_t client_port{0}; + udp client; + udp server; + size_t ping_count{}; + size_t pong_count{}; + bool success_{false}; + thespian::timeout server_receive_timeout{ + create_timeout(500s, array("udp", "server", "timeout"))}; + + explicit controller() + : client{udp::create("client")}, server{udp::create("server")} { + server_port = server.open(in6addr_loopback, server_port); + client.open(in6addr_loopback, client_port); + auto sent = client.sendto("ping", in6addr_loopback, server_port); + if (sent != 4) + abort(); + } + controller(controller &&) = delete; + controller(const controller &) = delete; + + ~controller() { server_receive_timeout.cancel(); } + + auto operator=(controller &&) -> controller & = delete; + auto operator=(const controller &) -> controller & = delete; + + auto client_receive(const buffer &m) { + in6_addr remote_ip{}; + port_t remote_port{}; + if (m("udp", "client", "receive", "pong", extract(remote_ip), + extract(remote_port))) { + check(remote_port == server_port); + client.close(); + } else if (m("udp", "client", "closed")) { + return exit("success"); + } else { + return unexpected(m); + } + return ok(); + } + + auto server_receive(const buffer &m) { + in6_addr remote_ip{}; + port_t remote_port{}; + if (m("udp", "server", "receive", "ping", extract(remote_ip), + extract(remote_port))) { + auto sent = server.sendto("pong", remote_ip, remote_port); + if (sent != 4) + return exit("unexpected_sendto_size", sent); + server.close(); + } else if (m("udp", "server", "timeout")) { + return exit("timeout"); + } else if (m("udp", "server", "closed")) { + ; + } else { + return unexpected(m); + } + return ok(); + } + + auto receive(const buffer &m) { + if (m("udp", "client", more)) + return client_receive(m); + if (m("udp", "server", more)) + return server_receive(m); + return unexpected(m); + } +}; + +} // namespace + +auto ip_udp_echo(context &ctx, bool &result, env_t env_) -> ::result { + return to_result(ctx.spawn_link( + [=]() { + link(env().proc("log")); + receive([p{make_shared()}](auto /*from*/, auto m) { + return p->receive(move(m)); + }); + return ok(); + }, + [&](auto s) { + if (s == "success") + result = true; + }, + "ip_udp_echo", move(env_))); +} diff --git a/test/metronome_test.cpp b/test/metronome_test.cpp new file mode 100644 index 0000000..4be00f8 --- /dev/null +++ b/test/metronome_test.cpp @@ -0,0 +1,81 @@ +#include "tests.hpp" + +#include "thespian/env.hpp" +#include +#include + +#include +#include +#include + +using cbor::buffer; +using std::move; +using std::shared_ptr; +using std::chrono::microseconds; +using std::chrono::system_clock; +using thespian::context; +using thespian::create_metronome; +using thespian::env; +using thespian::env_t; +using thespian::exit; +using thespian::exit_handler; +using thespian::handle; +using thespian::link; +using thespian::metronome; +using thespian::ok; +using thespian::receive; +using thespian::result; +using thespian::unexpected; + +using clk = std::chrono::system_clock; + +using namespace std::chrono_literals; + +namespace { + +auto initA() { + link(env().proc("log")); + struct state_t { + int i{0}; + system_clock::time_point start{clk::now()}; + metronome met{create_metronome(10ms)}; + state_t() { met.start(); } + handle log{env().proc("log")}; + + auto receive(const buffer &m) { + if (m("tick", i)) + ++i; + else + return unexpected(m); + { + auto end = clk::now(); + auto us = duration_cast(end - start).count(); + auto ret = log.send("tick@", us, "µs"); + if (not ret) + return ret; + } + if (i == 5) { + auto end = clk::now(); + auto us = duration_cast(end - start); + met.stop(); + return exit(us > 50000us ? "done" : "failed"); + } + return ok(); + } + }; + shared_ptr state{new state_t}; + receive([state](auto, auto m) mutable { return state->receive(m); }); + return ok(); +} + +} // namespace + +auto metronome_test(context &ctx, bool &result, env_t env) -> ::result { + return to_result(ctx.spawn_link( + initA, + [&](auto s) { + if (s == "done") + result = true; + }, + "metronome", move(env))); +} diff --git a/test/perf_cbor.cpp b/test/perf_cbor.cpp new file mode 100644 index 0000000..48c384b --- /dev/null +++ b/test/perf_cbor.cpp @@ -0,0 +1,140 @@ +#include "tests.hpp" + +#include "thespian/env.hpp" +#include "thespian/handle.hpp" +#include + +#include +#include + +using cbor::A; +using cbor::array; +using cbor::buffer; +using cbor::extract; +using cbor::type; +using std::move; +using std::string_view; +using std::to_string; +using std::chrono::duration_cast; +using std::chrono::microseconds; +using thespian::context; +using thespian::env; +using thespian::env_t; +using thespian::link; +using thespian::ok; +using thespian::result; + +using clk = std::chrono::system_clock; + +namespace { + +auto count{10000UL}; // NOLINT + +auto time(const char *name, void (*f)()) -> void { + auto start = clk::now(); + + for (uint64_t i{0}; i < count; ++i) + f(); + + auto end = clk::now(); + auto us = duration_cast(end - start).count(); + + auto _ = env().proc("log").send(to_string((count * 1.0L) / us), name, "/µs"); +} + +const auto ma = array("five", 5, "four", 4, array("three", 3)); + +auto test() -> result { + link(env().proc("log")); + auto _ = env().proc("log").send("buffer size:", ma.size()); + + time("noop", []() {}); + + time("encode_template", []() { + buffer m{array("five", 5, "four", 4, array("three", 3))}; + check(m.size() == 21); + }); + + time("encode_chain", []() { + buffer b; + b.array_header(5) + .push("five") + .push(5) + .push("four") + .push(4) + .array_header(2) + .push("three") + .push(3); + check(b.size() == 21); + }); + + time("encode_chain_reserve", []() { + buffer b; + b.reserve(21); + b.array_header(5) + .push("five") + .push(5) + .push("four") + .push(4) + .array_header(2) + .push("three") + .push(3); + check(b.size() == 21); + }); + + time("match", []() { + check(ma(type::string, type::number, type::string, type::number, + type::array)); + }); + + time("match2", []() { + check(ma(type::string, type::number, type::string, type::number, + A(type::string, type::number))); + }); + + time("extract_int", []() { + int64_t i{}; + check(ma(type::string, extract(i), type::more)); + check(i == 5); + }); + + time("extract_string", []() { + string_view s; + check(ma(extract(s), type::more)); + }); + + time("extract_int2", []() { + int64_t i{}; + check(ma(type::string, type::number, type::string, type::number, + A(type::string, extract(i)))); + check(i == 3); + }); + + time("extract_string2", []() { + string_view s; + check(ma(type::string, type::number, type::string, type::number, + A(extract(s), type::number))); + }); + + time("extract_array", []() { + buffer::range r; + check( + ma(type::string, type::number, type::string, type::number, extract(r))); + check(r(type::string, type::number)); + }); + return ok(); +} + +} // namespace + +auto perf_cbor(context &ctx, bool &result, env_t env) -> ::result { + if (env.is("long_run")) + count = 10000000; + return to_result(ctx.spawn_link( + test, + [&](auto s) { + check(s == "noreceive"); + result = true; + }, + "perf_cbor", move(env))); +} diff --git a/test/perf_hub.cpp b/test/perf_hub.cpp new file mode 100644 index 0000000..445224a --- /dev/null +++ b/test/perf_hub.cpp @@ -0,0 +1,153 @@ +#include "tests.hpp" + +#include "thespian/env.hpp" +#include "thespian/handle.hpp" +#include +#include +#include + +#include +#include +#include + +using cbor::extract; +using std::move; +using std::set; +using std::string; +using std::stringstream; +using std::underlying_type_t; +using std::chrono::duration_cast; +using std::chrono::milliseconds; +using thespian::context; +using thespian::env; +using thespian::env_t; +using thespian::handle; +using thespian::hub; +using thespian::link; +using thespian::ok; +using thespian::receive; +using thespian::result; +using thespian::self; +using thespian::spawn_link; +using thespian::unexpected; + +using clk = std::chrono::system_clock; + +namespace { + +enum class tag { + ready = 1, + done = 2, +}; + +auto tag_to_int(tag e) noexcept -> int { + return static_cast>(e); +} +auto tag_from_int(int i) -> tag { + return static_cast(static_cast(i)); +} + +auto subscriber(const hub &h, const handle &controller, const int num, + const int messages) -> result { + auto ret = h.subscribe(); + if (not ret) + return ret; + ret = controller.send(tag_to_int(tag::ready), num); + if (not ret) + return ret; + int count{1}; + receive([=](auto /*from*/, auto m) mutable { + if (!m("parmA", "parmB", "parmC", count)) + std::abort(); + if (count == messages) { + auto ret = controller.send(tag_to_int(tag::done), num); + if (not ret) + return ret; + } + ++count; + return ok(); + }); + return ok(); +} + +auto controller(const int subscriptions, const int messages) -> result { + const auto log = env().proc("log"); + const auto verbose = env().is("verbose"); + auto ret = hub::create("hub"); + if (not ret) + return to_result(ret); + auto h = ret.value(); + handle c = self(); + + auto _ = log.send("subscribing", subscriptions); + + set subs; + for (auto i{0}; i < subscriptions; ++i) { + auto ret = + spawn_link([=]() -> result { return subscriber(h, c, i, messages); }, + "subscriber"); + if (not ret) + return to_result(ret); + subs.insert(i); + } + _ = log.send("all", "spawned"); + set not_ready = subs; + set done = subs; + clk::time_point start; + + receive([=](auto, auto m) mutable { + int tag_{}, num{}; + if (!m(extract(tag_), extract(num))) + return unexpected(m); + tag tag = tag_from_int(tag_); + switch (tag) { + case tag::ready: + not_ready.erase(num); + if (not_ready.empty()) { + if (verbose) + _ = log.send("all", "ready"); + for (int i = 1; i <= messages; ++i) + h.broadcast("parmA", "parmB", "parmC", i); + start = clk::now(); + if (verbose) + _ = log.send("broadcasting", messages); + } + break; + + case tag::done: + done.erase(num); + if (done.empty()) { + if (verbose) { + clk::time_point end = clk::now(); + auto ms = duration_cast(end - start).count(); + double msgs = subscriptions * messages + subscriptions; + _ = log.send("all", "done"); + _ = log.send("time:", ms, "ms"); + _ = log.send( + static_cast((msgs / static_cast(ms)) * 1000), + "msg/s"); + } + return h.shutdown(); + } + } + return ok(); + }); + return ok(); +} +} // namespace + +auto perf_hub(context &ctx, bool &result, env_t env_) -> ::result { + const auto long_run = env_.is("long_run"); + const auto subscriptions = long_run ? 1000 : 100; + const auto messages = long_run ? 10000 : 100; + return to_result(ctx.spawn_link( + [=]() { + link(env().proc("log")); + return controller(subscriptions, messages); + }, + [&](auto s) { + if (s == "shutdown") + result = true; + }, + "controller", move(env_))); +} diff --git a/test/perf_ring.cpp b/test/perf_ring.cpp new file mode 100644 index 0000000..3c5ddba --- /dev/null +++ b/test/perf_ring.cpp @@ -0,0 +1,79 @@ +#include "tests.hpp" + +#include "thespian/env.hpp" +#include + +#include + +using cbor::extract; +using std::move; +using std::string; +using std::string_view; +using thespian::context; +using thespian::env; +using thespian::env_t; +using thespian::exit; +using thespian::handle; +using thespian::link; +using thespian::ok; +using thespian::receive; +using thespian::result; +using thespian::self; +using thespian::spawn_link; + +namespace { + +auto slave(const handle &last, int n) -> result { + handle next; + if (n) + next = spawn_link([=]() { return slave(last, n - 1); }, "slave").value(); + + receive([n, next, last](auto, auto m) { + return n ? next.send_raw(move(m)) : last.send_raw(move(m)); + }); + return ok(); +} + +auto controller(const int slaves) -> result { + auto verbose = env().is("long_run"); + int loop = 10; + handle last = self(); + auto ret = spawn_link([=]() { return slave(last, slaves); }, "slave"); + if (not ret) + return to_result(ret); + handle first = ret.value(); + auto ret2 = first.send("forward"); + if (not ret2) + return ret2; + receive([loop, first, verbose](auto, auto m) mutable { + if (loop) { + if (verbose) + auto _ = env().proc("log").send(loop); + --loop; + return first.send_raw(move(m)); + } + string_view s; + check(m(extract(s))); + return exit(s); + }); + return ok(); +} +} // namespace + +auto perf_ring(context &ctx, bool &result, env_t env_) -> ::result { + auto long_run = env_.is("long_run"); + auto slaves{long_run ? 100000 : 1000}; + return to_result(ctx.spawn_link( + [=]() { + link(env().proc("log")); + return controller(slaves); + }, + [&](auto s) { + if (s == "forward") { + result = true; + } else { + auto _ = env_.proc("log").send("failed:", s); + } + }, + "perf_ring", move(env_))); +} diff --git a/test/perf_spawn.cpp b/test/perf_spawn.cpp new file mode 100644 index 0000000..5f04a8e --- /dev/null +++ b/test/perf_spawn.cpp @@ -0,0 +1,73 @@ +#include "tests.hpp" + +#include "thespian/env.hpp" +#include + +#include + +using cbor::extract; +using std::move; +using thespian::context; +using thespian::env; +using thespian::env_t; +using thespian::exit; +using thespian::link; +using thespian::ok; +using thespian::receive; +using thespian::result; +using thespian::spawn_link; +using thespian::unexpected; + +namespace { + +auto slave(const int slaves, int spawned) -> result { + receive([slaves, spawned](auto, auto m) mutable { + int n{0}; + if (!m(extract(n))) + return unexpected(m); + if (n) { + ++spawned; + return thespian::to_result( + spawn_link([=]() { return slave(slaves, spawned); }, "slave") + .value() + .send(n - 1)); + } + if (spawned != slaves) { + auto _ = env().proc("log").send("spawned", spawned, "slaves !=", slaves); + return exit("failed"); + } + if (env().is("verbose")) + auto _ = env().proc("log").send("spawned", slaves, "slaves"); + return exit("done"); + }); + return ok(); +} + +auto controller(const int slaves) -> result { + auto ret = spawn_link([=]() { return slave(slaves, 1); }, "slave"); + if (not ret) + return to_result(ret); + auto ret2 = ret.value().send(slaves - 1); + if (not ret2) + return ret2; + receive([](auto, auto) { return exit(); }); + return ok(); +} +} // namespace + +auto perf_spawn(context &ctx, bool &result, env_t env_) -> ::result { + auto slaves{env_.is("long_run") ? 1000000 : 1000}; + return to_result((ctx.spawn_link( + [=]() { + link(env().proc("log")); + return controller(slaves); + }, + [&](auto s) { + if (s == "done") { + result = true; + } else { + auto _ = env_.proc("log").send(s); + } + }, + "perf_spawn", move(env_)))); +} diff --git a/test/spawn_exit.cpp b/test/spawn_exit.cpp new file mode 100644 index 0000000..0e26a78 --- /dev/null +++ b/test/spawn_exit.cpp @@ -0,0 +1,120 @@ +#include "tests.hpp" + +#include "thespian/env.hpp" +#include "thespian/handle.hpp" + +#include + +using std::move; +using thespian::behaviour; +using thespian::context; +using thespian::env; +using thespian::env_t; +using thespian::exit; +using thespian::exit_handler; +using thespian::ok; +using thespian::receive; +using thespian::result; +using thespian::self; +using thespian::spawn; +using thespian::spawn_link; +using thespian::to_result; +using thespian::trap; +using thespian::unexpected; + +namespace { + +auto initE() { + check(env().str("initAsays") == "yes"); + check(env().str("initBsays") == ""); + check(env().str("initCsays") == "no"); + check(env().num("intval") == 42); + check(!env().proc("A").expired()); + link(env().proc("A")); + auto ret = env().proc("A").send("shutdown"); + if (not ret) + return ret; + receive([](auto, auto) { return ok(); }); + return ok(); +} + +auto initD() { + auto ret = spawn(initE, "E"); + if (not ret) + return to_result(ret); + receive([](auto, auto m) { + if (m("die")) + return exit("died"); + return unexpected(m); + }); + return ok(); +} + +auto initC() { + env().str("initCsays") = "no"; + auto ret = spawn_link(initD, "D").value().send("die"); + if (not ret) + return to_result(ret); + trap(true); + receive([](auto /*from*/, auto m) { + if (m("exit", "died")) + return exit(); + return unexpected(m); + }); + return ok(); +} + +auto initB() { + env().str("initBsays") = "noyoudont"; + receive([](auto from, auto m) { + if (m("shutdown")) { + auto ret = from.send("done"); + if (not ret) + return ret; + return exit(); + } + return unexpected(m); + }); + return ok(); +} + +auto initA() { + link(env().proc("log")); + env().str("initAsays") = "yes"; + env().num("intval") = 42; + env().proc("A") = self(); + auto ret = spawn_link(initB, "B").value().send("shutdown"); + if (not ret) + return ret; + spawn_link(initC, "C").value(); + receive([i{0}](auto, auto m) mutable { + if (m("shutdown") || m("done")) + ++i; + else + return unexpected(m); + if (i == 2) + return exit("done"); + return ok(); + }); + return ok(); +} + +} // namespace + +auto spawn_exit(context &ctx, bool &result, env_t env) -> ::result { + + behaviour b; + check(!b); + b = []() { return ok(); }; + check(!!b); + b = behaviour{}; + check(!b); + + return to_result(ctx.spawn_link( + initA, + [&](auto s) { + if (s == "done") + result = true; + }, + "spawn_exit", move(env))); +} diff --git a/test/tests.cpp b/test/tests.cpp new file mode 100644 index 0000000..aa3c0aa --- /dev/null +++ b/test/tests.cpp @@ -0,0 +1,161 @@ +#include "tests.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using cbor::buffer; +using std::cerr; +using std::cout; +using std::lock_guard; +using std::map; +using std::move; +using std::mutex; +using std::ostream; +using std::string; +using thespian::env_t; +using thespian::handle; +using thespian::ok; + +namespace { + +using namespace std::chrono_literals; + +auto operator<<(ostream &s, const cbor::buffer::value_accessor::unvisitable_type + & /*unused*/) -> ostream & { + return s << "unvisitable_type"; +} + +struct logger { + thespian::timeout t{thespian::create_timeout(60s, cbor::array("timeout"))}; + const char *name; + mutex &trace_m; + bool verbose; + + explicit logger(const char *name, mutex &trace_m, bool verbose) + : name(name), trace_m(trace_m), verbose(verbose) {} + + auto receive(const buffer &m) { + if (verbose) { + std::lock_guard lock(trace_m); + cout << name << ": "; + auto dump = [&](auto val) { cout << val << " "; }; + for (const auto val : m) + val.visit(dump); + cout << '\n'; + } + return ok(); + } + + static auto start(thespian::context &ctx, const char *name, mutex &trace_m, + bool verbose, env_t env) -> handle { + return ctx + .spawn( + [name, &trace_m, verbose]() { + thespian::receive( + [p{make_shared(name, trace_m, verbose)}]( + auto, auto m) { return p->receive(move(m)); }); + return ok(); + }, + string("logger_") + name, move(env)) + .value(); + } +}; // namespace + +} // namespace + +extern "C" auto runtestcase(const char *name) -> int { + mutex trace_m; + + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) // NOLINT + abort(); + + auto gdb = getenv("JITDEBUG"); // NOLINT + + if (gdb) { + if (strcmp(gdb, "on") != 0) + install_debugger(); + else if (strcmp(gdb, "backtrace") != 0) + install_backtrace(); + } + + auto ctx = thespian::context::create(); + + map tests; + tests["cbor_match"] = cbor_match; + tests["debug"] = debug; + tests["endpoint_unx"] = endpoint_unx; + tests["endpoint_tcp"] = endpoint_tcp; + tests["hub_filter"] = hub_filter; + tests["ip_tcp_client_server"] = ip_tcp_client_server; + tests["ip_udp_echo"] = ip_udp_echo; + tests["metronome_test"] = metronome_test; + tests["perf_cbor"] = perf_cbor; + tests["perf_hub"] = perf_hub; + tests["perf_ring"] = perf_ring; + tests["perf_spawn"] = perf_spawn; + tests["spawn_exit"] = spawn_exit; + tests["timeout_test"] = timeout_test; + + env_t env{}; + env_t log_env{}; + auto trace = [&](const buffer &buf) { + lock_guard lock(trace_m); + cout << buf.to_json() << '\n'; + }; + log_env.on_trace(trace); + env.on_trace(trace); + if (getenv("TRACE")) { // NOLINT + thespian::debug::enable(*ctx); + env.enable_all_channels(); + log_env.enable_all_channels(); + } + if (getenv("TRACE_LIFETIME")) { // NOLINT + thespian::debug::enable(*ctx); + env.enable(thespian::channel::lifetime); + log_env.enable(thespian::channel::lifetime); + } + if (getenv("TRACE_EXECUTE")) { // NOLINT + env.enable(thespian::channel::execute); + } + if (getenv("TRACE_SEND")) { // NOLINT + env.enable(thespian::channel::send); + } + if (getenv("TRACE_RECEIVE")) { // NOLINT + env.enable(thespian::channel::receive); + } + + bool long_run = getenv("LONGRUN"); // NOLINT + bool verbose = getenv("VERBOSE"); // NOLINT + env.is("long_run") = long_run; + env.is("verbose") = verbose; + + if (verbose) + cout << '\n'; + + env.proc("log") = logger::start(*ctx, name, trace_m, verbose, move(log_env)); + + auto test = tests.find(name); + if (test == tests.end()) { + cerr << "invalid testcase: " << name << '\n'; + return 1; + } + bool result{false}; + test->second(*ctx, result, move(env)); + ctx->run(); + return result ? 0 : 1; +} diff --git a/test/tests.h b/test/tests.h new file mode 100644 index 0000000..ec7147a --- /dev/null +++ b/test/tests.h @@ -0,0 +1,2 @@ +int runtestcase(const char *name); // NOLINT + diff --git a/test/tests.hpp b/test/tests.hpp new file mode 100644 index 0000000..7a79741 --- /dev/null +++ b/test/tests.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include +#include + +constexpr auto check(bool expression) -> void { + if (!expression) + std::abort(); +} + +using testcase = auto(thespian::context &ctx, bool &result, thespian::env_t env) + -> thespian::result; + +testcase cbor_match; +testcase debug; +testcase endpoint_tcp; +testcase endpoint_unx; +testcase hub_filter; +testcase ip_tcp_client_server; +testcase ip_udp_echo; +testcase metronome_test; +testcase perf_cbor; +testcase perf_hub; +testcase perf_ring; +testcase perf_spawn; +testcase spawn_exit; +testcase timeout_test; diff --git a/test/tests.zig b/test/tests.zig new file mode 100644 index 0000000..d50b146 --- /dev/null +++ b/test/tests.zig @@ -0,0 +1,8 @@ +const std = @import("std"); +pub const cpp = @import("tests_cpp.zig"); +pub const cbor = @import("tests_cbor.zig"); +pub const thespian = @import("tests_thespian.zig"); + +test { + std.testing.refAllDecls(@This()); +} diff --git a/test/tests_cbor.zig b/test/tests_cbor.zig new file mode 100644 index 0000000..6ba45ca --- /dev/null +++ b/test/tests_cbor.zig @@ -0,0 +1,435 @@ +const std = @import("std"); +const cbor_mod = @import("cbor"); + +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const expectEqualDeep = std.testing.expectEqualDeep; +const expectError = std.testing.expectError; + +const fmt = cbor_mod.fmt; +const toJson = cbor_mod.toJson; +const toJsonPretty = cbor_mod.toJsonPretty; +const fromJson = cbor_mod.fromJson; +const decodeType = cbor_mod.decodeType; +const matchInt = cbor_mod.matchInt; +const matchIntValue = cbor_mod.matchIntValue; +const matchValue = cbor_mod.matchValue; +const match = cbor_mod.match; +const isNull = cbor_mod.isNull; +const writeArrayHeader = cbor_mod.writeArrayHeader; +const writeMapHeader = cbor_mod.writeMapHeader; +const writeValue = cbor_mod.writeValue; +const extract = cbor_mod.extract; +const extract_cbor = cbor_mod.extract_cbor; + +const more = cbor_mod.more; +const any = cbor_mod.any; +const string = cbor_mod.string; +const number = cbor_mod.number; +const array = cbor_mod.array; +const map = cbor_mod.map; + +test "cbor simple" { + var buf: [128]u8 = undefined; + try expectEqualDeep( + fmt(&buf, .{ "five", 5, "four", 4, .{ "three", 3 } }), + &[_]u8{ 0x85, 0x64, 0x66, 0x69, 0x76, 0x65, 0x05, 0x64, 0x66, 0x6f, 0x75, 0x72, 0x04, 0x82, 0x65, 0x74, 0x68, 0x72, 0x65, 0x65, 0x03 }, + ); +} + +test "cbor exit message" { + var buf: [128]u8 = undefined; + try expectEqualDeep( + fmt(&buf, .{ "exit", "normal" }), + &[_]u8{ 0x82, 0x64, 0x65, 0x78, 0x69, 0x74, 0x66, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C }, + ); +} + +test "cbor error.OutOfMemory" { + var buf: [128]u8 = undefined; + try expectEqualDeep( + fmt(&buf, .{ "exit", error.OutOfMemory }), + &[_]u8{ 0x82, 0x64, 0x65, 0x78, 0x69, 0x74, 0x71, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x2E, 0x4F, 0x75, 0x74, 0x4F, 0x66, 0x4D, 0x65, 0x6D, 0x6F, 0x72, 0x79 }, + ); +} + +test "cbor error.OutOfMemory json" { + var buf: [128]u8 = undefined; + var json_buf: [128]u8 = undefined; + const cbor = fmt(&buf, .{ "exit", error.OutOfMemory }); + try expectEqualDeep(toJson(cbor, &json_buf), + \\["exit","error.OutOfMemory"] + ); +} + +test "cbor.decodeType2" { + var buf = [_]u8{ 0x20, 0xDF }; + var iter: []const u8 = &buf; + const t = try decodeType(&iter); + try expectEqual(t.major, 1); + try expectEqual(t.minor, 0); + try expectEqual(iter[0], 0xDF); +} + +test "cbor.decodeType3" { + var buf = [_]u8{ 0x03, 0xDF }; + var iter: []const u8 = &buf; + const t = try decodeType(&iter); + try expectEqual(t.major, 0); + try expectEqual(t.minor, 3); + try expectEqual(iter[0], 0xDF); +} + +test "cbor.matchI64 small" { + var buf = [_]u8{ 1, 0xDF }; + var iter: []const u8 = &buf; + var val: i64 = 0; + try expect(try matchInt(i64, &iter, &val)); + try expectEqual(val, 1); + try expectEqual(iter[0], 0xDF); +} + +test "cbor.matchI64 1byte" { + var buf = [_]u8{ 0x18, 0x1A, 0xDF }; + var iter: []const u8 = &buf; + var val: i64 = 0; + try expect(try matchInt(i64, &iter, &val)); + try expectEqual(val, 26); + try expectEqual(iter[0], 0xDF); +} + +test "cbor.matchI64 2byte" { + var buf = [_]u8{ 0x19, 0x01, 0x07, 0xDF }; + var iter: []const u8 = &buf; + var val: i64 = 0; + try expect(try matchInt(i64, &iter, &val)); + try expectEqual(val, 263); + try expectEqual(iter[0], 0xDF); +} + +test "cbor.matchI64 8byte" { + var buf = [_]u8{ 0x1B, 0x00, 0x00, 0xEF, 0x6F, 0xC1, 0x4A, 0x0A, 0x1F, 0xDF }; + var iter: []const u8 = &buf; + var val: i64 = 0; + try expect(try matchInt(i64, &iter, &val)); + try expectEqual(val, 263263263263263); + try expectEqual(iter[0], 0xDF); +} + +test "cbor.matchI64 error.CborIntegerTooLarge" { + var buf = [_]u8{ 0x1B, 0xA9, 0x0A, 0xDE, 0x0D, 0x4E, 0x2B, 0x8A, 0x1F, 0xDF }; + var iter: []const u8 = &buf; + var val: i64 = 0; + const result = matchInt(i64, &iter, &val); + try expectError(error.CborIntegerTooLarge, result); +} + +test "cbor.matchI64 error.CborTooShort" { + var buf = [_]u8{ 0x19, 0x01 }; + var iter: []const u8 = &buf; + var val: i64 = 0; + const result = matchInt(i64, &iter, &val); + try expectError(error.CborTooShort, result); +} + +test "cbor.matchI64Value" { + var buf = [_]u8{ 7, 0xDF }; + var iter: []const u8 = &buf; + try expect(try matchIntValue(i64, &iter, 7)); +} + +test "cbor.matchValue(i64)" { + var buf: [128]u8 = undefined; + var iter = fmt(&buf, 7); + try expect(try matchValue(&iter, 7)); +} + +test "cbor.matchValue(i64) multi" { + var buf: [128]u8 = undefined; + const iter = fmt(&buf, 7); + const iter2 = fmt(buf[iter.len..], 8); + var iter3 = buf[0 .. iter.len + iter2.len]; + try expect(try matchValue(&iter3, 7)); + try expect(try matchValue(&iter3, 8)); +} + +test "cbor.match(.{i64...})" { + var buf: [128]u8 = undefined; + const v = .{ 5, 4, 3, 123456, 234567890 }; + const m = fmt(&buf, v); + try expect(try match(m, v)); +} + +test "cbor.match(.{i64... more})" { + var buf: [128]u8 = undefined; + const v = .{ 5, 4, 3, 123456, 234567890, 6, 5, 4, 3, 2, 1 }; + const m = fmt(&buf, v); + try expect(try match(m, .{ 5, 4, 3, more })); +} + +test "cbor.match(.{any, i64... more})" { + var buf: [128]u8 = undefined; + const v = .{ "cbor", 4, 3, 123456, 234567890, 6, 5, 4, 3, 2, 1 }; + const m = fmt(&buf, v); + try expect(try match(m, .{ any, 4, 3, more })); +} + +test "cbor.match(.{types...})" { + var buf: [128]u8 = undefined; + const v = .{ "five", 5, "four", 4, .{ "three", 3 } }; + const m = fmt(&buf, v); + try expect(try match(m, .{ string, number, string, number, array })); + + try expect(!try match(m, .{ string, number, string, number })); + try expect(!try match(m, .{ number, string, number, array, any })); + try expect(try match(m, .{ any, number, string, number, any })); + + try expect(try match(m, .{ "five", number, string, 4, any })); + try expect(try match(m, .{ "five", 5, "four", 4, array })); + try expect(try match(m, .{ "five", 5, more })); + try expect(try match(m, .{ "five", 5, "four", 4, array, more })); + try expect(!try match(m, .{ "five", 5, "four", 4, array, any })); + try expect(!try match(m, .{ "four", 5, "five", 4, array, any })); + try expect(try match(m, .{ "five", 5, "four", 4, .{ "three", 3 } })); +} + +test "cbor.nulls" { + var buf: [128]u8 = undefined; + try expect(isNull(fmt(&buf, .{}))); + try expect(!isNull(fmt(&buf, .{1}))); +} + +test "cbor.stream_writer" { + var buf: [128]u8 = undefined; + var stream = std.io.fixedBufferStream(&buf); + const writer = stream.writer(); + try writeArrayHeader(writer, 5); + try writeValue(writer, "five"); + try writeValue(writer, 5); + try writeValue(writer, "four"); + try writeValue(writer, 4); + try writeArrayHeader(writer, 2); + try writeValue(writer, "three"); + try writeValue(writer, 3); + try expect(try match(stream.getWritten(), .{ "five", 5, "four", 4, .{ "three", 3 } })); +} + +test "cbor.stream_object_writer" { + var buf: [128]u8 = undefined; + var stream = std.io.fixedBufferStream(&buf); + const writer = stream.writer(); + try writeMapHeader(writer, 3); + try writeValue(writer, "five"); + try writeValue(writer, 5); + try writeValue(writer, "four"); + try writeValue(writer, 4); + try writeValue(writer, "three"); + try writeValue(writer, 3); + const obj = stream.getWritten(); + var json_buf: [128]u8 = undefined; + const json = try toJson(obj, &json_buf); + try expectEqualDeep(json, + \\{"five":5,"four":4,"three":3} + ); +} + +test "cbor.match_bool" { + var buf: [128]u8 = undefined; + const m = fmt(&buf, .{ false, true, 5, "five" }); + try expect(try match(m, .{ false, true, 5, "five" })); + try expect(!try match(m, .{ true, false, 5, "five" })); +} + +test "cbor.extract_match" { + var buf: [128]u8 = undefined; + var t: bool = false; + const extractor = extract(&t); + var iter = fmt(&buf, true); + try expect(try extractor.extract(&iter)); + try expect(t); + iter = fmt(&buf, false); + try expect(try extractor.extract(&iter)); + try expect(!t); + + const m = fmt(&buf, .{ false, true, 5, "five" }); + try expect(try match(m, .{ extract(&t), true, 5, "five" })); + try expect(!t); + try expect(try match(m, .{ false, extract(&t), 5, "five" })); + try expect(t); + + var i: i64 = undefined; + try expect(try match(m, .{ false, true, extract(&i), "five" })); + try expect(i == 5); + + var u: u64 = undefined; + try expect(try match(m, .{ false, true, extract(&u), "five" })); + try expect(u == 5); + + var s: []const u8 = undefined; + try expect(try match(m, .{ false, true, 5, extract(&s) })); + try expect(std.mem.eql(u8, "five", s)); +} + +test "cbor.extract_cbor" { + var buf: [128]u8 = undefined; + const v = .{ "five", 5, "four", 4, .{ "three", 3 } }; + const m = fmt(&buf, v); + + try expect(try match(m, .{ "five", 5, "four", 4, .{ "three", 3 } })); + + var sub: []const u8 = undefined; + try expect(try match(m, .{ "five", 5, "four", 4, extract_cbor(&sub) })); + try expect(try match(sub, .{ "three", 3 })); +} + +test "cbor.extract_nested" { + var buf: [128]u8 = undefined; + const v = .{ "five", 5, "four", 4, .{ "three", 3 } }; + const m = fmt(&buf, v); + + var u: u64 = undefined; + try expect(try match(m, .{ "five", 5, "four", 4, .{ "three", extract(&u) } })); + try expect(u == 3); +} + +test "cbor.match_map" { + var buf: [128]u8 = undefined; + const v = .{ "five", 5, "four", 4, .{ .three = 3 } }; + const m = fmt(&buf, v); + try expect(try match(m, .{ "five", 5, "four", 4, map })); +} + +test "cbor.extract_map_cbor" { + var buf: [128]u8 = undefined; + const v = .{ "five", 5, "four", 4, .{ .three = 3 } }; + const m = fmt(&buf, v); + var map_cbor: []const u8 = undefined; + try expect(try match(m, .{ "five", 5, "four", 4, extract_cbor(&map_cbor) })); + var json_buf: [256]u8 = undefined; + const json = try toJson(map_cbor, &json_buf); + try expectEqualDeep(json, + \\{"three":3} + ); +} + +test "cbor.extract_map" { + var buf: [128]u8 = undefined; + const v = .{ "five", 5, "four", 4, .{ .three = 3 } }; + const m = fmt(&buf, v); + var obj = std.json.ObjectMap.init(std.testing.allocator); + defer obj.deinit(); + try expect(try match(m, .{ "five", 5, "four", 4, extract(&obj) })); + try expect(obj.contains("three")); + try expectEqual(obj.get("three").?, std.json.Value{ .integer = 3 }); +} + +test "cbor.extract_map_map" { + var buf: [128]u8 = undefined; + const v = .{ "five", 5, "four", 4, .{ .three = 3, .child = .{ .two = 2, .one = .{ 1, 2, 3, true, false, null } } } }; + const m = fmt(&buf, v); + var a = std.heap.ArenaAllocator.init(std.testing.allocator); + defer a.deinit(); + var obj = std.json.ObjectMap.init(a.allocator()); + defer obj.deinit(); + try expect(try match(m, .{ "five", 5, "four", 4, extract(&obj) })); + try expect(obj.contains("three")); + try expectEqual(obj.get("three").?, std.json.Value{ .integer = 3 }); + var child = obj.get("child").?.object; + try expectEqual(child.get("two"), std.json.Value{ .integer = 2 }); + + var json_buf: [256]u8 = undefined; + const json = try toJson(m, &json_buf); + try expectEqualDeep(json, + \\["five",5,"four",4,{"three":3,"child":{"two":2,"one":[1,2,3,true,false,null]}}] + ); +} + +test "cbor.extract_value" { + var buf: [128]u8 = undefined; + const v = .{ "five", 5, "four", 4, .{ .three = 3 } }; + const m = fmt(&buf, v); + var value = std.json.Value{ .null = {} }; + var value_int = std.json.Value{ .null = {} }; + try expect(try match(m, .{ "five", 5, extract(&value), extract(&value_int), any })); + try expectEqualDeep(value.string, "four"); + try expectEqualDeep(value_int.integer, 4); +} + +test "cbor.match_more_nested" { + var buf: [128]u8 = undefined; + const v = .{ .{124}, .{ "three", 3 } }; + const m = fmt(&buf, v); + try expect(try match(m, .{ .{more}, .{more} })); +} + +test "cbor.extract_number_limits" { + var buf: [128]u8 = undefined; + const bigint: u64 = 18446744073709551615; + var u: u64 = undefined; + var i: i64 = undefined; + const m = fmt(&buf, bigint); + try expect(try match(m, extract(&u))); + try expectError(error.CborIntegerTooLarge, match(m, extract(&i))); +} + +test "cbor.toJson" { + var buf: [128]u8 = undefined; + var json_buf: [128]u8 = undefined; + const a = fmt(&buf, .{ "string\r\n", "\tstring\t", "\r\nstring" }); + const json = try toJson(a, &json_buf); + try expect(try match(a, .{ "string\r\n", "\tstring\t", "\r\nstring" })); + try expectEqualDeep(json, + \\["string\r\n","\tstring\t","\r\nstring"] + ); +} + +test "cbor.toJson_object" { + var buf: [128]u8 = undefined; + var json_buf: [128]u8 = undefined; + const a = fmt(&buf, .{ .five = 5, .four = 4, .three = 3 }); + const json = try toJson(a, &json_buf); + try expectEqualDeep(json, + \\{"five":5,"four":4,"three":3} + ); +} + +test "cbor.toJsonPretty_object" { + var buf: [128]u8 = undefined; + var json_buf: [128]u8 = undefined; + const a = fmt(&buf, .{ .five = 5, .four = 4, .three = 3 }); + const json = try toJsonPretty(a, &json_buf); + try expectEqualDeep(json, + \\{ + \\ "five": 5, + \\ "four": 4, + \\ "three": 3 + \\} + ); +} + +test "cbor.fromJson_small" { + var cbor_buf: [128]u8 = undefined; + const json_buf: []const u8 = + \\[12345] + ; + const cbor = try fromJson(json_buf, &cbor_buf); + try expect(try match(cbor, .{12345})); +} + +test "cbor.fromJson" { + var cbor_buf: [128]u8 = undefined; + const json_buf: []const u8 = + \\["string\r\n","\tstring\t","\r\nstring",12345] + ; + const cbor = try fromJson(json_buf, &cbor_buf); + try expect(try match(cbor, .{ "string\r\n", "\tstring\t", "\r\nstring", 12345 })); +} + +test "cbor.fromJson_object" { + var cbor_buf: [128]u8 = undefined; + const json_buf: []const u8 = + \\{"five":5,"four":4,"three":3} + ; + const cbor = try fromJson(json_buf, &cbor_buf); + try expect(try match(cbor, map)); +} diff --git a/test/tests_cpp.zig b/test/tests_cpp.zig new file mode 100644 index 0000000..67ca3c4 --- /dev/null +++ b/test/tests_cpp.zig @@ -0,0 +1,65 @@ +const std = @import("std"); +const c = @cImport({ + @cInclude("tests.h"); +}); + +fn testcase(name: [*c]const u8) !void { + const result = c.runtestcase(name); + try std.testing.expectEqual(@as(c_int, 0), result); +} + +test "cbor_match" { + try testcase("cbor_match"); +} + +test "debug" { + try testcase("debug"); +} + +test "endpoint_unx" { + try testcase("endpoint_unx"); +} + +test "endpoint_tcp" { + try testcase("endpoint_tcp"); +} + +test "hub_filter" { + try testcase("hub_filter"); +} + +test "ip_tcp_client_server" { + try testcase("ip_tcp_client_server"); +} + +test "ip_udp_echo" { + try testcase("ip_udp_echo"); +} + +test "metronome_test" { + try testcase("metronome_test"); +} + +test "perf_cbor" { + try testcase("perf_cbor"); +} + +test "perf_hub" { + try testcase("perf_hub"); +} + +test "perf_ring" { + try testcase("perf_ring"); +} + +test "perf_spawn" { + try testcase("perf_spawn"); +} + +test "spawn_exit" { + try testcase("spawn_exit"); +} + +test "timeout_test" { + try testcase("timeout_test"); +} diff --git a/test/tests_thespian.zig b/test/tests_thespian.zig new file mode 100644 index 0000000..e69a695 --- /dev/null +++ b/test/tests_thespian.zig @@ -0,0 +1,14 @@ +const std = @import("std"); +const thespian = @import("thespian"); +const cbor = @import("cbor"); + +pub const unexpected = thespian.unexpected; +const message = thespian.message; +const error_message = thespian.error_message; + +test "thespian.unexpected" { + var buf: [512]u8 = undefined; + try std.testing.expectEqual(error.Exit, unexpected(message.fmt(.{"TEST"}))); + const json = try cbor.toJson(error_message(), &buf); + try std.testing.expectEqualStrings("[\"exit\",\"UNEXPECTED_MESSAGE: [\\\"TEST\\\"]\"]", json); +} diff --git a/test/timeout_test.cpp b/test/timeout_test.cpp new file mode 100644 index 0000000..038b9a5 --- /dev/null +++ b/test/timeout_test.cpp @@ -0,0 +1,64 @@ +#include "tests.hpp" + +#include "thespian/env.hpp" +#include "thespian/handle.hpp" +#include +#include + +#include +#include +#include + +using cbor::array; +using cbor::buffer; +using std::move; +using std::shared_ptr; +using std::chrono::microseconds; +using std::chrono::system_clock; +using thespian::context; +using thespian::create_timeout; +using thespian::env_t; +using thespian::exit; +using thespian::exit_handler; +using thespian::ok; +using thespian::receive; +using thespian::result; +using thespian::timeout; +using thespian::unexpected; + +using clk = std::chrono::system_clock; + +using namespace std::chrono_literals; + +namespace { + +auto initA() -> result { + thespian::link(thespian::env().proc("log")); + struct state_t { + system_clock::time_point start{clk::now()}; + timeout t{create_timeout(200ms, array("timeout"))}; + + auto receive(const buffer &m) { + if (!m("timeout")) + return unexpected(m); + auto end = clk::now(); + auto us = duration_cast(end - start); + return exit(us > 200ms ? "done" : "failed"); + } + }; + shared_ptr state{new state_t}; + receive([state](auto, auto m) mutable { return state->receive(m); }); + return ok(); +} + +} // namespace + +auto timeout_test(context &ctx, bool &result, env_t env) -> ::result { + return to_result(ctx.spawn_link( + initA, + [&](auto s) { + if (s == "done") + result = true; + }, + "timeout", move(env))); +} diff --git a/zig b/zig new file mode 100755 index 0000000..6b931ed --- /dev/null +++ b/zig @@ -0,0 +1,48 @@ +#!/bin/bash +set -e + +ARCH=$(uname -m) + +BASEDIR="$(cd "$(dirname "$0")" && pwd)" +ZIGDIR=$BASEDIR/.cache/zig +VERSION=$(< build.zig.version) +ZIGVER="zig-linux-$ARCH-$VERSION" +ZIG=$ZIGDIR/$ZIGVER/zig + +if [ "$1" == "update" ] ; then + curl -L --silent https://ziglang.org/download/index.json | jq -r '.master | .version' > build.zig.version + NEWVERSION=$(< build.zig.version) + + if [ "$VERSION" != "$NEWVERSION" ] ; then + $0 cdb + exec $0 + fi + echo zig version $VERSION is up-to-date + exit 0 +fi + +get_zig() { + ( + mkdir -p "$ZIGDIR" + cd "$ZIGDIR" + TARBALL="https://ziglang.org/builds/$ZIGVER.tar.xz" + + if [ ! -d "$ZIGVER" ] ; then + curl "$TARBALL" | tar -xJ + fi + ) +} +get_zig + +if [ "$1" == "cdb" ] ; then + rm -rf zig-cache + rm -rf zig-bin + rm -rf .cache/cdb + + $ZIG build + + (echo \[ ; cat .cache/cdb/* ; echo {}\]) | perl -0777 -pe 's/,\n\{\}//igs' | jq . | grep -v 'no-default-config' > compile_commands.json + exit 0 +fi + +exec $ZIG "$@"