Skip to content

Commit 73ea3dd

Browse files
author
Dmitry Malakhov
committed
send logs on crash example
1 parent bae25f2 commit 73ea3dd

File tree

13 files changed

+215
-27
lines changed

13 files changed

+215
-27
lines changed

.clang-tidy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ Checks: >
33
-performance-enum-size,
44
bugprone-*,
55
-bugprone-easily-swappable-parameters,
6+
-bugprone-suspicious-stringview-data-usage,
67
modernize-*,
78
-modernize-use-trailing-return-type,
89
portability-*,

.github/workflows/Codestyle.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@ jobs:
2626
run: devenv shell "xmake project -k compile_commands -y"
2727

2828
- name: clang-tidy checks
29-
run: devenv shell "find include src -type f \( -name \*.cpp -o -name \*.hpp \) -print0 | xargs -0 -n1 -P$(nproc) clang-tidy -p build"
29+
run: devenv shell "find include src example -type f \( -name \*.cpp -o -name \*.hpp \) -print0 | xargs -0 -n1 -P$(nproc) clang-tidy -p build"

example/SendLogsOnCrash.cpp

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/// Use case: there is an application, producing logs. Logs are initially stored
2+
/// in some in-memory buffer. Periodically, a flush operation is triggered and all
3+
/// logs are written to various outputs: to remote udp server and to file. But what if
4+
/// an app receives malicious signal (such as SIGTERM when std::terminate is called or
5+
/// SIGFPE if current c++ implementation raises it on zero division) then in-buffer
6+
/// logs are lost by default. To prevent this we have to write custom signal handler.
7+
/// And to speed this sighandler up we will use corosig-powered async io
8+
9+
#include "boost/outcome/success_failure.hpp"
10+
#include "corosig/reactor/Default.hpp"
11+
12+
#include <algorithm>
13+
#include <boost/outcome/try.hpp>
14+
#include <corosig/Coro.hpp>
15+
#include <corosig/ErrorTypes.hpp>
16+
#include <corosig/Parallel.hpp>
17+
#include <corosig/Result.hpp>
18+
#include <corosig/Sighandler.hpp>
19+
#include <corosig/io/File.hpp>
20+
#include <corosig/io/TcpSocket.hpp>
21+
#include <csignal>
22+
#include <fstream>
23+
#include <netinet/in.h>
24+
#include <thread>
25+
26+
namespace {
27+
28+
sockaddr_storage const SERVER_ADDR = [] {
29+
::sockaddr_storage addr;
30+
std::memset(&addr, 0, sizeof(addr));
31+
32+
auto *addr4 = (::sockaddr_in *)&addr;
33+
addr4->sin_family = AF_INET;
34+
addr4->sin_port = htons(8080);
35+
addr4->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
36+
return addr;
37+
}();
38+
39+
constexpr std::string_view FILE1 = "file1.log";
40+
constexpr std::string_view FILE2 = "file2.log";
41+
42+
std::vector<std::string> logs_buffer;
43+
44+
corosig::Fut<void, corosig::Error<corosig::AllocationError, corosig::SyscallError>>
45+
sighandler(int) noexcept {
46+
using namespace corosig;
47+
48+
auto write_to_file = [](char const *path) -> Fut<void, Error<AllocationError, SyscallError>> {
49+
using enum File::OpenFlags;
50+
BOOST_OUTCOME_CO_TRY(auto file, co_await File::open(path, CREATE | TRUNCATE | WRONLY));
51+
for (auto &log : logs_buffer) {
52+
BOOST_OUTCOME_CO_TRY(co_await file.write(log));
53+
}
54+
co_return success();
55+
};
56+
57+
auto send_via_tcp = []() -> Fut<void, Error<AllocationError, SyscallError>> {
58+
BOOST_OUTCOME_CO_TRY(auto socket, co_await TcpSocket::connect(SERVER_ADDR));
59+
for (auto &log : logs_buffer) {
60+
BOOST_OUTCOME_CO_TRY(co_await socket.write(log));
61+
}
62+
co_return success();
63+
};
64+
65+
BOOST_OUTCOME_CO_TRY(co_await when_all_succeed(write_to_file(FILE1.data()),
66+
write_to_file(FILE2.data()), send_via_tcp()));
67+
68+
co_return success();
69+
}
70+
71+
} // namespace
72+
73+
int main() {
74+
constexpr auto REACTOR_MEMORY = 8 * 1024;
75+
for (auto signal : {SIGILL, SIGFPE, SIGTERM, SIGABRT}) {
76+
corosig::set_sighandler<REACTOR_MEMORY, sighandler>(signal);
77+
}
78+
79+
std::string remote_server_data;
80+
auto remote_server_thread = std::jthread([&] {
81+
int srv_fd = ::socket(AF_INET, SOCK_STREAM, 0);
82+
assert(srv_fd >= 0);
83+
84+
sockaddr_in addr{};
85+
addr.sin_family = AF_INET;
86+
addr.sin_port = ::htons(8080);
87+
addr.sin_addr.s_addr = ::htonl(INADDR_LOOPBACK);
88+
89+
int opt = 1;
90+
setsockopt(srv_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
91+
::bind(srv_fd, (sockaddr *)&addr, sizeof(addr)); // NOLINT
92+
::listen(srv_fd, 1);
93+
94+
int client = ::accept(srv_fd, nullptr, nullptr);
95+
char buf[1024]; // NOLINT
96+
while (true) {
97+
ssize_t n = ::read(client, buf, sizeof(buf));
98+
if (n <= 0) {
99+
break;
100+
}
101+
remote_server_data += std::string_view{buf, size_t(n)};
102+
}
103+
::close(client);
104+
::close(srv_fd);
105+
});
106+
107+
logs_buffer.emplace_back("Log message 1\n");
108+
logs_buffer.emplace_back("Log message 2\n");
109+
logs_buffer.emplace_back("Log message 3\n");
110+
logs_buffer.emplace_back("Log message 4\n");
111+
112+
::raise(SIGFPE);
113+
114+
{
115+
std::ifstream file{FILE1.data()};
116+
if (!file.is_open()) {
117+
std::cerr << "Failed to open file 1\n";
118+
} else {
119+
std::cout << "File 1 contains\n" << file.rdbuf() << '\n';
120+
}
121+
}
122+
{
123+
std::ifstream file{FILE2.data()};
124+
if (!file.is_open()) {
125+
std::cerr << "Failed to open file 2\n";
126+
} else {
127+
std::cout << "File 2 contains\n" << file.rdbuf() << '\n';
128+
}
129+
}
130+
131+
remote_server_thread.join();
132+
std::cout << "Remote server received\n" << remote_server_data << '\n';
133+
134+
return 0;
135+
}

include/corosig/Coro.hpp

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,23 +37,19 @@ struct CoroutinePromiseType : CoroListNode {
3737

3838
template <typename... ARGS>
3939
static void *operator new(size_t n, ARGS &&...) noexcept {
40-
return reactor().allocate_frame(n);
40+
return REACTOR::instance().allocate_frame(n);
4141
}
4242

4343
static void operator delete(void *frame) noexcept {
44-
return reactor().free_frame(frame);
45-
}
46-
47-
static REACTOR &reactor() noexcept {
48-
return reactor_provider<REACTOR>::engine();
44+
return REACTOR::instance().free_frame(frame);
4945
}
5046

5147
void yield_to_reactor() noexcept {
52-
reactor().yield(*this);
48+
REACTOR::instance().yield(*this);
5349
}
5450

5551
void poll_to_reactor(PollListNode &node) noexcept {
56-
reactor().poll(node);
52+
REACTOR::instance().poll(node);
5753
}
5854

5955
[[noreturn]] static void unhandled_exception() noexcept {
@@ -134,7 +130,7 @@ struct [[nodiscard("forgot to await?")]] Fut {
134130

135131
Result<T, extend_error<E, SyscallError>> block_on() && noexcept {
136132
while (!m_value.has_value()) {
137-
Result res = promise_type::reactor().do_event_loop_iteration();
133+
Result res = REACTOR::instance().do_event_loop_iteration();
138134
if (!res) {
139135
return failure(std::move(res.assume_error()));
140136
}

include/corosig/Parallel.hpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
#include "corosig/Result.hpp"
44

55
#include <boost/mp11/algorithm.hpp>
6+
#include <concepts>
67
#include <optional>
8+
#include <type_traits>
9+
#include <utility>
10+
#include <variant>
711

812
namespace corosig {
913

@@ -31,13 +35,17 @@ std::optional<ERROR> first_error(std::tuple<RESULTS...> &t) noexcept {
3135
return error_opt;
3236
}
3337

38+
template <typename T>
39+
using WrapVoid = std::conditional_t<std::same_as<void, T>, std::monostate, T>;
40+
3441
} // namespace detail
3542

3643
template <typename... REACTOR, typename... R, typename... E>
37-
Fut<std::tuple<R...>, extend_error<E...>> when_all_succeed(Fut<R, E> &&...futs) noexcept {
44+
Fut<std::tuple<detail::WrapVoid<R>...>, extend_error<E...>>
45+
when_all_succeed(Fut<R, E> &&...futs) noexcept {
3846
Result results_res = co_await when_all(std::move(futs)...);
3947
if (results_res.has_error()) {
40-
co_return failure(results_res.assume_error());
48+
co_return failure(std::move(results_res.assume_error()));
4149
}
4250
std::tuple<Result<R, E>...> &results = results_res.assume_value();
4351

@@ -47,7 +55,13 @@ Fut<std::tuple<R...>, extend_error<E...>> when_all_succeed(Fut<R, E> &&...futs)
4755

4856
co_return success(std::apply(
4957
[](Result<R, E> &&...current_result) {
50-
return std::tuple<R...>{std::move(current_result.assume_value())...};
58+
return std::tuple{[](Result<R, E> &&r) {
59+
if constexpr (std::same_as<void, R>) {
60+
return std::monostate{};
61+
} else {
62+
return std::move(r.assume_value());
63+
}
64+
}(std::move(current_result))...};
5165
},
5266
std::move(results)));
5367
}

include/corosig/Result.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
namespace corosig {
77

88
template <typename R, typename E>
9-
struct Result : boost::outcome_v2::result<R, E, boost::outcome_v2::policy::terminate> {
9+
struct [[nodiscard]] Result
10+
: boost::outcome_v2::result<R, E, boost::outcome_v2::policy::terminate> {
1011
using boost::outcome_v2::result<R, E, boost::outcome_v2::policy::terminate>::result;
1112
};
1213

include/corosig/Sighandler.hpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#pragma once
2+
3+
#include "corosig/reactor/Default.hpp"
4+
5+
#include <csignal>
6+
#include <stdexcept>
7+
8+
namespace corosig {
9+
10+
namespace detail {
11+
12+
template <size_t MEMORY, auto F>
13+
void sighandler(int sig) noexcept {
14+
std::signal(sig, SIG_DFL);
15+
16+
Alloc::Memory<MEMORY> mem;
17+
auto &reactor = Reactor::instance();
18+
reactor = Reactor{mem};
19+
20+
(void)F(sig).block_on();
21+
}
22+
23+
} // namespace detail
24+
25+
template <size_t MEMORY, auto F>
26+
void set_sighandler(int sig) {
27+
// initialize underlying TLS with default reactor
28+
// important to do it here since it may cause allocation
29+
// which is not safe to do inside sighandler
30+
(void)Reactor::instance();
31+
32+
if (std::signal(sig, detail::sighandler<MEMORY, F>) == SIG_ERR) {
33+
throw std::runtime_error{"std::signal failed"};
34+
}
35+
}
36+
37+
} // namespace corosig

include/corosig/reactor/Custom.hpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,14 @@
88

99
namespace corosig {
1010

11-
template <typename REACTOR>
12-
struct reactor_provider;
13-
1411
template <typename REACTOR>
1512
concept AReactor = requires(REACTOR &reactor) {
1613
{ reactor.allocate_frame(std::declval<size_t>()) } noexcept -> std::same_as<void *>;
1714
{ reactor.free_frame(std::declval<void *>()) } noexcept -> std::same_as<void>;
1815
{ reactor.yield(std::declval<CoroListNode &>()) } noexcept -> std::same_as<void>;
1916
{ reactor.poll(std::declval<PollListNode &>()) } noexcept -> std::same_as<void>;
2017
{ reactor.do_event_loop_iteration() } noexcept;
21-
{ reactor_provider<REACTOR>::engine() } noexcept -> std::same_as<REACTOR &>;
18+
{ REACTOR::instance() } noexcept -> std::same_as<REACTOR &>;
2219
};
2320

2421
} // namespace corosig

include/corosig/reactor/Default.hpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
namespace corosig {
1616

1717
struct Reactor {
18+
static Reactor &instance() noexcept;
19+
1820
Reactor() noexcept = default;
1921

2022
template <size_t SIZE>
@@ -47,11 +49,6 @@ struct Reactor {
4749
Alloc m_alloc;
4850
};
4951

50-
template <>
51-
struct reactor_provider<Reactor> {
52-
static Reactor &engine() noexcept;
53-
};
54-
5552
static_assert(AReactor<Reactor>);
5653

5754
} // namespace corosig

src/reactor/Default.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ void resume(CoroList &ready) noexcept {
7373

7474
namespace corosig {
7575

76-
Reactor &reactor_provider<Reactor>::engine() noexcept {
76+
Reactor &Reactor::instance() noexcept {
7777
thread_local Reactor thread_local_reactor;
7878
return thread_local_reactor;
7979
}

0 commit comments

Comments
 (0)