diff --git a/README.md b/README.md index ebdb6bd2..a3a70ee5 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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) @@ -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. @@ -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`: @@ -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! diff --git a/src/engine/src/coincenter.cpp b/src/engine/src/coincenter.cpp index b12b251f..d4b6edf0 100644 --- a/src/engine/src/coincenter.cpp +++ b/src/engine/src/coincenter.cpp @@ -1,6 +1,7 @@ #include "coincenter.hpp" #include +#include #include #include #include @@ -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) @@ -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()); }