changed the double send buffers to a chained buffer using iovec send operations. buffers are recycled (saves memory and cpu) and memory copies are avoided (saves cpu). More statistics is added to track buffer usage

This commit is contained in:
Arvid Norberg 2007-09-29 16:14:03 +00:00
parent 09c1e0f007
commit b48948e231
19 changed files with 770 additions and 232 deletions

View File

@ -168,6 +168,10 @@ namespace libtorrent
// thread started to run the main downloader loop
struct session_impl: boost::noncopyable
{
// the size of each allocation that is chained in the send buffer
enum { send_buffer_size = 200 };
#ifndef NDEBUG
friend class ::libtorrent::peer_connection;
#endif
@ -329,6 +333,24 @@ namespace libtorrent
{ return m_dht_proxy; }
#endif
#ifdef TORRENT_STATS
void log_buffer_usage()
{
int send_buffer_capacity = 0;
int used_send_buffer = 0;
for (connection_map::const_iterator i = m_connections.begin()
, end(m_connections.end()); i != end; ++i)
{
send_buffer_capacity += i->second->send_buffer_capacity();
used_send_buffer += i->second->send_buffer_size();
}
assert(send_buffer_capacity >= used_send_buffer);
m_buffer_usage_logger << log_time() << " send_buffer_size: " << send_buffer_capacity << std::endl;
m_buffer_usage_logger << log_time() << " used_send_buffer: " << used_send_buffer << std::endl;
m_buffer_usage_logger << log_time() << " send_buffer_utilization: "
<< (used_send_buffer * 100.f / send_buffer_capacity) << std::endl;
}
#endif
void start_lsd();
void start_natpmp();
void start_upnp();
@ -339,11 +361,25 @@ namespace libtorrent
// handles delayed alerts
alert_manager m_alerts;
std::pair<char*, int> allocate_buffer(int size);
void free_buffer(char* buf, int size);
void free_disk_buffer(char* buf);
// private:
void on_lsd_peer(tcp::endpoint peer, sha1_hash const& ih);
// handles disk io requests asynchronously
// peers have pointers into the disk buffer
// pool, and must be destructed before this
// object.
disk_io_thread m_disk_thread;
// this pool is used to allocate and recycle send
// buffers from.
boost::pool<> m_send_buffers;
// this is where all active sockets are stored.
// the selector can sleep while there's no activity on
// them
@ -358,9 +394,6 @@ namespace libtorrent
// when they are destructed.
file_pool m_files;
// handles disk io requests asynchronously
disk_io_thread m_disk_thread;
// this is a list of half-open tcp connections
// (only outgoing connections)
// this has to be one of the last
@ -526,6 +559,10 @@ namespace libtorrent
// logger used to write bandwidth usage statistics
std::ofstream m_stats_logger;
int m_second_counter;
// used to log send buffer usage statistics
std::ofstream m_buffer_usage_logger;
// the number of send buffers that are allocated
int m_buffer_allocations;
#endif
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING)
boost::shared_ptr<logger> create_log(std::string const& name

View File

@ -208,7 +208,7 @@ namespace libtorrent
void write_cancel(peer_request const& r);
void write_bitfield(std::vector<bool> const& bitfield);
void write_have(int index);
void write_piece(peer_request const& r, char const* buffer);
void write_piece(peer_request const& r, char* buffer);
void write_handshake();
#ifndef TORRENT_DISABLE_EXTENSIONS
void write_extensions();
@ -270,8 +270,17 @@ namespace libtorrent
// these functions encrypt the send buffer if m_rc4_encrypted
// is true, otherwise it passes the call to the
// peer_connection functions of the same names
void send_buffer(char* begin, char* end);
void send_buffer(char* buf, int size);
buffer::interval allocate_send_buffer(int size);
template <class Destructor>
void append_send_buffer(char* buffer, int size, Destructor const& destructor)
{
#ifndef TORRENT_DISABLE_ENCRYPTION
if (m_rc4_encrypted)
m_RC4_handler->encrypt(buffer, size);
#endif
peer_connection::append_send_buffer(buffer, size, destructor);
}
void setup_send();
// Returns offset at which bytestream (src, src + src_size)

View File

@ -0,0 +1,192 @@
/*
Copyright (c) 2007, Arvid Norberg
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef TORRENT_CHAINED_BUFFER_HPP_INCLUDED
#define TORRENT_CHAINED_BUFFER_HPP_INCLUDED
#include <boost/function.hpp>
#include <asio.hpp>
#include <list>
#include <cstring>
namespace libtorrent
{
struct chained_buffer
{
chained_buffer(): m_bytes(0), m_capacity(0) {}
struct buffer_t
{
boost::function<void(char*)> free; // destructs the buffer
char* buf; // the first byte of the buffer
int size; // the total size of the buffer
char* start; // the first byte to send/receive in the buffer
int used_size; // this is the number of bytes to send/receive
};
bool empty() const { return m_bytes == 0; }
int size() const { return m_bytes; }
int capacity() const { return m_capacity; }
void pop_front(int bytes_to_pop)
{
assert(bytes_to_pop <= m_bytes);
while (bytes_to_pop > 0 && !m_vec.empty())
{
buffer_t& b = m_vec.front();
if (b.used_size > bytes_to_pop)
{
b.start += bytes_to_pop;
b.used_size -= bytes_to_pop;
m_bytes -= bytes_to_pop;
assert(m_bytes <= m_capacity);
assert(m_bytes >= 0);
assert(m_capacity >= 0);
break;
}
b.free(b.buf);
m_bytes -= b.used_size;
m_capacity -= b.size;
bytes_to_pop -= b.used_size;
assert(m_bytes >= 0);
assert(m_capacity >= 0);
assert(m_bytes <= m_capacity);
m_vec.pop_front();
}
}
template <class D>
void append_buffer(char* buffer, int size, int used_size, D const& destructor)
{
assert(size >= used_size);
buffer_t b;
b.buf = buffer;
b.size = size;
b.start = buffer;
b.used_size = used_size;
b.free = destructor;
m_vec.push_back(b);
m_bytes += used_size;
m_capacity += size;
assert(m_bytes <= m_capacity);
}
// returns the number of bytes available at the
// end of the last chained buffer.
int space_in_last_buffer()
{
if (m_vec.empty()) return 0;
buffer_t& b = m_vec.back();
return b.size - b.used_size - (b.start - b.buf);
}
// tries to copy the given buffer to the end of the
// last chained buffer. If there's not enough room
// it returns false
bool append(char const* buf, int size)
{
char* insert = allocate_appendix(size);
if (insert == 0) return false;
std::memcpy(insert, buf, size);
return true;
}
// tries to allocate memory from the end
// of the last buffer. If there isn't
// enough room, returns 0
char* allocate_appendix(int size)
{
if (m_vec.empty()) return 0;
buffer_t& b = m_vec.back();
char* insert = b.start + b.used_size;
if (insert + size > b.buf + b.size) return 0;
b.used_size += size;
m_bytes += size;
assert(m_bytes <= m_capacity);
return insert;
}
std::list<asio::const_buffer> const& build_iovec(int to_send)
{
m_tmp_vec.clear();
for (std::list<buffer_t>::iterator i = m_vec.begin()
, end(m_vec.end()); to_send > 0 && i != end; ++i)
{
if (i->used_size > to_send)
{
assert(to_send > 0);
m_tmp_vec.push_back(asio::const_buffer(i->start, to_send));
break;
}
assert(i->used_size > 0);
m_tmp_vec.push_back(asio::const_buffer(i->start, i->used_size));
to_send -= i->used_size;
}
return m_tmp_vec;
}
~chained_buffer()
{
for (std::list<buffer_t>::iterator i = m_vec.begin()
, end(m_vec.end()); i != end; ++i)
{
i->free(i->buf);
}
}
private:
// this is the list of all the buffers we want to
// send
std::list<buffer_t> m_vec;
// this is the number of bytes in the send buf.
// this will always be equal to the sum of the
// size of all buffers in vec
int m_bytes;
// the total size of all buffers in the chain
// including unused space
int m_capacity;
// this is the vector of buffers used when
// invoking the async write call
std::list<asio::const_buffer> m_tmp_vec;
};
}
#endif

View File

@ -94,6 +94,11 @@ namespace libtorrent
disk_io_thread(int block_size = 16 * 1024);
~disk_io_thread();
#ifdef TORRENT_STATS
int disk_allocations() const
{ return m_allocations; }
#endif
// aborts read operations
void stop(boost::intrusive_ptr<piece_manager> s);
void add_job(disk_io_job const& j
@ -110,6 +115,7 @@ namespace libtorrent
void operator()();
char* allocate_buffer();
void free_buffer(char* buf);
private:
@ -129,6 +135,9 @@ namespace libtorrent
#ifdef TORRENT_DISK_STATS
std::ofstream m_log;
#endif
#ifdef TORRENT_STATS
int m_allocations;
#endif
// thread for performing blocking disk io operations
boost::thread m_disk_io_thread;

View File

@ -51,6 +51,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include <boost/array.hpp>
#include <boost/optional.hpp>
#include <boost/cstdint.hpp>
#include <boost/pool/pool.hpp>
#ifdef _MSC_VER
#pragma warning(pop)
@ -73,6 +74,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/socket_type.hpp"
#include "libtorrent/intrusive_ptr_base.hpp"
#include "libtorrent/assert.hpp"
#include "libtorrent/chained_buffer.hpp"
namespace libtorrent
{
@ -356,14 +358,23 @@ namespace libtorrent
virtual boost::optional<piece_block_progress>
downloading_piece_progress() const
{
#ifdef TORRENT_VERBOSE_LOGGING
(*m_logger) << "downloading_piece_progress() dispatched to the base class!\n";
#endif
#ifdef TORRENT_VERBOSE_LOGGING
(*m_logger) << "downloading_piece_progress() dispatched to the base class!\n";
#endif
return boost::optional<piece_block_progress>();
}
void send_buffer(char const* begin, char const* end);
void send_buffer(char const* begin, int size);
buffer::interval allocate_send_buffer(int size);
template <class Destructor>
void append_send_buffer(char* buffer, int size, Destructor const& destructor)
{
m_send_buffer.append_buffer(buffer, size, size, destructor);
#ifdef TORRENT_STATS
m_ses.m_buffer_usage_logger << log_time() << " append_send_buffer: " << size << std::endl;
m_ses.log_buffer_usage();
#endif
}
void setup_send();
#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES
@ -376,6 +387,12 @@ namespace libtorrent
bool has_country() const { return m_country[0] != 0; }
#endif
int send_buffer_size() const
{ return m_send_buffer.size(); }
int send_buffer_capacity() const
{ return m_send_buffer.capacity(); }
protected:
virtual void get_specific_peer_info(peer_info& p) const = 0;
@ -388,7 +405,7 @@ namespace libtorrent
virtual void write_cancel(peer_request const& r) = 0;
virtual void write_have(int index) = 0;
virtual void write_keepalive() = 0;
virtual void write_piece(peer_request const& r, char const* buffer) = 0;
virtual void write_piece(peer_request const& r, char* buffer) = 0;
virtual void write_reject_request(peer_request const& r) = 0;
virtual void write_allow_fast(int piece) = 0;
@ -401,13 +418,6 @@ namespace libtorrent
virtual void on_sent(asio::error_code const& error
, std::size_t bytes_transferred) = 0;
int send_buffer_size() const
{
return (int)m_send_buffer[0].size()
+ (int)m_send_buffer[1].size()
- m_write_pos;
}
#ifndef TORRENT_DISABLE_ENCRYPTION
buffer::interval wr_recv_buffer()
{
@ -512,31 +522,13 @@ namespace libtorrent
int m_recv_pos;
buffer m_recv_buffer;
// this is the buffer where data that is
// to be sent is stored until it gets
// consumed by send(). Since asio requires
// the memory buffer that is given to async.
// operations to remain valid until the operation
// finishes, there has to be two buffers. While
// waiting for a async_write operation on one
// buffer, the other is used to write data to
// be queued up.
buffer m_send_buffer[2];
// the current send buffer is the one to write to.
// (m_current_send_buffer + 1) % 2 is the
// buffer we're currently waiting for.
int m_current_send_buffer;
chained_buffer m_send_buffer;
// the number of bytes we are currently reading
// from disk, that will be added to the send
// buffer as soon as they complete
int m_reading_bytes;
// if the sending buffer doesn't finish in one send
// operation, this is the position within that buffer
// where the next operation should continue
int m_write_pos;
// timeouts
ptime m_last_receive;
ptime m_last_sent;

View File

@ -184,6 +184,9 @@ namespace libtorrent
std::pair<bool, float> check_files(std::vector<bool>& pieces
, int& num_pieces, boost::recursive_mutex& mutex);
// frees a buffer that was returned from a read operation
void free_buffer(char* buf);
void write_resume_data(entry& rd) const;
bool verify_resume_data(entry& rd, std::string& error);

View File

@ -389,5 +389,18 @@ namespace libtorrent
#endif
#endif
namespace libtorrent
{
inline std::string log_time()
{
static ptime start = time_now();
char ret[200];
std::sprintf(ret, "%d", total_milliseconds(time_now() - start));
return ret;
}
}
#endif

View File

@ -122,7 +122,7 @@ namespace libtorrent
void write_request(peer_request const& r);
void write_cancel(peer_request const& r) {}
void write_have(int index) {}
void write_piece(peer_request const& r, char const* buffer) { assert(false); }
void write_piece(peer_request const& r, char* buffer) { assert(false); }
void write_keepalive() {}
void on_connected();
void write_reject_request(peer_request const&) {}

83
parse_buffer_log.py Normal file
View File

@ -0,0 +1,83 @@
import os, sys, time
lines = open(sys.argv[1], 'rb').readlines()
#keys = ['send_buffer_utilization']
keys = ['send_buffer_size', 'used_send_buffer', 'protocol_buffer']
#keys = ['send_buffer_alloc', 'send_buffer', 'allocate_buffer_alloc', 'allocate_buffer', 'protocol_buffer']
#keys = ['send_buffer_alloc', 'send_buffer', 'allocate_buffer_alloc', 'allocate_buffer', 'protocol_buffer', 'append_send_buffer']
average = ['send_buffer_utilization', 'send_buffer_size', 'used_send_buffer']
average_interval = 120000
render = 'lines'
time_limit = -1
if len(sys.argv) > 2:
time_limit = long(sys.argv[2])
# logfile format:
# <time(ms)> <key> <value>
# example:
# 16434 allocate_buffer: 17
for k in keys:
last_sample = 0
average_accumulator = 0
average_samples = 0
peak = 0
out = open(k + '.dat', 'wb')
eval_average = False
if k in average:
eval_average = True
peak_out = open(k + '_peak.dat', 'wb')
for l in lines:
l = l.split(' ')
if len(l) != 3:
print l
continue
try:
if l[1] == k + ':':
if time_limit != -1 and long(l[0]) > time_limit: break
time = l[0]
value = l[2]
if eval_average:
while long(time) > last_sample + average_interval:
last_sample = last_sample + average_interval
if average_samples < 1: average_samples = 1
print >>out, '%d %f' % (last_sample, average_accumulator / average_samples)
print >>peak_out, '%d %f' % (last_sample, peak)
average_accumulator = 0
average_samples = 0
peak = 0
average_accumulator = average_accumulator + float(value)
average_samples = average_samples + 1
if float(value) > peak: peak = float(value)
else:
print >>out, time + ' ' + value,
except:
print l
out = open('send_buffer.gnuplot', 'wb')
print >>out, "set term png size 1200,700"
print >>out, 'set output "send_buffer.png"'
print >>out, 'set xrange [0:*]'
print >>out, 'set xlabel "time (ms)"'
print >>out, 'set ylabel "bytes (B)"'
print >>out, "set style data lines"
print >>out, "set key box"
print >>out, 'plot',
for k in keys:
if k in average:
print >>out, ' "%s.dat" using 1:2 title "%s %d seconds average" with %s,' % (k, k, average_interval / 1000., render),
print >>out, ' "%s_peak.dat" using 1:2 title "%s %d seconds peak" with %s,' % (k, k, average_interval / 1000., render),
else:
print >>out, ' "%s.dat" using 1:2 title "%s" with %s,' % (k, k, render),
print >>out, 'x=0'
out.close()
os.system('gnuplot send_buffer.gnuplot');

28
parse_session_stats.py Normal file
View File

@ -0,0 +1,28 @@
import os, sys, time
keys = ['upload rate', 'download rate', 'downloading torrents', \
'seeding torrents', 'peers', 'connecting peers', 'disk block buffers']
axes = ['x1y2', 'x1y2', 'x1y1', 'x1y1', 'x1y1', 'x1y1', 'x1y1']
out = open('session_stats.gnuplot', 'wb')
print >>out, "set term png size 1200,700"
print >>out, 'set output "session_stats.png"'
print >>out, 'set xrange [0:*]'
print >>out, 'set xlabel "time (s)"'
print >>out, 'set ylabel "number"'
print >>out, 'set y2label "Rate (B/s)"'
print >>out, 'set y2range [0:*]'
print >>out, 'set y2tics 20000'
print >>out, "set style data lines"
print >>out, "set key box"
print >>out, 'plot',
column = 2
for k in keys:
print >>out, ' "%s" using 1:%d title "%s" axes %s with lines,' % (sys.argv[1], column, k, axes[column-2]),
column = column + 1
print >>out, 'x=0'
out.close()
os.system('gnuplot session_stats.gnuplot');

View File

@ -251,12 +251,10 @@ namespace libtorrent
(*m_logger) << time_now_string()
<< " ==> DHT_PORT [ " << listen_port << " ]\n";
#endif
buffer::interval packet = allocate_send_buffer(7);
detail::write_uint32(3, packet.begin);
detail::write_uint8(msg_dht_port, packet.begin);
detail::write_uint16(listen_port, packet.begin);
assert(packet.begin == packet.end);
setup_send();
char msg[] = {0,0,0,3, msg_dht_port, 0, 0};
char* ptr = msg + 5;
detail::write_uint16(listen_port, ptr);
send_buffer(msg, sizeof(msg));
}
void bt_peer_connection::write_have_all()
@ -270,8 +268,8 @@ namespace libtorrent
(*m_logger) << time_now_string()
<< " ==> HAVE_ALL\n";
#endif
char buf[] = {0,0,0,1, msg_have_all};
send_buffer(buf, buf + sizeof(buf));
char msg[] = {0,0,0,1, msg_have_all};
send_buffer(msg, sizeof(msg));
}
void bt_peer_connection::write_have_none()
@ -285,8 +283,8 @@ namespace libtorrent
(*m_logger) << time_now_string()
<< " ==> HAVE_NONE\n";
#endif
char buf[] = {0,0,0,1, msg_have_none};
send_buffer(buf, buf + sizeof(buf));
char msg[] = {0,0,0,1, msg_have_none};
send_buffer(msg, sizeof(msg));
}
void bt_peer_connection::write_reject_request(peer_request const& r)
@ -296,22 +294,12 @@ namespace libtorrent
assert(m_sent_handshake && m_sent_bitfield);
assert(associated_torrent().lock()->valid_metadata());
char buf[] = {0,0,0,13, msg_reject_request};
buffer::interval i = allocate_send_buffer(17);
std::copy(buf, buf + 5, i.begin);
i.begin += 5;
// index
detail::write_int32(r.piece, i.begin);
// begin
detail::write_int32(r.start, i.begin);
// length
detail::write_int32(r.length, i.begin);
assert(i.begin == i.end);
setup_send();
char msg[] = {0,0,0,13, msg_reject_request,0,0,0,0, 0,0,0,0, 0,0,0,0};
char* ptr = msg + 5;
detail::write_int32(r.piece, ptr); // index
detail::write_int32(r.start, ptr); // begin
detail::write_int32(r.length, ptr); // length
send_buffer(msg, sizeof(msg));
}
void bt_peer_connection::write_allow_fast(int piece)
@ -321,11 +309,10 @@ namespace libtorrent
assert(m_sent_handshake && m_sent_bitfield);
assert(associated_torrent().lock()->valid_metadata());
char buf[] = {0,0,0,5, msg_allowed_fast, 0, 0, 0, 0};
char* ptr = buf + 5;
char msg[] = {0,0,0,5, msg_allowed_fast, 0, 0, 0, 0};
char* ptr = msg + 5;
detail::write_int32(piece, ptr);
send_buffer(buf, buf + sizeof(buf));
send_buffer(msg, sizeof(msg));
}
void bt_peer_connection::get_specific_peer_info(peer_info& p) const
@ -556,8 +543,8 @@ namespace libtorrent
assert(secret);
hasher h;
const char keyA[] = "keyA";
const char keyB[] = "keyB";
static const char keyA[] = "keyA";
static const char keyB[] = "keyB";
// encryption rc4 longkeys
// outgoing connection : hash ('keyA',S,SKEY)
@ -587,17 +574,16 @@ namespace libtorrent
#endif
}
void bt_peer_connection::send_buffer(char* begin, char* end)
void bt_peer_connection::send_buffer(char* buf, int size)
{
assert (begin);
assert (end);
assert (end > begin);
assert (!m_rc4_encrypted || m_encrypted);
assert(buf);
assert(size > 0);
assert(!m_rc4_encrypted || m_encrypted);
if (m_rc4_encrypted)
m_RC4_handler->encrypt(begin, end - begin);
m_RC4_handler->encrypt(buf, size);
peer_connection::send_buffer(begin, end);
peer_connection::send_buffer(buf, size);
}
buffer::interval bt_peer_connection::allocate_send_buffer(int size)
@ -606,6 +592,7 @@ namespace libtorrent
if (m_rc4_encrypted)
{
assert(m_enc_send_buffer.left() == 0);
m_enc_send_buffer = peer_connection::allocate_send_buffer(size);
return m_enc_send_buffer;
}
@ -620,24 +607,24 @@ namespace libtorrent
{
assert(!m_rc4_encrypted || m_encrypted);
if (m_rc4_encrypted)
if (m_rc4_encrypted && m_enc_send_buffer.left())
{
assert (m_enc_send_buffer.begin);
assert (m_enc_send_buffer.end);
assert (m_enc_send_buffer.left() > 0);
assert(m_enc_send_buffer.begin);
assert(m_enc_send_buffer.end);
m_RC4_handler->encrypt (m_enc_send_buffer.begin, m_enc_send_buffer.left());
m_RC4_handler->encrypt(m_enc_send_buffer.begin, m_enc_send_buffer.left());
m_enc_send_buffer.end = m_enc_send_buffer.begin;
}
peer_connection::setup_send();
}
int bt_peer_connection::get_syncoffset(char const* src, int src_size,
char const* target, int target_size) const
char const* target, int target_size) const
{
assert (target_size >= src_size);
assert (src_size > 0);
assert (src);
assert (target);
assert(target_size >= src_size);
assert(src_size > 0);
assert(src);
assert(target);
int traverse_limit = target_size - src_size;
@ -1288,8 +1275,8 @@ namespace libtorrent
assert(m_sent_handshake && m_sent_bitfield);
char buf[] = {0,0,0,0};
send_buffer(buf, buf + sizeof(buf));
char msg[] = {0,0,0,0};
send_buffer(msg, sizeof(msg));
}
void bt_peer_connection::write_cancel(peer_request const& r)
@ -1299,22 +1286,12 @@ namespace libtorrent
assert(m_sent_handshake && m_sent_bitfield);
assert(associated_torrent().lock()->valid_metadata());
char buf[] = {0,0,0,13, msg_cancel};
buffer::interval i = allocate_send_buffer(17);
std::copy(buf, buf + 5, i.begin);
i.begin += 5;
// index
detail::write_int32(r.piece, i.begin);
// begin
detail::write_int32(r.start, i.begin);
// length
detail::write_int32(r.length, i.begin);
assert(i.begin == i.end);
setup_send();
char msg[17] = {0,0,0,13, msg_cancel};
char* ptr = msg + 5;
detail::write_int32(r.piece, ptr); // index
detail::write_int32(r.start, ptr); // begin
detail::write_int32(r.length, ptr); // length
send_buffer(msg, sizeof(msg));
}
void bt_peer_connection::write_request(peer_request const& r)
@ -1324,22 +1301,13 @@ namespace libtorrent
assert(m_sent_handshake && m_sent_bitfield);
assert(associated_torrent().lock()->valid_metadata());
char buf[] = {0,0,0,13, msg_request};
char msg[17] = {0,0,0,13, msg_request};
char* ptr = msg + 5;
buffer::interval i = allocate_send_buffer(17);
std::copy(buf, buf + 5, i.begin);
i.begin += 5;
// index
detail::write_int32(r.piece, i.begin);
// begin
detail::write_int32(r.start, i.begin);
// length
detail::write_int32(r.length, i.begin);
assert(i.begin == i.end);
setup_send();
detail::write_int32(r.piece, ptr); // index
detail::write_int32(r.start, ptr); // begin
detail::write_int32(r.length, ptr); // length
send_buffer(msg, sizeof(msg));
}
void bt_peer_connection::write_bitfield(std::vector<bool> const& bitfield)
@ -1526,7 +1494,7 @@ namespace libtorrent
if (is_choked()) return;
char msg[] = {0,0,0,1,msg_choke};
send_buffer(msg, msg + sizeof(msg));
send_buffer(msg, sizeof(msg));
}
void bt_peer_connection::write_unchoke()
@ -1536,7 +1504,7 @@ namespace libtorrent
assert(m_sent_handshake && m_sent_bitfield);
char msg[] = {0,0,0,1,msg_unchoke};
send_buffer(msg, msg + sizeof(msg));
send_buffer(msg, sizeof(msg));
}
void bt_peer_connection::write_interested()
@ -1546,7 +1514,7 @@ namespace libtorrent
assert(m_sent_handshake && m_sent_bitfield);
char msg[] = {0,0,0,1,msg_interested};
send_buffer(msg, msg + sizeof(msg));
send_buffer(msg, sizeof(msg));
}
void bt_peer_connection::write_not_interested()
@ -1556,7 +1524,7 @@ namespace libtorrent
assert(m_sent_handshake && m_sent_bitfield);
char msg[] = {0,0,0,1,msg_not_interested};
send_buffer(msg, msg + sizeof(msg));
send_buffer(msg, sizeof(msg));
}
void bt_peer_connection::write_have(int index)
@ -1567,34 +1535,39 @@ namespace libtorrent
assert(index < associated_torrent().lock()->torrent_file().num_pieces());
assert(m_sent_handshake && m_sent_bitfield);
const int packet_size = 9;
char msg[packet_size] = {0,0,0,5,msg_have};
char msg[] = {0,0,0,5,msg_have,0,0,0,0};
char* ptr = msg + 5;
detail::write_int32(index, ptr);
send_buffer(msg, msg + packet_size);
send_buffer(msg, sizeof(msg));
}
void bt_peer_connection::write_piece(peer_request const& r, char const* buffer)
void bt_peer_connection::write_piece(peer_request const& r, char* buffer)
{
INVARIANT_CHECK;
assert(m_sent_handshake && m_sent_bitfield);
const int packet_size = 4 + 5 + 4 + r.length;
boost::shared_ptr<torrent> t = associated_torrent().lock();
assert(t);
buffer::interval i = allocate_send_buffer(packet_size);
detail::write_int32(packet_size-4, i.begin);
detail::write_uint8(msg_piece, i.begin);
detail::write_int32(r.piece, i.begin);
detail::write_int32(r.start, i.begin);
char msg[4 + 1 + 4 + 4];
char* ptr = msg;
assert(r.length <= 16 * 1024);
detail::write_int32(r.length + 1 + 4 + 4, ptr);
detail::write_uint8(msg_piece, ptr);
detail::write_int32(r.piece, ptr);
detail::write_int32(r.start, ptr);
send_buffer(msg, sizeof(msg));
append_send_buffer(buffer, r.length
, boost::bind(&session_impl::free_disk_buffer
, boost::ref(m_ses), _1));
/*
buffer::interval i = allocate_send_buffer(r.length);
std::memcpy(i.begin, buffer, r.length);
assert(i.begin + r.length == i.end);
t->filesystem().free_buffer(buffer);
*/
m_payloads.push_back(range(send_buffer_size() - r.length, r.length));
setup_send();
}

View File

@ -37,18 +37,6 @@ POSSIBILITY OF SUCH DAMAGE.
#ifdef TORRENT_DISK_STATS
#include "libtorrent/time.hpp"
#include <boost/lexical_cast.hpp>
namespace
{
std::string log_time()
{
using namespace libtorrent;
static ptime start = time_now();
return boost::lexical_cast<std::string>(
total_milliseconds(time_now() - start));
}
}
#endif
@ -64,7 +52,9 @@ namespace libtorrent
#endif
, m_disk_io_thread(boost::ref(*this))
{
#ifdef TORRENT_STATS
m_allocations = 0;
#endif
#ifdef TORRENT_DISK_STATS
m_log.open("disk_io_thread.log", std::ios::trunc);
#endif
@ -188,9 +178,21 @@ namespace libtorrent
char* disk_io_thread::allocate_buffer()
{
boost::mutex::scoped_lock l(m_mutex);
#ifdef TORRENT_STATS
++m_allocations;
#endif
return (char*)m_pool.ordered_malloc();
}
void disk_io_thread::free_buffer(char* buf)
{
boost::mutex::scoped_lock l(m_mutex);
#ifdef TORRENT_STATS
--m_allocations;
#endif
m_pool.ordered_free(buf);
}
void disk_io_thread::operator()()
{
for (;;)
@ -225,10 +227,14 @@ namespace libtorrent
#ifdef TORRENT_DISK_STATS
m_log << log_time() << " read " << j.buffer_size << std::endl;
#endif
free_buffer = false;
if (j.buffer == 0)
{
l.lock();
j.buffer = (char*)m_pool.ordered_malloc();
#ifdef TORRENT_STATS
++m_allocations;
#endif
l.unlock();
assert(j.buffer_size <= m_block_size);
if (j.buffer == 0)
@ -238,10 +244,6 @@ namespace libtorrent
break;
}
}
else
{
free_buffer = false;
}
ret = j.storage->read_impl(j.buffer, j.piece, j.offset
, j.buffer_size);
@ -301,6 +303,9 @@ namespace libtorrent
{
l.lock();
m_pool.ordered_free(j.buffer);
#ifdef TORRENT_STATS
--m_allocations;
#endif
}
}
}

View File

@ -81,9 +81,7 @@ namespace libtorrent
, m_last_unchoke(min_time())
, m_packet_size(0)
, m_recv_pos(0)
, m_current_send_buffer(0)
, m_reading_bytes(0)
, m_write_pos(0)
, m_last_receive(time_now())
, m_last_sent(time_now())
, m_socket(s)
@ -161,9 +159,7 @@ namespace libtorrent
, m_last_unchoke(min_time())
, m_packet_size(0)
, m_recv_pos(0)
, m_current_send_buffer(0)
, m_reading_bytes(0)
, m_write_pos(0)
, m_last_receive(time_now())
, m_last_sent(time_now())
, m_socket(s)
@ -2082,8 +2078,7 @@ namespace libtorrent
p.remote_dl_rate = 0;
}
p.send_buffer_size = int(m_send_buffer[0].capacity()
+ m_send_buffer[1].capacity());
p.send_buffer_size = m_send_buffer.capacity();
}
void peer_connection::cut_receive_buffer(int size, int packet_size)
@ -2386,8 +2381,7 @@ namespace libtorrent
shared_ptr<torrent> t = m_torrent.lock();
if (m_bandwidth_limit[upload_channel].quota_left() == 0
&& (!m_send_buffer[m_current_send_buffer].empty()
|| !m_send_buffer[(m_current_send_buffer + 1) & 1].empty())
&& !m_send_buffer.empty()
&& !m_connecting
&& t
&& !m_ignore_bandwidth_limits)
@ -2415,32 +2409,21 @@ namespace libtorrent
assert(!m_writing);
int sending_buffer = (m_current_send_buffer + 1) & 1;
if (m_send_buffer[sending_buffer].empty())
{
// this means we have to swap buffer, because there's no
// previous buffer we're still waiting for.
std::swap(m_current_send_buffer, sending_buffer);
m_write_pos = 0;
}
// send the actual buffer
if (!m_send_buffer[sending_buffer].empty())
if (!m_send_buffer.empty())
{
int amount_to_send = (int)m_send_buffer[sending_buffer].size() - m_write_pos;
int amount_to_send = m_send_buffer.size();
int quota_left = m_bandwidth_limit[upload_channel].quota_left();
if (!m_ignore_bandwidth_limits && amount_to_send > quota_left)
amount_to_send = quota_left;
assert(amount_to_send > 0);
assert(m_write_pos < (int)m_send_buffer[sending_buffer].size());
#ifdef TORRENT_VERBOSE_LOGGING
(*m_logger) << "async_write " << amount_to_send << " bytes\n";
#endif
m_socket->async_write_some(asio::buffer(
&m_send_buffer[sending_buffer][m_write_pos], amount_to_send)
, bind(&peer_connection::on_send_data, self(), _1, _2));
std::list<asio::const_buffer> const& vec = m_send_buffer.build_iovec(amount_to_send);
m_socket->async_write_some(vec, bind(&peer_connection::on_send_data, self(), _1, _2));
m_writing = true;
}
@ -2511,10 +2494,32 @@ namespace libtorrent
m_recv_buffer.resize(m_packet_size);
}
void peer_connection::send_buffer(char const* begin, char const* end)
void peer_connection::send_buffer(char const* buf, int size)
{
buffer& buf = m_send_buffer[m_current_send_buffer];
buf.insert(buf.end(), begin, end);
int free_space = m_send_buffer.space_in_last_buffer();
if (free_space > size) free_space = size;
if (free_space > 0)
{
m_send_buffer.append(buf, free_space);
size -= free_space;
buf += free_space;
#ifdef TORRENT_STATS
m_ses.m_buffer_usage_logger << log_time() << " send_buffer: "
<< free_space << std::endl;
m_ses.log_buffer_usage();
#endif
}
if (size <= 0) return;
std::pair<char*, int> buffer = m_ses.allocate_buffer(size);
assert(buffer.second >= size);
std::memcpy(buffer.first, buf, size);
m_send_buffer.append_buffer(buffer.first, buffer.second, size
, bind(&session_impl::free_buffer, boost::ref(m_ses), _1, buffer.second));
#ifdef TORRENT_STATS
m_ses.m_buffer_usage_logger << log_time() << " send_buffer_alloc: " << size << std::endl;
m_ses.log_buffer_usage();
#endif
setup_send();
}
@ -2522,10 +2527,29 @@ namespace libtorrent
// return value is destructed
buffer::interval peer_connection::allocate_send_buffer(int size)
{
buffer& buf = m_send_buffer[m_current_send_buffer];
buf.resize(buf.size() + size);
buffer::interval ret(&buf[0] + buf.size() - size, &buf[0] + buf.size());
return ret;
char* insert = m_send_buffer.allocate_appendix(size);
if (insert == 0)
{
std::pair<char*, int> buffer = m_ses.allocate_buffer(size);
assert(buffer.second >= size);
m_send_buffer.append_buffer(buffer.first, buffer.second, size
, bind(&session_impl::free_buffer, boost::ref(m_ses), _1, buffer.second));
buffer::interval ret(buffer.first, buffer.first + size);
#ifdef TORRENT_STATS
m_ses.m_buffer_usage_logger << log_time() << " allocate_buffer_alloc: " << size << std::endl;
m_ses.log_buffer_usage();
#endif
return ret;
}
else
{
#ifdef TORRENT_STATS
m_ses.m_buffer_usage_logger << log_time() << " allocate_buffer: " << size << std::endl;
m_ses.log_buffer_usage();
#endif
buffer::interval ret(insert, insert + size);
return ret;
}
}
template<class T>
@ -2647,8 +2671,7 @@ namespace libtorrent
// if we have requests or pending data to be sent or announcements to be made
// we want to send data
return (!m_send_buffer[m_current_send_buffer].empty()
|| !m_send_buffer[(m_current_send_buffer + 1) & 1].empty())
return !m_send_buffer.empty()
&& (m_bandwidth_limit[upload_channel].quota_left() > 0
|| m_ignore_bandwidth_limits)
&& !m_connecting;
@ -2763,6 +2786,9 @@ namespace libtorrent
INVARIANT_CHECK;
assert(m_writing);
m_send_buffer.pop_front(bytes_transferred);
m_writing = false;
if (!m_ignore_bandwidth_limits)
@ -2772,9 +2798,6 @@ namespace libtorrent
(*m_logger) << "wrote " << bytes_transferred << " bytes\n";
#endif
m_write_pos += bytes_transferred;
if (error)
{
#ifdef TORRENT_VERBOSE_LOGGING
@ -2787,34 +2810,11 @@ namespace libtorrent
assert(!m_connecting);
assert(bytes_transferred > 0);
int sending_buffer = (m_current_send_buffer + 1) & 1;
assert(int(m_send_buffer[sending_buffer].size()) >= m_write_pos);
if (int(m_send_buffer[sending_buffer].size()) == m_write_pos)
{
m_send_buffer[sending_buffer].clear();
m_write_pos = 0;
}
m_last_sent = time_now();
on_sent(error, bytes_transferred);
fill_send_buffer();
if (m_choked)
{
for (int i = 0; i < 2; ++i)
{
if (int(m_send_buffer[i].size()) < 64
&& int(m_send_buffer[i].capacity()) > 128)
{
buffer tmp(m_send_buffer[i]);
tmp.swap(m_send_buffer[i]);
assert(m_send_buffer[i].capacity() == m_send_buffer[i].size());
}
}
}
setup_send();
}
catch (std::exception& e)
@ -2876,8 +2876,6 @@ namespace libtorrent
}
}
*/
assert(m_write_pos <= int(m_send_buffer[
(m_current_send_buffer + 1) & 1].size()));
// extremely expensive invariant check
/*

View File

@ -1426,16 +1426,16 @@ namespace libtorrent
int nonempty_connections = 0;
std::set<address> unique_test;
std::set<tcp::endpoint> unique_test2;
// std::set<tcp::endpoint> unique_test2;
for (const_iterator i = m_peers.begin();
i != m_peers.end(); ++i)
{
peer const& p = *i;
if (!m_torrent->settings().allow_multiple_connections_per_ip)
assert(unique_test.find(p.ip.address()) == unique_test.end());
assert(unique_test2.find(p.ip) == unique_test2.end());
unique_test.insert(p.ip.address());
unique_test2.insert(p.ip);
// if (!m_torrent->settings().allow_multiple_connections_per_ip)
// assert(unique_test.find(p.ip.address()) == unique_test.end());
// assert(unique_test2.find(p.ip) == unique_test2.end());
// unique_test.insert(p.ip.address());
// unique_test2.insert(p.ip);
++total_connections;
if (!p.connection)
{

View File

@ -507,7 +507,8 @@ namespace detail
std::pair<int, int> listen_port_range
, fingerprint const& cl_fprint
, char const* listen_interface)
: m_strand(m_io_service)
: m_send_buffers(send_buffer_size)
, m_strand(m_io_service)
, m_files(40)
, m_half_open(m_io_service)
, m_download_channel(m_io_service, peer_connection::download_channel)
@ -546,7 +547,7 @@ namespace detail
#endif
#ifdef TORRENT_STATS
m_stats_logger.open("session_stats.log");
m_stats_logger.open("session_stats.log", std::ios::trunc);
m_stats_logger <<
"1. second\n"
"2. upload rate\n"
@ -555,7 +556,9 @@ namespace detail
"5. seeding torrents\n"
"6. peers\n"
"7. connecting peers\n"
"8. disk block buffers\n"
"\n";
m_buffer_usage_logger.open("buffer_stats.log", std::ios::trunc);
m_second_counter = 0;
#endif
@ -999,7 +1002,8 @@ namespace detail
{
session_impl::mutex_t::scoped_lock l(m_mutex);
INVARIANT_CHECK;
// too expensive
// INVARIANT_CHECK;
if (e)
{
@ -1031,7 +1035,7 @@ namespace detail
else
++downloading_torrents;
}
int num_connections = 0;
int num_complete_connections = 0;
int num_half_open = 0;
for (connection_map::iterator i = m_connections.begin()
, end(m_connections.end()); i != end; ++i)
@ -1039,7 +1043,7 @@ namespace detail
if (i->second->is_connecting())
++num_half_open;
else
++num_connections;
++num_complete_connections;
}
m_stats_logger
@ -1048,8 +1052,9 @@ namespace detail
<< m_stat.download_rate() << "\t"
<< downloading_torrents << "\t"
<< seeding_torrents << "\t"
<< num_connections << "\t"
<< num_complete_connections << "\t"
<< num_half_open << "\t"
<< m_disk_thread.disk_allocations() << "\t"
<< std::endl;
#endif
@ -2259,6 +2264,35 @@ namespace detail
m_upnp.reset();
}
void session_impl::free_disk_buffer(char* buf)
{
m_disk_thread.free_buffer(buf);
}
std::pair<char*, int> session_impl::allocate_buffer(int size)
{
int num_buffers = (size + send_buffer_size - 1) / send_buffer_size;
#ifdef TORRENT_STATS
m_buffer_allocations += num_buffers;
m_buffer_usage_logger << log_time() << " protocol_buffer: "
<< (m_buffer_allocations * send_buffer_size) << std::endl;
#endif
return std::make_pair((char*)m_send_buffers.ordered_malloc(num_buffers)
, num_buffers * send_buffer_size);
}
void session_impl::free_buffer(char* buf, int size)
{
assert(size % send_buffer_size == 0);
int num_buffers = size / send_buffer_size;
#ifdef TORRENT_STATS
m_buffer_allocations -= num_buffers;
assert(m_buffer_allocations >= 0);
m_buffer_usage_logger << log_time() << " protocol_buffer: "
<< (m_buffer_allocations * send_buffer_size) << std::endl;
#endif
m_send_buffers.ordered_free(buf, num_buffers);
}
#ifndef NDEBUG
void session_impl::check_invariant() const

View File

@ -1065,6 +1065,11 @@ namespace libtorrent
return m_storage->verify_resume_data(rd, error);
}
void piece_manager::free_buffer(char* buf)
{
m_io_thread.free_buffer(buf);
}
void piece_manager::async_release_files(
boost::function<void(int, disk_io_job const&)> const& handler)
{

View File

@ -1464,9 +1464,6 @@ namespace libtorrent
m_policy->connection_closed(*p);
p->set_peer_info(0);
m_connections.erase(i);
#ifndef NDEBUG
m_policy->check_invariant();
#endif
}
catch (std::exception& e)
{

View File

@ -297,7 +297,7 @@ namespace libtorrent
(*m_logger) << request << "\n";
#endif
send_buffer(request.c_str(), request.c_str() + request.size());
send_buffer(request.c_str(), request.size());
}
// --------------------------

View File

@ -34,12 +34,16 @@ POSSIBILITY OF SUCH DAMAGE.
#include <iostream>
#include <vector>
#include <utility>
#include <set>
#include "libtorrent/buffer.hpp"
#include "libtorrent/chained_buffer.hpp"
#include "test.hpp"
using libtorrent::buffer;
using libtorrent::chained_buffer;
/*
template<class T>
T const& min_(T const& x, T const& y)
@ -113,7 +117,9 @@ void test_speed()
}
*/
int test_main()
// -- test buffer --
void test_buffer()
{
char data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
@ -157,6 +163,160 @@ int test_main()
buffer().swap(b);
TEST_CHECK(b.capacity() == 0);
}
// -- test chained buffer --
std::set<char*> buffer_list;
void free_buffer(char* m)
{
std::set<char*>::iterator i = buffer_list.find(m);
TEST_CHECK(i != buffer_list.end());
buffer_list.erase(i);
std::free(m);
}
char* allocate_buffer(int size)
{
char* mem = (char*)std::malloc(size);
buffer_list.insert(mem);
return mem;
}
template <class T>
int copy_buffers(T const& b, char* target)
{
int copied = 0;
for (typename T::const_iterator i = b.begin()
, end(b.end()); i != end; ++i)
{
memcpy(target, asio::buffer_cast<char const*>(*i), asio::buffer_size(*i));
target += asio::buffer_size(*i);
copied += asio::buffer_size(*i);
}
return copied;
}
bool compare_chained_buffer(chained_buffer& b, char const* mem, int size)
{
std::vector<char> flat(size);
std::list<asio::const_buffer> const& iovec2 = b.build_iovec(size);
int copied = copy_buffers(iovec2, &flat[0]);
TEST_CHECK(copied == size);
return std::memcmp(&flat[0], mem, size) == 0;
}
void test_chained_buffer()
{
char data[] = "foobar";
{
chained_buffer b;
TEST_CHECK(b.empty());
TEST_CHECK(b.capacity() == 0);
TEST_CHECK(b.size() == 0);
TEST_CHECK(b.space_in_last_buffer() == 0);
TEST_CHECK(buffer_list.empty());
char* b1 = allocate_buffer(512);
std::memcpy(b1, data, 6);
b.append_buffer(b1, 512, 6, (void(*)(char*))&free_buffer);
TEST_CHECK(buffer_list.size() == 1);
TEST_CHECK(b.capacity() == 512);
TEST_CHECK(b.size() == 6);
TEST_CHECK(!b.empty());
TEST_CHECK(b.space_in_last_buffer() == 512 - 6);
b.pop_front(3);
TEST_CHECK(b.capacity() == 512);
TEST_CHECK(b.size() == 3);
TEST_CHECK(!b.empty());
TEST_CHECK(b.space_in_last_buffer() == 512 - 6);
bool ret = b.append(data, 6);
TEST_CHECK(ret == true);
TEST_CHECK(b.capacity() == 512);
TEST_CHECK(b.size() == 9);
TEST_CHECK(!b.empty());
TEST_CHECK(b.space_in_last_buffer() == 512 - 12);
ret = b.append(data, 1024);
TEST_CHECK(ret == false);
char* b2 = allocate_buffer(512);
std::memcpy(b2, data, 6);
b.append_buffer(b2, 512, 6, (void(*)(char*))&free_buffer);
TEST_CHECK(buffer_list.size() == 2);
char* b3 = allocate_buffer(512);
std::memcpy(b3, data, 6);
b.append_buffer(b3, 512, 6, (void(*)(char*))&free_buffer);
TEST_CHECK(buffer_list.size() == 3);
TEST_CHECK(b.capacity() == 512 * 3);
TEST_CHECK(b.size() == 21);
TEST_CHECK(!b.empty());
TEST_CHECK(b.space_in_last_buffer() == 512 - 6);
TEST_CHECK(compare_chained_buffer(b, "barfoobar", 9));
for (int i = 1; i < 21; ++i)
TEST_CHECK(compare_chained_buffer(b, "barfoobarfoobarfoobar", i));
b.pop_front(5 + 6);
TEST_CHECK(buffer_list.size() == 2);
TEST_CHECK(b.capacity() == 512 * 2);
TEST_CHECK(b.size() == 10);
TEST_CHECK(!b.empty());
TEST_CHECK(b.space_in_last_buffer() == 512 - 6);
char const* str = "obarfooba";
TEST_CHECK(compare_chained_buffer(b, str, 9));
for (int i = 0; i < 9; ++i)
{
b.pop_front(1);
++str;
TEST_CHECK(compare_chained_buffer(b, str, 8 - i));
TEST_CHECK(b.size() == 9 - i);
}
char* b4 = allocate_buffer(20);
std::memcpy(b4, data, 6);
std::memcpy(b4 + 6, data, 6);
b.append_buffer(b4, 20, 12, (void(*)(char*))&free_buffer);
TEST_CHECK(b.space_in_last_buffer() == 8);
ret = b.append(data, 6);
TEST_CHECK(ret == true);
TEST_CHECK(b.space_in_last_buffer() == 2);
std::cout << b.space_in_last_buffer() << std::endl;
ret = b.append(data, 2);
TEST_CHECK(ret == true);
TEST_CHECK(b.space_in_last_buffer() == 0);
std::cout << b.space_in_last_buffer() << std::endl;
char* b5 = allocate_buffer(20);
std::memcpy(b4, data, 6);
b.append_buffer(b5, 20, 6, (void(*)(char*))&free_buffer);
b.pop_front(22);
TEST_CHECK(b.size() == 5);
}
TEST_CHECK(buffer_list.empty());
}
int test_main()
{
test_buffer();
test_chained_buffer();
return 0;
}