2020-11-03 17:34:10 +01:00
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
# include "telegram-bot-api/ClientManager.h"
# include "telegram-bot-api/ClientParameters.h"
# include "telegram-bot-api/HttpConnection.h"
# include "telegram-bot-api/HttpServer.h"
# include "telegram-bot-api/HttpStatConnection.h"
# include "telegram-bot-api/Query.h"
# include "telegram-bot-api/Stats.h"
# include "td/telegram/ClientActor.h"
# include "td/db/binlog/Binlog.h"
# include "td/db/TQueue.h"
# include "td/net/GetHostByNameActor.h"
# include "td/net/HttpInboundConnection.h"
# include "td/actor/actor.h"
# include "td/actor/PromiseFuture.h"
# include "td/utils/buffer.h"
# include "td/utils/common.h"
# include "td/utils/crypto.h"
# include "td/utils/ExitGuard.h"
# include "td/utils/FileLog.h"
# include "td/utils/format.h"
//#include "td/utils/GitInfo.h"
# include "td/utils/logging.h"
# include "td/utils/MemoryLog.h"
# include "td/utils/misc.h"
# include "td/utils/OptionParser.h"
2020-11-10 00:15:40 +01:00
# include "td/utils/port/IPAddress.h"
2020-11-03 17:34:10 +01:00
# include "td/utils/port/path.h"
# include "td/utils/port/rlimit.h"
# include "td/utils/port/signals.h"
# include "td/utils/port/stacktrace.h"
# include "td/utils/port/user.h"
# include "td/utils/Slice.h"
# include "td/utils/Status.h"
# include "td/utils/Time.h"
# include "memprof/memprof.h"
# include <algorithm>
# include <atomic>
# include <cstdlib>
# include <memory>
# include <tuple>
namespace telegram_bot_api {
static std : : atomic_flag need_rotate_log ;
static void rotate_log_signal_handler ( int sig ) {
need_rotate_log . clear ( ) ;
}
static std : : atomic_flag need_quit ;
static void quit_signal_handler ( int sig ) {
need_quit . clear ( ) ;
}
static td : : MemoryLog < 1 < < 20 > memory_log ;
void print_log ( ) {
auto buf = memory_log . get_buffer ( ) ;
auto pos = memory_log . get_pos ( ) ;
td : : signal_safe_write ( " ------- Log dump ------- \n " ) ;
td : : signal_safe_write ( buf . substr ( pos ) , false ) ;
td : : signal_safe_write ( buf . substr ( 0 , pos ) , false ) ;
td : : signal_safe_write ( " \n " , false ) ;
td : : signal_safe_write ( " ------------------------ \n " ) ;
}
static void fail_signal_handler ( int sig ) {
td : : signal_safe_write_signal_number ( sig ) ;
td : : Stacktrace : : PrintOptions options ;
options . use_gdb = true ;
td : : Stacktrace : : print_to_stderr ( options ) ;
print_log ( ) ;
_Exit ( EXIT_FAILURE ) ;
}
static std : : atomic_flag need_change_verbosity_level ;
static void change_verbosity_level_signal_handler ( int sig ) {
need_change_verbosity_level . clear ( ) ;
}
static std : : atomic_flag need_dump_log ;
static void dump_log_signal_handler ( int sig ) {
need_dump_log . clear ( ) ;
}
static void sigsegv_signal_handler ( int signum , void * addr ) {
td : : signal_safe_write_pointer ( addr ) ;
fail_signal_handler ( signum ) ;
}
int main ( int argc , char * argv [ ] ) {
SET_VERBOSITY_LEVEL ( VERBOSITY_NAME ( FATAL ) ) ;
td : : ExitGuard exit_guard ;
need_rotate_log . test_and_set ( ) ;
need_quit . test_and_set ( ) ;
need_change_verbosity_level . test_and_set ( ) ;
need_dump_log . test_and_set ( ) ;
td : : Stacktrace : : init ( ) ;
td : : setup_signals_alt_stack ( ) . ensure ( ) ;
td : : set_signal_handler ( td : : SignalType : : User , rotate_log_signal_handler ) . ensure ( ) ;
td : : ignore_signal ( td : : SignalType : : HangUp ) . ensure ( ) ;
td : : ignore_signal ( td : : SignalType : : Pipe ) . ensure ( ) ;
td : : set_signal_handler ( td : : SignalType : : Quit , quit_signal_handler ) . ensure ( ) ;
td : : set_signal_handler ( td : : SignalType : : Abort , fail_signal_handler ) . ensure ( ) ;
td : : set_signal_handler ( td : : SignalType : : Other , fail_signal_handler ) . ensure ( ) ;
td : : set_extended_signal_handler ( td : : SignalType : : Error , sigsegv_signal_handler ) . ensure ( ) ;
td : : set_runtime_signal_handler ( 0 , change_verbosity_level_signal_handler ) . ensure ( ) ;
td : : set_runtime_signal_handler ( 1 , dump_log_signal_handler ) . ensure ( ) ;
td : : init_openssl_threads ( ) ;
auto start_time = td : : Time : : now ( ) ;
auto shared_data = std : : make_shared < SharedData > ( ) ;
auto parameters = std : : make_unique < ClientParameters > ( ) ;
parameters - > shared_data_ = shared_data ;
parameters - > start_timestamp_ = start_time ;
auto net_query_stats = td : : create_net_query_stats ( ) ;
parameters - > net_query_stats_ = net_query_stats ;
td : : OptionParser options ;
bool need_print_usage = false ;
int http_port = 8081 ;
int http_stat_port = 0 ;
2020-11-10 00:15:40 +01:00
td : : string http_ip_address = " 0.0.0.0 " ;
td : : string http_stat_ip_address = " 0.0.0.0 " ;
2020-11-03 17:34:10 +01:00
td : : string log_file_path ;
int verbosity_level = 0 ;
td : : int64 log_max_file_size = 2000000000 ;
td : : string working_directory ;
td : : string temporary_directory ;
td : : string username ;
td : : string groupname ;
td : : uint64 max_connections = 0 ;
ClientManager : : TokenRange token_range { 0 , 1 } ;
parameters - > api_id_ = [ ] ( auto x ) - > td : : int32 {
if ( x ) {
return td : : to_integer < td : : int32 > ( td : : Slice ( x ) ) ;
}
return 0 ;
} ( std : : getenv ( " TELEGRAM_API_ID " ) ) ;
parameters - > api_hash_ = [ ] ( auto x ) - > std : : string {
if ( x ) {
return x ;
}
return std : : string ( ) ;
} ( std : : getenv ( " TELEGRAM_API_HASH " ) ) ;
2020-11-05 15:47:21 +01:00
options . set_usage ( td : : Slice ( argv [ 0 ] ) , " --api-id=<arg> --api-hash=<arg> [--local] [OPTION]... " ) ;
2020-11-03 17:34:10 +01:00
options . set_description ( " Telegram Bot API server " ) ;
options . add_option ( ' h ' , " help " , " display this help text and exit " , [ & ] { need_print_usage = true ; } ) ;
options . add_option ( ' \0 ' , " local " , " allow the Bot API server to serve local requests " ,
[ & ] { parameters - > local_mode_ = true ; } ) ;
2020-11-10 16:14:44 +01:00
options . add_option ( ' \0 ' , " insecure " , " allow the Bot API to send request via insecure HTTP " , [ & ] { parameters - > allow_http_ = true ; } ) ;
options . add_option ( ' \0 ' , " relative " , " use relative file path in local mode " , [ & ] { parameters - > use_relative_path_ = true ; } ) ;
2020-11-03 17:34:10 +01:00
options . add_checked_option (
' \0 ' , " api-id " ,
" application identifier for Telegram API access, which can be obtained at https://my.telegram.org (defaults to "
" the value of the TELEGRAM_API_ID environment variable) " ,
td : : OptionParser : : parse_integer ( parameters - > api_id_ ) ) ;
options . add_option ( ' \0 ' , " api-hash " ,
" application identifier hash for Telegram API access, which can be obtained at "
" https://my.telegram.org (defaults to the value of the TELEGRAM_API_HASH environment variable) " ,
td : : OptionParser : : parse_string ( parameters - > api_hash_ ) ) ;
options . add_checked_option ( ' p ' , " http-port " , PSLICE ( ) < < " HTTP listening port (default is " < < http_port < < " ) " ,
td : : OptionParser : : parse_integer ( http_port ) ) ;
options . add_checked_option ( ' s ' , " http-stat-port " , " HTTP statistics port " ,
td : : OptionParser : : parse_integer ( http_stat_port ) ) ;
options . add_option ( ' d ' , " dir " , " server working directory " , td : : OptionParser : : parse_string ( working_directory ) ) ;
options . add_option ( ' t ' , " temp-dir " , " directory for storing HTTP server temporary files " ,
td : : OptionParser : : parse_string ( temporary_directory ) ) ;
2020-11-10 00:15:40 +01:00
options . add_checked_option ( ' \0 ' , " filter " ,
" \" <remainder>/<modulo> \" . Allow only bots with 'bot_user_id % modulo == remainder' " ,
[ & ] ( td : : Slice rem_mod ) {
td : : Slice rem ;
td : : Slice mod ;
std : : tie ( rem , mod ) = td : : split ( rem_mod , ' / ' ) ;
TRY_RESULT ( rem_i , td : : to_integer_safe < td : : uint64 > ( rem ) ) ;
TRY_RESULT ( mod_i , td : : to_integer_safe < td : : uint64 > ( mod ) ) ;
if ( rem_i > = mod_i ) {
return td : : Status : : Error ( " Wrong argument specified: ensure that remainder < modulo " ) ;
}
token_range = { rem_i , mod_i } ;
return td : : Status : : OK ( ) ;
} ) ;
2020-11-03 17:34:10 +01:00
options . add_checked_option ( ' \0 ' , " max-webhook-connections " ,
" default value of the maximum webhook connections per bot " ,
td : : OptionParser : : parse_integer ( parameters - > default_max_webhook_connections_ ) ) ;
2020-11-10 00:15:40 +01:00
options . add_checked_option ( ' \0 ' , " http-ip-address " ,
" local IP address, HTTP connections to which will be accepted. By default, connections to "
" any local IPv4 address are accepted " ,
[ & ] ( td : : Slice ip_address ) {
TRY_STATUS ( td : : IPAddress : : get_ip_address ( ip_address . str ( ) ) ) ;
http_ip_address = ip_address . str ( ) ;
return td : : Status : : OK ( ) ;
} ) ;
options . add_checked_option ( ' \0 ' , " http-stat-ip-address " ,
" local IP address, HTTP statistics connections to which will be accepted. By default, "
" statistics connections to any local IPv4 address are accepted " ,
[ & ] ( td : : Slice ip_address ) {
TRY_STATUS ( td : : IPAddress : : get_ip_address ( ip_address . str ( ) ) ) ;
http_stat_ip_address = ip_address . str ( ) ;
return td : : Status : : OK ( ) ;
} ) ;
2020-11-03 17:34:10 +01:00
options . add_option ( ' l ' , " log " , " path to the file where the log will be written " ,
td : : OptionParser : : parse_string ( log_file_path ) ) ;
options . add_checked_option ( ' v ' , " verbosity " , " log verbosity level " , td : : OptionParser : : parse_integer ( verbosity_level ) ) ;
options . add_checked_option (
' \0 ' , " log-max-file-size " ,
PSLICE ( ) < < " maximum size of the log file in bytes before it will be auto-rotated (default is "
< < log_max_file_size < < " ) " ,
td : : OptionParser : : parse_integer ( log_max_file_size ) ) ;
options . add_option ( ' u ' , " username " , " effective user name to switch to " , td : : OptionParser : : parse_string ( username ) ) ;
options . add_option ( ' g ' , " groupname " , " effective group name to switch to " , td : : OptionParser : : parse_string ( groupname ) ) ;
options . add_checked_option ( ' c ' , " max-connections " , " maximum number of open file descriptors " ,
td : : OptionParser : : parse_integer ( max_connections ) ) ;
2020-11-11 19:45:15 +01:00
options . add_checked_option ( ' \0 ' , " max-batch-operations " , PSLICE ( ) < < " maximum number of batch operations (default: " < < parameters - > max_batch_operations < < " ) " ,
td : : OptionParser : : parse_integer ( parameters - > max_batch_operations ) ) ;
2020-11-03 17:34:10 +01:00
options . add_checked_option (
' \0 ' , " proxy " , PSLICE ( ) < < " HTTP proxy server for outgoing webhook requests in the format http://host:port " ,
[ & ] ( td : : Slice address ) {
if ( td : : begins_with ( address , " http:// " ) ) {
address . remove_prefix ( 7 ) ;
} else if ( td : : begins_with ( address , " https:// " ) ) {
address . remove_prefix ( 8 ) ;
}
return parameters - > webhook_proxy_ip_address_ . init_host_port ( address . str ( ) ) ;
} ) ;
options . add_check ( [ & ] {
if ( parameters - > api_id_ < = 0 | | parameters - > api_hash_ . empty ( ) ) {
return td : : Status : : Error ( " You must provide valid api-id and api-hash obtained at https://my.telegram.org " ) ;
}
return td : : Status : : OK ( ) ;
} ) ;
options . add_check ( [ & ] {
if ( verbosity_level < 0 ) {
return td : : Status : : Error ( " Wrong verbosity level specified " ) ;
}
return td : : Status : : OK ( ) ;
} ) ;
auto r_non_options = options . run ( argc , argv , 0 ) ;
if ( need_print_usage ) {
LOG ( PLAIN ) < < options ;
return 0 ;
}
if ( r_non_options . is_error ( ) ) {
LOG ( PLAIN ) < < argv [ 0 ] < < " : " < < r_non_options . error ( ) ;
LOG ( PLAIN ) < < options ;
return 1 ;
}
class CombineLog : public td : : LogInterface {
public :
void append ( td : : CSlice slice , int log_level ) override {
if ( first_ & & log_level < = first_verbosity_level_ ) {
first_ - > append ( slice , log_level ) ;
}
if ( second_ & & log_level < = second_verbosity_level_ ) {
second_ - > append ( slice , log_level ) ;
}
}
void set_first ( LogInterface * first ) {
first_ = first ;
}
void set_second ( LogInterface * second ) {
second_ = second ;
}
void set_first_verbosity_level ( int verbosity_level ) {
first_verbosity_level_ = verbosity_level ;
}
void set_second_verbosity_level ( int verbosity_level ) {
second_verbosity_level_ = verbosity_level ;
}
void rotate ( ) override {
if ( first_ ) {
first_ - > rotate ( ) ;
}
if ( second_ ) {
second_ - > rotate ( ) ;
}
}
td : : vector < td : : string > get_file_paths ( ) override {
td : : vector < td : : string > result ;
if ( first_ ) {
td : : append ( result , first_ - > get_file_paths ( ) ) ;
}
if ( second_ ) {
td : : append ( result , second_ - > get_file_paths ( ) ) ;
}
return result ;
}
private :
LogInterface * first_ = nullptr ;
int first_verbosity_level_ = VERBOSITY_NAME ( FATAL ) ;
LogInterface * second_ = nullptr ;
int second_verbosity_level_ = VERBOSITY_NAME ( FATAL ) ;
} ;
CombineLog log ;
log . set_first ( td : : default_log_interface ) ;
log . set_second ( & memory_log ) ;
td : : log_interface = & log ;
td : : FileLog file_log ;
td : : TsLog ts_log ( & file_log ) ;
auto init_status = [ & ] {
if ( max_connections ! = 0 ) {
TRY_STATUS_PREFIX ( td : : set_resource_limit ( td : : ResourceLimitType : : NoFile , max_connections ) ,
" Can't set file descriptor limit: " ) ;
}
if ( ! username . empty ( ) ) {
TRY_STATUS_PREFIX ( td : : change_user ( username , groupname ) , " Can't change effective user: " ) ;
}
if ( ! working_directory . empty ( ) ) {
TRY_STATUS_PREFIX ( td : : chdir ( working_directory ) , " Can't set working directory: " ) ;
}
if ( ! temporary_directory . empty ( ) ) {
TRY_STATUS_PREFIX ( td : : set_temporary_dir ( temporary_directory ) , " Can't set temporary directory: " ) ;
}
if ( ! log_file_path . empty ( ) ) {
TRY_STATUS_PREFIX ( file_log . init ( log_file_path , log_max_file_size ) , " Can't open log file: " ) ;
log . set_first ( & ts_log ) ;
}
return td : : Status : : OK ( ) ;
} ( ) ;
if ( init_status . is_error ( ) ) {
LOG ( PLAIN ) < < init_status . error ( ) ;
LOG ( PLAIN ) < < options ;
return 1 ;
}
if ( parameters - > default_max_webhook_connections_ < = 0 ) {
parameters - > default_max_webhook_connections_ = parameters - > local_mode_ ? 100 : 40 ;
}
: : td : : VERBOSITY_NAME ( dns_resolver ) = VERBOSITY_NAME ( WARNING ) ;
int memory_verbosity_level = td : : max ( VERBOSITY_NAME ( INFO ) , verbosity_level ) ;
SET_VERBOSITY_LEVEL ( memory_verbosity_level ) ;
log . set_first_verbosity_level ( verbosity_level ) ;
log . set_second_verbosity_level ( memory_verbosity_level ) ;
// LOG(WARNING) << "Bot API server with commit " << td::GitInfo::commit() << ' '
// << (td::GitInfo::is_dirty() ? "(dirty)" : "") << " started";
LOG ( WARNING ) < < " Bot API server started " ;
const int threads_n = 5 ; // +3 for Td, one for slow HTTP connections and one for DNS resolving
td : : ConcurrentScheduler sched ;
sched . init ( threads_n ) ;
td : : GetHostByNameActor : : Options get_host_by_name_options ;
get_host_by_name_options . scheduler_id = threads_n ;
parameters - > get_host_by_name_actor_id_ =
sched . create_actor_unsafe < td : : GetHostByNameActor > ( 0 , " GetHostByName " , std : : move ( get_host_by_name_options ) )
. release ( ) ;
auto client_manager =
sched . create_actor_unsafe < ClientManager > ( 0 , " ClientManager " , std : : move ( parameters ) , token_range ) . release ( ) ;
sched
. create_actor_unsafe < HttpServer > (
2020-11-10 00:15:40 +01:00
0 , " HttpServer " , http_ip_address , http_port ,
2020-11-03 17:34:10 +01:00
[ client_manager , shared_data ] {
return td : : ActorOwn < td : : HttpInboundConnection : : Callback > (
td : : create_actor < HttpConnection > ( " HttpConnection " , client_manager , shared_data ) ) ;
} )
. release ( ) ;
if ( http_stat_port ! = 0 ) {
sched
. create_actor_unsafe < HttpServer > (
2020-11-10 00:15:40 +01:00
0 , " HttpStatsServer " , http_stat_ip_address , http_stat_port ,
2020-11-03 17:34:10 +01:00
[ client_manager ] {
return td : : ActorOwn < td : : HttpInboundConnection : : Callback > (
td : : create_actor < HttpStatConnection > ( " HttpStatConnection " , client_manager ) ) ;
} )
. release ( ) ;
}
sched . start ( ) ;
2020-11-10 00:27:18 +01:00
double next_cron_time = start_time ;
2020-11-03 17:34:10 +01:00
double last_dump_time = start_time - 1000.0 ;
double last_tqueue_gc_time = start_time - 1000.0 ;
td : : int64 tqueue_deleted_events = 0 ;
td : : int64 last_tqueue_deleted_events = 0 ;
bool close_flag = false ;
std : : atomic_bool can_quit { false } ;
ServerCpuStat : : instance ( ) ; // create ServerCpuStat instance
while ( true ) {
2020-11-10 00:27:18 +01:00
sched . run_main ( next_cron_time - td : : Time : : now ( ) ) ;
2020-11-03 17:34:10 +01:00
if ( ! need_rotate_log . test_and_set ( ) ) {
td : : log_interface - > rotate ( ) ;
}
if ( ! need_quit . test_and_set ( ) ) {
if ( close_flag ) {
LOG ( WARNING ) < < " Receive stop signal again. Exit immediately... " ;
std : : _Exit ( 0 ) ;
}
LOG ( WARNING ) < < " Stopping engine by a signal " ;
close_flag = true ;
auto guard = sched . get_main_guard ( ) ;
send_closure ( client_manager , & ClientManager : : close , td : : PromiseCreator : : lambda ( [ & can_quit ] ( td : : Unit ) {
can_quit . store ( true ) ;
td : : Scheduler : : instance ( ) - > yield ( ) ;
} ) ) ;
}
if ( can_quit . exchange ( false ) ) {
break ;
}
if ( ! need_change_verbosity_level . test_and_set ( ) ) {
auto current_global_verbosity_level = GET_VERBOSITY_LEVEL ( ) ;
SET_VERBOSITY_LEVEL ( 256 - current_global_verbosity_level ) ;
verbosity_level = 256 - verbosity_level ;
log . set_first_verbosity_level ( verbosity_level ) ;
}
auto next_verbosity_level = shared_data - > next_verbosity_level_ . exchange ( - 1 ) ;
if ( next_verbosity_level ! = - 1 ) {
verbosity_level = next_verbosity_level ;
memory_verbosity_level = td : : max ( VERBOSITY_NAME ( INFO ) , verbosity_level ) ;
SET_VERBOSITY_LEVEL ( memory_verbosity_level ) ;
log . set_first_verbosity_level ( verbosity_level ) ;
log . set_second_verbosity_level ( memory_verbosity_level ) ;
}
if ( ! need_dump_log . test_and_set ( ) ) {
print_log ( ) ;
}
double now = td : : Time : : now ( ) ;
2020-11-10 00:27:18 +01:00
if ( now > = next_cron_time ) {
next_cron_time + = 1.0 ;
2020-11-03 17:34:10 +01:00
ServerCpuStat : : update ( now ) ;
}
if ( now > last_tqueue_gc_time + 60.0 ) {
auto unix_time = shared_data - > get_unix_time ( now ) ;
LOG ( INFO ) < < " Run TQueue GC at " < < unix_time ;
last_tqueue_gc_time = now ;
auto guard = sched . get_main_guard ( ) ;
auto deleted_events = shared_data - > tqueue_ - > run_gc ( unix_time ) ;
LOG ( INFO ) < < " TQueue GC deleted " < < deleted_events < < " events " ;
tqueue_deleted_events + = deleted_events ;
if ( tqueue_deleted_events > last_tqueue_deleted_events + 10000 ) {
LOG ( WARNING ) < < " TQueue GC already deleted " < < tqueue_deleted_events < < " events since the start " ;
last_tqueue_deleted_events = tqueue_deleted_events ;
}
}
if ( now > last_dump_time + 300.0 ) {
last_dump_time = now ;
if ( is_memprof_on ( ) ) {
LOG ( WARNING ) < < " Memory dump: " ;
td : : vector < AllocInfo > v ;
dump_alloc ( [ & ] ( const AllocInfo & info ) { v . push_back ( info ) ; } ) ;
std : : sort ( v . begin ( ) , v . end ( ) , [ ] ( const AllocInfo & a , const AllocInfo & b ) { return a . size > b . size ; } ) ;
size_t total_size = 0 ;
size_t other_size = 0 ;
int count = 0 ;
for ( auto & info : v ) {
if ( count + + < 50 ) {
LOG ( WARNING ) < < td : : format : : as_size ( info . size ) < < td : : format : : as_array ( info . backtrace ) ;
} else {
other_size + = info . size ;
}
total_size + = info . size ;
}
LOG ( WARNING ) < < td : : tag ( " other " , td : : format : : as_size ( other_size ) ) ;
LOG ( WARNING ) < < td : : tag ( " total " , td : : format : : as_size ( total_size ) ) ;
LOG ( WARNING ) < < td : : tag ( " total traces " , get_ht_size ( ) ) ;
LOG ( WARNING ) < < td : : tag ( " fast_backtrace_success_rate " , get_fast_backtrace_success_rate ( ) ) ;
}
LOG ( WARNING ) < < td : : tag ( " buffer_mem " , td : : format : : as_size ( td : : BufferAllocator : : get_buffer_mem ( ) ) ) ;
LOG ( WARNING ) < < td : : tag ( " buffer_slice_size " , td : : format : : as_size ( td : : BufferAllocator : : get_buffer_slice_size ( ) ) ) ;
auto query_count = shared_data - > query_count_ . load ( ) ;
LOG ( WARNING ) < < td : : tag ( " pending queries " , query_count ) ;
td : : uint64 i = 0 ;
bool was_gap = false ;
for ( auto end = & shared_data - > query_list_ , cur = end - > prev ; cur ! = end ; cur = cur - > prev , i + + ) {
if ( i < 20 | | i > query_count - 20 | | i % ( query_count / 50 + 1 ) = = 0 ) {
if ( was_gap ) {
LOG ( WARNING ) < < " ... " ;
was_gap = false ;
}
LOG ( WARNING ) < < static_cast < Query & > ( * cur ) ;
} else {
was_gap = true ;
}
}
td : : dump_pending_network_queries ( * net_query_stats ) ;
}
}
LOG ( WARNING ) < < " --------------------FINISH ENGINE-------------------- " ;
CHECK ( net_query_stats . use_count ( ) = = 1 ) ;
net_query_stats = nullptr ;
sched . finish ( ) ;
SET_VERBOSITY_LEVEL ( VERBOSITY_NAME ( FATAL ) ) ;
td : : log_interface = td : : default_log_interface ;
return 0 ;
}
} // namespace telegram_bot_api
int main ( int argc , char * argv [ ] ) {
return telegram_bot_api : : main ( argc , argv ) ;
}