Initial Release

This commit is contained in:
CJ van den Berg 2024-02-08 22:23:40 +01:00
commit 5a00e06cb9
81 changed files with 12670 additions and 0 deletions

2
.clang-tidy Normal file
View file

@ -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-*'

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/.cache/
/zig-out/
/zig-cache/

16
.vscode/launch.json vendored Normal file
View file

@ -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",
}
]
}

5
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,5 @@
{
"clangd.arguments": [
"--enable-config"
]
}

52
.vscode/tasks.json vendored Normal file
View file

@ -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"
}
]
}

21
LICENSE Normal file
View file

@ -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.

125
build.zig Normal file
View file

@ -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);
}

1
build.zig.version Normal file
View file

@ -0,0 +1 @@
0.12.0-dev.2644+42fcca49c

22
build.zig.zon Normal file
View file

@ -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",
},
}

26
include/cbor/c/cbor.h Normal file
View file

@ -0,0 +1,26 @@
#pragma once
#include <thespian/c/string_view.h>
#include <stddef.h> // NOLINT
#include <stdint.h> // 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

463
include/cbor/cbor.hpp Normal file
View file

@ -0,0 +1,463 @@
#pragma once
#include <cstdint>
#include <functional>
#include <ostream>
#include <string>
#include <string_view>
#include <vector>
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 <typename T> inline auto is_more(const T & /*unused*/) -> bool {
return false;
}
template <> inline auto is_more<type>(const type &t) -> bool {
return t == type::more;
}
using buffer_base = std::vector<uint8_t>;
class buffer : public buffer_base {
private:
using iter = const_iterator;
public:
using extractor = std::function<bool(iter &, const iter &)>;
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<int64_t>(v));
}
[[nodiscard]] static auto match_value(iter &b, const iter &e, int32_t v)
-> bool {
return match_value(b, e, static_cast<int64_t>(v));
}
[[nodiscard]] static auto match_value(iter &b, const iter &e, uint32_t v)
-> bool {
return match_value(b, e, static_cast<int64_t>(v));
}
[[nodiscard]] static auto match_value(iter &b, const iter &e, int16_t v)
-> bool {
return match_value(b, e, static_cast<int64_t>(v));
}
[[nodiscard]] static auto match_value(iter &b, const iter &e, uint16_t v)
-> bool {
return match_value(b, e, static_cast<int64_t>(v));
}
[[nodiscard]] static auto match_value(iter &b, const iter &e, int8_t v)
-> bool {
return match_value(b, e, static_cast<int64_t>(v));
}
[[nodiscard]] static auto match_value(iter &b, const iter &e, uint8_t v)
-> bool {
return match_value(b, e, static_cast<int64_t>(v));
}
template <typename T>
[[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 <typename T, typename... Ts>
[[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<Ts>(parms)...);
}
auto push_typed_val(int type, uint64_t value) -> void;
auto push_parms() -> buffer & { return *this; }
template <typename T, typename... Ts>
auto push_parms(const T &p, Ts &&...parms) -> buffer & {
push(p);
return push_parms(std::forward<Ts>(parms)...);
}
template <typename... Ts>
[[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<Ts>(parms)...);
}
template <typename... Ts>
[[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<Ts>(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 <typename... Ts>
[[nodiscard]] auto operator()(Ts &&...parms) const -> bool {
auto b = raw_cbegin();
auto e = raw_cend();
return match_array(b, e, std::forward<Ts>(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 <typename T> 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 <typename T> 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 <typename... Ts> auto array(Ts &&...parms) -> buffer & {
array_header(sizeof...(parms));
return push_parms(std::forward<Ts>(parms)...);
}
auto map() -> buffer & { return push_null(); }
template <typename... Ts> 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<Ts>(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 <typename F> auto visit(F f) const -> decltype(f(true)) {
type t{type_()};
switch (t) {
case type::string:
return f(static_cast<std::string_view>(*this));
case type::number:
return f(static_cast<int64_t>(*this));
case type::boolean:
return f(static_cast<bool>(*this));
case type::array:
case type::map:
return f(static_cast<range>(*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 <typename... Ts>
[[nodiscard]] auto operator()(Ts &&...parms) const -> bool {
auto b = raw_cbegin();
return match_array(b, raw_cend(), std::forward<Ts>(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 <typename... Ts> friend auto A(Ts &&...parms) -> buffer::extractor;
template <typename... Ts> friend auto M(Ts &&...parms) -> buffer::extractor;
};
template <typename... Ts> auto array(Ts &&...parms) -> buffer {
buffer b;
b.reserve(buffer_reserve);
b.array(std::forward<Ts>(parms)...);
return b;
}
template <typename... Ts> auto map(Ts &&...parms) -> buffer {
buffer b;
b.reserve(buffer_reserve);
b.map(std::forward<Ts>(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 <typename... Ts> auto A(Ts &&...parms) -> buffer::extractor {
return [=](buffer::iter &b, const buffer::iter &e) mutable {
return buffer::match_array(b, e, std::forward<Ts>(parms)...);
};
}
template <typename... Ts> auto M(Ts &&...parms) -> buffer::extractor {
return [=](buffer::iter &b, const buffer::iter &e) mutable {
return buffer::match_map(b, e, std::forward<Ts>(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

28
include/cbor/cbor_in.hpp Normal file
View file

@ -0,0 +1,28 @@
#pragma once
#include "cbor.hpp"
#include <cstring>
#include <netinet/in.h>
namespace cbor {
template <> inline auto buffer::push<in6_addr>(const in6_addr &a) -> buffer & {
push(std::string_view(reinterpret_cast<const char *>(&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

View file

@ -0,0 +1,13 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
void install_debugger();
void install_backtrace();
void install_jitdebugger();
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,39 @@
#pragma once
#include <thespian/c/env.h>
#include <thespian/c/handle.h>
// 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)

43
include/thespian/c/env.h Normal file
View file

@ -0,0 +1,43 @@
#pragma once
#include <thespian/c/handle.h>
#include <thespian/c/string_view.h>
#include <thespian/c/trace.h>
// 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)

View file

@ -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)

View file

@ -0,0 +1,29 @@
#pragma once
#include "thespian/c/string_view.h"
#include <cbor/c/cbor.h>
// NOLINTBEGIN(modernize-*, hicpp-*)
#include <stdbool.h>
#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-*)

View file

@ -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)

View file

@ -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)

View file

@ -0,0 +1,19 @@
#pragma once
#include <cbor/c/cbor.h>
// 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)

View file

@ -0,0 +1,19 @@
#pragma once
// NOLINTBEGIN(modernize-*, hicpp-*)
#include <stddef.h>
#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-*)

View file

@ -0,0 +1,21 @@
#pragma once
#include <cbor/c/cbor.h>
// 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)

View file

@ -0,0 +1,40 @@
#pragma once
#include <cbor/c/cbor.h>
// NOLINTBEGIN(modernize-*, hicpp-*)
#include <limits.h>
#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-*)

View file

@ -0,0 +1,46 @@
#pragma once
#include "env.hpp"
#include "handle.hpp"
#include <functional>
#include <memory>
namespace thespian {
using behaviour = std::function<auto()->result>;
using exit_handler = std::function<void(std::string_view)>;
struct context {
using dtor = void (*)(context *);
using ref = std::unique_ptr<context, dtor>;
using last_exit_handler = std::function<void()>;
[[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<handle, error>;
[[nodiscard]] auto spawn(behaviour b, std::string_view name, env_t env)
-> expected<handle, error>;
[[nodiscard]] auto spawn_link(behaviour b, exit_handler eh,
std::string_view name)
-> expected<handle, error>;
[[nodiscard]] auto spawn_link(behaviour b, exit_handler eh,
std::string_view name, env_t env)
-> expected<handle, error>;
protected:
context() = default;
~context() = default;
public:
context(const context &) = delete;
context(context &&) = delete;
void operator=(const context &) = delete;
void operator=(context &&) = delete;
};
} // namespace thespian

View file

@ -0,0 +1,22 @@
#pragma once
#include "context.hpp"
#include "handle.hpp"
#include <string>
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<handle, error>;
} // namespace tcp
} // namespace thespian::debug

View file

@ -0,0 +1,33 @@
#pragma once
#include "handle.hpp"
#include "tcp.hpp"
#include "unx.hpp"
#include <chrono>
#include <string>
using port_t = unsigned short;
namespace thespian::endpoint {
using namespace std::chrono_literals;
namespace tcp {
auto listen(in6_addr, port_t) -> expected<handle, error>;
auto connect(in6_addr, port_t, std::chrono::milliseconds retry_time = 50ms,
size_t retry_count = 5) -> expected<handle, error>;
} // namespace tcp
namespace unx {
using thespian::unx::mode;
auto listen(std::string_view, mode = mode::abstract) -> expected<handle, error>;
auto connect(std::string_view, mode = mode::abstract,
std::chrono::milliseconds retry_time = 50ms,
size_t retry_count = 5) -> expected<handle, error>;
} // namespace unx
} // namespace thespian::endpoint

51
include/thespian/env.hpp Normal file
View file

@ -0,0 +1,51 @@
#pragma once
#include "handle.hpp"
#include "trace.hpp"
#include <any>
#include <cstddef>
#include <map>
namespace thespian {
struct env_t {
void enable_all_channels() {
trace_channels = static_cast<channel_set>(channel::all);
}
void disable_all_channels() { trace_channels = 0; }
void enable(channel c) { trace_channels |= static_cast<channel_set>(c); }
void disable(channel c) { trace_channels ^= static_cast<channel_set>(c); }
auto enabled(channel c) -> bool {
return trace_channels & static_cast<channel_set>(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<std::string, std::string> s;
std::map<std::string, bool> b;
std::map<std::string, int64_t> i;
std::map<std::string, handle> h;
channel_set trace_channels{0};
trace_handler trace_handler;
};
auto env() -> env_t &;
} // namespace thespian

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,32 @@
#pragma once
#include <memory>
#include <string_view>
#include <vector>
namespace thespian {
struct file_descriptor_impl;
using file_descriptor_dtor = void (*)(file_descriptor_impl *);
using file_descriptor_ref =
std::unique_ptr<file_descriptor_impl, file_descriptor_dtor>;
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

View file

@ -0,0 +1,49 @@
#pragma once
#include <cbor/cbor.hpp>
#include "expected.hpp"
#include <memory>
#include <utility>
namespace thespian {
template <typename T, typename E>
using expected = std::experimental::expected<T, E>;
using error = cbor::buffer;
using result = expected<void, error>;
using to_error = std::experimental::unexpected<error>;
[[nodiscard]] inline auto ok() -> result { return result{}; }
template <typename T> [[nodiscard]] auto ok(T v) { return result{v}; }
template <typename T> [[nodiscard]] auto to_result(T ret) -> result {
if (ret)
return ok();
return to_error(ret.error());
}
struct instance;
using ref = std::weak_ptr<instance>;
struct handle {
[[nodiscard]] auto send_raw(cbor::buffer) const -> result;
template <typename... Ts> [[nodiscard]] auto send(Ts &&...parms) const {
return send_raw(cbor::array(std::forward<Ts>(parms)...));
}
template <typename... Ts>
auto exit(const std::string &error, Ts &&...details) const {
return send_raw(cbor::array("exit", error, std::forward<Ts>(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

46
include/thespian/hub.hpp Normal file
View file

@ -0,0 +1,46 @@
#pragma once
#include "handle.hpp"
#include <functional>
#include <memory>
namespace thespian {
struct hub : private handle {
using filter = std::function<bool(const cbor::buffer &)>;
hub() = default;
auto expired() -> bool { return handle::expired(); }
template <typename... Ts> auto broadcast(Ts &&...parms) const {
return send("broadcast", cbor::array(std::forward<Ts>(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<hub, error>;
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<pipe>);
std::shared_ptr<pipe> 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

View file

@ -0,0 +1,51 @@
#pragma once
#include "context.hpp"
#include "env.hpp"
#include "handle.hpp"
#include "trace.hpp"
#include <functional>
#include <map>
#include <string>
#include <string_view>
namespace thespian {
using receiver = std::function<result(handle from, cbor::buffer)>;
using sync_receiver = std::function<cbor::buffer(handle from, cbor::buffer)>;
[[nodiscard]] auto send_raw(cbor::buffer) -> result;
template <typename... Ts> [[nodiscard]] auto send(Ts &&...parms) -> result {
return send_raw(cbor::array(std::forward<Ts>(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<handle, error>;
[[nodiscard]] auto spawn_link(behaviour, std::string_view name)
-> expected<handle, error>;
[[nodiscard]] auto spawn(behaviour, std::string_view name, env_t)
-> expected<handle, error>;
[[nodiscard]] auto spawn_link(behaviour, std::string_view name, env_t)
-> expected<handle, error>;
[[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

View file

@ -0,0 +1,25 @@
#pragma once
#include <chrono>
#include <functional>
#include <memory>
namespace thespian {
struct metronome_impl;
using metronome_dtor = void (*)(metronome_impl *);
using metronome_ref = std::unique_ptr<metronome_impl, metronome_dtor>;
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

View file

@ -0,0 +1,25 @@
#pragma once
#include <cbor/cbor.hpp>
#include <chrono>
#include <functional>
#include <memory>
namespace thespian {
struct signal_impl;
using signal_dtor = void (*)(signal_impl *);
using signal_ref = std::unique_ptr<signal_impl, signal_dtor>;
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

View file

@ -0,0 +1,29 @@
#pragma once
#include <memory>
#include <string_view>
#include <vector>
namespace thespian {
struct socket_impl;
using socket_dtor = void (*)(socket_impl *);
using socket_ref = std::unique_ptr<socket_impl, socket_dtor>;
struct socket {
static auto create(std::string_view tag, int fd) -> socket;
auto write(std::string_view) -> void;
auto write(const std::vector<uint8_t> &) -> 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

47
include/thespian/tcp.hpp Normal file
View file

@ -0,0 +1,47 @@
#pragma once
#include <memory>
#include <netinet/in.h>
#include <string_view>
#include <vector>
using port_t = unsigned short;
namespace thespian::tcp {
struct acceptor_impl;
using acceptor_dtor = void (*)(acceptor_impl *);
using acceptor_ref = std::unique_ptr<acceptor_impl, acceptor_dtor>;
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<connector_impl, connector_dtor>;
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

View file

@ -0,0 +1,28 @@
#pragma once
#include <cbor/cbor.hpp>
#include <chrono>
#include <functional>
#include <memory>
namespace thespian {
struct timeout_impl;
using timeout_dtor = void (*)(timeout_impl *);
using timeout_ref = std::unique_ptr<timeout_impl, timeout_dtor>;
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

View file

@ -0,0 +1,37 @@
#pragma once
#include "thespian/handle.hpp"
#include <cbor/cbor.hpp>
#include <functional>
#include <limits>
#include <string>
#include <vector>
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<channel_set>::max(),
};
using trace_handler = std::function<void(const cbor::buffer &)>;
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

33
include/thespian/udp.hpp Normal file
View file

@ -0,0 +1,33 @@
#pragma once
#include <cbor/cbor_in.hpp>
#include <cstddef>
#include <functional>
#include <memory>
using port_t = unsigned short;
namespace thespian {
struct udp_impl;
using udp_dtor = void (*)(udp_impl *);
using udp_ref = std::unique_ptr<udp_impl, udp_dtor>;
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

43
include/thespian/unx.hpp Normal file
View file

@ -0,0 +1,43 @@
#pragma once
#include <memory>
#include <string_view>
#include <vector>
namespace thespian::unx {
enum class mode { file, abstract };
struct acceptor_impl;
using acceptor_dtor = void (*)(acceptor_impl *);
using acceptor_ref = std::unique_ptr<acceptor_impl, acceptor_dtor>;
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<connector_impl, connector_dtor>;
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

102
src/backtrace.cpp Normal file
View file

@ -0,0 +1,102 @@
#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <unistd.h>
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<char *const *>(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();
}

66
src/c/context.cpp Normal file
View file

@ -0,0 +1,66 @@
#include "thespian/env.hpp"
#include <__type_traits/is_swappable.h>
#include <thespian/c/context.h>
#include <thespian/c/handle.h>
#include <thespian/context.hpp>
#include <thespian/handle.hpp>
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<thespian_context>( // NOLINT
context::create(reinterpret_cast<context::dtor *>(d))); // NOLINT
}
void thespian_context_run(thespian_context ctx) {
reinterpret_cast<context *>(ctx)->run(); // NOLINT
}
void thespian_context_on_last_exit(thespian_context ctx,
thespian_last_exit_handler h) {
reinterpret_cast<context *>(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<context *>(ctx_); // NOLINT
thespian::env_t empty_env_{};
thespian::env_t &env_ =
env ? *reinterpret_cast<thespian::env_t *>(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<thespian_handle>( // NOLINT
new thespian::handle{ret.value()});
return 0;
}
}

92
src/c/env.cpp Normal file
View file

@ -0,0 +1,92 @@
#include <cbor/cbor.hpp>
#include <thespian/c/env.h>
#include <thespian/c/handle.h>
#include <thespian/env.hpp>
#include <thespian/trace.hpp>
#include <cassert>
namespace {
auto to_env(thespian_env e) -> thespian::env_t & {
assert(e);
return *reinterpret_cast<thespian::env_t *>(e); // NOLINT
}
auto destroy(thespian_env e) -> void {
delete reinterpret_cast<thespian::env_t *>(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<thespian::channel>(c));
}
void thespian_env_disable(thespian_env e, thespian_trace_channel c) {
to_env(e).disable(static_cast<thespian::channel>(c));
}
auto thespian_env_enabled(thespian_env e, thespian_trace_channel c) -> bool {
return to_env(e).enabled(static_cast<thespian::channel>(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<thespian_handle>(&ret); // NOLINT
}
void thespian_env_proc_set(thespian_env e, c_string_view key,
thespian_handle value) {
thespian::handle *h{reinterpret_cast<thespian::handle *>( // NOLINT
value)};
to_env(e).proc({key.base, key.len}) = *h;
}
auto thespian_env_clone(thespian_env e) -> thespian_env {
return reinterpret_cast<thespian_env>( // 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>(&thespian::env()); // NOLINT
}
auto thespian_env_new() -> thespian_env {
return reinterpret_cast<thespian_env>(new thespian::env_t); // NOLINT
}
}

76
src/c/file_descriptor.cpp Normal file
View file

@ -0,0 +1,76 @@
#include <thespian/c/context.h>
#include <thespian/c/file_descriptor.h>
#include <thespian/file_descriptor.hpp>
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<thespian_file_descriptor_handle *>(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<file_descriptor_impl *>(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<file_descriptor_impl *>(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<file_descriptor_impl *>(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<file_descriptor_impl *>(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-*)

70
src/c/handle.cpp Normal file
View file

@ -0,0 +1,70 @@
#include <thespian/c/handle.h>
#include <thespian/handle.hpp>
#include <thespian/instance.hpp>
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<thespian::handle *>( // NOLINT(*-reinterpret-cast)
h)};
if (!h_)
return nullptr;
return reinterpret_cast<thespian_handle>( // NOLINT(*-reinterpret-cast)
new thespian::handle{*h_});
}
void thespian_handle_destroy(thespian_handle h) {
thespian::handle *h_{
reinterpret_cast<thespian::handle *>( // NOLINT(*-reinterpret-cast)
h)};
delete h_;
}
auto thespian_handle_send_raw(thespian_handle h, cbor_buffer m)
-> thespian_result {
thespian::handle *h_{
reinterpret_cast<thespian::handle *>( // 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<thespian::handle *>( // 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<thespian::handle *>( // NOLINT(*-reinterpret-cast)
h)};
return h_->expired();
}
}

65
src/c/instance.cpp Normal file
View file

@ -0,0 +1,65 @@
#include "cbor/c/cbor.h"
#include "thespian/c/handle.h"
#include <thespian/c/instance.h>
#include <thespian/instance.hpp>
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<thespian_handle>( // 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<thespian::handle *>( // NOLINT(*-reinterpret-cast)
h)};
thespian::link(*h_);
}
auto thespian_self() -> thespian_handle {
auto &self = thespian::self_ref();
return reinterpret_cast<thespian_handle>(&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<thespian::env_t *>(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<thespian_handle>( // NOLINT
new thespian::handle{ret.value()});
return 0;
}
}

76
src/c/metronome.cpp Normal file
View file

@ -0,0 +1,76 @@
#include <thespian/c/context.h>
#include <thespian/c/metronome.h>
#include <thespian/metronome.hpp>
#include <chrono>
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<thespian_metronome_handle *>(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<thespian_metronome_handle *>(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<metronome_impl *>(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<metronome_impl *>(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<metronome_impl *>(handle)); // NOLINT
} catch (const std::exception &e) {
thespian_set_last_error(e.what());
} catch (...) {
thespian_set_last_error("unknown thespian_metronome_destroy error");
}
}
}

50
src/c/signal.cpp Normal file
View file

@ -0,0 +1,50 @@
#include <thespian/c/context.h>
#include <thespian/c/signal.h>
#include <thespian/signal.hpp>
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<thespian_signal_handle *>(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<signal_impl *>(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<signal_impl *>(handle)); // NOLINT
} catch (const std::exception &e) {
thespian_set_last_error(e.what());
} catch (...) {
thespian_set_last_error("unknown thespian_signal_destroy error");
}
}
}

73
src/c/timeout.cpp Normal file
View file

@ -0,0 +1,73 @@
#include <thespian/c/context.h>
#include <thespian/c/timeout.h>
#include <thespian/timeout.hpp>
#include <chrono>
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<thespian_timeout_handle *>(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<thespian_timeout_handle *>(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<timeout_impl *>(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<timeout_impl *>(handle)); // NOLINT
} catch (const std::exception &e) {
thespian_set_last_error(e.what());
} catch (...) {
thespian_set_last_error("unknown thespian_timeout_destroy error");
}
}
}

25
src/c/trace.cpp Normal file
View file

@ -0,0 +1,25 @@
#include <thespian/c/trace.h>
#include <thespian/trace.hpp>
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);
}
}

926
src/cbor.cpp Normal file
View file

@ -0,0 +1,926 @@
#include <cbor/c/cbor.h>
#include <cbor/cbor.hpp>
#include <array>
#include <iomanip>
#include <limits>
#include <sstream>
#include <string>
#include <string_view>
#include <tuple>
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<char, int_str_max> 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<uint8_t, uint8_t, uint8_t> {
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 <typename T> 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<T>::min()) or
i_ > int64_t(numeric_limits<T>::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 <typename T> 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<T>::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<unsigned>(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<const buffer_base &>(*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 <typename T> 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<type>(b, e); }
buffer::value_accessor::operator int64_t() const { return get<int64_t>(b, e); }
buffer::value_accessor::operator uint64_t() const { return get<int64_t>(b, e); }
buffer::value_accessor::operator int32_t() const {
return get<int64_t>(b, e); // NOLINT(*-narrowing-conversions)
}
buffer::value_accessor::operator uint32_t() const { return get<int64_t>(b, e); }
buffer::value_accessor::operator int16_t() const {
return get<int64_t>(b, e); // NOLINT(*-narrowing-conversions)
}
buffer::value_accessor::operator uint16_t() const { return get<int64_t>(b, e); }
buffer::value_accessor::operator int8_t() const {
return get<int64_t>(b, e); // NOLINT(*-narrowing-conversions)
}
buffer::value_accessor::operator uint8_t() const { return get<int64_t>(b, e); }
buffer::value_accessor::operator bool() const { return get<bool>(b, e); }
buffer::value_accessor::operator string_view() const {
return get<string_view>(b, e);
}
buffer::value_accessor::operator buffer::range() const {
return get<buffer::range>(b, e);
}
} // namespace cbor

914
src/cbor.zig Normal file
View file

@ -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();
}

207
src/executor.hpp Normal file
View file

@ -0,0 +1,207 @@
#pragma once
#include <array>
#include <chrono>
#include <cstdint>
#include <functional>
#include <memory>
#include <netinet/in.h>
#include <system_error>
#include <vector>
using port_t = unsigned short;
namespace thespian::executor {
struct strand;
struct context_impl;
using context_ref = std::shared_ptr<context_impl>;
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<strand_impl>;
struct strand {
void post(std::function<void()>);
strand_ref ref;
};
struct signal_impl;
using signal_dtor = void (*)(signal_impl *);
using signal_ref = std::unique_ptr<signal_impl, signal_dtor>;
struct signal {
using handler = std::function<void(const std::error_code &, int signum)>;
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<timer_impl, timer_dtor>;
struct timer {
using handler = std::function<void(const std::error_code &)>;
explicit timer(context &);
explicit timer(strand &);
void expires_at(std::chrono::time_point<std::chrono::system_clock>);
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<socket_impl, socket_dtor>;
struct socket {
using handler =
std::function<void(const std::error_code &, std::size_t received)>;
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<char, receive_buffer_size> receive_buffer{};
socket_ref ref;
};
} // namespace udp
namespace tcp {
struct socket_impl;
using socket_dtor = void (*)(socket_impl *);
using socket_ref = std::unique_ptr<socket_impl, socket_dtor>;
struct socket {
using connect_handler = std::function<void(const std::error_code &)>;
using read_handler =
std::function<void(const std::error_code &, std::size_t)>;
using write_handler =
std::function<void(const std::error_code &, std::size_t)>;
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<uint8_t> &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<char, receive_buffer_size> read_buffer{};
socket_ref ref;
};
struct acceptor_impl;
using acceptor_dtor = void (*)(acceptor_impl *);
using acceptor_ref = std::unique_ptr<acceptor_impl, acceptor_dtor>;
struct acceptor {
using handler = std::function<void(int fd, const std::error_code &)>;
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<socket_impl, socket_dtor>;
struct socket {
using connect_handler = std::function<void(const std::error_code &)>;
using read_handler =
std::function<void(const std::error_code &, std::size_t)>;
using write_handler =
std::function<void(const std::error_code &, std::size_t)>;
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<uint8_t> &data, write_handler);
void read(read_handler);
void close();
auto release() -> int;
std::array<char, receive_buffer_size> read_buffer{};
socket_ref ref;
};
struct acceptor_impl;
using acceptor_dtor = void (*)(acceptor_impl *);
using acceptor_ref = std::unique_ptr<acceptor_impl, acceptor_dtor>;
struct acceptor {
using handler = std::function<void(int fd, const std::error_code &)>;
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<watcher_impl, watcher_dtor>;
struct watcher {
using handler = std::function<void(const std::error_code &)>;
explicit watcher(strand &, int fd);
void wait_read(handler);
void wait_write(handler);
void cancel();
watcher_ref ref;
};
} // namespace file_descriptor
} // namespace thespian::executor

645
src/executor_asio.cpp Normal file
View file

@ -0,0 +1,645 @@
#include "asio/error_code.hpp"
#include "asio/posix/descriptor_base.hpp"
#include "executor.hpp"
#include <cstddef>
#include <memory>
#include <asio/basic_socket_acceptor.hpp>
#include <asio/bind_executor.hpp>
#include <asio/io_context.hpp>
#include <asio/ip/address_v6.hpp>
#include <asio/ip/tcp.hpp>
#include <asio/ip/udp.hpp>
#include <asio/local/stream_protocol.hpp>
#include <asio/posix/stream_descriptor.hpp>
#include <asio/signal_set.hpp>
#include <asio/socket_base.hpp>
#include <asio/strand.hpp>
#include <asio/system_timer.hpp>
#include <asio/thread.hpp>
#include <csignal>
#include <vector>
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<long>(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<io_context>(threads)} {}
unique_ptr<io_context> asio;
atomic<size_t> pending;
atomic<size_t> 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<void()> 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<strand_impl>(ref)};
}
void strand::post(function<void()> f) { ref->post(move(f)); }
auto context::run() -> void {
const auto spawn_threads = max(threads - 1, 0L);
vector<thread *> 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<bool> 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<bool> token_{make_shared<bool>(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<system_clock> 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<bool> 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<bool> token_{make_shared<bool>(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<system_clock> 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<bool> 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<bool> token_{make_shared<bool>(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<bool> 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<uint8_t> &data, socket::write_handler h) {
ctx->pending.fetch_add(1, memory_order_relaxed);
weak_ptr<bool> 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<bool> 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<bool> token_{make_shared<bool>(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<uint8_t> &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<bool> 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<bool> token_{make_shared<bool>(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<bool> 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<uint8_t> &data, socket::write_handler h) {
ctx->pending.fetch_add(1, memory_order_relaxed);
weak_ptr<bool> 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<bool> 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<bool> token_{make_shared<bool>(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<uint8_t> &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<bool> 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<bool> token_{make_shared<bool>(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<bool> 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<bool> 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<bool> token_{make_shared<bool>(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

157
src/hub.cpp Normal file
View file

@ -0,0 +1,157 @@
#include <thespian/debug.hpp>
#include <thespian/endpoint.hpp>
#include <thespian/hub.hpp>
#include <thespian/instance.hpp>
#include <deque>
#include <mutex>
#include <set>
#include <vector>
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<mutex> lock(m_);
q_.push_back(move(f));
return from.send("subscribe_filtered");
}
auto pop() -> filter {
lock_guard<mutex> lock(m_);
filter f = move(q_.front());
q_.pop_front();
return f;
}
private:
deque<filter> 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<subscribe_queue> 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<string> 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<pair<handle, filter>>;
subscriber_list_t subscribers_;
shared_ptr<subscribe_queue> q_;
};
hub::hub(const handle &h, shared_ptr<pipe> 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<subscribe_queue &>(*pipe_).push(
static_cast<const handle &>(*this), move(f));
}
auto hub::listen(string_view desc) const -> result {
return send("listen", desc);
}
auto hub::create(string_view name) -> expected<hub, error> {
auto q = make_shared<subscribe_queue>();
auto ret = spawn_link(
[=]() {
receive([p{make_shared<hub_impl>(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<const handle &>(b);
}
auto operator==(const hub &a, const handle &b) -> bool {
return static_cast<const handle &>(a) == b;
}
} // namespace thespian

1988
src/instance.cpp Normal file

File diff suppressed because it is too large Load diff

233
src/subprocess.zig Normal file
View file

@ -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();
}
};

716
src/thespian.zig Normal file
View file

@ -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();
}
};

166
src/trace.cpp Normal file
View file

@ -0,0 +1,166 @@
#include <thespian/trace.hpp>
#include <atomic>
#include <fstream>
#include <memory>
#include <mutex>
#include <sstream>
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 <typename T, typename Q> 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<node *> 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<recursive_mutex> 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<T *>(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<cbor_file, buffer> {
using trace_file::trace_file;
auto on_trace(const buffer &msg) -> void {
s.write(reinterpret_cast<const char *>(msg.data()), msg.size()); // NOLINT
s.write(reinterpret_cast<const char *>(cbor_cr.data()), // NOLINT
cbor_cr.size()); // NOLINT
}
};
auto f = make_shared<cbor_file>(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<json_file, buffer> {
using trace_file::trace_file;
auto on_trace(const buffer &msg) -> void {
msg.to_json(s);
s << '\n';
}
};
auto f = make_shared<json_file>(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<mermaid_file, buffer> {
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<mermaid_file>(file_name);
on_trace([f](auto m) { f->push(move(m)); });
}
} // namespace thespian

315
test/cbor_match.cpp Normal file
View file

@ -0,0 +1,315 @@
#include "tests.hpp"
#include <thespian/instance.hpp>
#include <thespian/trace.hpp>
#include <limits>
#include <string>
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_)));
};

196
test/debug.cpp Normal file
View file

@ -0,0 +1,196 @@
#include "tests.hpp"
#include "thespian/handle.hpp"
#include <thespian/debug.hpp>
#include <thespian/instance.hpp>
#include <thespian/socket.hpp>
#include <thespian/tcp.hpp>
#include <thespian/trace.hpp>
#include <array>
#include <cstring>
#include <memory>
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<handle, error> {
return spawn_link(
[=]() {
::receive([p{make_shared<debuggee>()}](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<thespian::socket> 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<thespian::socket>(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<controller>(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_)));
}

93
test/endpoint_tcp.cpp Normal file
View file

@ -0,0 +1,93 @@
#include "tests.hpp"
#include "cbor/cbor.hpp"
#include "thespian/env.hpp"
#include "thespian/handle.hpp"
#include <thespian/endpoint.hpp>
#include <thespian/instance.hpp>
#include <thespian/socket.hpp>
#include <thespian/tcp.hpp>
#include <cstring>
#include <map>
#include <utility>
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<controller>(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_)));
}

104
test/endpoint_unx.cpp Normal file
View file

@ -0,0 +1,104 @@
#include "tests.hpp"
#include <thespian/endpoint.hpp>
#include <thespian/instance.hpp>
#include <thespian/socket.hpp>
#include <thespian/timeout.hpp>
#include <thespian/unx.hpp>
#include <cstring>
#include <map>
#include <sstream>
#include <unistd.h>
#include <utility>
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<controller>(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_)));
}

127
test/hub_filter.cpp Normal file
View file

@ -0,0 +1,127 @@
#include "tests.hpp"
#include "thespian/env.hpp"
#include "thespian/handle.hpp"
#include <thespian/hub.hpp>
#include <thespian/instance.hpp>
#include <map>
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();
}
map<string, auto(*)(const hub &, const handle &, string)->result>
subscription_defs // NOLINT
= {
{"sub_plain", sub_plain},
{"sub_filtered", sub_filtered},
};
using submap = map<string, handle>;
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)));
}

View file

@ -0,0 +1,225 @@
#include "tests.hpp"
#include "thespian/handle.hpp"
#include <cbor/cbor_in.hpp>
#include <thespian/debug.hpp>
#include <thespian/instance.hpp>
#include <thespian/socket.hpp>
#include <thespian/tcp.hpp>
#include <netinet/in.h>
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<client_connection>(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<client>(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<server_connection>(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<server>()};
_ = 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)));
}

122
test/ip_udp_echo.cpp Normal file
View file

@ -0,0 +1,122 @@
#include "tests.hpp"
#include "thespian/env.hpp"
#include "thespian/handle.hpp"
#include <thespian/instance.hpp>
#include <thespian/timeout.hpp>
#include <thespian/udp.hpp>
#include <netinet/in.h>
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<controller>()}](auto /*from*/, auto m) {
return p->receive(move(m));
});
return ok();
},
[&](auto s) {
if (s == "success")
result = true;
},
"ip_udp_echo", move(env_)));
}

81
test/metronome_test.cpp Normal file
View file

@ -0,0 +1,81 @@
#include "tests.hpp"
#include "thespian/env.hpp"
#include <thespian/instance.hpp>
#include <thespian/metronome.hpp>
#include <chrono>
#include <memory>
#include <string>
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<microseconds>(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<microseconds>(end - start);
met.stop();
return exit(us > 50000us ? "done" : "failed");
}
return ok();
}
};
shared_ptr<state_t> 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)));
}

140
test/perf_cbor.cpp Normal file
View file

@ -0,0 +1,140 @@
#include "tests.hpp"
#include "thespian/env.hpp"
#include "thespian/handle.hpp"
#include <thespian/instance.hpp>
#include <chrono>
#include <string>
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<microseconds>(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)));
}

153
test/perf_hub.cpp Normal file
View file

@ -0,0 +1,153 @@
#include "tests.hpp"
#include "thespian/env.hpp"
#include "thespian/handle.hpp"
#include <thespian/hub.hpp>
#include <thespian/instance.hpp>
#include <thespian/trace.hpp>
#include <chrono>
#include <set>
#include <sstream>
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<underlying_type_t<tag>>(e);
}
auto tag_from_int(int i) -> tag {
return static_cast<tag>(static_cast<int>(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<int> 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<int> not_ready = subs;
set<int> 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<milliseconds>(end - start).count();
double msgs = subscriptions * messages + subscriptions;
_ = log.send("all", "done");
_ = log.send("time:", ms, "ms");
_ = log.send(
static_cast<int>((msgs / static_cast<double>(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_)));
}

79
test/perf_ring.cpp Normal file
View file

@ -0,0 +1,79 @@
#include "tests.hpp"
#include "thespian/env.hpp"
#include <thespian/instance.hpp>
#include <string>
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_)));
}

73
test/perf_spawn.cpp Normal file
View file

@ -0,0 +1,73 @@
#include "tests.hpp"
#include "thespian/env.hpp"
#include <thespian/instance.hpp>
#include <string>
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_))));
}

120
test/spawn_exit.cpp Normal file
View file

@ -0,0 +1,120 @@
#include "tests.hpp"
#include "thespian/env.hpp"
#include "thespian/handle.hpp"
#include <thespian/instance.hpp>
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)));
}

161
test/tests.cpp Normal file
View file

@ -0,0 +1,161 @@
#include "tests.hpp"
#include <cstring>
#include <thespian/backtrace.h>
#include <thespian/context.hpp>
#include <thespian/debug.hpp>
#include <thespian/env.hpp>
#include <thespian/instance.hpp>
#include <thespian/timeout.hpp>
#include <thespian/trace.hpp>
#include <csignal>
#include <cstdlib>
#include <iostream>
#include <map>
#include <mutex>
#include <string>
#include <utility>
#include <vector>
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<mutex> 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<logger>(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<string, testcase *> 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<mutex> 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;
}

2
test/tests.h Normal file
View file

@ -0,0 +1,2 @@
int runtestcase(const char *name); // NOLINT

30
test/tests.hpp Normal file
View file

@ -0,0 +1,30 @@
#pragma once
#include <thespian/context.hpp>
#include <thespian/env.hpp>
#include <cstdlib>
#include <string_view>
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;

8
test/tests.zig Normal file
View file

@ -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());
}

435
test/tests_cbor.zig Normal file
View file

@ -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));
}

65
test/tests_cpp.zig Normal file
View file

@ -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");
}

14
test/tests_thespian.zig Normal file
View file

@ -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);
}

64
test/timeout_test.cpp Normal file
View file

@ -0,0 +1,64 @@
#include "tests.hpp"
#include "thespian/env.hpp"
#include "thespian/handle.hpp"
#include <thespian/instance.hpp>
#include <thespian/timeout.hpp>
#include <chrono>
#include <memory>
#include <string>
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<microseconds>(end - start);
return exit(us > 200ms ? "done" : "failed");
}
};
shared_ptr<state_t> 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)));
}

48
zig Executable file
View file

@ -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 "$@"