Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,23 @@ Checks: >
-bugprone-suspicious-stringview-data-usage,
modernize-*,
-modernize-use-trailing-return-type,
-modernize-use-integer-sign-comparison,
portability-*,
readability-*,
-readability-identifier-length,
-readability-uppercase-literal-suffix,
-readability-static-accessed-through-instance,
-readability-make-member-function-const,
-readability-avoid-return-with-void-value,
-readability-named-parameter,
-readability-magic-numbers,


# Turn all the warnings from the checks above into errors.
WarningsAsErrors: "*"

CheckOptions:
- { key: readability-function-cognitive-complexity.IgnoreMacros, value: true }
- { key: readability-identifier-naming.GlobalConstantCase, value: UPPER_CASE }
- { key: readability-identifier-naming.ConstexprVariableCase, value: UPPER_CASE }
- { key: readability-identifier-naming.LocalConstantCase, value: lower_case }
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/Codestyle.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ jobs:
run: devenv shell echo

- name: clang-format checks
run: devenv shell "find . -type f \( -name \*.cpp -o -name \*.hpp \) -print0 | xargs -0 -n1 -P$(nproc) clang-format --Werror -n"
run: devenv shell "xmake format -ne"

- name: generate compile-commands
run: devenv shell "xmake project -k compile_commands -y"

- name: clang-tidy checks
run: devenv shell "find include src example -type f \( -name \*.cpp -o -name \*.hpp \) -print0 | xargs -0 -n1 -P$(nproc) clang-tidy -p build"
run: devenv shell "xmake check clang.tidy"
2 changes: 1 addition & 1 deletion .github/workflows/TestUbuntu.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ jobs:
- name: devenv.sh evaluation
run: devenv shell echo

- run: devenv shell "xmake f --toolchain=${{ matrix.toolchain }} --mode=${{ matrix.config }} --root -y"
- run: devenv shell "xmake config --toolchain=${{ matrix.toolchain }} --mode=${{ matrix.config }} --root -y"
- run: devenv shell "xmake build -y"
- run: devenv shell "xmake test -y"
60 changes: 60 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# CONTRIBUTING

PRs and issues are welcome. If you are in doubt, if some change is even needed, then open an issue to discuss it. Is you are confident enough, there is nobody to stop you to introduce changes via PR without initial discussion, but there is always a chance of doing useless job, so, consider the first option.

Also be polite to other people and blablabla.

## Running examples

Just type:

```shell
xmake run example_NAME
```

## One devenv to rule them all

This project uses [devenv](https://github.com/cachix/devenv). Long story short, this is a tool which helps to ensure all developers have the same versions of all essential tools. And this is important since clang-tidy and clang-format produce different output depending on version. And this is checked in CI (because code formatting is automatable robot job, not an art)

To enter devenv run

```shell
devenv shell [command]
```

Where optional command param may be replaced with editor of your choice.

If somehow environment is not reproducible on your machine by just running suggested command, consider opening an Issue or/and PR.

## Actual workflow

This project uses [xmake](https://github.com/xmake-io/xmake) as it's build system, dependency manager, and workflow driver (whatever is that).

Here is the list of commands you need to use to pass CI

Select the config:

```shell
xmake config --toolchain=(gcc|clang++) --mode(debug|asan|tsan|release)
```

You will need to make tests pass in all of them. 99% of the cases passing it in tsan mode guarantees passage with all other modes and toolchains.

Run tests:

```shell
xmake test
```

And, if you are an LSP-slave, like me

```shell
xmake project -k compile_commands
```

Then run codestyle-related checks:

```shell
xmake format
xmake check clang.tidy
```
20 changes: 10 additions & 10 deletions devenv.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1759939975,
"lastModified": 1762889687,
"owner": "cachix",
"repo": "devenv",
"rev": "6eda3b7af3010d289e6e8e047435956fc80c1395",
"rev": "3b4fb549962342c928aae1bbea3a13f0eeed2703",
"type": "github"
},
"original": {
Expand All @@ -19,10 +19,10 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1747046372,
"lastModified": 1761588595,
"owner": "edolstra",
"repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
"type": "github"
},
"original": {
Expand All @@ -40,10 +40,10 @@
]
},
"locked": {
"lastModified": 1759523803,
"lastModified": 1762868777,
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "cfc9f7bb163ad8542029d303e599c0f7eee09835",
"rev": "c5c3147730384576196fb5da048a6e45dee10d56",
"type": "github"
},
"original": {
Expand All @@ -60,10 +60,10 @@
]
},
"locked": {
"lastModified": 1709087332,
"lastModified": 1762808025,
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c",
"type": "github"
},
"original": {
Expand All @@ -74,10 +74,10 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1758532697,
"lastModified": 1761313199,
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "207a4cb0e1253c7658c6736becc6eb9cace1f25f",
"rev": "d1c30452ebecfc55185ae6d1c983c09da0c274ff",
"type": "github"
},
"original": {
Expand Down
10 changes: 3 additions & 7 deletions devenv.nix
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
# https://devenv.sh/
{ pkgs, ... }:
{
packages = with pkgs; [
llvmPackages_latest.clang-tools
xmake
gdb
];
{ pkgs, ... }: {
packages = with pkgs; [ llvmPackages_21.clang-tools xmake gdb ];

languages.nix.enable = true;
languages.cplusplus.enable = true;
}
122 changes: 72 additions & 50 deletions example/SendLogsOnCrash.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
/// Use case: there is an application, producing logs. Logs are initially stored
/// in some in-memory buffer. Periodically, a flush operation is triggered and all
/// logs are written to various outputs: to remote udp server and to file. But what if
/// an app receives malicious signal (such as SIGTERM when std::terminate is called or
/// an app receives malicious signal (such as SIGABRT when std::terminate is called or
/// SIGFPE if current c++ implementation raises it on zero division) then in-buffer
/// logs are lost by default. To prevent this we have to write custom signal handler.
/// And to speed this sighandler up we will use corosig-powered async io

#include <boost/outcome/try.hpp>
#include "corosig/reactor/Reactor.hpp"

#include <corosig/Coro.hpp>
#include <corosig/ErrorTypes.hpp>
#include <corosig/Parallel.hpp>
Expand All @@ -15,9 +16,13 @@
#include <corosig/io/File.hpp>
#include <corosig/io/TcpSocket.hpp>
#include <csignal>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <netinet/in.h>
#include <system_error>
#include <thread>
#include <vector>

namespace {

Expand All @@ -37,31 +42,72 @@ constexpr std::string_view FILE2 = "file2.log";

std::vector<std::string> logs_buffer;

corosig::Fut<void, corosig::Error<corosig::AllocationError, corosig::SyscallError>>
sighandler(int) noexcept {
using namespace corosig;
namespace sighandling {

auto write_to_file = [](char const *path) -> Fut<void, Error<AllocationError, SyscallError>> {
using enum File::OpenFlags;
BOOST_OUTCOME_CO_TRY(auto file, co_await File::open(path, CREATE | TRUNCATE | WRONLY));
for (auto &log : logs_buffer) {
BOOST_OUTCOME_CO_TRY(co_await file.write(log));
}
co_return success();
};
using namespace corosig;

auto send_via_tcp = []() -> Fut<void, Error<AllocationError, SyscallError>> {
BOOST_OUTCOME_CO_TRY(auto socket, co_await TcpSocket::connect(SERVER_ADDR));
for (auto &log : logs_buffer) {
BOOST_OUTCOME_CO_TRY(co_await socket.write(log));
}
co_return success();
};
Fut<void, Error<AllocationError, SyscallError>> write_to_file(Reactor &r,
char const *path) noexcept {
using enum File::OpenFlags;
COROSIG_CO_TRY(auto file, co_await File::open(r, path, CREATE | TRUNCATE | WRONLY));
for (auto &log : logs_buffer) {
COROSIG_CO_TRYV(co_await file.write(r, log));
}
co_return Ok{};
};

Fut<void, Error<AllocationError, SyscallError>> send_via_tcp(Reactor &r) {
COROSIG_CO_TRY(auto socket, co_await TcpSocket::connect(r, SERVER_ADDR));
for (auto &log : logs_buffer) {
COROSIG_CO_TRYV(co_await socket.write(r, log));
}
co_return Ok{};
};

BOOST_OUTCOME_CO_TRY(co_await when_all_succeed(write_to_file(FILE1.data()),
write_to_file(FILE2.data()), send_via_tcp()));
Fut<void, Error<AllocationError, SyscallError>> sighandler(Reactor &r, int) noexcept {

co_return success();
COROSIG_CO_TRYV(co_await when_all_succeed(r, write_to_file(r, FILE1.data()),
write_to_file(r, FILE2.data()), send_via_tcp(r)));
co_return Ok{};
}

} // namespace sighandling

void run_tcp_server(std::string &out) {
int srv_fd = ::socket(AF_INET, SOCK_STREAM, 0);
if (srv_fd == -1) {
throw std::system_error{errno, std::system_category(), "socket"};
}

sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = ::htons(8080);
addr.sin_addr.s_addr = ::htonl(INADDR_LOOPBACK);

int opt = 1;
if (::setsockopt(srv_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
throw std::system_error{errno, std::system_category(), "setsockopt"};
}

if (::bind(srv_fd, (sockaddr *)&addr, sizeof(addr)) == -1) {
throw std::system_error{errno, std::system_category(), "bind"};
}

if (::listen(srv_fd, 1) == -1) {
throw std::system_error{errno, std::system_category(), "listen"};
}

int client = ::accept(srv_fd, nullptr, nullptr);
std::array<char, 1024> buf;
while (true) {
ssize_t n = ::read(client, buf.begin(), buf.size());
if (n <= 0) {
break;
}
out += std::string_view{buf.begin(), size_t(n)};
}
::close(client);
::close(srv_fd);
}

} // namespace
Expand All @@ -70,36 +116,11 @@ int main() {
try {
constexpr auto REACTOR_MEMORY = 8 * 1024;
for (auto signal : {SIGILL, SIGFPE, SIGTERM, SIGABRT}) {
corosig::set_sighandler<REACTOR_MEMORY, sighandler>(signal);
corosig::set_sighandler<REACTOR_MEMORY, sighandling::sighandler>(signal);
}

std::string remote_server_data;
auto remote_server_thread = std::jthread([&] {
int srv_fd = ::socket(AF_INET, SOCK_STREAM, 0);
assert(srv_fd >= 0);

sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = ::htons(8080);
addr.sin_addr.s_addr = ::htonl(INADDR_LOOPBACK);

int opt = 1;
setsockopt(srv_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
::bind(srv_fd, (sockaddr *)&addr, sizeof(addr)); // NOLINT
::listen(srv_fd, 1);

int client = ::accept(srv_fd, nullptr, nullptr);
char buf[1024]; // NOLINT
while (true) {
ssize_t n = ::read(client, buf, sizeof(buf));
if (n <= 0) {
break;
}
remote_server_data += std::string_view{buf, size_t(n)};
}
::close(client);
::close(srv_fd);
});
auto remote_server_thread = std::jthread(run_tcp_server, std::ref(remote_server_data));

logs_buffer.emplace_back("Log message 1\n");
logs_buffer.emplace_back("Log message 2\n");
Expand Down Expand Up @@ -131,5 +152,6 @@ int main() {
return 0;
} catch (const std::exception &e) {
std::cout << e.what() << '\n';
return 1;
}
}
Loading
Loading