Skip to content

Commit

Permalink
[Feature] - Handle interrupt and termination signals to gracefully sh…
Browse files Browse the repository at this point in the history
…utdown
  • Loading branch information
sjanel committed Mar 2, 2024
1 parent cbb700b commit af95530
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 14 deletions.
31 changes: 20 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ Main features:
- [Simple usage](#simple-usage)
- [Multiple commands](#multiple-commands)
- [Piping commands](#piping-commands)
- [Repeat option](#repeat-option)
- [Interrupt signal handling for graceful shutdown](#interrupt-signal-handling-for-graceful-shutdown)
- [Logging](#logging)
- [Activity history](#activity-history)
- [Parallel requests](#parallel-requests)
Expand Down Expand Up @@ -121,7 +123,6 @@ Main features:
- [Standard - full information](#standard---full-information-2)
- [Sell - with information from previous 'piped' command](#sell---with-information-from-previous-piped-command-1)
- [Monitoring options](#monitoring-options)
- [Repeat](#repeat)
- [Limitations](#limitations)
- [Examples of use cases](#examples-of-use-cases)
- [Get an overview of your portfolio in Korean Won](#get-an-overview-of-your-portfolio-in-korean-won)
Expand Down Expand Up @@ -218,6 +219,23 @@ coincenter buy 1500XLM,binance withdraw kraken sell

The 1500XLM will be considered for withdraw from Binance if the buy is completed, and the XLM arrived on Kraken considered for selling when the withdraw completes.

##### Repeat option

`coincenter` commands are normally executed only once, and program is terminated after it.
To continuously query the same option use `-r <[n]>` or `--repeat <[n]>` (the `n` integer is optional) to repeat `n` times the given command(s) (or undefinably if no `n` given).

**Warning**: for trades and withdraw commands, use with care.

Between each repeat you can set a waiting time with `--repeat-time` option which expects a time duration.

It can be useful to store logs for an extended period of time and for [monitoring](#monitoring-options) data export purposes.

##### Interrupt signal handling for graceful shutdown

`coincenter` can exit gracefully with `SIGINT` and `SIGTERM` signals. When it receives such a signal, `coincenter` will stop processing commands after current one (ignoring the [repeat](#repeat-option) as well).

This allows to flush correctly the latest data (caches, logs, etc) to files at termination.

#### Logging

`coincenter` uses [spdlog](https://github.com/gabime/spdlog) for logging.
Expand Down Expand Up @@ -1002,15 +1020,6 @@ coincenter trade 80%KRW-ADA,upbit withdraw-apply kraken
Currently, its support is experimental and in development for all major options of `coincenter` (private and market data requests).
The metrics are exported in *push* mode to the gateway at each new query. You can configure the IP address, port, username and password (if any) thanks to command line options (refer to the help to see their names).

#### Repeat

`coincenter` commands are normally executed only once, and program is terminated after it.
To continuously query the same option to export regular metrics to Prometheus, you can use `--repeat` option. **Warning**: for trades and withdraw commands, use with care.

Without a following numeric value, the command will repeat endlessly. You can fix a specific number of repeats by giving a number.

Between each repeat you can set a waiting time with `--repeat-time` option which expects a time duration.

### Limitations

Be aware of the following limitations of `coincenter`:
Expand All @@ -1019,7 +1028,7 @@ Be aware of the following limitations of `coincenter`:
This is to ensure safety between withdrawals performed between exchanges.
- Only absolute withdraw fees are currently supported.
In some cases (seen in Huobi), withdraw fee can be a percentage. They will be considered as 0.
- Not really a limitation, but some **sensitive actions are not possible** by the exchanges API.
- Not really a limitation of `coincenter` itself, but some **sensitive actions are not possible** by the exchanges API.
For instance, withdrawal for Kraken is only possible for a stored destination made from the website first.

And probably more that I did not thought of, or never encountered. Feel free to open an issue and I will check it out if it's feasible!
Expand Down
23 changes: 20 additions & 3 deletions src/engine/src/coincenter.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "coincenter.hpp"

#include <algorithm>
#include <csignal>
#include <optional>
#include <span>
#include <thread>
Expand Down Expand Up @@ -35,8 +36,20 @@ void FillTransferableCommandResults(const TradeResultPerExchange &tradeResultPer
}
}
}

} // namespace

volatile sig_atomic_t g_signalStatus = 0;

// According to the standard, 'SignalHandler' function should have C linkage:
// (https://en.cppreference.com/w/cpp/utility/program/signal
// Thus it's not possible to use a lambda and pass some
// objects to it. This is why for this rare occasion we will rely on a static variable. This solution has been inspired
// by: https://wiki.sei.cmu.edu/confluence/display/cplusplus/MSC54-CPP.+A+signal+handler+must+be+a+plain+old+function
extern "C" void SignalHandler(int sigNum) {
log::warn("Signal {} received, will stop after current request", sigNum);
g_signalStatus = sigNum;
}

using UniquePublicSelectedExchanges = ExchangeRetriever::UniquePublicSelectedExchanges;

Coincenter::Coincenter(const CoincenterInfo &coincenterInfo, const ExchangeSecretsInfo &exchangeSecretsInfo)
Expand All @@ -47,13 +60,17 @@ Coincenter::Coincenter(const CoincenterInfo &coincenterInfo, const ExchangeSecre
_metricsExporter(coincenterInfo.metricGatewayPtr()),
_exchangePool(coincenterInfo, _fiatConverter, _commonAPI, _apiKeyProvider),
_exchangesOrchestrator(coincenterInfo.requestsConfig(), _exchangePool.exchanges()),
_queryResultPrinter(coincenterInfo.apiOutputType(), _coincenterInfo.loggingInfo()) {}
_queryResultPrinter(coincenterInfo.apiOutputType(), _coincenterInfo.loggingInfo()) {
// Register the signal handler to gracefully shutdown the main loop for repeated requests.
std::signal(SIGINT, SignalHandler);
std::signal(SIGTERM, SignalHandler);
}

int Coincenter::process(const CoincenterCommands &coincenterCommands) {
int nbCommandsProcessed = 0;
const auto commands = coincenterCommands.commands();
const int nbRepeats = commands.empty() ? 0 : coincenterCommands.repeats();
for (int repeatPos = 0; repeatPos != nbRepeats; ++repeatPos) {
for (int repeatPos = 0; repeatPos != nbRepeats && g_signalStatus == 0; ++repeatPos) {
if (repeatPos != 0) {
std::this_thread::sleep_for(coincenterCommands.repeatTime());
}
Expand Down

0 comments on commit af95530

Please sign in to comment.