2014-04-17 16:00:52 +00:00
# include <fc/network/rate_limiting.hpp>
2014-04-17 23:39:15 +00:00
# include <fc/network/tcp_socket_io_hooks.hpp>
2014-04-17 16:00:52 +00:00
# include <fc/network/tcp_socket.hpp>
# include <fc/network/ip.hpp>
# include <fc/fwd_impl.hpp>
# include <fc/asio.hpp>
# include <fc/log/logger.hpp>
# include <fc/io/stdio.hpp>
# include <fc/exception/exception.hpp>
2014-04-17 23:39:15 +00:00
# include <fc/thread/thread.hpp>
2014-04-17 16:00:52 +00:00
namespace fc
{
namespace detail
{
// data about a read or write we're managing
class rate_limited_operation
{
public :
size_t length ;
size_t permitted_length ;
promise < size_t > : : ptr completion_promise ;
rate_limited_operation ( size_t length ,
promise < size_t > : : ptr & & completion_promise ) :
length ( length ) ,
permitted_length ( 0 ) ,
completion_promise ( completion_promise )
{ }
virtual void perform_operation ( ) = 0 ;
} ;
class rate_limited_tcp_write_operation : public rate_limited_operation
{
public :
boost : : asio : : ip : : tcp : : socket & socket ;
const char * buffer ;
rate_limited_tcp_write_operation ( boost : : asio : : ip : : tcp : : socket & socket ,
const char * buffer ,
size_t length ,
promise < size_t > : : ptr completion_promise ) :
rate_limited_operation ( length , std : : move ( completion_promise ) ) ,
socket ( socket ) ,
buffer ( buffer )
{ }
virtual void perform_operation ( ) override
{
asio : : async_write_some ( socket ,
boost : : asio : : buffer ( buffer , permitted_length ) ,
completion_promise ) ;
}
} ;
class rate_limited_tcp_read_operation : public rate_limited_operation
{
public :
boost : : asio : : ip : : tcp : : socket & socket ;
char * buffer ;
rate_limited_tcp_read_operation ( boost : : asio : : ip : : tcp : : socket & socket ,
char * buffer ,
size_t length ,
promise < size_t > : : ptr completion_promise ) :
rate_limited_operation ( length , std : : move ( completion_promise ) ) ,
socket ( socket ) ,
buffer ( buffer )
{ }
virtual void perform_operation ( ) override
{
asio : : async_read_some ( socket ,
boost : : asio : : buffer ( buffer , permitted_length ) ,
completion_promise ) ;
}
} ;
struct is_operation_shorter
{
// less than operator designed to bring the shortest operations to the end
bool operator ( ) ( const rate_limited_operation * lhs , const rate_limited_operation * rhs )
{
return lhs - > length > rhs - > length ;
}
} ;
2014-04-17 23:39:15 +00:00
class rate_limiting_group_impl : public tcp_socket_io_hooks
2014-04-17 16:00:52 +00:00
{
public :
uint32_t _upload_bytes_per_second ;
uint32_t _download_bytes_per_second ;
2014-05-08 18:55:51 +00:00
microseconds _granularity ; // how often to add tokens to the bucket
uint32_t _read_tokens ;
uint32_t _unused_read_tokens ; // gets filled with tokens for unused bytes (if I'm allowed to read 200 bytes and I try to read 200 bytes, but can only read 50, tokens for the other 150 get returned here)
uint32_t _write_tokens ;
uint32_t _unused_write_tokens ;
2014-04-17 16:00:52 +00:00
2014-05-08 18:55:51 +00:00
typedef std : : list < rate_limited_operation * > rate_limited_operation_list ;
2014-04-17 16:00:52 +00:00
rate_limited_operation_list _read_operations_in_progress ;
rate_limited_operation_list _read_operations_for_next_iteration ;
rate_limited_operation_list _write_operations_in_progress ;
rate_limited_operation_list _write_operations_for_next_iteration ;
2014-04-17 23:39:15 +00:00
time_point _last_read_iteration_time ;
2014-04-17 16:00:52 +00:00
time_point _last_write_iteration_time ;
2014-05-08 18:55:51 +00:00
future < void > _process_pending_reads_loop_complete ;
promise < void > : : ptr _new_read_operation_available_promise ;
future < void > _process_pending_writes_loop_complete ;
promise < void > : : ptr _new_write_operation_available_promise ;
2014-04-17 23:39:15 +00:00
2014-04-17 16:00:52 +00:00
rate_limiting_group_impl ( uint32_t upload_bytes_per_second , uint32_t download_bytes_per_second ) ;
2014-04-17 23:39:15 +00:00
virtual size_t readsome ( boost : : asio : : ip : : tcp : : socket & socket , char * buffer , size_t length ) override ;
virtual size_t writesome ( boost : : asio : : ip : : tcp : : socket & socket , const char * buffer , size_t length ) override ;
2014-04-17 16:00:52 +00:00
void process_pending_reads ( ) ;
void process_pending_writes ( ) ;
2014-04-17 23:39:15 +00:00
void process_pending_operations ( time_point & last_iteration_start_time ,
uint32_t & limit_bytes_per_second ,
rate_limited_operation_list & operations_in_progress ,
2014-05-08 18:55:51 +00:00
rate_limited_operation_list & operations_for_next_iteration ,
uint32_t & tokens ,
uint32_t & unused_tokens ) ;
2014-04-17 16:00:52 +00:00
} ;
rate_limiting_group_impl : : rate_limiting_group_impl ( uint32_t upload_bytes_per_second , uint32_t download_bytes_per_second ) :
_upload_bytes_per_second ( upload_bytes_per_second ) ,
_download_bytes_per_second ( download_bytes_per_second ) ,
2014-05-08 18:55:51 +00:00
_granularity ( milliseconds ( 50 ) ) ,
_read_tokens ( _download_bytes_per_second ) ,
_unused_read_tokens ( 0 ) ,
_write_tokens ( _upload_bytes_per_second ) ,
_unused_write_tokens ( 0 )
2014-04-17 16:00:52 +00:00
{
}
size_t rate_limiting_group_impl : : readsome ( boost : : asio : : ip : : tcp : : socket & socket , char * buffer , size_t length )
{
2014-04-17 23:39:15 +00:00
if ( _download_bytes_per_second )
{
promise < size_t > : : ptr completion_promise ( new promise < size_t > ( ) ) ;
2014-05-08 18:55:51 +00:00
rate_limited_tcp_read_operation read_operation ( socket , buffer , length , completion_promise ) ;
_read_operations_for_next_iteration . push_back ( & read_operation ) ;
// launch the read processing loop it if isn't running, or signal it to resume if it's paused.
if ( ! _process_pending_reads_loop_complete . valid ( ) | | _process_pending_reads_loop_complete . ready ( ) )
2014-04-17 23:39:15 +00:00
_process_pending_reads_loop_complete = async ( [ = ] ( ) { process_pending_reads ( ) ; } ) ;
2014-05-08 18:55:51 +00:00
else if ( _new_read_operation_available_promise )
_new_read_operation_available_promise - > set_value ( ) ;
size_t bytes_read = completion_promise - > wait ( ) ;
_unused_read_tokens + = read_operation . permitted_length - bytes_read ;
return bytes_read ;
2014-04-17 23:39:15 +00:00
}
else
return asio : : read_some ( socket , boost : : asio : : buffer ( buffer , length ) ) ;
2014-04-17 16:00:52 +00:00
}
size_t rate_limiting_group_impl : : writesome ( boost : : asio : : ip : : tcp : : socket & socket , const char * buffer , size_t length )
{
if ( _upload_bytes_per_second )
{
promise < size_t > : : ptr completion_promise ( new promise < size_t > ( ) ) ;
2014-05-08 18:55:51 +00:00
rate_limited_tcp_write_operation write_operation ( socket , buffer , length , completion_promise ) ;
_write_operations_for_next_iteration . push_back ( & write_operation ) ;
// launch the write processing loop it if isn't running, or signal it to resume if it's paused.
if ( ! _process_pending_writes_loop_complete . valid ( ) | | _process_pending_writes_loop_complete . ready ( ) )
2014-04-17 23:39:15 +00:00
_process_pending_writes_loop_complete = async ( [ = ] ( ) { process_pending_writes ( ) ; } ) ;
2014-05-08 18:55:51 +00:00
else if ( _new_write_operation_available_promise )
_new_write_operation_available_promise - > set_value ( ) ;
size_t bytes_written = completion_promise - > wait ( ) ;
_unused_write_tokens + = write_operation . permitted_length - bytes_written ;
return bytes_written ;
2014-04-17 16:00:52 +00:00
}
else
return asio : : write_some ( socket , boost : : asio : : buffer ( buffer , length ) ) ;
}
void rate_limiting_group_impl : : process_pending_reads ( )
{
2014-04-17 23:39:15 +00:00
for ( ; ; )
{
process_pending_operations ( _last_read_iteration_time , _download_bytes_per_second ,
2014-05-08 18:55:51 +00:00
_read_operations_in_progress , _read_operations_for_next_iteration , _read_tokens , _unused_read_tokens ) ;
_new_read_operation_available_promise = new promise < void > ( ) ;
try
{
if ( _read_operations_in_progress . empty ( ) )
_new_read_operation_available_promise - > wait ( ) ;
else
_new_read_operation_available_promise - > wait ( _granularity ) ;
}
catch ( const timeout_exception & )
{
}
_new_read_operation_available_promise . reset ( ) ;
2014-04-17 23:39:15 +00:00
}
2014-04-17 16:00:52 +00:00
}
void rate_limiting_group_impl : : process_pending_writes ( )
{
2014-04-17 23:39:15 +00:00
for ( ; ; )
{
process_pending_operations ( _last_write_iteration_time , _upload_bytes_per_second ,
2014-05-08 18:55:51 +00:00
_write_operations_in_progress , _write_operations_for_next_iteration , _write_tokens , _unused_write_tokens ) ;
_new_write_operation_available_promise = new promise < void > ( ) ;
try
{
if ( _write_operations_in_progress . empty ( ) )
_new_write_operation_available_promise - > wait ( ) ;
else
_new_write_operation_available_promise - > wait ( _granularity ) ;
}
catch ( const timeout_exception & )
{
}
_new_write_operation_available_promise . reset ( ) ;
2014-04-17 23:39:15 +00:00
}
2014-04-17 16:00:52 +00:00
}
2014-04-17 23:39:15 +00:00
void rate_limiting_group_impl : : process_pending_operations ( time_point & last_iteration_start_time ,
uint32_t & limit_bytes_per_second ,
rate_limited_operation_list & operations_in_progress ,
2014-05-08 18:55:51 +00:00
rate_limited_operation_list & operations_for_next_iteration ,
uint32_t & tokens ,
uint32_t & unused_tokens )
2014-04-17 16:00:52 +00:00
{
// lock here for multithreaded
2014-05-08 18:55:51 +00:00
std : : copy ( operations_for_next_iteration . begin ( ) ,
operations_for_next_iteration . end ( ) ,
2014-04-17 16:00:52 +00:00
std : : back_inserter ( operations_in_progress ) ) ;
operations_for_next_iteration . clear ( ) ;
2014-04-17 23:39:15 +00:00
// find out how much time since our last read/write
time_point this_iteration_start_time = time_point : : now ( ) ;
if ( limit_bytes_per_second ) // the we are limiting up/download speed
2014-04-17 16:00:52 +00:00
{
2014-04-17 23:39:15 +00:00
microseconds time_since_last_iteration = this_iteration_start_time - last_iteration_start_time ;
2014-04-17 16:00:52 +00:00
if ( time_since_last_iteration > seconds ( 1 ) )
time_since_last_iteration = seconds ( 1 ) ;
else if ( time_since_last_iteration < microseconds ( 0 ) )
time_since_last_iteration = microseconds ( 0 ) ;
2014-05-08 18:55:51 +00:00
tokens + = ( uint32_t ) ( ( limit_bytes_per_second * time_since_last_iteration . count ( ) ) / 1000000 ) ;
tokens + = unused_tokens ;
unused_tokens = 0 ;
tokens = std : : min ( tokens , limit_bytes_per_second ) ;
2014-04-17 16:00:52 +00:00
2014-05-08 18:55:51 +00:00
if ( tokens )
2014-04-17 16:00:52 +00:00
{
2014-04-17 23:39:15 +00:00
// sort the pending reads/writes in order of the number of bytes they need to write, smallest first
2014-04-17 16:00:52 +00:00
std : : vector < rate_limited_operation * > operations_sorted_by_length ;
operations_sorted_by_length . reserve ( operations_in_progress . size ( ) ) ;
2014-05-08 18:55:51 +00:00
for ( rate_limited_operation * operation_data : operations_in_progress )
operations_sorted_by_length . push_back ( operation_data ) ;
2014-04-17 16:00:52 +00:00
std : : sort ( operations_sorted_by_length . begin ( ) , operations_sorted_by_length . end ( ) , is_operation_shorter ( ) ) ;
2014-04-17 23:39:15 +00:00
// figure out how many bytes each reader/writer is allowed to read/write
2014-05-08 18:55:51 +00:00
uint32_t bytes_remaining_to_allocate = tokens ;
2014-04-17 16:00:52 +00:00
while ( ! operations_sorted_by_length . empty ( ) )
{
2014-04-17 23:39:15 +00:00
uint32_t bytes_permitted_for_this_operation = bytes_remaining_to_allocate / operations_sorted_by_length . size ( ) ;
uint32_t bytes_allocated_for_this_operation = std : : min ( operations_sorted_by_length . back ( ) - > length , bytes_permitted_for_this_operation ) ;
operations_sorted_by_length . back ( ) - > permitted_length = bytes_allocated_for_this_operation ;
bytes_remaining_to_allocate - = bytes_allocated_for_this_operation ;
2014-04-17 16:00:52 +00:00
operations_sorted_by_length . pop_back ( ) ;
}
2014-05-08 18:55:51 +00:00
tokens = bytes_remaining_to_allocate ;
2014-04-17 16:00:52 +00:00
2014-04-17 23:39:15 +00:00
// kick off the reads/writes in first-come order
2014-04-17 16:00:52 +00:00
for ( auto iter = operations_in_progress . begin ( ) ; iter ! = operations_in_progress . end ( ) ; )
{
if ( ( * iter ) - > permitted_length > 0 )
{
( * iter ) - > perform_operation ( ) ;
iter = operations_in_progress . erase ( iter ) ;
}
else
+ + iter ;
}
}
}
2014-05-08 18:55:51 +00:00
else // down/upload speed is unlimited
2014-04-17 16:00:52 +00:00
{
// we shouldn't end up here often. If the rate is unlimited, we should just execute
// the operation immediately without being queued up. This should only be hit if
// we change from a limited rate to unlimited
for ( auto iter = operations_in_progress . begin ( ) ;
iter ! = operations_in_progress . end ( ) ;
+ + iter )
{
( * iter ) - > permitted_length = ( * iter ) - > length ;
( * iter ) - > perform_operation ( ) ;
}
operations_in_progress . clear ( ) ;
}
2014-04-17 23:39:15 +00:00
last_iteration_start_time = this_iteration_start_time ;
2014-04-17 16:00:52 +00:00
}
}
rate_limiting_group : : rate_limiting_group ( uint32_t upload_bytes_per_second , uint32_t download_bytes_per_second ) :
my ( new detail : : rate_limiting_group_impl ( upload_bytes_per_second , download_bytes_per_second ) )
{
}
rate_limiting_group : : ~ rate_limiting_group ( )
{
}
2014-04-17 23:39:15 +00:00
void rate_limiting_group : : set_upload_limit ( uint32_t upload_bytes_per_second )
{
my - > _upload_bytes_per_second = upload_bytes_per_second ;
}
uint32_t rate_limiting_group : : get_upload_limit ( ) const
{
return my - > _upload_bytes_per_second ;
}
void rate_limiting_group : : set_download_limit ( uint32_t download_bytes_per_second )
{
my - > _download_bytes_per_second = download_bytes_per_second ;
}
uint32_t rate_limiting_group : : get_download_limit ( ) const
{
return my - > _download_bytes_per_second ;
}
void rate_limiting_group : : add_tcp_socket ( tcp_socket * tcp_socket_to_limit )
{
tcp_socket_to_limit - > set_io_hooks ( my . get ( ) ) ;
}
void rate_limiting_group : : remove_tcp_socket ( tcp_socket * tcp_socket_to_stop_limiting )
{
tcp_socket_to_stop_limiting - > set_io_hooks ( NULL ) ;
}
2014-04-17 16:00:52 +00:00
} // namespace fc