Skip to content

Commit 99e485e

Browse files
committed
feat: portable top-level error-handling
1 parent 615958a commit 99e485e

4 files changed

Lines changed: 154 additions & 40 deletions

File tree

README.md

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -125,22 +125,22 @@ NB: this app uses `make` as a recipe book, but it's not essential:
125125
- Valgrind
126126
- pipeline
127127
- ✅ custom docker build image with all dependencies (hosted on GHCR for faster pipelines)
128-
- ✅ reusable pipeline components + Release pipeline
129-
- https://github.com/googleapis/release-please
128+
- ✅ reusable pipeline components
129+
- release pipeline
130130
- ✅ cron
131131
- ✅ comprehensive clang-tidy & clang-format checks
132132
- ✅ sonarcloud
133133
- ✅ cached dependencies
134+
- https://github.com/googleapis/release-please
134135
- containerised pipeline integration tests / dind
135-
- https://github.com/googleapis/release-please
136136
- local github action runner (`act`)
137137
- ccache or precomiled headers
138138
- testing
139139
- ✅ coverage gutters
140140
- ✅ dependency injection
141141
- integration test with mocked Binance server
142142
- FTXUI snapshot testing
143-
- performance
143+
- performance / latency
144144
- ✅ store prices and sizes as integrals (ticks as `uint64_t`) for performance
145145
- ✅ cache line alignment
146146
- ✅ tcmalloc (Full) / gperftools
@@ -149,7 +149,9 @@ NB: this app uses `make` as a recipe book, but it's not essential:
149149
- profiling (valgrind/cachegrind)
150150
- profile-guided optimization (pgo)
151151
- load test with mocked FIX server
152-
- latency
152+
- set process priority
153+
- NIC affinity
154+
- QoS
153155
- sparse arrays & flat matrix
154156
- memory-mapped files
155157
- (analyse) find Binance's server location for a low-latency connection
@@ -159,18 +161,18 @@ NB: this app uses `make` as a recipe book, but it's not essential:
159161
- kernel space vs user space
160162
- RT OS
161163
- ✅ logging
162-
- ✅ fast
163-
- compiled out 'debug' logging for release builds
164-
- thread name in logs
165-
- rolling
166-
- structured
167-
- basic schema (severity, correlationId)
168-
- release process
169-
- versioning
170-
- master branch merge check for conventional commit message (e.g. regex)
171-
- maybe a merge git gook check
172-
- automated semantic versioning
173-
- github-changelog-generator
164+
- ✅ fast
165+
- error handling
166+
- compiled out 'debug' logging for release builds
167+
- thread name in logs
168+
- rolling
169+
- structured
170+
- basic schema (severity, correlationId)
171+
- versioning
172+
- master branch merge check for conventional commit message (e.g. regex)
173+
- maybe a merge git gook check
174+
- automated semantic versioning
175+
- github-changelog-generator
174176
- observability
175177
- opentelemetry (asynchronous)
176178
- grafana+tempo via docker-compose

src/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
1. order updates (prices/volumes)
1010
2. trade updates
1111
- NB: Binance uses a custom authentication mechanism
12+
- NB: the `Config` class is important
1213

1314
## core
1415
- core trading engine functionality

src/main.cpp

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
#include <csignal>
2+
#include <cstdlib>
3+
#include <exception>
4+
#include <iostream>
5+
#include <new>
16
#include <string>
27

38
#include "binance/config.h"
@@ -6,36 +11,47 @@
611
#include "spdlog/sinks/basic_file_sink.h"
712
#include "spdlog/spdlog.h"
813
#include "ui/app/ui_app.h"
14+
#include "utils/crash.h"
915
#include "utils/env.h"
1016
#include "utils/threading.h"
1117

1218
int main() {
13-
utils::Threading::set_thread_name("main");
19+
try {
20+
utils::Threading::set_thread_name("main");
1421

15-
// log config
16-
spdlog::cfg::load_env_levels("LOG_LEVEL");
17-
const char* log_path = std::getenv("LOG_PATH");
18-
const auto logger = spdlog::basic_logger_mt("basic_logger", log_path);
19-
spdlog::set_default_logger(logger);
20-
// Set a global pattern without the logger name
21-
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] [%t] %v");
22-
spdlog::flush_every(std::chrono::microseconds(100));
23-
spdlog::info("hello");
24-
utils::Env::log_current_architecture();
22+
// log config
23+
spdlog::cfg::load_env_levels("LOG_LEVEL");
24+
const char* log_path = std::getenv("LOG_PATH");
25+
const auto logger = spdlog::basic_logger_mt("basic_logger", log_path);
26+
spdlog::set_default_logger(logger);
27+
// Set a global pattern without the logger name
28+
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] [%t] %v");
29+
spdlog::flush_every(std::chrono::microseconds(100));
30+
spdlog::info("hello");
31+
utils::Env::log_current_architecture();
2532

26-
// Binance market data connectivity
27-
auto b_conf = binance::Config::from_env();
28-
auto b_worker = binance::Worker::from_conf(b_conf);
29-
b_worker.start();
33+
// configure error-handling
34+
utils::Crash::setup_crash_handlers();
3035

31-
// ui app (reads from Binance's queues)
32-
auto ui =
33-
ui::App::from_env(b_worker.get_order_queue(), b_worker.get_trade_queue(), b_conf);
34-
ui.start();
36+
// Binance market data connectivity
37+
auto b_conf = binance::Config::from_env();
38+
auto b_worker = binance::Worker::from_conf(b_conf);
39+
b_worker.start();
3540

36-
if (ui.thread_exception) {
37-
std::rethrow_exception(ui.thread_exception);
41+
// ui app (reads from Binance's queues)
42+
auto ui =
43+
ui::App::from_env(b_worker.get_order_queue(), b_worker.get_trade_queue(), b_conf);
44+
// blocking
45+
ui.start();
46+
47+
if (ui.thread_exception) {
48+
std::rethrow_exception(ui.thread_exception);
49+
}
50+
b_worker.stop();
51+
spdlog::info("goodbye");
52+
} catch (const std::exception& e) {
53+
spdlog::error("[EXCEPTION] Caught exception. ex [{}]", e.what());
54+
} catch (...) {
55+
spdlog::error("[EXCEPTION] Caught unknown exception");
3856
}
39-
b_worker.stop();
40-
spdlog::info("goodbye");
4157
}

src/utils/crash.h

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#pragma once
2+
3+
#include <csignal>
4+
#include <cstdlib>
5+
#include <exception>
6+
#include <new>
7+
8+
#include "spdlog/spdlog.h"
9+
10+
#ifdef _WIN32
11+
#include <io.h>
12+
#define write _write
13+
#else
14+
#include <unistd.h>
15+
#endif
16+
17+
namespace utils {
18+
19+
/// @brief Portable crash handling utility
20+
class Crash {
21+
public:
22+
/// @brief std::terminate handler
23+
static void handle_terminate() {
24+
spdlog::error("[CRITICAL] Unhandled exception or terminate called!");
25+
std::abort(); // ensure process exits
26+
}
27+
28+
/// @brief std::new_handler for OOM
29+
static void handle_bad_alloc() {
30+
spdlog::error("[CRITICAL] Memory allocation failed (std::bad_alloc)");
31+
std::terminate(); // invokes terminate handler
32+
}
33+
34+
/// @brief Signal handler (async-signal-safe)
35+
static void handle_signal(int sig) {
36+
constexpr char prefix[] = "CRITICAL: Fatal signal caught: ";
37+
constexpr char suffix[] = "\n";
38+
char buf[64];
39+
size_t len = 0;
40+
41+
// copy prefix
42+
for (char c : prefix) {
43+
buf[len++] = c;
44+
}
45+
46+
// convert signal number
47+
int_to_str(sig, buf, len);
48+
49+
// copy suffix
50+
for (char c : suffix) {
51+
buf[len++] = c;
52+
}
53+
54+
// write to stderr and exit
55+
[[maybe_unused]] ssize_t n = write(2, buf, len);
56+
_exit(1);
57+
}
58+
59+
/// @brief Setup function
60+
static void setup_crash_handlers() {
61+
// Catch unhandled exceptions
62+
std::set_terminate(handle_terminate);
63+
// Catch std::bad_alloc (new OOM)
64+
std::set_new_handler(handle_bad_alloc);
65+
// Catch common fatal signals
66+
std::signal(SIGSEGV, handle_signal); // invalid memory access
67+
std::signal(SIGABRT, handle_signal); // abort()
68+
std::signal(SIGFPE, handle_signal); // divide by zero
69+
std::signal(SIGILL, handle_signal); // illegal instruction
70+
#ifdef __unix__
71+
std::signal(SIGBUS, handle_signal); // bus error (POSIX)
72+
#endif
73+
// SIGKILL and SIGSTOP cannot be caught
74+
}
75+
76+
private:
77+
static inline void int_to_str(int n, char* buf, size_t& len) {
78+
if (n == 0) {
79+
buf[len++] = '0';
80+
return;
81+
}
82+
char tmp[16];
83+
int i = 0;
84+
while (n > 0 && i < 16) {
85+
tmp[i++] = '0' + (n % 10);
86+
n /= 10;
87+
}
88+
// reverse digits
89+
for (int j = i - 1; j >= 0; --j) {
90+
buf[len++] = tmp[j];
91+
}
92+
}
93+
};
94+
95+
} // namespace utils

0 commit comments

Comments
 (0)