From 7351389ce8aee43059881b36bfd6247f75853db0 Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Sun, 6 Jul 2014 19:18:00 +0000 Subject: [PATCH] land libtorrent_aio branch in trunk --- .regression.yml | 2 +- CMakeLists.txt | 19 +- ChangeLog | 29 + Jamfile | 75 +- Makefile.am | 2 + bindings/python/setup.py | 114 - bindings/python/src/alert.cpp | 4 +- bindings/python/src/create_torrent.cpp | 2 +- bindings/python/src/magnet_uri.cpp | 3 +- bindings/python/src/module.cpp | 2 +- bindings/python/src/peer_info.cpp | 3 + bindings/python/src/session.cpp | 437 +- bindings/python/src/session_settings.cpp | 30 +- bindings/python/src/torrent_handle.cpp | 36 +- bindings/python/src/torrent_info.cpp | 12 +- bindings/python/src/torrent_status.cpp | 2 + configure.ac | 15 +- docs/building.html | 37 +- docs/building.rst | 33 +- docs/contributing.html | 2 +- docs/contributing.rst | 2 +- docs/dht_rss.html | 2 +- docs/dht_rss.rst | 2 +- docs/dht_sec.html | 2 +- docs/dht_sec.rst | 2 +- docs/dht_store.html | 2 +- docs/dht_store.rst | 2 +- docs/examples.html | 58 +- docs/examples.rst | 60 +- docs/features.html | 6 +- docs/features.rst | 6 +- docs/folx.png | Bin 14818 -> 0 bytes docs/gen_reference_doc.py | 19 +- docs/gen_settings_doc.py | 95 + docs/gen_todo.py | 11 +- docs/hacking.html | 210 - docs/hacking.rst | 82 +- docs/makefile | 13 +- docs/manual-ref.html | 960 -- docs/manual.html | 12943 ++++++++++++++++ docs/manual.rst | 205 +- docs/read_disk_buffers.png | Bin 50638 -> 14183 bytes docs/reference-Alerts.html | 2486 --- docs/reference-Bencoding.html | 785 - docs/reference-Core.html | 3550 ----- docs/reference-Create_Torrents.html | 426 - docs/reference-Custom_Storage.html | 621 - docs/reference-Error_Codes.html | 1111 -- docs/reference-Filter.html | 211 - docs/reference-Plugins.html | 613 - docs/reference-RSS.html | 298 - docs/reference-Session.html | 743 +- docs/reference-Settings.html | 2198 --- docs/reference-Storage.html | 604 - docs/reference-String.html | 158 - docs/reference-Time.html | 236 - docs/reference-Utility.html | 430 - docs/reference.html | 322 - docs/settings.rst | 3217 ++++ docs/stats_counters.rst | 368 + docs/todo.html | 3505 +---- docs/troubleshooting.png | Bin 499216 -> 278213 bytes docs/tuning.html | 50 +- docs/tuning.rst | 33 +- docs/utp.html | 2 +- docs/utp.rst | 2 +- docs/write_disk_buffers.png | Bin 50619 -> 14209 bytes examples/Jamfile | 4 + examples/Makefile.am | 3 + examples/client_test.cpp | 1113 +- examples/connection_tester.cpp | 153 +- examples/dump_torrent.cpp | 10 +- examples/make_torrent.cpp | 10 +- examples/rss_reader.cpp | 23 +- examples/run_benchmarks.py | 367 +- examples/simple_client.cpp | 9 +- examples/stats_counters.cpp | 49 + examples/upnp_test.cpp | 12 +- gather_todo.py | 53 + include/libtorrent/Makefile.am | 33 + include/libtorrent/add_torrent_params.hpp | 22 +- include/libtorrent/alert.hpp | 9 +- include/libtorrent/alert_dispatcher.hpp | 2 +- include/libtorrent/alert_observer.hpp | 58 + include/libtorrent/alert_types.hpp | 334 +- include/libtorrent/allocator.hpp | 3 + include/libtorrent/assert.hpp | 10 +- include/libtorrent/atomic.hpp | 131 + include/libtorrent/aux_/session_impl.hpp | 773 +- include/libtorrent/aux_/session_interface.hpp | 346 + include/libtorrent/aux_/session_settings.hpp | 82 + include/libtorrent/bandwidth_manager.hpp | 16 +- include/libtorrent/bandwidth_queue_entry.hpp | 10 +- include/libtorrent/bandwidth_socket.hpp | 3 - include/libtorrent/bitfield.hpp | 264 +- include/libtorrent/block_cache.hpp | 535 + include/libtorrent/bt_peer_connection.hpp | 87 +- include/libtorrent/buffer.hpp | 64 +- include/libtorrent/byteswap.hpp | 50 + include/libtorrent/chained_buffer.hpp | 35 +- include/libtorrent/config.hpp | 159 +- include/libtorrent/connection_interface.hpp | 55 + include/libtorrent/connection_queue.hpp | 68 +- include/libtorrent/cpuid.hpp | 63 + include/libtorrent/crc32c.hpp | 48 + include/libtorrent/create_torrent.hpp | 11 +- include/libtorrent/deadline_timer.hpp | 62 +- include/libtorrent/debug.hpp | 90 +- include/libtorrent/disk_buffer_holder.hpp | 31 +- include/libtorrent/disk_buffer_pool.hpp | 131 +- include/libtorrent/disk_interface.hpp | 119 + include/libtorrent/disk_io_job.hpp | 226 + include/libtorrent/disk_io_thread.hpp | 672 +- include/libtorrent/disk_job_pool.hpp | 72 + .../{settings.hpp => disk_observer.hpp} | 32 +- include/libtorrent/enum_net.hpp | 99 +- include/libtorrent/error_code.hpp | 65 +- include/libtorrent/escape_string.hpp | 6 + include/libtorrent/extensions.hpp | 24 +- .../extensions/metadata_transfer.hpp | 3 + include/libtorrent/file.hpp | 138 +- include/libtorrent/file_pool.hpp | 77 +- include/libtorrent/file_storage.hpp | 23 +- include/libtorrent/hasher.hpp | 4 +- include/libtorrent/http_connection.hpp | 142 +- include/libtorrent/http_parser.hpp | 23 +- include/libtorrent/http_seed_connection.hpp | 8 +- include/libtorrent/http_stream.hpp | 2 - .../libtorrent/http_tracker_connection.hpp | 12 +- include/libtorrent/i2p_stream.hpp | 10 +- include/libtorrent/intrusive_ptr_base.hpp | 5 +- include/libtorrent/ip_filter.hpp | 17 +- include/libtorrent/kademlia/dht_tracker.hpp | 21 +- include/libtorrent/kademlia/dos_blocker.hpp | 75 + include/libtorrent/kademlia/logging.hpp | 2 +- include/libtorrent/kademlia/node.hpp | 8 +- include/libtorrent/kademlia/observer.hpp | 21 +- include/libtorrent/kademlia/routing_table.hpp | 12 +- include/libtorrent/kademlia/rpc_manager.hpp | 18 +- .../kademlia/traversal_algorithm.hpp | 20 +- include/libtorrent/lazy_entry.hpp | 30 +- include/libtorrent/link.hpp | 77 + include/libtorrent/linked_list.hpp | 151 + include/libtorrent/max.hpp | 3 + include/libtorrent/network_thread_pool.hpp | 78 + include/libtorrent/part_file.hpp | 112 + include/libtorrent/peer_class.hpp | 145 + include/libtorrent/peer_class_set.hpp | 67 + include/libtorrent/peer_class_type_filter.hpp | 141 + include/libtorrent/peer_connection.hpp | 821 +- .../libtorrent/peer_connection_interface.hpp | 96 + include/libtorrent/peer_info.hpp | 209 +- include/libtorrent/performance_counters.hpp | 393 + include/libtorrent/piece_picker.hpp | 253 +- include/libtorrent/platform_util.hpp | 12 + include/libtorrent/policy.hpp | 525 +- include/libtorrent/proxy_base.hpp | 12 +- include/libtorrent/ptime.hpp | 139 - include/libtorrent/request_blocks.hpp | 49 + include/libtorrent/resolver.hpp | 81 + include/libtorrent/resolver_interface.hpp | 64 + include/libtorrent/session.hpp | 536 +- include/libtorrent/session_settings.hpp | 146 +- include/libtorrent/session_status.hpp | 14 +- include/libtorrent/settings_pack.hpp | 1577 ++ include/libtorrent/sha1_hash.hpp | 109 +- include/libtorrent/size_type.hpp | 1 + include/libtorrent/sliding_average.hpp | 9 +- include/libtorrent/socket_io.hpp | 2 + include/libtorrent/socks5_stream.hpp | 4 +- include/libtorrent/ssl_stream.hpp | 4 + include/libtorrent/stat.hpp | 57 +- include/libtorrent/stat_cache.hpp | 82 + include/libtorrent/storage.hpp | 1089 +- include/libtorrent/storage_defs.hpp | 28 +- include/libtorrent/string_util.hpp | 14 + include/libtorrent/tailqueue.hpp | 93 + include/libtorrent/thread.hpp | 5 + include/libtorrent/thread_pool.hpp | 161 + include/libtorrent/time.hpp | 89 +- include/libtorrent/timestamp_history.hpp | 23 +- include/libtorrent/torrent.hpp | 885 +- include/libtorrent/torrent_handle.hpp | 98 +- include/libtorrent/torrent_info.hpp | 92 +- include/libtorrent/torrent_peer.hpp | 273 + include/libtorrent/torrent_peer_allocator.hpp | 96 + include/libtorrent/tracker_manager.hpp | 79 +- include/libtorrent/udp_socket.hpp | 25 +- include/libtorrent/udp_tracker_connection.hpp | 19 +- include/libtorrent/uncork_interface.hpp | 55 + include/libtorrent/upnp.hpp | 8 + include/libtorrent/utp_socket_manager.hpp | 45 +- include/libtorrent/utp_stream.hpp | 33 +- include/libtorrent/vector_utils.hpp | 69 + include/libtorrent/version.hpp | 4 +- include/libtorrent/web_connection_base.hpp | 45 +- include/libtorrent/web_peer_connection.hpp | 19 +- parse_requests.py | 51 + src/Makefile.am | 22 +- src/alert.cpp | 92 +- src/allocator.cpp | 12 +- src/assert.cpp | 51 +- src/bandwidth_manager.cpp | 38 +- src/bandwidth_queue_entry.cpp | 2 +- src/block_cache.cpp | 1801 +++ src/bt_peer_connection.cpp | 815 +- src/chained_buffer.cpp | 43 +- src/connection_queue.cpp | 234 +- src/crc32c.cpp | 132 + src/create_torrent.cpp | 158 +- src/disk_buffer_holder.cpp | 39 +- src/disk_buffer_pool.cpp | 579 +- src/disk_io_job.cpp | 86 + src/disk_io_thread.cpp | 5707 ++++--- src/disk_job_pool.cpp | 110 + src/enum_net.cpp | 30 +- src/error_code.cpp | 6 +- src/escape_string.cpp | 35 + src/file.cpp | 1372 +- src/file_pool.cpp | 191 +- src/file_storage.cpp | 58 +- src/hasher.cpp | 4 +- src/http_connection.cpp | 194 +- src/http_parser.cpp | 12 +- src/http_seed_connection.cpp | 73 +- src/http_stream.cpp | 32 +- src/http_tracker_connection.cpp | 52 +- src/i2p_stream.cpp | 35 +- src/instantiate_connection.cpp | 20 +- src/ip_filter.cpp | 4 +- src/kademlia/dht_tracker.cpp | 80 +- src/kademlia/dos_blocker.cpp | 100 + src/kademlia/get_peers.cpp | 3 + src/kademlia/node.cpp | 81 +- src/kademlia/refresh.cpp | 12 +- src/kademlia/routing_table.cpp | 21 +- src/kademlia/rpc_manager.cpp | 106 +- src/kademlia/traversal_algorithm.cpp | 41 +- src/lazy_bdecode.cpp | 81 +- src/lt_trackers.cpp | 4 +- src/metadata_transfer.cpp | 40 +- src/parse_url.cpp | 2 +- src/part_file.cpp | 393 + src/peer_class.cpp | 140 + src/peer_class_set.cpp | 60 + src/peer_connection.cpp | 3221 ++-- src/performance_counters.cpp | 87 + src/piece_picker.cpp | 1583 +- src/platform_util.cpp | 100 + src/policy.cpp | 1408 +- src/proxy_base.cpp | 55 + src/request_blocks.cpp | 296 + src/resolver.cpp | 119 + src/rss.cpp | 125 +- src/session.cpp | 849 +- src/session_impl.cpp | 4769 ++++-- src/session_stats.cpp | 446 + src/settings.cpp | 143 - src/settings_pack.cpp | 726 + src/smart_ban.cpp | 122 +- src/socket_io.cpp | 52 + src/socket_type.cpp | 5 + src/socks5_stream.cpp | 79 +- src/stat.cpp | 1 - src/stat_cache.cpp | 97 + src/storage.cpp | 3366 ++-- src/string_util.cpp | 73 + src/tailqueue.cpp | 129 + src/thread.cpp | 17 + src/time.cpp | 173 +- src/timestamp_history.cpp | 6 +- src/torrent.cpp | 5341 ++++--- src/torrent_handle.cpp | 147 +- src/torrent_info.cpp | 584 +- src/torrent_peer.cpp | 281 + src/torrent_peer_allocator.cpp | 134 + src/tracker_manager.cpp | 18 +- src/udp_socket.cpp | 47 +- src/udp_tracker_connection.cpp | 54 +- src/upnp.cpp | 58 +- src/ut_metadata.cpp | 74 +- src/ut_pex.cpp | 44 +- src/utp_socket_manager.cpp | 56 +- src/utp_stream.cpp | 94 +- src/web_connection_base.cpp | 43 +- src/web_peer_connection.cpp | 1172 +- test/Jamfile | 12 + test/Makefile.am | 20 + test/main.cpp | 7 +- test/setup_transfer.cpp | 167 +- test/setup_transfer.hpp | 15 +- test/test.hpp | 3 + test/test_auto_unchoke.cpp | 59 +- test/test_bandwidth_limiter.cpp | 27 +- test/test_bencoding.cpp | 49 + test/test_bitfield.cpp | 5 +- test/test_block_cache.cpp | 469 + test/test_buffer.cpp | 15 +- test/test_checking.cpp | 51 +- test/test_connection_queue.cpp | 144 + test/test_dht.cpp | 32 +- test/test_dos_blocker.cpp | 65 + test/test_fast_extension.cpp | 20 +- test/test_fence.cpp | 211 + test/test_file.cpp | 11 +- test/test_http_connection.cpp | 16 +- test/test_lsd.cpp | 20 +- test/test_magnet.cpp | 58 +- test/test_metadata_extension.cpp | 27 +- test/test_part_file.cpp | 148 + test/test_pe_crypto.cpp | 78 +- test/test_peer_classes.cpp | 117 + test/test_pex.cpp | 80 +- test/test_piece_picker.cpp | 303 +- test/test_policy.cpp | 439 + test/test_primitives.cpp | 154 +- test/test_priority.cpp | 130 +- test/test_privacy.cpp | 76 +- test/test_read_piece.cpp | 9 +- test/test_recheck.cpp | 103 + test/test_remap_files.cpp | 38 +- test/test_rss.cpp | 12 +- test/test_session.cpp | 32 +- test/test_settings_pack.cpp | 82 + test/test_ssl.cpp | 69 +- test/test_stat_cache.cpp | 81 + test/test_storage.cpp | 761 +- test/test_string.cpp | 44 + test/test_swarm.cpp | 109 +- test/test_tailqueue.cpp | 162 + test/test_threads.cpp | 3 +- test/test_torrent.cpp | 86 +- test/test_torrent_info.cpp | 10 +- test/test_torrent_parse.cpp | 115 +- test/test_tracker.cpp | 30 +- test/test_trackers_extension.cpp | 8 +- test/test_transfer.cpp | 277 +- test/test_url_seed.cpp | 2 +- test/test_utp.cpp | 48 +- test/test_web_seed.cpp | 2 +- test/test_web_seed_ban.cpp | 2 +- test/test_web_seed_chunked.cpp | 2 +- test/test_web_seed_http.cpp | 2 +- test/test_web_seed_http_pw.cpp | 2 +- test/test_web_seed_socks4.cpp | 2 +- test/test_web_seed_socks5.cpp | 2 +- test/test_web_seed_socks5_pw.cpp | 2 +- test/upnp.xml | 1 - test/web_seed_suite.cpp | 148 +- tools/Jamfile | 1 + tools/dht_put.cpp | 14 +- tools/parse_access_log.cpp | 180 + tools/parse_disk_buffer_log.py | 5 +- tools/parse_sample.py | 6 + tools/parse_session_stats.py | 206 +- tools/run_benchmark.py | 11 +- tools/run_tests.py | 3 +- 357 files changed, 58707 insertions(+), 40644 deletions(-) delete mode 100644 bindings/python/setup.py delete mode 100644 docs/folx.png create mode 100644 docs/gen_settings_doc.py delete mode 100644 docs/hacking.html delete mode 100644 docs/manual-ref.html create mode 100644 docs/manual.html delete mode 100644 docs/reference-Alerts.html delete mode 100644 docs/reference-Bencoding.html delete mode 100644 docs/reference-Core.html delete mode 100644 docs/reference-Create_Torrents.html delete mode 100644 docs/reference-Custom_Storage.html delete mode 100644 docs/reference-Error_Codes.html delete mode 100644 docs/reference-Filter.html delete mode 100644 docs/reference-Plugins.html delete mode 100644 docs/reference-RSS.html delete mode 100644 docs/reference-Settings.html delete mode 100644 docs/reference-Storage.html delete mode 100644 docs/reference-String.html delete mode 100644 docs/reference-Time.html delete mode 100644 docs/reference-Utility.html delete mode 100644 docs/reference.html create mode 100644 docs/settings.rst create mode 100644 docs/stats_counters.rst create mode 100644 examples/stats_counters.cpp create mode 100644 gather_todo.py create mode 100644 include/libtorrent/alert_observer.hpp create mode 100644 include/libtorrent/atomic.hpp create mode 100644 include/libtorrent/aux_/session_interface.hpp create mode 100644 include/libtorrent/aux_/session_settings.hpp create mode 100644 include/libtorrent/block_cache.hpp create mode 100644 include/libtorrent/byteswap.hpp create mode 100644 include/libtorrent/connection_interface.hpp create mode 100644 include/libtorrent/cpuid.hpp create mode 100644 include/libtorrent/crc32c.hpp create mode 100644 include/libtorrent/disk_interface.hpp create mode 100644 include/libtorrent/disk_io_job.hpp create mode 100644 include/libtorrent/disk_job_pool.hpp rename include/libtorrent/{settings.hpp => disk_observer.hpp} (68%) create mode 100644 include/libtorrent/kademlia/dos_blocker.hpp create mode 100644 include/libtorrent/link.hpp create mode 100644 include/libtorrent/linked_list.hpp create mode 100644 include/libtorrent/network_thread_pool.hpp create mode 100644 include/libtorrent/part_file.hpp create mode 100644 include/libtorrent/peer_class.hpp create mode 100644 include/libtorrent/peer_class_set.hpp create mode 100644 include/libtorrent/peer_class_type_filter.hpp create mode 100644 include/libtorrent/peer_connection_interface.hpp create mode 100644 include/libtorrent/performance_counters.hpp create mode 100644 include/libtorrent/platform_util.hpp delete mode 100644 include/libtorrent/ptime.hpp create mode 100644 include/libtorrent/request_blocks.hpp create mode 100644 include/libtorrent/resolver.hpp create mode 100644 include/libtorrent/resolver_interface.hpp create mode 100644 include/libtorrent/settings_pack.hpp create mode 100644 include/libtorrent/stat_cache.hpp create mode 100644 include/libtorrent/tailqueue.hpp create mode 100644 include/libtorrent/thread_pool.hpp create mode 100644 include/libtorrent/torrent_peer.hpp create mode 100644 include/libtorrent/torrent_peer_allocator.hpp create mode 100644 include/libtorrent/uncork_interface.hpp create mode 100644 include/libtorrent/vector_utils.hpp create mode 100644 parse_requests.py create mode 100644 src/block_cache.cpp create mode 100644 src/crc32c.cpp create mode 100644 src/disk_io_job.cpp create mode 100644 src/disk_job_pool.cpp create mode 100644 src/kademlia/dos_blocker.cpp create mode 100644 src/part_file.cpp create mode 100644 src/peer_class.cpp create mode 100644 src/peer_class_set.cpp create mode 100644 src/performance_counters.cpp create mode 100644 src/platform_util.cpp create mode 100644 src/proxy_base.cpp create mode 100644 src/request_blocks.cpp create mode 100644 src/resolver.cpp create mode 100644 src/session_stats.cpp delete mode 100644 src/settings.cpp create mode 100644 src/settings_pack.cpp create mode 100644 src/stat_cache.cpp create mode 100644 src/tailqueue.cpp create mode 100644 src/torrent_peer.cpp create mode 100644 src/torrent_peer_allocator.cpp create mode 100644 test/test_block_cache.cpp create mode 100644 test/test_connection_queue.cpp create mode 100644 test/test_dos_blocker.cpp create mode 100644 test/test_fence.cpp create mode 100644 test/test_part_file.cpp create mode 100644 test/test_peer_classes.cpp create mode 100644 test/test_policy.cpp create mode 100644 test/test_recheck.cpp create mode 100644 test/test_settings_pack.cpp create mode 100644 test/test_stat_cache.cpp create mode 100644 test/test_tailqueue.cpp delete mode 100644 test/upnp.xml create mode 100644 tools/parse_access_log.cpp diff --git a/.regression.yml b/.regression.yml index 72ceb3125..61e0cf2b8 100644 --- a/.regression.yml +++ b/.regression.yml @@ -24,7 +24,7 @@ build_features: project: libtorrent -branch: trunk +branch: aio clean: - test_tmp_* diff --git a/CMakeLists.txt b/CMakeLists.txt index 8845017ed..fa9961386 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ set(sources bandwidth_limit bandwidth_manager bandwidth_queue_entry + block_cache bloom_filter chained_buffer connection_queue @@ -32,31 +33,43 @@ set(sources identify_client ip_filter ip_voter + performance_counters + peer_class + peer_class_set peer_connection bt_peer_connection web_peer_connection http_seed_connection instantiate_connection natpmp + part_file packet_buffer piece_picker + platform_util + proxy_base policy puff random + request_blocks rss session session_impl - settings + session_stats + settings_pack socket_io socket_type socks5_stream stat + stat_cache storage + tailqueue time timestamp_history torrent torrent_handle torrent_info + torrent_peer + torrent_peer_allocator tracker_manager http_tracker_connection utf8 @@ -68,6 +81,8 @@ set(sources logger file_pool lsd + disk_io_job + disk_job_pool disk_buffer_pool disk_io_thread enum_net @@ -298,7 +313,7 @@ set_target_properties(torrent-rasterbar PROPERTIES SOVERSION 1 VERSION 1) -set (VERSION "1.0.0") +set (VERSION "1.1.0") get_property (COMPILETIME_OPTIONS_LIST DIRECTORY ${CMAKE_CURRENT_SOURCE_DIRECTORY} diff --git a/ChangeLog b/ChangeLog index edeee56ca..463346be8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,32 @@ + * deprecate proxy settings in favor of regular settings + * deprecate separate settings for peer protocol encryption + * support specifying listen interfaces and outgoing interfaces as device + names (eth0, en2, tun0 etc.) + * support for using purgrable memory as disk cache on Mac OS. + * be more aggressive in corking sockets, to coalesce messages into larger packets. + * pre-emptively unchoke peers to save one round-trip at connection start-up. + * add session constructor overload that takes a settings_pack + * torrent_info is no longer an intrusive_ptr type. It is held by shared_ptr. This is a non-backwards compatible change + * move listen interface and port to the settings + * move use_interfaces() to be a setting + * extend storage interface to allow deferred flushing and flush the part-file metadata periodically + * make statistics propagate instantly rather than on the second tick + * support for partfiles, where partial pieces belonging to skipped files are put + * support using multiple threads for socket operations (especially useful for high performance SSL connections) + * allow setting rate limits for arbitrary peer groups. Generalizes per-torrent rate limits, and local peer limits + * improved disk cache complexity O(1) instead of O(log(n)) + * add feature to allow storing disk cache blocks in an mmapped file (presumably on an SSD) + * optimize peer connection distribution logic across torrents to scale better with many torrents + * replaced std::map with boost::unordered_map for torrent list, to scale better with many torrents + * optimized piece picker + * optimized disk cache + * optimized .torrent file parsing + * optimized initialization of storage when adding a torrent + * added support for adding torrents asynchronously (for improved startup performance) + * added support for asynchronous disk I/O + * almost completely changed the storage interface (for custom storage) + * added support for hashing pieces in multiple threads + * fix compiler warnings 1.0 release diff --git a/Jamfile b/Jamfile index 430682115..120660020 100755 --- a/Jamfile +++ b/Jamfile @@ -23,7 +23,7 @@ if $(BOOST_ROOT) use-project /boost : $(BOOST_ROOT) ; } -VERSION = 1.0.0 ; +VERSION = 1.1.0 ; rule coverage ( properties * ) { @@ -178,10 +178,12 @@ rule linking ( properties * ) # library, so that it properly exports its symbols result += BOOST_ALL_DYN_LINK ; result += /boost/system//boost_system/static/BOOST_ALL_DYN_LINK ; + result += /boost/chrono//boost_chrono/static/BOOST_ALL_DYN_LINK ; } else { result += /boost/system//boost_system/static ; + result += /boost/chrono//boost_chrono/static ; } if gcc in $(properties) && shared in $(properties) @@ -193,14 +195,7 @@ rule linking ( properties * ) else { result += /boost/system//boost_system/shared ; - - # when building a shared library with openssl, for some reason we really need - # boost date time (at least on windows). Hopefully a future version of asio - # will allow disabling this dependency - if openssl in $(properties) - { - result += /boost/date_time//boost_date_time/shared ; - } + result += /boost/chrono//boost_chrono/shared ; } result += $(BOOST_ROOT) BOOST_ALL_NO_LIB @@ -211,11 +206,7 @@ rule linking ( properties * ) else { result += boost_system ; - - if shared in $(properties) && openssl in $(properties) - { - result += boost_date_time ; - } + result += boost_chrono ; # on mac the boost headers are installed in # a directory that isn't automatically accessable @@ -326,14 +317,6 @@ rule tag ( name : type ? : property-set ) feature tcmalloc : no yes : composite propagated link-incompatible ; -feature timer : auto boost absolute performance clock system_time - : composite propagated link-incompatible ; -feature.compose boost : TORRENT_USE_BOOST_DATE_TIME=1 ; -feature.compose absolute : TORRENT_USE_ABSOLUTE_TIME=1 ; -feature.compose performance : TORRENT_USE_PERFORMANCE_TIMER=1 ; -feature.compose clock : TORRENT_USE_CLOCK_GETTIME=1 ; -feature.compose system_time : TORRENT_USE_SYSTEM_TIME=1 ; - feature ipv6 : on off : composite propagated link-incompatible ; feature.compose off : TORRENT_USE_IPV6=0 ; @@ -350,6 +333,9 @@ feature need-librt : no yes : composite propagated link-incompatible ; feature fiemap : off on : composite propagated ; feature.compose on : HAVE_LINUX_FIEMAP_H ; +feature file-leak-logging : off on : composite propagated ; +feature.compose on : TORRENT_DEBUG_FILE_LEAKS=1 ; + feature i2p : on off : composite propagated ; feature.compose on : TORRENT_USE_I2P=1 ; feature.compose off : TORRENT_USE_I2P=0 ; @@ -364,6 +350,9 @@ feature.compose on : TORRENT_USE_VALGRIND=1 ; feature full-stats : on off : composite propagated link-incompatible ; feature.compose off : TORRENT_DISABLE_FULL_STATS ; +feature memory-optimization : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_OPTIMIZE_MEMORY_USAGE ; + feature asserts : auto on off production system : composite propagated ; feature.compose on : TORRENT_RELEASE_ASSERTS=1 ; feature.compose production : TORRENT_PRODUCTION_ASSERTS=1 TORRENT_RELEASE_ASSERTS=1 ; @@ -381,9 +370,13 @@ feature.compose off : TORRENT_DISABLE_EXTENSIONS ; feature asio-debugging : off on : composite propagated link-incompatible ; feature.compose on : TORRENT_ASIO_DEBUGGING ; +feature picker-debugging : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_DEBUG_REFCOUNTS ; + # deprecated use allocator=pool instead -feature pool-allocators : on off : composite propagated link-incompatible ; +feature pool-allocators : on off debug : composite propagated link-incompatible ; feature.compose off : TORRENT_DISABLE_POOL_ALLOCATOR ; +feature.compose debug : TORRENT_DISABLE_POOL_ALLOCATOR TORRENT_DEBUG_BUFFERS ; feature allocator : pool system debug : composite propagated ; feature.compose system : TORRENT_DISABLE_POOL_ALLOCATOR ; @@ -409,6 +402,9 @@ feature.compose on : TORRENT_REQUEST_LOGGING ; feature disk-stats : off on : composite propagated link-incompatible ; feature.compose on : TORRENT_DISK_STATS ; +feature buffer-stats : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_BUFFER_STATS ; + feature simulate-slow-read : off on : composite propagated ; feature.compose on : TORRENT_SIMULATE_SLOW_READ ; @@ -455,6 +451,9 @@ feature fpic : off on : composite propagated link-incompatible ; feature.compose on : -fPIC ; feature.compose off : darwin:-mdynamic-no-pic ; +feature profile-calls : off on : composite propagated link-incompatible ; +feature.compose on : TORRENT_PROFILE_CALLS=1 ; + # controls whether or not to export some internal # libtorrent functions. Used for unit testing feature export-extra : off on : composite propagated ; @@ -487,9 +486,9 @@ lib boost_system : : darwin boost_system-mt $(boost-library-sea lib boost_system : : solaris boost_system $(boost-library-search-path) ; lib boost_system : : boost_system ; -lib boost_date_time : : darwin boost_date_time-mt $(boost-library-search-path) ; -lib boost_date_time : : solaris boost_date_time $(boost-library-search-path) ; -lib boost_date_time : : boost_date_time ; +lib boost_chrono : : darwin boost_chrono-mt $(boost-library-search-path) ; +lib boost_chrono : : solaris boost_chrono $(boost-library-search-path) ; +lib boost_chrono : : boost_chrono ; # openssl on linux/bsd/macos etc. lib gcrypt : : gcrypt shared /opt/local/lib ; @@ -497,6 +496,7 @@ lib z : : shared z /usr/lib ; lib crypto : : crypto /usr/lib z ; lib ssl : : ssl shared crypto /opt/local/lib ; lib dl : : shared dl ; +lib aio : : shared aio ; # time functions used on linux require librt lib librt : : rt shared ; @@ -523,11 +523,16 @@ SOURCES = bandwidth_limit bandwidth_manager bandwidth_queue_entry + block_cache bloom_filter chained_buffer connection_queue + crc32c create_torrent disk_buffer_holder + disk_buffer_pool + disk_io_job + disk_job_pool entry error_code file_storage @@ -544,6 +549,7 @@ SOURCES = ip_filter ip_voter peer_connection + platform_util bt_peer_connection web_connection_base web_peer_connection @@ -554,12 +560,13 @@ SOURCES = packet_buffer piece_picker policy + proxy_base puff random rss session session_impl - settings + settings_pack socket_io socket_type socks5_stream @@ -568,11 +575,14 @@ SOURCES = torrent torrent_handle torrent_info + torrent_peer + torrent_peer_allocator time tracker_manager http_tracker_connection udp_tracker_connection sha1 + tailqueue timestamp_history udp_socket upnp @@ -591,6 +601,14 @@ SOURCES = ConvertUTF thread xml_parse + peer_class + peer_class_set + part_file + stat_cache + request_blocks + session_stats + performance_counters + resolver # -- extensions -- metadata_transfer @@ -610,6 +628,7 @@ KADEMLIA_SOURCES = routing_table traversal_algorithm logging + dos_blocker get_peers item get_item @@ -683,6 +702,7 @@ lib torrent ./ed25519/src multi shared:TORRENT_BUILDING_SHARED + BOOST_NO_DEPRECATED # on windows, when linking statically against asio # but producing a DLL, everything inside the DLL needs @@ -708,6 +728,7 @@ lib torrent : # usage requirements $(usage-requirements) shared:TORRENT_LINKING_SHARED + ; headers = [ path.glob-tree include/libtorrent : *.hpp ] ; diff --git a/Makefile.am b/Makefile.am index 7c01bce81..01fd6ff35 100644 --- a/Makefile.am +++ b/Makefile.am @@ -104,6 +104,8 @@ DOCS_PAGES = \ docs/python_binding.rst \ docs/tuning.html \ docs/tuning.rst \ + docs/settings.rst \ + docs/stats_counters.rst \ docs/troubleshooting.html \ docs/udp_tracker_protocol.html \ docs/udp_tracker_protocol.rst \ diff --git a/bindings/python/setup.py b/bindings/python/setup.py deleted file mode 100644 index fd4613038..000000000 --- a/bindings/python/setup.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env python - -from distutils import sysconfig -from distutils.core import setup, Extension -import os -import platform -import sys -import shutil -import multiprocessing -import subprocess - -def parse_cmd(cmdline, prefix, keep_prefix = False): - ret = [] - for token in cmdline.split(): - if token[:len(prefix)] == prefix: - if keep_prefix: - ret.append(token) - else: - ret.append(token[len(prefix):]) - return ret - -def arch(): - if platform.system() != 'Darwin': return [] - a = os.uname()[4] - if a == 'Power Macintosh': a = 'ppc' - return ['-arch', a] - -def target_specific(): - - if platform.system() != 'Darwin': return [] - - # on mavericks, clang will fail when unknown arguments are - # passed in. python distutils will pass in arguments it doesn't - # know about - return ['-Wno-error=unused-command-line-argument-hard-error-in-future'] - -try: - extra_cmd = open('compile_flags').read() -except: - extra_cmd = None - -try: - ldflags = open('link_flags').read() -except: - ldflags = None - -ext = None -packages = None - -if '--bjam' in sys.argv or ldflags == None or extra_cmd == None: - - del sys.argv[sys.argv.index('--bjam')] - - if not '--help' in sys.argv \ - and not '--help-commands' in sys.argv: - - toolset = '' - file_ext = '.so' - - if platform.system() == 'Windows': - # msvc 9.0 (2008) is the official windows compiler for python 2.6 - # http://docs.python.org/whatsnew/2.6.html#build-and-c-api-changes - toolset = ' msvc-9.0' - file_ext = '.pyd' - - parallell_builds = ' -j%d' % multiprocessing.cpu_count() - - # build libtorrent using bjam and build the installer with distutils - cmdline = 'bjam boost=source link=static geoip=static boost-link=static release optimization=space stage_module --abbreviate-paths' + toolset + parallell_builds - print cmdline - if os.system(cmdline) != 0: - print('build failed') - sys.exit(1) - - try: os.mkdir('build') - except: pass - try: shutil.rmtree('build/lib') - except: pass - try: os.mkdir('build/lib') - except: pass - try: os.mkdir('libtorrent') - except: pass - shutil.copyfile('libtorrent' + file_ext, 'build/lib/libtorrent' + file_ext) - - packages = ['libtorrent'] - -else: - - source_list = os.listdir(os.path.join(os.path.dirname(__file__), "src")) - source_list = [os.path.join("src", s) for s in source_list if s.endswith(".cpp")] - - ext = [Extension('libtorrent', - sources = source_list, - language='c++', - include_dirs = ['../../include'] + parse_cmd(extra_cmd, '-I'), - library_dirs = ['../../src/.libs'] + parse_cmd(extra_cmd, '-L'), - extra_link_args = ldflags.split() + arch(), - extra_compile_args = parse_cmd(extra_cmd, '-D', True) + arch() \ - + target_specific(), - libraries = ['torrent-rasterbar'] + parse_cmd(extra_cmd, '-l'))] - -setup(name = 'python-libtorrent', - version = '1.0.0', - author = 'Arvid Norberg', - author_email = 'arvid@rasterbar.com', - description = 'Python bindings for libtorrent-rasterbar', - long_description = 'Python bindings for libtorrent-rasterbar', - url = 'http://libtorrent.org', - platforms = [platform.system() + '-' + platform.machine()], - license = 'BSD', - packages = packages, - ext_modules = ext -) - diff --git a/bindings/python/src/alert.cpp b/bindings/python/src/alert.cpp index 70b217c18..2a3d2c01a 100644 --- a/bindings/python/src/alert.cpp +++ b/bindings/python/src/alert.cpp @@ -285,8 +285,10 @@ void bind_alert() class_, noncopyable>( "listen_failed_alert", no_init) - .def_readonly("endpoint", &listen_failed_alert::endpoint) + .def_readonly("interface", &listen_failed_alert::interface) .def_readonly("error", &listen_failed_alert::error) + .def_readonly("operation", &listen_failed_alert::operation) + .def_readonly("sock_type", &listen_failed_alert::sock_type) ; class_, noncopyable>( diff --git a/bindings/python/src/create_torrent.cpp b/bindings/python/src/create_torrent.cpp index f486b5a13..de49b19f6 100644 --- a/bindings/python/src/create_torrent.cpp +++ b/bindings/python/src/create_torrent.cpp @@ -5,7 +5,7 @@ #include #include #include -#include "libtorrent/intrusive_ptr_base.hpp" +#include "libtorrent/torrent_info.hpp" using namespace boost::python; using namespace libtorrent; diff --git a/bindings/python/src/magnet_uri.cpp b/bindings/python/src/magnet_uri.cpp index 5c0553c31..132e613d3 100644 --- a/bindings/python/src/magnet_uri.cpp +++ b/bindings/python/src/magnet_uri.cpp @@ -10,13 +10,14 @@ using namespace boost::python; using namespace libtorrent; +namespace lt = libtorrent; extern void dict_to_add_torrent_params(dict params, add_torrent_params& p); namespace { #ifndef TORRENT_NO_DEPRECATE - torrent_handle _add_magnet_uri(session& s, std::string uri, dict params) + torrent_handle _add_magnet_uri(lt::session& s, std::string uri, dict params) { add_torrent_params p; diff --git a/bindings/python/src/module.cpp b/bindings/python/src/module.cpp index 678358337..c85b822ca 100644 --- a/bindings/python/src/module.cpp +++ b/bindings/python/src/module.cpp @@ -39,12 +39,12 @@ BOOST_PYTHON_MODULE(libtorrent) bind_fingerprint(); bind_sha1_hash(); bind_entry(); + bind_torrent_handle(); bind_session(); bind_torrent_info(); #if TORRENT_USE_WSTRING bind_unicode_string_conversion(); #endif - bind_torrent_handle(); bind_torrent_status(); bind_session_settings(); bind_version(); diff --git a/bindings/python/src/peer_info.cpp b/bindings/python/src/peer_info.cpp index ac6ef0db1..5beacc2e8 100644 --- a/bindings/python/src/peer_info.cpp +++ b/bindings/python/src/peer_info.cpp @@ -70,8 +70,11 @@ void bind_peer_info() .def_readonly("total_upload", &peer_info::total_upload) .def_readonly("pid", &peer_info::pid) .add_property("pieces", get_pieces) +#ifndef TORRENT_NO_DEPRECATE .def_readonly("upload_limit", &peer_info::upload_limit) .def_readonly("download_limit", &peer_info::download_limit) + .def_readonly("load_balancing", &peer_info::load_balancing) +#endif .add_property("last_request", get_last_request) .add_property("last_active", get_last_active) .add_property("download_queue_time", get_download_queue_time) diff --git a/bindings/python/src/session.cpp b/bindings/python/src/session.cpp index 308ef87e1..5d50f4987 100644 --- a/bindings/python/src/session.cpp +++ b/bindings/python/src/session.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -25,41 +26,45 @@ using namespace boost::python; using namespace libtorrent; +namespace lt = libtorrent; namespace { - void listen_on(session& s, int min_, int max_, char const* interface, int flags) +#ifndef TORRENT_NO_DEPRECATE + void listen_on(lt::session& s, int min_, int max_, char const* interface, int flags) { allow_threading_guard guard; error_code ec; s.listen_on(std::make_pair(min_, max_), ec, interface, flags); if (ec) throw libtorrent_exception(ec); } +#endif - void outgoing_ports(session& s, int _min, int _max) + void outgoing_ports(lt::session& s, int _min, int _max) { allow_threading_guard guard; - session_settings settings = s.settings(); - settings.outgoing_ports = std::make_pair(_min, _max); - s.set_settings(settings); + settings_pack p; + p.set_int(settings_pack::outgoing_port, _min); + p.set_int(settings_pack::num_outgoing_ports, _max - _min); + s.apply_settings(p); return; } #ifndef TORRENT_DISABLE_DHT - void add_dht_node(session& s, tuple n) + void add_dht_node(lt::session& s, tuple n) { std::string ip = extract(n[0]); int port = extract(n[1]); s.add_dht_node(std::make_pair(ip, port)); } - void add_dht_router(session& s, std::string router_, int port_) + void add_dht_router(lt::session& s, std::string router_, int port_) { allow_threading_guard guard; return s.add_dht_router(std::make_pair(router_, port_)); } #endif - void add_extension(session& s, object const& e) + void add_extension(lt::session& s, object const& e) { #ifndef TORRENT_DISABLE_EXTENSIONS if (!extract(e).check()) return; @@ -81,89 +86,72 @@ namespace #endif // TORRENT_DISABLE_EXTENSIONS } - void session_set_settings(session& ses, dict const& sett_dict) + void session_set_settings(lt::session& ses, dict const& sett_dict) { - bencode_map_entry* map; - int len; - boost::tie(map, len) = aux::settings_map(); - - session_settings sett; - for (int i = 0; i < len; ++i) + settings_pack p; + list iterkeys = (list)sett_dict.iterkeys(); + for (int i = 0; i < boost::python::len(iterkeys); i++) { - if (!sett_dict.has_key(map[i].name)) continue; + std::string key = extract(iterkeys[i]); - void* dest = ((char*)&sett) + map[i].offset; - char const* name = map[i].name; - switch (map[i].type) + int sett = setting_by_name(key); + if (sett == 0) continue; + + TORRENT_TRY { - case std_string: - *((std::string*)dest) = extract(sett_dict[name]); - break; - case character: - *((char*)dest) = extract(sett_dict[name]); - break; - case boolean: - *((bool*)dest) = extract(sett_dict[name]); - break; - case integer: - *((int*)dest) = extract(sett_dict[name]); - break; - case floating_point: - *((float*)dest) = extract(sett_dict[name]); - break; + object value = sett_dict[key]; + switch (sett & settings_pack::type_mask) + { + case settings_pack::string_type_base: + p.set_str(sett, extract(value)); + break; + case settings_pack::int_type_base: + p.set_int(sett, extract(value)); + break; + case settings_pack::bool_type_base: + p.set_bool(sett, extract(value)); + break; + } } + TORRENT_CATCH(...) {} } - if (!sett_dict.has_key("outgoing_port")) - sett.outgoing_ports.first = extract(sett_dict["outgoing_port"]); - if (!sett_dict.has_key("num_outgoing_ports")) - sett.outgoing_ports.second = sett.outgoing_ports.first + extract(sett_dict["num_outgoing_ports"]); + allow_threading_guard guard; - ses.set_settings(sett); + ses.apply_settings(p); } - dict session_get_settings(session const& ses) + dict session_get_settings(lt::session const& ses) { - session_settings sett; + aux::session_settings sett; { allow_threading_guard guard; - sett = ses.settings(); + sett = ses.get_settings(); } - dict sett_dict; - bencode_map_entry* map; - int len; - boost::tie(map, len) = aux::settings_map(); - for (int i = 0; i < len; ++i) + dict ret; + for (int i = settings_pack::string_type_base; + i < settings_pack::max_string_setting_internal; ++i) { - void const* dest = ((char const*)&sett) + map[i].offset; - char const* name = map[i].name; - switch (map[i].type) - { - case std_string: - sett_dict[name] = *((std::string const*)dest); - break; - case character: - sett_dict[name] = *((char const*)dest); - break; - case boolean: - sett_dict[name] = *((bool const*)dest); - break; - case integer: - sett_dict[name] = *((int const*)dest); - break; - case floating_point: - sett_dict[name] = *((float const*)dest); - break; - } + ret[name_for_setting(i)] = sett.get_str(i); } - sett_dict["outgoing_port"] = sett.outgoing_ports.first; - sett_dict["num_outgoing_ports"] = sett.outgoing_ports.second - sett.outgoing_ports.first + 1; - return sett_dict; + + for (int i = settings_pack::int_type_base; + i < settings_pack::max_int_setting_internal; ++i) + { + ret[name_for_setting(i)] = sett.get_int(i); + } + + for (int i = settings_pack::bool_type_base; + i < settings_pack::max_bool_setting_internal; ++i) + { + ret[name_for_setting(i)] = sett.get_bool(i); + } + return ret; } #ifndef BOOST_NO_EXCEPTIONS #ifndef TORRENT_NO_DEPRECATE - torrent_handle add_torrent_depr(session& s, torrent_info const& ti + torrent_handle add_torrent_depr(lt::session& s, torrent_info const& ti , std::string const& save, entry const& resume , storage_mode_t storage_mode, bool paused) { @@ -176,9 +164,9 @@ namespace void dict_to_add_torrent_params(dict params, add_torrent_params& p) { - // torrent_info objects are always held by an intrusive_ptr in the python binding + // torrent_info objects are always held by a shared_ptr in the python binding if (params.has_key("ti") && params.get("ti") != boost::python::object()) - p.ti = extract >(params["ti"]); + p.ti = extract >(params["ti"]); if (params.has_key("info_hash")) p.info_hash = extract(params["info_hash"]); @@ -257,7 +245,7 @@ namespace namespace { - torrent_handle add_torrent(session& s, dict params) + torrent_handle add_torrent(lt::session& s, dict params) { add_torrent_params p; dict_to_add_torrent_params(params, p); @@ -272,7 +260,7 @@ namespace #endif } - void async_add_torrent(session& s, dict params) + void async_add_torrent(lt::session& s, dict params) { add_torrent_params p; dict_to_add_torrent_params(params, p); @@ -299,7 +287,7 @@ namespace dict_to_add_torrent_params(dict(params["add_args"]), feed.add_args); } - feed_handle add_feed(session& s, dict params) + feed_handle add_feed(lt::session& s, dict params) { feed_settings feed; // this static here is a bit of a hack. It will @@ -368,25 +356,27 @@ namespace return ret; } - void start_natpmp(session& s) +#ifndef TORRENT_NO_DEPRECATE + void start_natpmp(lt::session& s) { allow_threading_guard guard; s.start_natpmp(); } - void start_upnp(session& s) + void start_upnp(lt::session& s) { allow_threading_guard guard; s.start_upnp(); } +#endif - alert const* wait_for_alert(session& s, int ms) + alert const* wait_for_alert(lt::session& s, int ms) { allow_threading_guard guard; return s.wait_for_alert(milliseconds(ms)); } - list get_torrents(session& s) + list get_torrents(lt::session& s) { list ret; std::vector torrents; @@ -402,6 +392,22 @@ namespace return ret; } + cache_status get_cache_info1(lt::session& s, torrent_handle h, int flags) + { + cache_status ret; + s.get_cache_info(&ret, h, flags); + return ret; + } + +#ifndef TORRENT_NO_DEPRECATE + cache_status get_cache_status(lt::session& s) + { + cache_status ret; + s.get_cache_info(&ret); + return ret; + } +#endif + dict get_utp_stats(session_status const& st) { dict ret; @@ -413,45 +419,47 @@ namespace return ret; } - list get_cache_info(session& ses, sha1_hash ih) +#ifndef TORRENT_NO_DEPRECATE + list get_cache_info2(lt::session& ses, sha1_hash ih) { - std::vector ret; + std::vector ret; - { - allow_threading_guard guard; - ses.get_cache_info(ih, ret); - } + { + allow_threading_guard guard; + ses.get_cache_info(ih, ret); + } - list pieces; - ptime now = time_now(); - for (std::vector::iterator i = ret.begin() - , end(ret.end()); i != end; ++i) - { - dict d; - d["piece"] = i->piece; - d["last_use"] = total_milliseconds(now - i->last_use) / 1000.f; - d["next_to_hash"] = i->next_to_hash; - d["kind"] = i->kind; - pieces.append(d); - } - return pieces; + list pieces; + ptime now = time_now(); + for (std::vector::iterator i = ret.begin() + , end(ret.end()); i != end; ++i) + { + dict d; + d["piece"] = i->piece; + d["last_use"] = total_milliseconds(now - i->last_use) / 1000.f; + d["next_to_hash"] = i->next_to_hash; + d["kind"] = i->kind; + pieces.append(d); + } + return pieces; } +#endif #ifndef TORRENT_DISABLE_GEO_IP - void load_asnum_db(session& s, std::string file) + void load_asnum_db(lt::session& s, std::string file) { allow_threading_guard guard; s.load_asnum_db(file.c_str()); } - void load_country_db(session& s, std::string file) + void load_country_db(lt::session& s, std::string file) { allow_threading_guard guard; s.load_country_db(file.c_str()); } #endif - entry save_state(session const& s, boost::uint32_t flags) + entry save_state(lt::session const& s, boost::uint32_t flags) { allow_threading_guard guard; entry e; @@ -459,7 +467,7 @@ namespace return e; } - object pop_alert(session& ses) + object pop_alert(lt::session& ses) { std::auto_ptr a; { @@ -470,7 +478,7 @@ namespace return object(boost::shared_ptr(a.release())); } - list pop_alerts(session& ses) + list pop_alerts(lt::session& ses) { std::deque alerts; { @@ -487,7 +495,7 @@ namespace return ret; } - void load_state(session& ses, entry const& st) + void load_state(lt::session& ses, entry const& st) { allow_threading_guard guard; @@ -504,10 +512,10 @@ namespace void bind_session() { -#ifndef TORRENT_DISABLE_DHT - void (session::*start_dht0)() = &session::start_dht; #ifndef TORRENT_NO_DEPRECATE - void (session::*start_dht1)(entry const&) = &session::start_dht; +#ifndef TORRENT_DISABLE_DHT + void (lt::session::*start_dht0)() = <::session::start_dht; + void (lt::session::*start_dht1)(entry const&) = <::session::start_dht; #endif #endif @@ -584,13 +592,13 @@ void bind_session() #endif ; - enum_("options_t") - .value("delete_files", session::delete_files) + enum_("options_t") + .value("delete_files", lt::session::delete_files) ; - enum_("session_flags_t") - .value("add_default_plugins", session::add_default_plugins) - .value("start_default_features", session::start_default_features) + enum_("session_flags_t") + .value("add_default_plugins", lt::session::add_default_plugins) + .value("start_default_features", lt::session::start_default_features) ; enum_("add_torrent_params_flags_t") @@ -614,56 +622,76 @@ void bind_session() .def_readonly("blocks_read", &cache_status::blocks_read) .def_readonly("blocks_read_hit", &cache_status::blocks_read_hit) .def_readonly("reads", &cache_status::reads) +#ifndef TORRENT_NO_DEPRECATE .def_readonly("queued_bytes", &cache_status::queued_bytes) .def_readonly("cache_size", &cache_status::cache_size) +#endif + .def_readonly("write_cache_size", &cache_status::write_cache_size) .def_readonly("read_cache_size", &cache_status::read_cache_size) + .def_readonly("pinned_blocks", &cache_status::pinned_blocks) .def_readonly("total_used_buffers", &cache_status::total_used_buffers) - .def_readonly("average_queue_time", &cache_status::average_queue_time) .def_readonly("average_read_time", &cache_status::average_read_time) .def_readonly("average_write_time", &cache_status::average_write_time) .def_readonly("average_hash_time", &cache_status::average_hash_time) .def_readonly("average_job_time", &cache_status::average_job_time) - .def_readonly("average_sort_time", &cache_status::average_sort_time) - .def_readonly("job_queue_length", &cache_status::job_queue_length) .def_readonly("cumulative_job_time", &cache_status::cumulative_job_time) .def_readonly("cumulative_read_time", &cache_status::cumulative_read_time) .def_readonly("cumulative_write_time", &cache_status::cumulative_write_time) .def_readonly("cumulative_hash_time", &cache_status::cumulative_hash_time) - .def_readonly("cumulative_sort_time", &cache_status::cumulative_sort_time) .def_readonly("total_read_back", &cache_status::total_read_back) .def_readonly("read_queue_size", &cache_status::read_queue_size) + .def_readonly("blocked_jobs", &cache_status::blocked_jobs) + .def_readonly("queued_jobs", &cache_status::queued_jobs) + .def_readonly("peak_queued", &cache_status::peak_queued) + .def_readonly("pending_jobs", &cache_status::pending_jobs) + .def_readonly("num_jobs", &cache_status::num_jobs) + .def_readonly("num_read_jobs", &cache_status::num_read_jobs) + .def_readonly("num_write_jobs", &cache_status::num_write_jobs) + .def_readonly("arc_mru_size", &cache_status::arc_mru_size) + .def_readonly("arc_mru_ghost_size", &cache_status::arc_mru_ghost_size) + .def_readonly("arc_mfu_size", &cache_status::arc_mfu_size) + .def_readonly("arc_mfu_ghost_size", &cache_status::arc_mfu_ghost_size) ; - class_("session", no_init) + class_("session", no_init) .def( - init(( - arg("fingerprint")=fingerprint("LT",0,1,0,0) - , arg("flags")=session::start_default_features | session::add_default_plugins)) + init(( + arg("settings") + , arg("fingerprint")=fingerprint("LT",0,1,0,0) + , arg("flags")=lt::session::start_default_features | lt::session::add_default_plugins)) ) - .def("post_torrent_updates", allow_threads(&session::post_torrent_updates)) + .def( + init(( + arg("fingerprint")=fingerprint("LT",0,1,0,0) + , arg("flags")=lt::session::start_default_features | lt::session::add_default_plugins + , arg("alert_mask")=alert::error_notification)) + ) + .def("post_torrent_updates", allow_threads(<::session::post_torrent_updates)) +#ifndef TORRENT_NO_DEPRECATE .def( "listen_on", &listen_on , (arg("min"), "max", arg("interface") = (char const*)0, arg("flags") = 0) ) +#endif .def("outgoing_ports", &outgoing_ports) - .def("is_listening", allow_threads(&session::is_listening)) - .def("listen_port", allow_threads(&session::listen_port)) - .def("status", allow_threads(&session::status)) + .def("is_listening", allow_threads(<::session::is_listening)) + .def("listen_port", allow_threads(<::session::listen_port)) + .def("status", allow_threads(<::session::status)) #ifndef TORRENT_DISABLE_DHT .def("add_dht_node", add_dht_node) .def( "add_dht_router", &add_dht_router , (arg("router"), "port") ) - .def("is_dht_running", allow_threads(&session::is_dht_running)) - .def("set_dht_settings", allow_threads(&session::set_dht_settings)) - .def("start_dht", allow_threads(start_dht0)) - .def("stop_dht", allow_threads(&session::stop_dht)) + .def("is_dht_running", allow_threads(<::session::is_dht_running)) + .def("set_dht_settings", allow_threads(<::session::set_dht_settings)) #ifndef TORRENT_NO_DEPRECATE + .def("start_dht", allow_threads(start_dht0)) + .def("stop_dht", allow_threads(<::session::stop_dht)) .def("start_dht", allow_threads(start_dht1)) - .def("dht_state", allow_threads(&session::dht_state)) - .def("set_dht_proxy", allow_threads(&session::set_dht_proxy)) - .def("dht_proxy", allow_threads(&session::dht_proxy)) + .def("dht_state", allow_threads(<::session::dht_state)) + .def("set_dht_proxy", allow_threads(<::session::set_dht_proxy)) + .def("dht_proxy", allow_threads(<::session::dht_proxy)) #endif #endif .def("add_torrent", &add_torrent) @@ -681,27 +709,27 @@ void bind_session() #endif #endif .def("add_feed", &add_feed) - .def("remove_torrent", allow_threads(&session::remove_torrent), arg("option") = 0) + .def("remove_torrent", allow_threads(<::session::remove_torrent), arg("option") = 0) #ifndef TORRENT_NO_DEPRECATE - .def("set_local_download_rate_limit", allow_threads(&session::set_local_download_rate_limit)) - .def("local_download_rate_limit", allow_threads(&session::local_download_rate_limit)) + .def("set_local_download_rate_limit", allow_threads(<::session::set_local_download_rate_limit)) + .def("local_download_rate_limit", allow_threads(<::session::local_download_rate_limit)) - .def("set_local_upload_rate_limit", allow_threads(&session::set_local_upload_rate_limit)) - .def("local_upload_rate_limit", allow_threads(&session::local_upload_rate_limit)) + .def("set_local_upload_rate_limit", allow_threads(<::session::set_local_upload_rate_limit)) + .def("local_upload_rate_limit", allow_threads(<::session::local_upload_rate_limit)) - .def("set_download_rate_limit", allow_threads(&session::set_download_rate_limit)) - .def("download_rate_limit", allow_threads(&session::download_rate_limit)) + .def("set_download_rate_limit", allow_threads(<::session::set_download_rate_limit)) + .def("download_rate_limit", allow_threads(<::session::download_rate_limit)) - .def("set_upload_rate_limit", allow_threads(&session::set_upload_rate_limit)) - .def("upload_rate_limit", allow_threads(&session::upload_rate_limit)) + .def("set_upload_rate_limit", allow_threads(<::session::set_upload_rate_limit)) + .def("upload_rate_limit", allow_threads(<::session::upload_rate_limit)) - .def("set_max_uploads", allow_threads(&session::set_max_uploads)) - .def("set_max_connections", allow_threads(&session::set_max_connections)) - .def("max_connections", allow_threads(&session::max_connections)) - .def("set_max_half_open_connections", allow_threads(&session::set_max_half_open_connections)) - .def("num_connections", allow_threads(&session::num_connections)) - .def("set_settings", &session::set_settings) - .def("settings", &session::settings) + .def("set_max_uploads", allow_threads(<::session::set_max_uploads)) + .def("set_max_connections", allow_threads(<::session::set_max_connections)) + .def("max_connections", allow_threads(<::session::max_connections)) + .def("set_max_half_open_connections", allow_threads(<::session::set_max_half_open_connections)) + .def("num_connections", allow_threads(<::session::num_connections)) + .def("set_settings", <::session::set_settings) + .def("settings", <::session::settings) .def("get_settings", &session_get_settings) #else .def("settings", &session_get_settings) @@ -709,8 +737,8 @@ void bind_session() #endif .def("set_settings", &session_set_settings) #ifndef TORRENT_DISABLE_ENCRYPTION - .def("set_pe_settings", allow_threads(&session::set_pe_settings)) - .def("get_pe_settings", allow_threads(&session::get_pe_settings)) + .def("set_pe_settings", allow_threads(<::session::set_pe_settings)) + .def("get_pe_settings", allow_threads(<::session::get_pe_settings)) #endif #ifndef TORRENT_DISABLE_GEO_IP .def("load_asnum_db", &load_asnum_db) @@ -719,69 +747,72 @@ void bind_session() .def("load_state", &load_state) .def("save_state", &save_state, (arg("entry"), arg("flags") = 0xffffffff)) #ifndef TORRENT_NO_DEPRECATE - .def("set_severity_level", allow_threads(&session::set_severity_level)) - .def("set_alert_queue_size_limit", allow_threads(&session::set_alert_queue_size_limit)) + .def("set_severity_level", allow_threads(<::session::set_severity_level)) + .def("set_alert_queue_size_limit", allow_threads(<::session::set_alert_queue_size_limit)) + .def("set_alert_mask", allow_threads(<::session::set_alert_mask)) #endif - .def("set_alert_mask", allow_threads(&session::set_alert_mask)) .def("pop_alert", &pop_alert) .def("pop_alerts", &pop_alerts) .def("wait_for_alert", &wait_for_alert, return_internal_reference<>()) .def("add_extension", &add_extension) #ifndef TORRENT_NO_DEPRECATE - .def("set_peer_proxy", allow_threads(&session::set_peer_proxy)) - .def("set_tracker_proxy", allow_threads(&session::set_tracker_proxy)) - .def("set_web_seed_proxy", allow_threads(&session::set_web_seed_proxy)) - .def("peer_proxy", allow_threads(&session::peer_proxy)) - .def("tracker_proxy", allow_threads(&session::tracker_proxy)) - .def("web_seed_proxy", allow_threads(&session::web_seed_proxy)) + .def("set_peer_proxy", allow_threads(<::session::set_peer_proxy)) + .def("set_tracker_proxy", allow_threads(<::session::set_tracker_proxy)) + .def("set_web_seed_proxy", allow_threads(<::session::set_web_seed_proxy)) + .def("peer_proxy", allow_threads(<::session::peer_proxy)) + .def("tracker_proxy", allow_threads(<::session::tracker_proxy)) + .def("web_seed_proxy", allow_threads(<::session::web_seed_proxy)) #endif #if TORRENT_USE_I2P - .def("set_i2p_proxy", allow_threads(&session::set_i2p_proxy)) - .def("i2p_proxy", allow_threads(&session::i2p_proxy)) + .def("set_i2p_proxy", allow_threads(<::session::set_i2p_proxy)) + .def("i2p_proxy", allow_threads(<::session::i2p_proxy)) #endif - .def("set_proxy", allow_threads(&session::set_proxy)) - .def("proxy", allow_threads(&session::proxy)) - .def("start_upnp", &start_upnp) - .def("stop_upnp", allow_threads(&session::stop_upnp)) - .def("start_lsd", allow_threads(&session::start_lsd)) - .def("stop_lsd", allow_threads(&session::stop_lsd)) - .def("start_natpmp", &start_natpmp) - .def("stop_natpmp", allow_threads(&session::stop_natpmp)) - .def("set_ip_filter", allow_threads(&session::set_ip_filter)) - .def("get_ip_filter", allow_threads(&session::get_ip_filter)) - .def("find_torrent", allow_threads(&session::find_torrent)) + .def("set_proxy", allow_threads(<::session::set_proxy)) + .def("proxy", allow_threads(<::session::proxy)) + .def("set_ip_filter", allow_threads(<::session::set_ip_filter)) + .def("get_ip_filter", allow_threads(<::session::get_ip_filter)) + .def("find_torrent", allow_threads(<::session::find_torrent)) .def("get_torrents", &get_torrents) - .def("pause", allow_threads(&session::pause)) - .def("resume", allow_threads(&session::resume)) - .def("is_paused", allow_threads(&session::is_paused)) - .def("id", allow_threads(&session::id)) - .def("get_cache_status", allow_threads(&session::get_cache_status)) - .def("get_cache_info", get_cache_info) - .def("set_peer_id", allow_threads(&session::set_peer_id)) + .def("pause", allow_threads(<::session::pause)) + .def("resume", allow_threads(<::session::resume)) + .def("is_paused", allow_threads(<::session::is_paused)) + .def("id", allow_threads(<::session::id)) + .def("get_cache_info", &get_cache_info1, (arg("handle") = torrent_handle(), arg("flags") = 0)) +#ifndef TORRENT_NO_DEPRECATE + .def("start_upnp", &start_upnp) + .def("stop_upnp", allow_threads(<::session::stop_upnp)) + .def("start_lsd", allow_threads(<::session::start_lsd)) + .def("stop_lsd", allow_threads(<::session::stop_lsd)) + .def("start_natpmp", &start_natpmp) + .def("stop_natpmp", allow_threads(<::session::stop_natpmp)) + .def("get_cache_status", &get_cache_status) + .def("get_cache_info", &get_cache_info2) +#endif + .def("set_peer_id", allow_threads(<::session::set_peer_id)) ; - enum_("save_state_flags_t") - .value("save_settings", session::save_settings) - .value("save_dht_settings", session::save_dht_settings) - .value("save_dht_state", session::save_dht_state) - .value("save_i2p_proxy", session::save_i2p_proxy) - .value("save_encryption_settings", session:: save_encryption_settings) - .value("save_as_map", session::save_as_map) - .value("save_proxy", session::save_proxy) + enum_("save_state_flags_t") + .value("save_settings", lt::session::save_settings) + .value("save_dht_settings", lt::session::save_dht_settings) + .value("save_dht_state", lt::session::save_dht_state) + .value("save_i2p_proxy", lt::session::save_i2p_proxy) + .value("save_encryption_settings", lt::session:: save_encryption_settings) + .value("save_as_map", lt::session::save_as_map) + .value("save_proxy", lt::session::save_proxy) #ifndef TORRENT_NO_DEPRECATE - .value("save_dht_proxy", session::save_dht_proxy) - .value("save_peer_proxy", session::save_peer_proxy) - .value("save_web_proxy", session::save_web_proxy) - .value("save_tracker_proxy", session::save_tracker_proxy) + .value("save_dht_proxy", lt::session::save_dht_proxy) + .value("save_peer_proxy", lt::session::save_peer_proxy) + .value("save_web_proxy", lt::session::save_web_proxy) + .value("save_tracker_proxy", lt::session::save_tracker_proxy) #endif ; - enum_("listen_on_flags_t") #ifndef TORRENT_NO_DEPRECATE - .value("listen_reuse_address", session::listen_reuse_address) -#endif - .value("listen_no_system_port", session::listen_no_system_port) + enum_("listen_on_flags_t") + .value("listen_reuse_address", lt::session::listen_reuse_address) + .value("listen_no_system_port", lt::session::listen_no_system_port) ; +#endif class_("feed_handle") .def("update_feed", &feed_handle::update_feed) @@ -792,11 +823,23 @@ void bind_session() register_ptr_to_python >(); - def("high_performance_seed", high_performance_seed); - def("min_memory_usage", min_memory_usage); + typedef void (*mem_preset2)(settings_pack& s); + typedef void (*perf_preset2)(settings_pack& s); +#ifndef TORRENT_NO_DEPRECATE + typedef session_settings (*mem_preset1)(); + typedef session_settings (*perf_preset1)(); + + def("high_performance_seed", (perf_preset1)high_performance_seed); + def("min_memory_usage", (mem_preset1)min_memory_usage); scope().attr("create_metadata_plugin") = "metadata_transfer"; +#endif + + def("high_performance_seed", (perf_preset2)high_performance_seed); + def("min_memory_usage", (mem_preset2)min_memory_usage); + scope().attr("create_ut_metadata_plugin") = "ut_metadata"; scope().attr("create_ut_pex_plugin") = "ut_pex"; scope().attr("create_smart_ban_plugin") = "smart_ban"; } + diff --git a/bindings/python/src/session_settings.cpp b/bindings/python/src/session_settings.cpp index 4db41e316..1329f1dbf 100644 --- a/bindings/python/src/session_settings.cpp +++ b/bindings/python/src/session_settings.cpp @@ -10,6 +10,7 @@ using namespace libtorrent; void bind_session_settings() { +#ifndef TORRENT_NO_DEPRECATE class_("session_settings") .def_readwrite("user_agent", &session_settings::user_agent) .def_readwrite("tracker_completion_timeout", &session_settings::tracker_completion_timeout) @@ -42,7 +43,7 @@ void bind_session_settings() .def_readwrite("num_want", &session_settings::num_want) .def_readwrite("initial_picker_threshold", &session_settings::initial_picker_threshold) .def_readwrite("allowed_fast_set_size", &session_settings::allowed_fast_set_size) - .def_readwrite("suggest_mode", &session_settings::suggest_mode) + // this is no longer used .def_readwrite("max_queued_disk_bytes", &session_settings::max_queued_disk_bytes) .def_readwrite("max_queued_disk_bytes_low_watermark", &session_settings::max_queued_disk_bytes_low_watermark) .def_readwrite("handshake_timeout", &session_settings::handshake_timeout) @@ -54,10 +55,6 @@ void bind_session_settings() .def_readwrite("send_buffer_low_watermark", &session_settings::send_buffer_low_watermark) .def_readwrite("send_buffer_watermark", &session_settings::send_buffer_watermark) .def_readwrite("send_buffer_watermark_factor", &session_settings::send_buffer_watermark_factor) -#ifndef TORRENT_NO_DEPRECATE - .def_readwrite("auto_upload_slots", &session_settings::auto_upload_slots) - .def_readwrite("auto_upload_slots_rate_based", &session_settings::auto_upload_slots_rate_based) -#endif .def_readwrite("choking_algorithm", &session_settings::choking_algorithm) .def_readwrite("seed_choking_algorithm", &session_settings::seed_choking_algorithm) .def_readwrite("use_parole_mode", &session_settings::use_parole_mode) @@ -71,7 +68,6 @@ void bind_session_settings() .def_readwrite("disk_io_read_mode", &session_settings::disk_io_read_mode) .def_readwrite("coalesce_reads", &session_settings::coalesce_reads) .def_readwrite("coalesce_writes", &session_settings::coalesce_writes) - .def_readwrite("outgoing_ports", &session_settings::outgoing_ports) .def_readwrite("peer_tos", &session_settings::peer_tos) .def_readwrite("active_downloads", &session_settings::active_downloads) .def_readwrite("active_seeds", &session_settings::active_seeds) @@ -193,16 +189,6 @@ void bind_session_settings() .def_readwrite("use_disk_cache_pool", &session_settings::use_disk_cache_pool) ; - enum_("proxy_type") - .value("none", proxy_settings::none) - .value("socks4", proxy_settings::socks4) - .value("socks5", proxy_settings::socks5) - .value("socks5_pw", proxy_settings::socks5_pw) - .value("http", proxy_settings::http) - .value("http_pw", proxy_settings::http_pw) - .value("i2p_proxy", proxy_settings::i2p_proxy) - ; - enum_("disk_cache_algo_t") .value("lru", session_settings::lru) .value("largest_contiguous", session_settings::largest_contiguous) @@ -238,6 +224,18 @@ void bind_session_settings() .value("peer_proportional", session_settings::peer_proportional) ; +#endif + + enum_("proxy_type") + .value("none", proxy_settings::none) + .value("socks4", proxy_settings::socks4) + .value("socks5", proxy_settings::socks5) + .value("socks5_pw", proxy_settings::socks5_pw) + .value("http", proxy_settings::http) + .value("http_pw", proxy_settings::http_pw) + .value("i2p_proxy", proxy_settings::i2p_proxy) + ; + class_("proxy_settings") .def_readwrite("hostname", &proxy_settings::hostname) .def_readwrite("port", &proxy_settings::port) diff --git a/bindings/python/src/torrent_handle.cpp b/bindings/python/src/torrent_handle.cpp index c79655d15..c83813fe6 100644 --- a/bindings/python/src/torrent_handle.cpp +++ b/bindings/python/src/torrent_handle.cpp @@ -115,19 +115,34 @@ list get_peer_info(torrent_handle const& handle) void prioritize_pieces(torrent_handle& info, object o) { std::vector result; + std::vector > piece_list; try { object iter_obj = object( handle<>( PyObject_GetIter( o.ptr() ) )); while( 1 ) { object obj = extract( iter_obj.attr( "next" )() ); - result.push_back(extract( obj )); + extract val1(obj); + if (val1.check()) + { + result.push_back(val1); + continue; + } + extract > val2(obj); + if (val2.check()) + { + piece_list.push_back(val2); + continue; + } } } catch( error_already_set ) { PyErr_Clear(); - info.prioritize_pieces(result); + if (result.size()) + info.prioritize_pieces(result); + else + info.prioritize_pieces(piece_list); return; } } @@ -308,19 +323,21 @@ void connect_peer(torrent_handle& th, tuple ip, int source) #ifndef TORRENT_NO_DEPRECATE #if BOOST_VERSION > 104200 -boost::intrusive_ptr get_torrent_info(torrent_handle const& h) +boost::shared_ptr get_torrent_info(torrent_handle const& h) { - return boost::intrusive_ptr(&h.get_torrent_info()); + allow_threading_guard guard; + return h.torrent_file(); } #else -boost::intrusive_ptr get_torrent_info(torrent_handle const& h) +boost::shared_ptr get_torrent_info(torrent_handle const& h) { - // I can't figure out how to expose intrusive_ptr + // I can't figure out how to expose shared_ptr // as well as supporting mutable instances. So, this hack is better // than compilation errors. It seems to work on newer versions of boost though - return boost::intrusive_ptr(const_cast(&h.get_torrent_info())); + allow_threading_guard guard; + return boost::const_pointer_cast(h.torrent_file()); } #endif @@ -334,7 +351,8 @@ void set_peer_download_limit(torrent_handle& th, tuple const& ip, int limit) { th.set_peer_download_limit(tuple_to_endpoint(ip), limit); } -#endif + +#endif // TORRENT_NO_DEPRECAE void add_piece(torrent_handle& th, int piece, char const *data, int flags) { @@ -408,7 +426,7 @@ void bind_torrent_handle() #endif // deprecated #ifndef TORRENT_NO_DEPRECATE - .def("get_torrent_info", get_torrent_info) + .def("get_torrent_info", &get_torrent_info) .def("super_seeding", super_seeding0) .def("filter_piece", _(&torrent_handle::filter_piece)) .def("is_piece_filtered", _(&torrent_handle::is_piece_filtered)) diff --git a/bindings/python/src/torrent_info.cpp b/bindings/python/src/torrent_info.cpp index f0b2e195b..192775c54 100644 --- a/bindings/python/src/torrent_info.cpp +++ b/bindings/python/src/torrent_info.cpp @@ -3,9 +3,10 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) #include +#include #include -#include "libtorrent/intrusive_ptr_base.hpp" #include "libtorrent/session_settings.hpp" +#include "libtorrent/time.hpp" using namespace boost::python; using namespace libtorrent; @@ -186,14 +187,14 @@ void bind_torrent_info() .def_readwrite("size", &file_slice::size) ; - class_ >("torrent_info", no_init) + class_ >("torrent_info", no_init) #ifndef TORRENT_NO_DEPRECATE .def(init(arg("e"))) #endif .def(init((arg("info_hash"), arg("flags") = 0))) .def(init((arg("buffer"), arg("length"), arg("flags") = 0))) .def(init((arg("file"), arg("flags") = 0))) - .def(init((arg("ti"), arg("flags") = 0))) + .def(init((arg("ti")))) #if TORRENT_USE_WSTRING && !defined TORRENT_NO_DEPRECATE .def(init((arg("file"), arg("flags") = 0))) #endif @@ -268,7 +269,6 @@ void bind_torrent_info() .add_property("send_stats", &get_send_stats) .def("reset", &announce_entry::reset) - .def("failed", &announce_entry::failed, arg("retry_interval") = 0) .def("can_announce", &announce_entry::can_announce) .def("is_working", &announce_entry::is_working) .def("trim", &announce_entry::trim) @@ -282,8 +282,8 @@ void bind_torrent_info() ; #if BOOST_VERSION > 104200 - implicitly_convertible, boost::intrusive_ptr >(); - boost::python::register_ptr_to_python >(); + implicitly_convertible, boost::shared_ptr >(); + boost::python::register_ptr_to_python >(); #endif } diff --git a/bindings/python/src/torrent_status.cpp b/bindings/python/src/torrent_status.cpp index a891ea475..724b4bc3b 100644 --- a/bindings/python/src/torrent_status.cpp +++ b/bindings/python/src/torrent_status.cpp @@ -107,7 +107,9 @@ void bind_torrent_status() ; enum_("states") +#ifndef TORRENT_NO_DEPRECATE .value("queued_for_checking", torrent_status::queued_for_checking) +#endif .value("checking_files", torrent_status::checking_files) .value("downloading_metadata", torrent_status::downloading_metadata) .value("downloading", torrent_status::downloading) diff --git a/configure.ac b/configure.ac index 31bfa5f35..0cfa40e35 100644 --- a/configure.ac +++ b/configure.ac @@ -5,7 +5,7 @@ AC_PREREQ([2.63]) -AC_INIT([libtorrent-rasterbar],[1.0.0],[arvid@rasterbar.com], +AC_INIT([libtorrent-rasterbar],[1.1.0],[arvid@rasterbar.com], [libtorrent-rasterbar],[http://www.libtorrent.org]) AC_CONFIG_SRCDIR([src/torrent.cpp]) AC_CONFIG_AUX_DIR([build-aux]) @@ -41,7 +41,7 @@ dnl increment AGE, Otherwise AGE is reset to 0. If CURRENT has changed, dnl REVISION is set to 0, otherwise REVISION is incremented. dnl --------------------------------------------------------------------------- -m4_define([VERSION_INFO_CURRENT],[8]) +m4_define([VERSION_INFO_CURRENT],[9]) m4_define([VERSION_INFO_REVISION],[0]) m4_define([VERSION_INFO_AGE],[0]) INTERFACE_VERSION_INFO=VERSION_INFO_CURRENT:VERSION_INFO_REVISION:VERSION_INFO_AGE @@ -555,15 +555,6 @@ AS_CASE(["$ARG_ENABLE_PYTHON_BINDING"], AS_ECHO AS_ECHO "Checking for external libraries:" -AC_MSG_CHECKING([for FIEMAP support]) -AC_CHECK_HEADERS([linux/types.h]) -AC_CHECK_HEADERS([linux/fiemap.h], [], [], -[#ifdef HAVE_LINUX_TYPES_H -# include -#endif -]) - - AC_MSG_CHECKING([whether to link against system libgeoip]) #depends: $ac_arg_enable_geoip AS_CASE(["$ARG_WITH_LIBGEOIP"], ["yes"|"system"], [ @@ -641,6 +632,8 @@ AM_CONDITIONAL([WITH_OPENSSL], [test "x$ARG_ENABLE_ENCRYPTION" = "xyes" -o "x$AR AC_DEFINE([BOOST_ASIO_HASH_MAP_BUCKETS],[1021],[Define to fix a wrong behavior in boost 1.39.]) COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DBOOST_ASIO_HASH_MAP_BUCKETS=1021 " +COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -msse4.2 " + AC_DEFINE([BOOST_EXCEPTION_DISABLE],[1],[Define to disable the boost.exception features.]) COMPILETIME_OPTIONS="$COMPILETIME_OPTIONS -DBOOST_EXCEPTION_DISABLE " diff --git a/docs/building.html b/docs/building.html index 39b4cad99..e83276a3c 100644 --- a/docs/building.html +++ b/docs/building.html @@ -49,7 +49,7 @@ Author: Arvid Norberg, arvid@rasterbar.com Version: -1.0.0 +1.1.0
@@ -110,16 +110,16 @@ to step 3 (assuming you also have boost build installed).

Step 1: Download boost

You'll find boost here.

Extract the archive to some directory where you want it. For the sake of this -guide, let's assume you extract the package to c:\boost_1_34_0 (I'm using +guide, let's assume you extract the package to c:\boost_1_55_0 (I'm using a windows path in this example since if you're on linux/unix you're more likely -to use the autotools). You'll need at least version 1.34 of the boost library +to use the autotools). You'll need at least version 1.49 of the boost library in order to build libtorrent.

Step 2: Setup BBv2

First you need to build bjam. You do this by opening a terminal (In windows, run cmd). Change directory to -c:\boost_1_34_0\tools\jam\src. Then run the script called +c:\boost_1_55_0\tools\jam\src. Then run the script called build.bat or build.sh on a unix system. This will build bjam and place it in a directory starting with bin. and then have the name of your platform. Copy the bjam.exe (or bjam on a unix system) to a place @@ -133,20 +133,20 @@ set the environment variable BOOST_BUILD_PATH. bjam where it can find boost-build, your configuration file and all the toolsets (descriptions used by boost-build to know how to use different compilers on different platforms). Assuming the boost install path above, set -it to c:\boost_1_34_0\tools\build\v2.

+it to c:\boost_1_55_0\tools\build\v2.

To set an environment variable in windows, type for example:

-set BOOST_BUILD_PATH=c:\boost_1_34_0\tools\build\v2
+set BOOST_BUILD_PATH=c:\boost_1_55_0\tools\build\v2
 

In a terminal window.

The last thing to do to complete the setup of BBv2 is to modify your -user-config.jam file. It is located in c:\boost_1_34_0\tools\build\v2. +user-config.jam file. It is located in c:\boost_1_55_0\tools\build\v2. Depending on your platform and which compiler you're using, you should add a line for each compiler and compiler version you have installed on your system that you want to be able to use with BBv2. For example, if you're using -Microsoft Visual Studio 7.1 (2003), just add a line:

+Microsoft Visual Studio 12 (2013), just add a line:

-using msvc : 7.1 ;
+using msvc : 12.0 ;
 

If you use GCC, add the line:

@@ -173,7 +173,7 @@ using darwin : 4.0 : g++-4.0 ;
 

When building libtorrent, the Jamfile expects the environment variable BOOST_ROOT to be set to the boost installation directory. It uses this to find the boost libraries it depends on, so they can be built and their headers -files found. So, set this to c:\boost_1_34_0. You only need this if you're +files found. So, set this to c:\boost_1_55_0. You only need this if you're building against a source distribution of boost.

Then the only thing left is simply to invoke bjam. If you want to specify a specific toolset to use (compiler) you can just add that to the commandline. @@ -239,8 +239,8 @@ from a cygwin terminal, you'll have to run it from a BOOST_BUILD_PATH and BOOST_ROOT) should be in the typical unix-format (e.g. -/cygdrive/c/boost_1_34_0). In the windows environment, they should have the typical -windows format (c:/boost_1_34_0).

+/cygdrive/c/boost_1_55_0). In the windows environment, they should have the typical +windows format (c:/boost_1_55_0).

Note

In Jamfiles, spaces are separators. It's typically easiest to avoid spaces @@ -468,7 +468,7 @@ awareness except on windows). asserts

  • off - disable all asserts
  • -
  • peoduction - enable asserts in release +
  • production - enable asserts in release builds, but don't abort, just log them to extern char const* libtorrent_assert_log.
  • on - enable asserts in debug builds (this is @@ -507,6 +507,17 @@ parse_session_stats.py script (requires gnuplot)
+profile-calls +
    +
  • off - default. No additional call profiling.
  • +
  • on - Enable logging of stack traces of +calls into libtorrent that are blocking. On +session shutdown, a file blocking_calls.txt +is written with stack traces of blocking calls +ordered by the number of them.
  • +
+ +

The variant feature is implicit, which means you don't need to specify diff --git a/docs/building.rst b/docs/building.rst index 784a9156a..c0b43d255 100644 --- a/docs/building.rst +++ b/docs/building.rst @@ -3,7 +3,7 @@ libtorrent manual ================= :Author: Arvid Norberg, arvid@rasterbar.com -:Version: 1.0.0 +:Version: 1.1.0 .. contents:: Table of contents :depth: 2 @@ -75,9 +75,9 @@ You'll find boost here__. __ http://sourceforge.net/project/showfiles.php?group_id=7586&package_id=8041&release_id=619445 Extract the archive to some directory where you want it. For the sake of this -guide, let's assume you extract the package to ``c:\boost_1_34_0`` (I'm using +guide, let's assume you extract the package to ``c:\boost_1_55_0`` (I'm using a windows path in this example since if you're on linux/unix you're more likely -to use the autotools). You'll need at least version 1.34 of the boost library +to use the autotools). You'll need at least version 1.49 of the boost library in order to build libtorrent. @@ -86,7 +86,7 @@ Step 2: Setup BBv2 First you need to build ``bjam``. You do this by opening a terminal (In windows, run ``cmd``). Change directory to -``c:\boost_1_34_0\tools\jam\src``. Then run the script called +``c:\boost_1_55_0\tools\jam\src``. Then run the script called ``build.bat`` or ``build.sh`` on a unix system. This will build ``bjam`` and place it in a directory starting with ``bin.`` and then have the name of your platform. Copy the ``bjam.exe`` (or ``bjam`` on a unix system) to a place @@ -101,22 +101,22 @@ set the environment variable ``BOOST_BUILD_PATH``. This is the path that tells ``bjam`` where it can find boost-build, your configuration file and all the toolsets (descriptions used by boost-build to know how to use different compilers on different platforms). Assuming the boost install path above, set -it to ``c:\boost_1_34_0\tools\build\v2``. +it to ``c:\boost_1_55_0\tools\build\v2``. To set an environment variable in windows, type for example:: - set BOOST_BUILD_PATH=c:\boost_1_34_0\tools\build\v2 + set BOOST_BUILD_PATH=c:\boost_1_55_0\tools\build\v2 In a terminal window. The last thing to do to complete the setup of BBv2 is to modify your -``user-config.jam`` file. It is located in ``c:\boost_1_34_0\tools\build\v2``. +``user-config.jam`` file. It is located in ``c:\boost_1_55_0\tools\build\v2``. Depending on your platform and which compiler you're using, you should add a line for each compiler and compiler version you have installed on your system that you want to be able to use with BBv2. For example, if you're using -Microsoft Visual Studio 7.1 (2003), just add a line:: +Microsoft Visual Studio 12 (2013), just add a line:: - using msvc : 7.1 ; + using msvc : 12.0 ; If you use GCC, add the line:: @@ -148,7 +148,7 @@ Step 3: Building libtorrent When building libtorrent, the ``Jamfile`` expects the environment variable ``BOOST_ROOT`` to be set to the boost installation directory. It uses this to find the boost libraries it depends on, so they can be built and their headers -files found. So, set this to ``c:\boost_1_34_0``. You only need this if you're +files found. So, set this to ``c:\boost_1_55_0``. You only need this if you're building against a source distribution of boost. Then the only thing left is simply to invoke ``bjam``. If you want to specify @@ -220,8 +220,8 @@ from a cygwin terminal, you'll have to run it from a ``cmd`` terminal. The same cygwin, if you're building with gcc in cygwin you'll have to run it from a cygwin terminal. Also, make sure the paths are correct in the different environments. In cygwin, the paths (``BOOST_BUILD_PATH`` and ``BOOST_ROOT``) should be in the typical unix-format (e.g. -``/cygdrive/c/boost_1_34_0``). In the windows environment, they should have the typical -windows format (``c:/boost_1_34_0``). +``/cygdrive/c/boost_1_55_0``). In the windows environment, they should have the typical +windows format (``c:/boost_1_55_0``). .. note:: In Jamfiles, spaces are separators. It's typically easiest to avoid spaces @@ -367,7 +367,7 @@ Build features: | | awareness except on windows). | +--------------------------+----------------------------------------------------+ | ``asserts`` | * ``off`` - disable all asserts | -| | * ``peoduction`` - enable asserts in release | +| | * ``production`` - enable asserts in release | | | builds, but don't abort, just log them to | | | ``extern char const* libtorrent_assert_log``. | | | * ``on`` - enable asserts in debug builds (this is | @@ -392,6 +392,13 @@ Build features: | | is rotated every hour. It can be parsed by the | | | parse_session_stats.py script (requires gnuplot) | +--------------------------+----------------------------------------------------+ +| ``profile-calls`` | * ``off`` - default. No additional call profiling. | +| | * ``on`` - Enable logging of stack traces of | +| | calls into libtorrent that are blocking. On | +| | session shutdown, a file ``blocking_calls.txt`` | +| | is written with stack traces of blocking calls | +| | ordered by the number of them. | ++--------------------------+----------------------------------------------------+ .. _MaxMind: http://www.maxmind.com/app/api diff --git a/docs/contributing.html b/docs/contributing.html index 876b7b0a7..ee43cd1dd 100644 --- a/docs/contributing.html +++ b/docs/contributing.html @@ -49,7 +49,7 @@ Author: Arvid Norberg, arvid@rasterbar.com Version: -1.0.0 +1.1.0

diff --git a/docs/contributing.rst b/docs/contributing.rst index 56da6e1ad..84a829488 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -3,7 +3,7 @@ libtorrent manual ================= :Author: Arvid Norberg, arvid@rasterbar.com -:Version: 1.0.0 +:Version: 1.1.0 .. contents:: Table of contents :depth: 2 diff --git a/docs/dht_rss.html b/docs/dht_rss.html index 31dff2363..65ee6f369 100644 --- a/docs/dht_rss.html +++ b/docs/dht_rss.html @@ -49,7 +49,7 @@ Author: Arvid Norberg, arvid@rasterbar.com Version: -1.0.0 +1.1.0
diff --git a/docs/dht_rss.rst b/docs/dht_rss.rst index e63efc2be..168b478ff 100644 --- a/docs/dht_rss.rst +++ b/docs/dht_rss.rst @@ -3,7 +3,7 @@ BitTorrent extension for DHT RSS feeds ====================================== :Author: Arvid Norberg, arvid@rasterbar.com -:Version: 1.0.0 +:Version: 1.1.0 .. contents:: Table of contents :depth: 2 diff --git a/docs/dht_sec.html b/docs/dht_sec.html index b13d7611a..3d89f5441 100644 --- a/docs/dht_sec.html +++ b/docs/dht_sec.html @@ -49,7 +49,7 @@ Author: Arvid Norberg, arvid@rasterbar.com Version: -1.0.0 +1.1.0
diff --git a/docs/dht_sec.rst b/docs/dht_sec.rst index 150a6c03e..3cbef7d94 100644 --- a/docs/dht_sec.rst +++ b/docs/dht_sec.rst @@ -3,7 +3,7 @@ BitTorrent DHT security extension ================================= :Author: Arvid Norberg, arvid@rasterbar.com -:Version: 1.0.0 +:Version: 1.1.0 .. contents:: Table of contents :depth: 2 diff --git a/docs/dht_store.html b/docs/dht_store.html index 4a825f285..3b746ebf7 100644 --- a/docs/dht_store.html +++ b/docs/dht_store.html @@ -49,7 +49,7 @@ Author: Arvid Norberg, arvid@rasterbar.com Version: -1.0.0 +1.1.0
diff --git a/docs/dht_store.rst b/docs/dht_store.rst index d1f11a8f5..f1b1bb749 100644 --- a/docs/dht_store.rst +++ b/docs/dht_store.rst @@ -3,7 +3,7 @@ BitTorrent extension for arbitrary DHT store ============================================ :Author: Arvid Norberg, arvid@rasterbar.com -:Version: 1.0.0 +:Version: 1.1.0 .. contents:: Table of contents :depth: 2 diff --git a/docs/examples.html b/docs/examples.html index ff7de0014..6b8e95b8d 100644 --- a/docs/examples.html +++ b/docs/examples.html @@ -178,40 +178,40 @@ int main(int argc, char* argv[]) int main(int argc, char* argv[]) { using namespace libtorrent; -#if BOOST_VERSION < 103400 - namespace fs = boost::filesystem; - fs::path::default_name_check(fs::no_check); -#endif -if (argc != 2) -{ - std::cerr << "usage: ./simple_client torrent-file\n" - "to stop the client, press return.\n"; - return 1; -} - -#ifndef BOOST_NO_EXCEPTIONS - try -#endif + if (argc != 2) { - session s; - s.listen_on(std::make_pair(6881, 6889)); - add_torrent_params p; - p.save_path = "./"; - p.ti = new torrent_info(argv[1]); - s.add_torrent(p); + fputs("usage: ./simple_client torrent-file\n" + "to stop the client, press return.\n", stderr); + return 1; + } - // wait for the user to end - char a; - std::cin.unsetf(std::ios_base::skipws); - std::cin >> a; - } -#ifndef BOOST_NO_EXCEPTIONS - catch (std::exception& e) + session s; + error_code ec; + s.listen_on(std::make_pair(6881, 6889), ec); + if (ec) { - std::cout << e.what() << "\n"; + fprintf(stderr, "failed to open listen socket: %s\n", ec.message().c_str()); + return 1; } -#endif + add_torrent_params p; + p.save_path = "./"; + p.ti = boost::make_shared<torrent_info>(argv[1], ec); + if (ec) + { + fprintf(stderr, "%s\n", ec.message().c_str()); + return 1; + } + s.add_torrent(p, ec); + if (ec) + { + fprintf(stderr, "%s\n", ec.message().c_str()); + return 1; + } + + // wait for the user to end + char a; + scanf("%c\n", &a); return 0; }
diff --git a/docs/examples.rst b/docs/examples.rst index 1ad5aa012..731577d28 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -130,40 +130,40 @@ This is a simple client. It doesn't have much output to keep it simple:: int main(int argc, char* argv[]) { using namespace libtorrent; - #if BOOST_VERSION < 103400 - namespace fs = boost::filesystem; - fs::path::default_name_check(fs::no_check); - #endif - - if (argc != 2) - { - std::cerr << "usage: ./simple_client torrent-file\n" - "to stop the client, press return.\n"; - return 1; - } - - #ifndef BOOST_NO_EXCEPTIONS - try - #endif + + if (argc != 2) { - session s; - s.listen_on(std::make_pair(6881, 6889)); - add_torrent_params p; - p.save_path = "./"; - p.ti = new torrent_info(argv[1]); - s.add_torrent(p); - - // wait for the user to end - char a; - std::cin.unsetf(std::ios_base::skipws); - std::cin >> a; + fputs("usage: ./simple_client torrent-file\n" + "to stop the client, press return.\n", stderr); + return 1; } - #ifndef BOOST_NO_EXCEPTIONS - catch (std::exception& e) + + session s; + error_code ec; + s.listen_on(std::make_pair(6881, 6889), ec); + if (ec) { - std::cout << e.what() << "\n"; + fprintf(stderr, "failed to open listen socket: %s\n", ec.message().c_str()); + return 1; } - #endif + add_torrent_params p; + p.save_path = "./"; + p.ti = boost::make_shared(argv[1], ec); + if (ec) + { + fprintf(stderr, "%s\n", ec.message().c_str()); + return 1; + } + s.add_torrent(p, ec); + if (ec) + { + fprintf(stderr, "%s\n", ec.message().c_str()); + return 1; + } + + // wait for the user to end + char a; + scanf("%c\n", &a); return 0; } diff --git a/docs/features.html b/docs/features.html index 60a319bb1..b27595afe 100644 --- a/docs/features.html +++ b/docs/features.html @@ -49,7 +49,7 @@ Author: Arvid Norberg, arvid@rasterbar.com Version: -1.0.0 +1.1.0
@@ -122,6 +122,10 @@ ratio rather than downloading the torrent.
  • uses a separate disk I/O thread to not have the disk ever block on network or client interaction. (see threads).
  • +
  • uses asynchronous disk I/O when available (overlapped I/O, kaio, and posix-aio) +to make optimal use of disk bandwidth capacity
  • +
  • supports verifying the SHA-1 hash of pieces in multiple threads, to take full +advantage of multi core machines.
  • supports files > 2 gigabytes.
  • fast resume support, a way to get rid of the costly piece check at the start of a resumed torrent. Saves the storage state, piece_picker state diff --git a/docs/features.rst b/docs/features.rst index 1e1322565..711c616e1 100644 --- a/docs/features.rst +++ b/docs/features.rst @@ -3,7 +3,7 @@ libtorrent manual ================= :Author: Arvid Norberg, arvid@rasterbar.com -:Version: 1.0.0 +:Version: 1.1.0 .. contents:: Table of contents :depth: 2 @@ -61,6 +61,10 @@ disk management * uses a separate disk I/O thread to not have the disk ever block on network or client interaction. (see threads_). +* uses asynchronous disk I/O when available (overlapped I/O, kaio, and posix-aio) + to make optimal use of disk bandwidth capacity +* supports verifying the SHA-1 hash of pieces in multiple threads, to take full + advantage of multi core machines. * supports files > 2 gigabytes. * fast resume support, a way to get rid of the costly piece check at the start of a resumed torrent. Saves the storage state, piece_picker state diff --git a/docs/folx.png b/docs/folx.png deleted file mode 100644 index 521317d2376db8f56699430d06033c69e07badfe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14818 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 IM6N<$g0;LVI{*Lx diff --git a/docs/gen_reference_doc.py b/docs/gen_reference_doc.py index af69d56d2..01baf5651 100644 --- a/docs/gen_reference_doc.py +++ b/docs/gen_reference_doc.py @@ -31,6 +31,7 @@ symbols = {} preprocess_rst = \ { 'manual.rst':'manual-ref.rst', + 'settings.rst':'settings-ref.rst' } # some pre-defined sections from the main manual @@ -45,6 +46,9 @@ symbols = \ "metadata-from-peers_": "manual-ref.html#metadata-from-peers", "magnet-links_": "manual-ref.html#magnet-links", "ssl-torrents_": "manual-ref.html#ssl-torrents", + "dynamic-loading-of-torrent-files_": "manual-ref.html#dynamic-loading-of-torrent-files", + "session-statistics_": "manual-ref.html#session-statistics", + "peer-classes_": "manual-ref.html#peer-classes" } static_links = \ @@ -95,6 +99,7 @@ category_mapping = { 'thread.hpp': 'Utility', 'ip_filter.hpp': 'Filter', 'session_settings.hpp': 'Settings', + 'settings_pack.hpp': 'Settings', } category_fun_mapping = { @@ -747,7 +752,19 @@ def linkify_symbols(string): lines = string.split('\n') ret = [] in_literal = False + lno = 0 for l in lines: + lno += 1 + # don't touch headlines, i.e. lines whose + # next line entirely contains one of =, - or . + if (lno < len(lines)-1): next_line = lines[lno] + else: next_line = '' + + if len(next_line) > 0 and lines[lno].replace('=',''). \ + replace('-','').replace('.', '') == '': + ret.append(l) + continue + if l.startswith('|'): ret.append(l) continue @@ -812,7 +829,7 @@ def print_link(name, target): def dump_link_targets(): global link_targets - ret = '' + ret = '\n' for l in link_targets: ret += '__ %s\n' % l link_targets = [] diff --git a/docs/gen_settings_doc.py b/docs/gen_settings_doc.py new file mode 100644 index 000000000..17a18897c --- /dev/null +++ b/docs/gen_settings_doc.py @@ -0,0 +1,95 @@ +f = open('../include/libtorrent/settings_pack.hpp') + +out = open('settings.rst', 'w+') + +def print_field(str, width): + return '%s%s' % (str, ' ' * (width - len(str))) + +def render_section(names, description, type, default_values): + max_name_len = max(len(max(names, key=len)), len('name')) + max_type_len = max(len(type), len('type')) + max_val_len = max(len(max(default_values, key=len)), len('default')) + + # add link targets for the rest of the manual to reference + for n in names: + print >>out, '.. _%s:\n' % n + + if len(names) > 0: + print >>out, '.. raw:: html\n' + for n in names: + print >>out, '\t' % n + print >>out, '' + + separator = '+-' + ('-' * max_name_len) + '-+-' + ('-' * max_type_len) + '-+-' + ('-' * max_val_len) + '-+' + + # build a table for the settings, their type and default value + print >>out, separator + print >>out, '| %s | %s | %s |' % (print_field('name', max_name_len), print_field('type', max_type_len), print_field('default', max_val_len)) + print >>out, separator.replace('-', '=') + for i in range(len(names)): + print >>out, '| %s | %s | %s |' % (print_field(names[i], max_name_len), print_field(type, max_type_len), print_field(default_values[i], max_val_len)) + print >>out, separator + print >>out + print >>out, description + +mode = '' + +# parse out default values for settings +f2 = open('../src/settings_pack.cpp') +def_map = {} +for l in f2: + l = l.strip() + if not l.startswith('SET(') \ + and not l.startswith('SET_NOPREV(') \ + and not l.startswith('DEPRECATED_SET('): continue + + l = l.split('(')[1].split(',') + def_map[l[0]] = l[1].strip() + print '%s = %s' % (l[0], l[1].strip()) + +description = '' +names = [] + +for l in f: + if 'enum string_types' in l: mode = 'string' + if 'enum bool_types' in l: mode = 'bool' + if 'enum int_types' in l: mode = 'int' + if '#ifndef TORRENT_NO_DEPRECATE' in l: mode += 'skip' + if '#endif' in l: mode = mode[0:-4] + + if mode == '': continue + if mode[-4:] == 'skip': continue + + l = l.lstrip() + + if l == '' and len(names) > 0: + if description == '': + for n in names: + print 'WARNING: no description for "%s"' % n + else: + default_values = [] + for n in names: + default_values.append(def_map[n]) + render_section(names, description, mode, default_values) + description = '' + names = [] + + if l.startswith('};'): + mode = '' + continue + + if l.startswith('// '): + description += l[3:] + continue + + l = l.strip() + if l.endswith(','): + l = l[:-1] # strip trailing comma + if '=' in l: l = l.split('=')[0].strip() + if l.endswith('_internal'): continue + + names.append(l) + +out.close() +f.close() + diff --git a/docs/gen_todo.py b/docs/gen_todo.py index 4ac480b9e..43a87c18d 100644 --- a/docs/gen_todo.py +++ b/docs/gen_todo.py @@ -3,12 +3,12 @@ import os paths = ['src/*.cpp', 'src/kademlia/*.cpp', 'include/libtorrent/*.hpp', 'include/libtorrent/kademlia/*.hpp', 'include/libtorrent/aux_/*.hpp', 'include/libtorrent/extensions/*.hpp'] -os.system('(cd .. ; ctags %s 2>/dev/null)' % ' '.join(paths)) +os.system('ctags %s 2>/dev/null' % ' '.join(paths)) files = [] for p in paths: - files.extend(glob.glob(os.path.join('..', p))) + files.extend(glob.glob(p)) items = [] @@ -48,7 +48,7 @@ for f in files: line = line[1:].strip() items[-1]['todo'] = line prio = items[-1]['priority'] - if prio >= 0 and prio <= 3: priority_count[prio] += 1 + if prio >= 0 and prio <= 4: priority_count[prio] += 1 continue if state == '': @@ -112,14 +112,15 @@ out.write('''

    libtorrent todo-list

    +%d urgent %d important %d relevant %d feasible %d notes ''' % \ - (priority_count[3], priority_count[2], priority_count[1], priority_count[0])) + (priority_count[4], priority_count[3], priority_count[2], priority_count[1], priority_count[0])) -prio_colors = [ '#ccc', '#ccf', '#cfc', '#fcc', '#fdd'] +prio_colors = [ '#ccc', '#ccf', '#cfc', '#fcc', '#f44'] index = 0 for i in items: diff --git a/docs/hacking.html b/docs/hacking.html deleted file mode 100644 index 30aa2b929..000000000 --- a/docs/hacking.html +++ /dev/null @@ -1,210 +0,0 @@ - - - - - - -libtorrent hacking - - - - - - - - -
    -
    -
    - -
    - -
    -

    libtorrent hacking

    -
    --- - - - - - -
    Author:Arvid Norberg, arvid@rasterbar.com
    Version:1.0.0
    - -

    This describe some of the internals of libtorrent. If you're looking for -something to contribute, please take a look at the todo list.

    -
    -

    terminology

    -

    This section describes some of the terminology used throughout the -libtorrent source. Having a good understanding of some of these keywords -helps understanding what's going on.

    -

    A piece is a part of the data of a torrent that has a SHA-1 hash in -the .torrent file. Pieces are almost always a power of two in size, but not -necessarily. Each piece is plit up in blocks, which is a 16 kiB. A block -never spans two pieces. If a piece is smaller than 16 kiB or not divisible -by 16 kiB, there are blocks smaller than that.

    -

    16 kiB is a de-facto standard of the largest transfer unit in the bittorrent -protocol. Clients typically reject any request for larger pieces than this.

    -

    The piece picker is the part of a bittorrent client that is responsible for -the logic to determine which requests to send to peers. It doesn't actually -pick full pieces, but blocks (from pieces).

    -

    The file layout of a torrent is represented by file storage objects. This -class contains a list of all files in the torrent (in a well defined order), -the size of the pieces and implicitly the total size of the whole torrent and -number of pieces. The file storage determines the mapping from pieces -to files. This representation may be quite complex in order to keep it extremely -compact. This is useful to load very large torrents without exploding in memory -usage.

    -

    A torrent object represents all the state of swarm download. This includes -a piece picker, a list of peer connections, file storage (torrent file). One -important distiction is between a connected peer (peer_connection) and a peer -we just know about, and may have been connected to, and may connect to in the -future (policy::peer). The list of (not connected) peers may grow very large -if not limited (through tracker responses, DHT and peer exchange). This list -is typically limited to a few thousand peers.

    -

    The policy in libtorrent is somewhat poorly named. It was initially intended -to be a customization point where a client could define peer selection behavior -and unchoke logic. It didn't end up being though, and a more accurate name would -be peer_list. It really just maintains a potentially large list of known peers -for a swarm (not necessarily connected).

    -
    -
    -

    structure

    -

    This is the high level structure of libtorrent. Bold types are part of the public -interface:

    -
    -+=========+  pimpl     +-------------------+
    -| session | ---------> | aux::session_impl |
    -+=========+            +-------------------+
    -                m_torrents[]  |  |
    -+================+            |  |
    -| torrent_handle | ------+    |  |
    -+================+       |    |  |
    -                         |    |  | m_connections[]
    -                         |    |  |
    -                         |    |  +---------------------+
    -         m_picker        v    v                        |
    - +--------------+      +---------+---------+-- . .     |
    - | piece_picker | <--+-| torrent | torrent | to        |
    - +--------------+    | +---------+---------+-- . .     |
    -      m_torrent_file |      | m_connections[]          |
    - +==============+    |      |                          |
    - | torrent_info | <--+      v                          v
    - +==============+    |     +-----------------+-----------------+-- . .
    -            m_policy |     | peer_connection | peer_connection | pe
    - +--------+          |     +-----------------+-----------------+-- . .
    - | policy | <--------+      |             | m_socket
    - +--------+                 |             |
    -   | m_peers[]              |             v
    -   |                        |            +-----------------------+
    -   |                        |            | socket_type (variant) |
    -   v                        |            +-----------------------+
    -+--------------+            |
    -| policy::peer |            |
    -+--------------+            |
    -| policy::peer |            |
    -+--------------+ m_peer_info|
    -| policy::peer | <----------+
    -+--------------+
    -.              .
    -+ - - - - - - -+
    -
    -
    -

    session_impl

    -

    This is the session state object, containing all session global information, such as:

    -
    -
      -
    • the list of all torrents m_torrent.
    • -
    • the list of all peer connections m_connections.
    • -
    • the global rate limits m_settings.
    • -
    • the DHT state m_dht.
    • -
    • the port mapping state, m_upnp and m_natpmp.
    • -
    -
    -
    -
    -

    session

    -

    This is the public interface to the session. It implements pimpl (pointer to implementation) -in order to hide the internal representation of the session_impl object from the user and -make binary compatibility simpler to maintain.

    -
    -
    -

    torrent_handle

    -

    This is the public interface to a torrent. It holds a weak reference to the internal -torrent object and manipulates it by sending messages to the network thread.

    -
    -
    -

    torrent

    -
    -
    -

    peer_connection

    -
    -
    -

    policy

    -
    -
    -

    piece_picker

    -
    -
    -

    torrent_info

    -
    -
    -
    -

    threads

    -

    libtorrent starts 2 or 3 threads.

    -
    -
      -
    • The first thread is the main thread that will sit -idle in a kqueue() or epoll call most of the time. -This thread runs the main loop that will send and receive -data on all connections.
    • -
    • The second thread is the disk I/O thread. All disk read and write operations -are passed to this thread and messages are passed back to the main thread when -the operation completes. The disk thread also verifies the piece hashes.
    • -
    • The third and forth threads are spawned by asio on systems that don't support -non-blocking host name resolution to simulate non-blocking getaddrinfo().
    • -
    -
    -
    -
- - diff --git a/docs/hacking.rst b/docs/hacking.rst index b1e0cec14..91477f86e 100644 --- a/docs/hacking.rst +++ b/docs/hacking.rst @@ -3,7 +3,7 @@ libtorrent hacking ================== :Author: Arvid Norberg, arvid@rasterbar.com -:Version: 1.0.0 +:Version: 1.1.0 .. contents:: Table of contents :depth: 2 @@ -142,19 +142,85 @@ torrent_info threads ======= -libtorrent starts 2 or 3 threads. +libtorrent starts 3 to 5 threads. * The first thread is the main thread that will sit - idle in a ``kqueue()`` or ``epoll`` call most of the time. - This thread runs the main loop that will send and receive - data on all connections. + idle in a ``select()`` call most of the time. This thread runs the main loop + that will send and receive data on all connections. In reality it's typically + not actually in ``select()``, but in ``kqueue()``, ``epoll_wait()`` or ``poll``, + depending on operating system. * The second thread is the disk I/O thread. All disk read and write operations are passed to this thread and messages are passed back to the main thread when - the operation completes. The disk thread also verifies the piece hashes. + the operation completes. - * The third and forth threads are spawned by asio on systems that don't support - non-blocking host name resolution to simulate non-blocking getaddrinfo(). + * The third thread is the SHA-1 hash thread. By default there's only one hash thread, + but on multi-core machines downloading at very high rates, libtorrent can be configured + to start any number of hashing threads, to take full use of multi core systems. + (see ``session_settings::hashing_threads``). + * The fourth and fifth threads are spawned by asio on systems that don't support + asynchronous host name resolution, in order to simulate non-blocking ``getaddrinfo()``. +disk cache +========== + +The disk cache implements *ARC*, Adaptive Replacement Cache. This consists of a number of LRUs: + +1. lru L1 (recently used) +2. lru L1 ghost (recently evicted) +3. lru L2 (frequently used) +4. lru L2 ghost (recently evicted) +5. volatile read blocks +6. write cache (blocks waiting to be flushed to disk) + +.. parsed-literal:: + + <--- recently used frequently used ---> + +--------------+--------------+ +--------------+--------------+ + | L1 **ghost** | L1 | | L2 | L2 **ghost** | + +--------------+--------------+ +--------------+--------------+ + + <---------- cache_size ----------> + + <---------------------- 2 x cache_size ------------------------> + +These LRUs are stored in ``block_cache`` in an array ``m_lru``. + +The cache algorithm works like this:: + + if (L1->is_hit(piece)) { + L1->erase(piece); + L2->push_back(piece); + } else if (L2->is_hit(piece)) { + L2->erase(piece); + L2->push_back(page); + } else if (L1->size() == cache_size) { + L1->pop_front(); + L1->push_back(piece); + } else { + if (L1->size() + L2->size() == 2*chache_size) { + L2->pop_front(); + } + L1->push_back(piece); + } + +It's a bit more complicated since within L1 and L2 in this pseudo code +have to separate the ghost entries and the in-cache entries. + +Note that the most recently used and more frequently used pieces are at +the *back* of the lists. Iterating over a list gives you low priority pieces +first. + +In libtorrent pieces are cached, not individual blocks, a single peer would +typically trigger many cache hits when downloading a piece. Since ARC is +sensitive to extra cache hits (a piece is moved to L2 the second time it's +hit) libtorrent only move the cache entry on cache hits when it's hit by +another peer than the last peer that hit it. + +Another difference compared to the ARC paper is that libtorrent caches pieces, +which aren't necessarily fully allocated. This means the real cache size is +specified in number of blocks, not pieces, so there's not clear number of pieces +to keep in the ghost lists. There's an ``m_num_arc_pieces`` member in ``block_cache`` +that defines the *arc cache size*, in pieces, rather than blocks. diff --git a/docs/makefile b/docs/makefile index e5432888f..29132a2c4 100644 --- a/docs/makefile +++ b/docs/makefile @@ -52,6 +52,15 @@ epub: $(TARGETS:=.epub) $(FIGURES:=.png) all: html +settings.rst: ../include/libtorrent/settings_pack.hpp + python gen_settings_doc.py + +#stats_counters.rst: ../src/session_stats.cpp +# python gen_stats_doc.py + +manual.rst: settings.rst stats_counters.rst + touch manual.rst + troubleshooting_thumb.png: troubleshooting.png convert troubleshooting.png -resize 800x800 troubleshooting_thumb.png @@ -61,7 +70,7 @@ troubleshooting.png: troubleshooting.dot todo.html:gen_todo.py ../src/*.cpp ../include/libtorrent/*.hpp python gen_todo.py -$(REFERENCE_TARGETS:=.rst):gen_reference_doc.py ../include/libtorrent/*.hpp ../include/libtorrent/kademlia/*.hpp manual.rst +$(REFERENCE_TARGETS:=.rst):gen_reference_doc.py ../include/libtorrent/*.hpp ../include/libtorrent/kademlia/*.hpp manual.rst settings.rst python gen_reference_doc.py %.epub:%.rst @@ -83,5 +92,5 @@ $(REFERENCE_TARGETS:=.rst):gen_reference_doc.py ../include/libtorrent/*.hpp ../i cp $@ $(WEB_PATH)/$@ clean: - rm -f $(TARGETS:=.html) $(TARGETS:=.pdf) todo.html reference*.html reference*.rst + rm -f $(TARGETS:=.html) $(TARGETS:=.pdf) settings.rst todo.html reference*.html reference*.rst diff --git a/docs/manual-ref.html b/docs/manual-ref.html deleted file mode 100644 index f653fca4a..000000000 --- a/docs/manual-ref.html +++ /dev/null @@ -1,960 +0,0 @@ - - - - - - -libtorrent API Documentation - - - - - - - - -
-
-
- -
- -
-

libtorrent API Documentation

- --- - - - - - -
Author:Arvid Norberg, arvid@rasterbar.com
Version:1.0.0
- -
-

overview

-

The interface of libtorrent consists of a few classes. The main class is -the session, it contains the main loop that serves all torrents.

-

The basic usage is as follows:

- -

Each class and function is described in this manual.

-

For a description on how to create torrent files, see create_torrent.

-
-
-

things to keep in mind

-

A common problem developers are facing is torrents stopping without explanation. -Here is a description on which conditions libtorrent will stop your torrents, -how to find out about it and what to do about it.

-

Make sure to keep track of the paused state, the error state and the upload -mode of your torrents. By default, torrents are auto-managed, which means -libtorrent will pause them, unpause them, scrape them and take them out -of upload-mode automatically.

-

Whenever a torrent encounters a fatal error, it will be stopped, and the -torrent_status::error will describe the error that caused it. If a torrent -is auto managed, it is scraped periodically and paused or resumed based on -the number of downloaders per seed. This will effectively seed torrents that -are in the greatest need of seeds.

-

If a torrent hits a disk write error, it will be put into upload mode. This -means it will not download anything, but only upload. The assumption is that -the write error is caused by a full disk or write permission errors. If the -torrent is auto-managed, it will periodically be taken out of the upload -mode, trying to write things to the disk again. This means torrent will recover -from certain disk errors if the problem is resolved. If the torrent is not -auto managed, you have to call set_upload_mode() to turn -downloading back on again.

-
-
-

network primitives

-

There are a few typedefs in the libtorrent namespace which pulls -in network types from the asio namespace. These are:

-
-typedef asio::ip::address address;
-typedef asio::ip::address_v4 address_v4;
-typedef asio::ip::address_v6 address_v6;
-using asio::ip::tcp;
-using asio::ip::udp;
-
-

These are declared in the <libtorrent/socket.hpp> header.

-

The using statements will give easy access to:

-
-tcp::endpoint
-udp::endpoint
-
-

Which are the endpoint types used in libtorrent. An endpoint is an address -with an associated port.

-

For documentation on these types, please refer to the asio documentation.

-
-
-

exceptions

-

Many functions in libtorrent have two versions, one that throws exceptions on -errors and one that takes an error_code reference which is filled with the -error code on errors.

-

There is one exception class that is used for errors in libtorrent, it is based -on boost.system's error_code class to carry the error code.

-

For more information, see libtorrent_exception and error_code_enum.

-
-

translating error codes

-

The error_code::message() function will typically return a localized error string, -for system errors. That is, errors that belong to the generic or system category.

-

Errors that belong to the libtorrent error category are not localized however, they -are only available in english. In order to translate libtorrent errors, compare the -error category of the error_code object against libtorrent::get_libtorrent_category(), -and if matches, you know the error code refers to the list above. You can provide -your own mapping from error code to string, which is localized. In this case, you -cannot rely on error_code::message() to generate your strings.

-

The numeric values of the errors are part of the API and will stay the same, although -new error codes may be appended at the end.

-

Here's a simple example of how to translate error codes:

-
-std::string error_code_to_string(boost::system::error_code const& ec)
-{
-        if (ec.category() != libtorrent::get_libtorrent_category())
-        {
-                return ec.message();
-        }
-        // the error is a libtorrent error
-
-        int code = ec.value();
-        static const char const* swedish[] =
-        {
-                "inget fel",
-                "en fil i torrenten kolliderar med en fil fran en annan torrent",
-                "hash check misslyckades",
-                "torrentfilen ar inte en dictionary",
-                "'info'-nyckeln saknas eller ar korrupt i torrentfilen",
-                "'info'-faltet ar inte en dictionary",
-                "'piece length' faltet saknas eller ar korrupt i torrentfilen",
-                "torrentfilen saknar namnfaltet",
-                "ogiltigt namn i torrentfilen (kan vara en attack)",
-                // ... more strings here
-        };
-
-        // use the default error string in case we don't have it
-        // in our translated list
-        if (code < 0 || code >= sizeof(swedish)/sizeof(swedish[0]))
-                return ec.message();
-
-        return swedish[code];
-}
-
-
-
- -
-

queuing

-

libtorrent supports queuing. Which means it makes sure that a limited number of -torrents are being downloaded at any given time, and once a torrent is completely -downloaded, the next in line is started.

-

Torrents that are auto managed are subject to the queuing and the active -torrents limits. To make a torrent auto managed, set auto_managed to true -when adding the torrent (see async_add_torrent() and add_torrent()).

-

The limits of the number of downloading and seeding torrents are controlled via -active_downloads, active_seeds and active_limit in -session_settings. These limits takes non auto managed torrents into account as -well. If there are more non-auto managed torrents being downloaded than the -active_downloads setting, any auto managed torrents will be queued until -torrents are removed so that the number drops below the limit.

-

The default values are 8 active downloads and 5 active seeds.

-

At a regular interval, torrents are checked if there needs to be any -re-ordering of which torrents are active and which are queued. This interval -can be controlled via auto_manage_interval in session_settings. It defaults -to every 30 seconds.

-

For queuing to work, resume data needs to be saved and restored for all -torrents. See save_resume_data().

-
-

downloading

-

Torrents that are currently being downloaded or incomplete (with bytes still to -download) are queued. The torrents in the front of the queue are started to be -actively downloaded and the rest are ordered with regards to their queue -position. Any newly added torrent is placed at the end of the queue. Once a -torrent is removed or turns into a seed, its queue position is -1 and all -torrents that used to be after it in the queue, decreases their position in -order to fill the gap.

-

The queue positions are always in a sequence without any gaps.

-

Lower queue position means closer to the front of the queue, and will be -started sooner than torrents with higher queue positions.

-

To query a torrent for its position in the queue, or change its position, see: -queue_position(), queue_position_up(), queue_position_down(), -queue_position_top() and queue_position_bottom().

-
-
-

seeding

-

Auto managed seeding torrents are rotated, so that all of them are allocated a -fair amount of seeding. Torrents with fewer completed seed cycles are -prioritized for seeding. A seed cycle is completed when a torrent meets either -the share ratio limit (uploaded bytes / downloaded bytes), the share time ratio -(time seeding / time downloaing) or seed time limit (time seeded).

-

The relevant settings to control these limits are share_ratio_limit, -seed_time_ratio_limit and seed_time_limit in session_settings.

-
-
-
-

fast resume

-

The fast resume mechanism is a way to remember which pieces are downloaded -and where they are put between sessions. You can generate fast resume data by -calling save_resume_data() on torrent_handle. You can -then save this data to disk and use it when resuming the torrent. libtorrent -will not check the piece hashes then, and rely on the information given in the -fast-resume data. The fast-resume data also contains information about which -blocks, in the unfinished pieces, were downloaded, so it will not have to -start from scratch on the partially downloaded pieces.

-

To use the fast-resume data you simply give it to async_add_torrent() and -add_torrent(), and it will skip the time consuming checks. It may have to do -the checking anyway, if the fast-resume data is corrupt or doesn't fit the -storage for that torrent, then it will not trust the fast-resume data and just -do the checking.

-
-

file format

-

The file format is a bencoded dictionary containing the following fields:

- ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
file-formatstring: "libtorrent resume file"
file-versioninteger: 1
info-hashstring, the info hash of the torrent this data is saved for.
blocks per pieceinteger, the number of blocks per piece. Must be: piece_size -/ (16 * 1024). Clamped to be within the range [1, 256]. It -is the number of blocks per (normal sized) piece. Usually -each block is 16 * 1024 bytes in size. But if piece size is -greater than 4 megabytes, the block size will increase.
piecesA string with piece flags, one character per piece. -Bit 1 means we have that piece. -Bit 2 means we have verified that this piece is correct. -This only applies when the torrent is in seed_mode.
slots

list of integers. The list maps slots to piece indices. It -tells which piece is on which slot. If piece index is -2 it -means it is free, that there's no piece there. If it is -1, -means the slot isn't allocated on disk yet. The pieces have -to meet the following requirement:

-

If there's a slot at the position of the piece index, -the piece must be located in that slot.

-
total_uploadedinteger. The number of bytes that have been uploaded in -total for this torrent.
total_downloadedinteger. The number of bytes that have been downloaded in -total for this torrent.
active_timeinteger. The number of seconds this torrent has been active. -i.e. not paused.
seeding_timeinteger. The number of seconds this torrent has been active -and seeding.
num_seedsinteger. An estimate of the number of seeds on this torrent -when the resume data was saved. This is scrape data or based -on the peer list if scrape data is unavailable.
num_downloadersinteger. An estimate of the number of downloaders on this -torrent when the resume data was last saved. This is used as -an initial estimate until we acquire up-to-date scrape info.
upload_rate_limitinteger. In case this torrent has a per-torrent upload rate -limit, this is that limit. In bytes per second.
download_rate_limitinteger. The download rate limit for this torrent in case -one is set, in bytes per second.
max_connectionsinteger. The max number of peer connections this torrent -may have, if a limit is set.
max_uploadsinteger. The max number of unchoked peers this torrent may -have, if a limit is set.
seed_modeinteger. 1 if the torrent is in seed mode, 0 otherwise.
file_prioritylist of integers. One entry per file in the torrent. Each -entry is the priority of the file with the same index.
piece_prioritystring of bytes. Each byte is interpreted as an integer and -is the priority of that piece.
auto_managedinteger. 1 if the torrent is auto managed, otherwise 0.
sequential_downloadinteger. 1 if the torrent is in sequential download mode, -0 otherwise.
pausedinteger. 1 if the torrent is paused, 0 otherwise.
trackerslist of lists of strings. The top level list lists all -tracker tiers. Each second level list is one tier of -trackers.
mapped_fileslist of strings. If any file in the torrent has been -renamed, this entry contains a list of all the filenames. -In the same order as in the torrent file.
url-listlist of strings. List of url-seed URLs used by this torrent. -The urls are expected to be properly encoded and not contain -any illegal url characters.
httpseedslist of strings. List of httpseed URLs used by this torrent. -The urls are expected to be properly encoded and not contain -any illegal url characters.
merkle treestring. In case this torrent is a merkle torrent, this is a -string containing the entire merkle tree, all nodes, -including the root and all leaves. The tree is not -necessarily complete, but complete enough to be able to send -any piece that we have, indicated by the have bitmask.
save_pathstring. The save path where this torrent was saved. This is -especially useful when moving torrents with move_storage() -since this will be updated.
peers

list of dictionaries. Each dictionary has the following -layout:

- ---- - - - - - - - - -
ipstring, the ip address of the peer. This is -not a binary representation of the ip -address, but the string representation. It -may be an IPv6 string or an IPv4 string.
portinteger, the listen port of the peer
-

These are the local peers we were connected to when this -fast-resume data was saved.

-
unfinished

list of dictionaries. Each dictionary represents an -piece, and has the following layout:

- ---- - - - - - - - - - - - -
pieceinteger, the index of the piece this entry -refers to.
bitmaskstring, a binary bitmask representing the -blocks that have been downloaded in this -piece.
adler32The adler32 checksum of the data in the -blocks specified by bitmask.
-
file sizeslist where each entry corresponds to a file in the file list -in the metadata. Each entry has a list of two values, the -first value is the size of the file in bytes, the second -is the time stamp when the last time someone wrote to it. -This information is used to compare with the files on disk. -All the files must match exactly this information in order -to consider the resume data as current. Otherwise a full -re-check is issued.
allocationThe allocation mode for the storage. Can be either full -or compact. If this is full, the file sizes and -timestamps are disregarded. Pieces are assumed not to have -moved around even if the files have been modified after the -last resume data checkpoint.
-
-
-
-

storage allocation

-

There are two modes in which storage (files on disk) are allocated in libtorrent.

-
    -
  1. The traditional full allocation mode, where the entire files are filled up -with zeros before anything is downloaded. Files are allocated on demand, the -first time anything is written to them. The main benefit of this mode is that -it avoids creating heavily fragmented files.
  2. -
  3. The sparse allocation, sparse files are used, and pieces are downloaded -directly to where they belong. This is the recommended (and default) mode.
  4. -
-

In previous versions of libtorrent, a 3rd mode was supported, compact -allocation. Support for this is deprecated and will be removed in future -versions of libtorrent. It's still described in here for completeness.

-

The allocation mode is selected when a torrent is started. It is passed as an -argument to session::add_torrent() or session::async_add_torrent().

-

The decision to use full allocation or compact allocation typically depends on -whether any files have priority 0 and if the filesystem supports sparse files.

-
-

sparse allocation

-

On filesystems that supports sparse files, this allocation mode will only use -as much space as has been downloaded.

-

The main drawback of this mode is that it may create heavily fragmented files.

-
-
    -
  • It does not require an allocation pass on startup.
  • -
-
-
-
-

full allocation

-

When a torrent is started in full allocation mode, the disk-io thread -will make sure that the entire storage is allocated, and fill any gaps with zeros. -It will of course still check for existing pieces and fast resume data. The main -drawbacks of this mode are:

-
-
    -
  • It may take longer to start the torrent, since it will need to fill the files -with zeroes. This delay is linear to the size of the download.
  • -
  • The download may occupy unnecessary disk space between download sessions.
  • -
  • Disk caches usually perform poorly with random access to large files -and may slow down the download some.
  • -
-
-

The benefits of this mode are:

-
-
    -
  • Downloaded pieces are written directly to their final place in the files and -the total number of disk operations will be fewer and may also play nicer to -filesystems' file allocation, and reduce fragmentation.
  • -
  • No risk of a download failing because of a full disk during download, once -all files have been created.
  • -
-
-
-
-

compact allocation

-
-

Note

-

Note that support for compact allocation is deprecated in libttorrent, and will -be removed in future versions.

-
-

The compact allocation will only allocate as much storage as it needs to keep -the pieces downloaded so far. This means that pieces will be moved around to be -placed at their final position in the files while downloading (to make sure the -completed download has all its pieces in the correct place). So, the main -drawbacks are:

-
-
    -
  • More disk operations while downloading since pieces are moved around.
  • -
  • Potentially more fragmentation in the filesystem.
  • -
  • Cannot be used while having files with priority 0.
  • -
-
-

The benefits though, are:

-
-
    -
  • No startup delay, since the files don't need allocating.
  • -
  • The download will not use unnecessary disk space.
  • -
  • Disk caches perform much better than in full allocation and raises the -download speed limit imposed by the disk.
  • -
  • Works well on filesystems that don't support sparse files.
  • -
-
-

The algorithm that is used when allocating pieces and slots isn't very -complicated. For the interested, a description follows.

-

storing a piece:

-
    -
  1. let A be a newly downloaded piece, with index n.
  2. -
  3. let s be the number of slots allocated in the file we're -downloading to. (the number of pieces it has room for).
  4. -
  5. if n >= s then allocate a new slot and put the piece there.
  6. -
  7. if n < s then allocate a new slot, move the data at -slot n to the new slot and put A in slot n.
  8. -
-

allocating a new slot:

-
    -
  1. if there's an unassigned slot (a slot that doesn't -contain any piece), return that slot index.
  2. -
  3. append the new slot at the end of the file (or find an unused slot).
  4. -
  5. let i be the index of newly allocated slot
  6. -
  7. if we have downloaded piece index i already (to slot j) then
      -
    1. move the data at slot j to slot i.
    2. -
    3. return slot index j as the newly allocated free slot.
    4. -
    -
  8. -
  9. return i as the newly allocated slot.
  10. -
-
-
-
-

extensions

-

These extensions all operates within the extension protocol. The name of the -extension is the name used in the extension-list packets, and the payload is -the data in the extended message (not counting the length-prefix, message-id -nor extension-id).

-

Note that since this protocol relies on one of the reserved bits in the -handshake, it may be incompatible with future versions of the mainline -bittorrent client.

-

These are the extensions that are currently implemented.

-
-

metadata from peers

-

Extension name: "LT_metadata"

-

This extension is deprecated in favor of the more widely supported -ut_metadata extension, see BEP 9. The point with this extension is that -you don't have to distribute the metadata (.torrent-file) separately. The -metadata can be distributed through the bittorrent swarm. The only thing you -need to download such a torrent is the tracker url and the info-hash of the -torrent.

-

It works by assuming that the initial seeder has the metadata and that the -metadata will propagate through the network as more peers join.

-

There are three kinds of messages in the metadata extension. These packets are -put as payload to the extension message. The three packets are:

-
-
    -
  • request metadata
  • -
  • metadata
  • -
  • don't have metadata
  • -
-
-

request metadata:

- ----- - - - - - - - - - - - - - - - - - - - - -
sizenamedescription
uint8_tmsg_typeDetermines the kind of message this is -0 means 'request metadata'
uint8_tstartThe start of the metadata block that -is requested. It is given in 256:ths -of the total size of the metadata, -since the requesting client don't know -the size of the metadata.
uint8_tsizeThe size of the metadata block that is -requested. This is also given in -256:ths of the total size of the -metadata. The size is given as size-1. -That means that if this field is set -0, the request wants one 256:th of the -metadata.
-

metadata:

- ----- - - - - - - - - - - - - - - - - - - - - - - - - -
sizenamedescription
uint8_tmsg_type1 means 'metadata'
int32_ttotal_sizeThe total size of the metadata, given -in number of bytes.
int32_toffsetThe offset of where the metadata block -in this message belongs in the final -metadata. This is given in bytes.
uint8_t[]metadataThe actual metadata block. The size of -this part is given implicit by the -length prefix in the bittorrent -protocol packet.
-

Don't have metadata:

- ----- - - - - - - - - - - - - -
sizenamedescription
uint8_tmsg_type2 means 'I don't have metadata'. -This message is sent as a reply to a -metadata request if the the client -doesn't have any metadata.
-
-
-

dont_have

-

Extension name: "lt_dont_have"

-

The dont_have extension message is used to tell peers that the client no -longer has a specific piece. The extension message should be advertised in the -m dictionary as lt_dont_have. The message format mimics the regular -HAVE bittorrent message.

-

Just like all extension messages, the first 2 bytes in the mssage itself are 20 -(the bittorrent extension message) and the message ID assigned to this -extension in the m dictionary in the handshake.

- ----- - - - - - - - - - - - - -
sizenamedescription
uint32_tpieceindex of the piece the peer no longer -has.
-

The length of this message (including the extension message prefix) is 6 bytes, -i.e. one byte longer than the normal HAVE message, because of the extension -message wrapping.

-
-
-

HTTP seeding

-

There are two kinds of HTTP seeding. One with that assumes a smart (and polite) -client and one that assumes a smart server. These are specified in BEP 19 -and BEP 17 respectively.

-

libtorrent supports both. In the libtorrent source code and API, BEP 19 urls -are typically referred to as url seeds and BEP 17 urls are typically referred -to as HTTP seeds.

-

The libtorrent implementation of BEP 19 assumes that, if the URL ends with a -slash ('/'), the filename should be appended to it in order to request pieces -from that file. The way this works is that if the torrent is a single-file -torrent, only that filename is appended. If the torrent is a multi-file -torrent, the torrent's name '/' the file name is appended. This is the same -directory structure that libtorrent will download torrents into.

-
-
-
-

piece picker

-

The piece picker in libtorrent has the following features:

-
    -
  • rarest first
  • -
  • sequential download
  • -
  • random pick
  • -
  • reverse order picking
  • -
  • parole mode
  • -
  • prioritize partial pieces
  • -
  • prefer whole pieces
  • -
  • piece affinity by speed category
  • -
  • piece priorities
  • -
-
-

internal representation

-

It is optimized by, at all times, keeping a list of pieces ordered by rarity, -randomly shuffled within each rarity class. This list is organized as a single -vector of contigous memory in RAM, for optimal memory locality and to eliminate -heap allocations and frees when updating rarity of pieces.

-

Expensive events, like a peer joining or leaving, are evaluated lazily, since -it's cheaper to rebuild the whole list rather than updating every single piece -in it. This means as long as no blocks are picked, peers joining and leaving is -no more costly than a single peer joining or leaving. Of course the special -cases of peers that have all or no pieces are optimized to not require -rebuilding the list.

-
-
-

picker strategy

-

The normal mode of the picker is of course rarest first, meaning pieces that -few peers have are preferred to be downloaded over pieces that more peers have. -This is a fundamental algorithm that is the basis of the performance of -bittorrent. However, the user may set the piece picker into sequential download -mode. This mode simply picks pieces sequentially, always preferring lower piece -indices.

-

When a torrent starts out, picking the rarest pieces means increased risk that -pieces won't be completed early (since there are only a few peers they can be -downloaded from), leading to a delay of having any piece to offer to other -peers. This lack of pieces to trade, delays the client from getting started -into the normal tit-for-tat mode of bittorrent, and will result in a long -ramp-up time. The heuristic to mitigate this problem is to, for the first few -pieces, pick random pieces rather than rare pieces. The threshold for when to -leave this initial picker mode is determined by -session_settings::initial_picker_threshold.

-
-
-

reverse order

-

An orthogonal setting is reverse order, which is used for snubbed peers. -Snubbed peers are peers that appear very slow, and might have timed out a piece -request. The idea behind this is to make all snubbed peers more likely to be -able to do download blocks from the same piece, concentrating slow peers on as -few pieces as possible. The reverse order means that the most common pieces are -picked, instead of the rarest pieces (or in the case of sequential download, -the last pieces, intead of the first).

-
-
-

parole mode

-

Peers that have participated in a piece that failed the hash check, may be put -in parole mode. This means we prefer downloading a full piece from this -peer, in order to distinguish which peer is sending corrupt data. Whether to do -this is or not is controlled by session_settings::use_parole_mode.

-

In parole mode, the piece picker prefers picking one whole piece at a time for -a given peer, avoiding picking any blocks from a piece any other peer has -contributed to (since that would defeat the purpose of parole mode).

-
-
-

prioritize partial pieces

-

This setting determines if partially downloaded or requested pieces should -always be preferred over other pieces. The benefit of doing this is that the -number of partial pieces is minimized (and hence the turn-around time for -downloading a block until it can be uploaded to others is minimized). It also -puts less stress on the disk cache, since fewer partial pieces need to be kept -in the cache. Whether or not to enable this is controlled by -session_settings::prioritize_partial_pieces.

-

The main benefit of not prioritizing partial pieces is that the rarest first -algorithm gets to have more influence on which pieces are picked. The picker is -more likely to truly pick the rarest piece, and hence improving the performance -of the swarm.

-

This setting is turned on automatically whenever the number of partial pieces -in the piece picker exceeds the number of peers we're connected to times 1.5. -This is in order to keep the waste of partial pieces to a minimum, but still -prefer rarest pieces.

-
-
-

prefer whole pieces

-

The prefer whole pieces setting makes the piece picker prefer picking entire -pieces at a time. This is used by web connections (both http seeding -standards), in order to be able to coalesce the small bittorrent requests to -larger HTTP requests. This significantly improves performance when downloading -over HTTP.

-

It is also used by peers that are downloading faster than a certain threshold. -The main advantage is that these peers will better utilize the other peer's -disk cache, by requesting all blocks in a single piece, from the same peer.

-

This threshold is controlled by session_settings::whole_pieces_threshold.

-

TODO: piece affinity by speed category -TODO: piece priorities

-
-
-
-

SSL torrents

-

Torrents may have an SSL root (CA) certificate embedded in them. Such torrents -are called SSL torrents. An SSL torrent talks to all bittorrent peers over -SSL. The protocols are layered like this:

-
-+-----------------------+
-| BitTorrent protocol   |
-+-----------------------+
-| SSL                   |
-+-----------+-----------+
-| TCP       | uTP       |
-|           +-----------+
-|           | UDP       |
-+-----------+-----------+
-
-

During the SSL handshake, both peers need to authenticate by providing a -certificate that is signed by the CA certificate found in the .torrent file. -These peer certificates are expected to be privided to peers through some other -means than bittorrent. Typically by a peer generating a certificate request -which is sent to the publisher of the torrent, and the publisher returning a -signed certificate.

-

In libtorrent, set_ssl_certificate() in torrent_handle is used to tell -libtorrent where to find the peer certificate and the private key for it. When -an SSL torrent is loaded, the torrent_need_cert_alert is posted to remind the -user to provide a certificate.

-

A peer connecting to an SSL torrent MUST provide the SNI TLS extension -(server name indication). The server name is the hex encoded info-hash of the -torrent to connect to. This is required for the client accepting the connection -to know which certificate to present.

-

SSL connections are accepted on a separate socket from normal bittorrent -connections. To pick which port the SSL socket should bind to, set -session_settings::ssl_listen to a different port. It defaults to port 4433. -This setting is only taken into account when the normal listen socket is opened -(i.e. just changing this setting won't necessarily close and re-open the SSL -socket). To not listen on an SSL socket at all, set ssl_listen to 0.

-

This feature is only available if libtorrent is build with openssl support -(TORRENT_USE_OPENSSL) and requires at least openSSL version 1.0, since it -needs SNI support.

-

Peer certificates must have at least one SubjectAltName field of type -dNSName. At least one of the fields must exactly match the name of the -torrent. This is a byte-by-byte comparison, the UTF-8 encoding must be -identical (i.e. there's no unicode normalization going on). This is the -recommended way of verifying certificates for HTTPS servers according to RFC -2818. Note the difference that for torrents only dNSName fields are taken -into account (not IP address fields). The most specific (i.e. last) Common -Name field is also taken into account if no SubjectAltName did not match.

-

If any of these fields contain a single asterisk ("*"), the certificate is -considered covering any torrent, allowing it to be reused for any torrent.

-

The purpose of matching the torrent name with the fields in the peer -certificate is to allow a publisher to have a single root certificate for all -torrents it distributes, and issue separate peer certificates for each torrent. -A peer receiving a certificate will not necessarily be able to access all -torrents published by this root certificate (only if it has a "star cert").

-
-

testing

-

To test incoming SSL connections to an SSL torrent, one can use the following -openssl command:

-
-openssl s_client -cert <peer-certificate>.pem -key <peer-private-key>.pem -CAfile \
-   <torrent-cert>.pem -debug -connect 127.0.0.1:4433 -tls1 -servername <info-hash>
-
-

To create a root certificate, the Distinguished Name (DN) is not taken into -account by bittorrent peers. You still need to specify something, but from -libtorrent's point of view, it doesn't matter what it is. libtorrent only makes -sure the peer certificates are signed by the correct root certificate.

-

One way to create the certificates is to use the CA.sh script that comes -with openssl, like thisi (don't forget to enter a common Name for the -certificate):

-
-CA.sh -newca
-CA.sh -newreq
-CA.sh -sign
-
-

The torrent certificate is located in ./demoCA/private/demoCA/cacert.pem, -this is the pem file to include in the .torrent file.

-

The peer's certificate is located in ./newcert.pem and the certificate's -private key in ./newkey.pem.

-
-
-
- - diff --git a/docs/manual.html b/docs/manual.html new file mode 100644 index 000000000..4cbfceb80 --- /dev/null +++ b/docs/manual.html @@ -0,0 +1,12943 @@ + + + + + + +libtorrent API Documentation + + + + + + + + +
+
+
+ +
+ +
+

libtorrent API Documentation

+ +++ + + + + + +
Author:Arvid Norberg, arvid@rasterbar.com
Version:1.1.0
+ +
+

overview

+

The interface of libtorrent consists of a few classes. The main class is +the session, it contains the main loop that serves all torrents.

+

The basic usage is as follows:

+ +

Each class and function is described in this manual.

+

For a description on how to create torrent files, see make_torrent.

+
+
+

things to keep in mind

+

A common problem developers are facing is torrents stopping without explanation. +Here is a description on which conditions libtorrent will stop your torrents, +how to find out about it and what to do about it.

+

Make sure to keep track of the paused state, the error state and the upload +mode of your torrents. By default, torrents are auto-managed, which means +libtorrent will pause them, unpause them, scrape them and take them out +of upload-mode automatically.

+

Whenever a torrent encounters a fatal error, it will be stopped, and the +torrent_status::error will describe the error that caused it. If a torrent +is auto managed, it is scraped periodically and paused or resumed based on +the number of downloaders per seed. This will effectively seed torrents that +are in the greatest need of seeds.

+

If a torrent hits a disk write error, it will be put into upload mode. This +means it will not download anything, but only upload. The assumption is that +the write error is caused by a full disk or write permission errors. If the +torrent is auto-managed, it will periodically be taken out of the upload +mode, trying to write things to the disk again. This means torrent will recover +from certain disk errors if the problem is resolved. If the torrent is not +auto managed, you have to call set_upload_mode() to turn +downloading back on again.

+
+
+

network primitives

+

There are a few typedefs in the libtorrent namespace which pulls +in network types from the asio namespace. These are:

+
+typedef asio::ip::address address;
+typedef asio::ip::address_v4 address_v4;
+typedef asio::ip::address_v6 address_v6;
+using asio::ip::tcp;
+using asio::ip::udp;
+
+

These are declared in the <libtorrent/socket.hpp> header.

+

The using statements will give easy access to:

+
+tcp::endpoint
+udp::endpoint
+
+

Which are the endpoint types used in libtorrent. An endpoint is an address +with an associated port.

+

For documentation on these types, please refer to the asio documentation.

+
+
+

session

+

The session class has the following synopsis:

+
+class session: public boost::noncopyable
+{
+
+        session(fingerprint const& print
+                = libtorrent::fingerprint(
+                "LT", 0, 1, 0, 0)
+                , int flags = start_default_features
+                        | add_default_plugins
+                , int alert_mask = alert::error_notification);
+
+        session(
+                fingerprint const& print
+                , std::pair<int, int> listen_port_range
+                , char const* listen_interface = 0
+                , int flags = start_default_features
+                        | add_default_plugins
+                , int alert_mask = alert::error_notification);
+
+        enum save_state_flags_t
+        {
+                save_settings = 0x001,
+                save_dht_settings = 0x002,
+                save_dht_state = 0x004,
+                save_proxy = 0x008,
+                save_i2p_proxy = 0x010,
+                save_encryption_settings = 0x020,
+                save_as_map = 0x040,
+                save_feeds = 0x080,
+        };
+
+        void load_state(lazy_entry const& e);
+        void save_state(entry& e, boost::uint32_t flags) const;
+
+        torrent_handle add_torrent(
+                add_torrent_params const& params);
+        torrent_handle add_torrent(
+                add_torrent_params const& params
+                , error_code& ec);
+
+        void async_add_torrent(add_torrent_params const& params);
+
+        void pause();
+        void resume();
+
+        session_proxy abort();
+
+        enum options_t
+        {
+                none = 0,
+                delete_files = 1
+        };
+
+        enum session_flags_t
+        {
+                add_default_plugins = 1,
+                start_default_features = 2
+        };
+
+        void remove_torrent(torrent_handle const& h
+                , int options = none);
+        torrent_handle find_torrent(sha_hash const& ih);
+
+        std::vector<torrent_handle> get_torrents() const;
+        void get_torrent_status(std::vector<torrent_status>* ret
+                , boost::function<bool(torrent_status const&)> const& pred
+                , boost::uint32_t flags = 0) const;
+        void refresh_torrent_status(std::vector<torrent_status>* ret
+                , boost::uint32_t flags) const;
+        void post_torrent_updates();
+
+        stats_metrics session_stats_metrics() const;
+        void post_session_stats();
+
+#ifndef TORRENT_NO_DEPRECATE
+        void set_settings(session_settings const& settings);
+        session_settings settings() const;
+#endif
+
+        void apply_settings(settings_pack const& s);
+        aux::session_settings get_settings() const;
+
+        void set_pe_settings(pe_settings const& settings);
+
+        void set_proxy(proxy_settings const& s);
+        proxy_settings proxy() const;
+
+        int num_uploads() const;
+        int num_connections() const;
+
+        void load_asnum_db(char const* file);
+        void load_asnum_db(wchar_t const* file);
+        void load_country_db(char const* file);
+        void load_country_db(wchar_t const* file);
+        int as_for_ip(address const& adr);
+
+        void set_ip_filter(ip_filter const& f);
+        ip_filter get_ip_filter() const;
+
+        session_status status() const;
+
+        enum {
+                global_peer_class_id,
+                tcp_peer_class_id,
+                local_peer_class_id
+        };
+
+        int create_peer_class();
+        void delete_peer_class(int cid);
+
+        peer_class_info get_peer_class(int cid);
+        void set_peer_class(int cid, peer_class_info const& pci);
+
+        void set_peer_class_filter(ip_filter const& f);
+        void set_peer_class_type_filter(peer_class_type_filter const& f);
+
+        bool is_listening() const;
+        unsigned short listen_port() const;
+
+        enum {
+                listen_reuse_address = 1,
+                listen_no_system_port = 2
+        };
+
+        void listen_on(
+                std::pair<int, int> const& port_range
+                , error_code& ec
+                , char const* interface = 0
+                , int flags = 0);
+
+        void use_interfaces(char const* net_interface) const;
+
+        std::auto_ptr<alert> pop_alert();
+        alert const* wait_for_alert(time_duration max_wait);
+        void set_alert_mask(int m);
+        size_t set_alert_queue_size_limit(
+                size_t queue_size_limit_);
+        void set_alert_dispatch(boost::function<void(std::auto_ptr<alert>)> const& fun);
+
+        feed_handle add_feed(feed_settings const& feed);
+        void remove_feed(feed_handle h);
+        void get_feeds(std::vector<feed_handle>& f) const;
+
+        void add_extension(boost::function<
+                boost::shared_ptr<torrent_plugin>(torrent*)> ext);
+
+        void start_dht();
+        void stop_dht();
+        void set_dht_settings(
+                dht_settings const& settings);
+        entry dht_state() const;
+        void add_dht_node(std::pair<std::string
+                , int> const& node);
+        void add_dht_router(std::pair<std::string
+                , int> const& node);
+        bool is_dht_running() const;
+
+        void start_lsd();
+        void stop_lsd();
+
+        upnp* start_upnp();
+        void stop_upnp();
+
+        natpmp* start_natpmp();
+        void stop_natpmp();
+};
+
+

Once it's created, the session object will spawn the main thread that will do all the work. +The main thread will be idle as long it doesn't have any torrents to participate in.

+
+

session()

+
+
+session(fingerprint const& print
+        = libtorrent::fingerprint("LT", 0, 1, 0, 0)
+        , int flags = start_default_features
+                | add_default_plugins
+        , int alert_mask = alert::error_notification);
+
+session(fingerprint const& print
+        , std::pair<int, int> listen_port_range
+        , char const* listen_interface = 0
+        , int flags = start_default_features
+                | add_default_plugins
+        , int alert_mask = alert::error_notification);
+
+
+

If the fingerprint in the first overload is omited, the client will get a default +fingerprint stating the version of libtorrent. The fingerprint is a short string that will be +used in the peer-id to identify the client and the client's version. For more details see the +fingerprint class. The constructor that only takes a fingerprint will not open a +listen port for the session, to get it running you'll have to call session::listen_on(). +The other constructor, that takes a port range and an interface as well as the fingerprint +will automatically try to listen on a port on the given interface. For more information about +the parameters, see listen_on() function.

+

The flags paramater can be used to start default features (upnp & nat-pmp) and default plugins +(ut_metadata, ut_pex and smart_ban). The default is to start those things. If you do not want +them to start, pass 0 as the flags parameter.

+

The alert_mask is the same mask that you would send to set_alert_mask().

+
+
+

~session()

+

The destructor of session will notify all trackers that our torrents have been shut down. +If some trackers are down, they will time out. All this before the destructor of session +returns. So, it's advised that any kind of interface (such as windows) are closed before +destructing the session object. Because it can take a few second for it to finish. The +timeout can be set with apply_settings().

+
+
+

load_state() save_state()

+
+
+void load_state(lazy_entry const& e);
+void save_state(entry& e, boost::uint32_t flags) const;
+
+
+

loads and saves all session settings, including dht_settings, encryption settings and proxy +settings. save_state writes all keys to the entry that's passed in, which needs to +either not be initialized, or initialized as a dictionary.

+

load_state expects a lazy_entry which can be built from a bencoded buffer with +lazy_bdecode().

+

The flags arguments passed in to save_state can be used to filter which parts +of the session state to save. By default, all state is saved (except for the individual +torrents). These are the possible flags. A flag that's set, means those settings are saved:

+
+enum save_state_flags_t
+{
+        save_settings =     0x001,
+        save_dht_settings = 0x002,
+        save_dht_state =    0x004,
+        save_proxy =        0x008,
+        save_i2p_proxy =    0x010,
+        save_encryption_settings = 0x020,
+        save_as_map =       0x040,
+        save_feeds =        0x080
+};
+
+
+
+

pause() resume() is_paused()

+
+
+void pause();
+void resume();
+bool is_paused() const;
+
+
+

Pausing the session has the same effect as pausing every torrent in it, except that +torrents will not be resumed by the auto-manage mechanism. Resuming will restore the +torrents to their previous paused state. i.e. the session pause state is separate from +the torrent pause state. A torrent is inactive if it is paused or if the session is +paused.

+
+
+

set_load_function()

+
+
+typedef boost::function<void(sha1_hash const&, std::vector<char>&
+        , error_code&)> user_load_function_t;
+
+void set_load_function(user_load_function_t fun);
+
+
+

This function enables dynamic loading of torrent files. When a torrent is unloaded +but needs to be availabe in memory, this function is called from within the libtorrent +network thread. From within this thread, you can not use any of the public APIs of +libtorrent itself. The the info-hash of the torrent is passed in to the function and it +is expected to fill in the passed in vector<char> with the .torrent file corresponding +to it.

+

If there is an error loading the torrent file, the error_code (ec) should be +set to reflect the error. In such case, the torrent itself is stopped and set to an +error state with the corresponding error code.

+

Given that the function is called from the internal network thread of libtorrent, it's +important to not stall. libtorrent will not be able to send nor receive any data until +the function call returns.

+

The signature of the function to pass in is:

+
+void fun(sha1_hash const& info_hash, std::vector<char>& buf, error_code& ec);
+
+
+
+

abort()

+
+
+session_proxy abort();
+
+
+

In case you want to destruct the session asynchrounously, you can request a session +destruction proxy. If you don't do this, the destructor of the session object will +block while the trackers are contacted. If you keep one session_proxy to the +session when destructing it, the destructor will not block, but start to close down +the session, the destructor of the proxy will then synchronize the threads. So, the +destruction of the session is performed from the session destructor call until the +session_proxy destructor call. The session_proxy does not have any operations +on it (since the session is being closed down, no operations are allowed on it). The +only valid operation is calling the destructor:

+
+class session_proxy
+{
+public:
+        session_proxy();
+        ~session_proxy()
+};
+
+
+
+

async_add_torrent() add_torrent()

+
+
+typedef boost::function<storage_interface*(storage_params const& parms) storage_constructor_type;
+
+struct add_torrent_params
+{
+        add_torrent_params(storage_constructor_type s);
+
+        enum flags_t
+        {
+                flag_seed_mode = 0x001,
+                flag_override_resume_data = 0x002,
+                flag_upload_mode = 0x004,
+                flag_share_mode = 0x008,
+                flag_apply_ip_filter = 0x010,
+                flag_paused = 0x020,
+                flag_auto_managed = 0x040.
+                flag_duplicate_is_error = 0x080,
+                flag_merge_resume_trackers = 0x100,
+                flag_update_subscribe = 0x200,
+                flag_super_seeding = 0x400,
+                flag_sequential_download = 0x800,
+                flag_pinned = 0x1000
+        };
+
+        int version;
+        boost::intrusive_ptr<torrent_info> ti;
+#ifndef TORRENT_NO_DEPRECATE
+        char const* tracker_url;
+#endif
+        std::vector<std::string> trackers;
+        std::vector<std::pair<std::string, int> > dht_nodes;
+        sha1_hash info_hash;
+        std::string name;
+        std::string save_path;
+        std::vector<char>* resume_data;
+        storage_mode_t storage_mode;
+        storage_constructor_type storage;
+        void* userdata;
+        std::vector<boost::uint8_t> const* file_priorities;
+        std::string trackerid;
+        std::string url;
+        std::string uuid;
+        std::string source_feed_url;
+        boost::uint64_t flags;
+        int max_uploads;
+        int max_connections;
+        int upload_limit;
+        int download_limit;
+};
+
+torrent_handle add_torrent(add_torrent_params const& params);
+torrent_handle add_torrent(add_torrent_params const& params
+        , error_code& ec);
+void async_add_torrent(add_torrent_params const& params);
+
+
+

You add torrents through the add_torrent() function where you give an +object with all the parameters. The add_torrent() overloads will block +until the torrent has been added (or failed to be added) and returns an +error code and a torrent_handle. In order to add torrents more efficiently, +consider using async_add_torrent() which returns immediately, without +waiting for the torrent to add. Notification of the torrent being added is sent +as add_torrent_alert.

+

The overload that does not take an error_code throws an exception on +error and is not available when building without exception support.

+

The only mandatory parameters are save_path which is the directory where you +want the files to be saved. You also need to specify either the ti (the +torrent file), the info_hash (the info hash of the torrent) or the url +(the URL to where to download the .torrent file from). If you specify the +info-hash, the torrent file will be downloaded from peers, which requires them to +support the metadata extension. For the metadata extension to work, libtorrent must +be built with extensions enabled (TORRENT_DISABLE_EXTENSIONS must not be +defined). It also takes an optional name argument. This may be left empty in case no +name should be assigned to the torrent. In case it's not, the name is used for +the torrent as long as it doesn't have metadata. See torrent_handle::name.

+

If the torrent doesn't have a tracker, but relies on the DHT to find peers, the +trackers (or the deprecated tracker_url) can specify tracker urls that +for the torrent.

+

If you specify a url, the torrent will be set in downloading_metadata state +until the .torrent file has been downloaded. If there's any error while downloading, +the torrent will be stopped and the torrent error state (torrent_status::error) +will indicate what went wrong. The url may refer to a magnet link, a regular +http URL or a file-URL. Using a file-URL to load torrents allows for loading them +asyncronously. When used with async_add_torrent() it can provide a completely non- +blocking mechanism for adding torrents, without requiring them to be loaded +from disk first.

+

If it refers to an HTTP URL, the info-hash for the added torrent will not be the +true info-hash of the .torrent. Instead a placeholder, unique, info-hash is used +which is later updated once the .torrent file has been downloaded.

+

Once the info-hash change happens, a torrent_update_alert is posted.

+

dht_nodes is a list of hostname and port pairs, representing DHT nodes to be +added to the session (if DHT is enabled). The hostname may be an IP address.

+

If the torrent you are trying to add already exists in the session (is either queued +for checking, being checked or downloading) add_torrent() will throw +libtorrent_exception which derives from std::exception unless duplicate_is_error +is set to false. In that case, add_torrent will return the handle to the existing +torrent.

+

The optional parameter, resume_data can be given if up to date fast-resume data +is available. The fast-resume data can be acquired from a running torrent by calling +save_resume_data() on torrent_handle. See fast resume. The vector that is +passed in will be swapped into the running torrent instance with std::vector::swap().

+

The storage_mode parameter refers to the layout of the storage for this torrent. +There are 3 different modes:

+
+
storage_mode_sparse
+
All pieces will be written to the place where they belong and sparse files +will be used. This is the recommended, and default mode.
+
storage_mode_allocate
+
All pieces will be written to their final position, all files will be +allocated in full when the torrent is first started. This is done with +fallocate() and similar calls. This mode minimizes fragmentation.
+
storage_mode_compact
+
this mode is deprecated and will be removed in future versions of libtorrent +The storage will grow as more pieces are downloaded, and pieces +are rearranged to finally be in their correct places once the entire torrent has been +downloaded.
+
+

For more information, see storage allocation.

+

storage can be used to customize how the data is stored. The default +storage will simply write the data to the files it belongs to, but it could be +overridden to save everything to a single file at a specific location or encrypt the +content on disk for instance. For more information about the storage_interface +that needs to be implemented for a custom storage, see storage_interface.

+

The userdata parameter is optional and will be passed on to the extension +constructor functions, if any (see add_extension()).

+

The torrent_handle returned by add_torrent() can be used to retrieve information +about the torrent's progress, its peers etc. It is also used to abort a torrent.

+

file_priorities can be set to control the initial file priorities when adding +a torrent. The semantics are the same as for torrent_handle::prioritize_files().

+

version is filled in by the constructor and should be left untouched. It +is used for forward binary compatibility.

+

trackerid is the default tracker id to be used when announcing to trackers. By default +this is empty, and no tracker ID is used, since this is an optional argument. If +a tracker returns a tracker ID, that ID is used instead of this.

+

if uuid is specified, it is used to find duplicates. If another torrent is already +running with the same UUID as the one being added, it will be considered a duplicate. This +is mainly useful for RSS feed items which has UUIDs specified.

+

source_feed_url should point to the URL of the RSS feed this torrent comes from, +if it comes from an RSS feed.

+

flags is a 64 bit integer used for flags controlling aspects of this torrent +and how it's added. These are the flags:

+
+enum flags_t
+{
+        flag_seed_mode = 0x001,
+        flag_override_resume_data = 0x002,
+        flag_upload_mode = 0x004,
+        flag_share_mode = 0x008,
+        flag_apply_ip_filter = 0x010,
+        flag_paused = 0x020,
+        flag_auto_managed = 0x040.
+        flag_duplicate_is_error = 0x080,
+        flag_merge_resume_trackers = 0x100,
+        flag_update_subscribe = 0x200,
+        flag_super_seeding = 0x400,
+        flag_sequential_download = 0x800,
+        flag_pinned = 0x1000
+}
+
+

flag_apply_ip_filter determines if the IP filter should apply to this torrent or not. By +default all torrents are subject to filtering by the IP filter (i.e. this flag is set by +default). This is useful if certain torrents needs to be excempt for some reason, being +an auto-update torrent for instance.

+

flag_merge_resume_trackers defaults to off and specifies whether tracker URLs loaded from +resume data should be added to the trackers in the torrent or replace the trackers.

+

flag_update_subscribe is on by default and means that this torrent will be part of state +updates when calling post_torrent_updates().

+

flag_paused specifies whether or not the torrent is to be started in a paused +state. I.e. it won't connect to the tracker or any of the peers until it's +resumed. This is typically a good way of avoiding race conditions when setting +configuration options on torrents before starting them.

+

If you pass in resume data, the paused state of the torrent when the resume data +was saved will override the paused state you pass in here. You can override this +by setting flag_override_resume_data.

+

If the torrent is auto-managed (flag_auto_managed), the torrent may be resumed +at any point, regardless of how it paused. If it's important to manually control +when the torrent is paused and resumed, don't make it auto managed.

+

If flag_auto_managed is set, the torrent will be queued, started and seeded +automatically by libtorrent. When this is set, the torrent should also be started +as paused. The default queue order is the order the torrents were added. They +are all downloaded in that order. For more details, see queuing.

+

If you pass in resume data, the auto_managed state of the torrent when the resume data +was saved will override the auto_managed state you pass in here. You can override this +by setting override_resume_data.

+

If flag_seed_mode is set, libtorrent will assume that all files are present +for this torrent and that they all match the hashes in the torrent file. Each time +a peer requests to download a block, the piece is verified against the hash, unless +it has been verified already. If a hash fails, the torrent will automatically leave +the seed mode and recheck all the files. The use case for this mode is if a torrent +is created and seeded, or if the user already know that the files are complete, this +is a way to avoid the initial file checks, and significantly reduce the startup time.

+

Setting flag_seed_mode on a torrent without metadata (a .torrent file) is a no-op +and will be ignored.

+

If resume data is passed in with this torrent, the seed mode saved in there will +override the seed mode you set here.

+

If flag_override_resume_data is set, the paused and auto_managed +state of the torrent are not loaded from the resume data, but the states requested +by the flags in add_torrent_params will override them.

+

If flag_upload_mode is set, the torrent will be initialized in upload-mode, +which means it will not make any piece requests. This state is typically entered +on disk I/O errors, and if the torrent is also auto managed, it will be taken out +of this state periodically. This mode can be used to avoid race conditions when +adjusting priorities of pieces before allowing the torrent to start downloading.

+

If the torrent is auto-managed (flag_auto_managed), the torrent will eventually +be taken out of upload-mode, regardless of how it got there. If it's important to +manually control when the torrent leaves upload mode, don't make it auto managed.

+

flag_share_mode determines if the torrent should be added in share mode or not. +Share mode indicates that we are not interested in downloading the torrent, but +merley want to improve our share ratio (i.e. increase it). A torrent started in +share mode will do its best to never download more than it uploads to the swarm. +If the swarm does not have enough demand for upload capacity, the torrent will +not download anything. This mode is intended to be safe to add any number of torrents +to, without manual screening, without the risk of downloading more than is uploaded.

+

A torrent in share mode sets the priority to all pieces to 0, except for the pieces +that are downloaded, when pieces are decided to be downloaded. This affects the progress +bar, which might be set to "100% finished" most of the time. Do not change file or piece +priorities for torrents in share mode, it will make it not work.

+

The share mode has one setting, the share ratio target, see share_mode_target. +for more info.

+

flag_super_seeding sets the torrent into super seeding mode. If the torrent +is not a seed, this flag has no effect. It has the same effect as calling +torrent_handle::super_seeding(true) on the torrent handle immediately +after adding it.

+

flag_sequential_download sets the sequential download state for the torrent. +It has the same effect as calling torrent_handle::sequential_download(true) +on the torrent handle immediately after adding it.

+

flag_pinned indicates that this torrent should never be unloaded from RAM, even +if unloading torrents are allowed in general. Setting this makes the torrent +excempt from loading/unloading management.

+

max_uploads, max_connections, upload_limit, download_limit correspond +to the set_max_uploads(), set_max_connections(), set_upload_limit() and +set_download_limit() functions on torrent_handle. These values let you initialize +these settings when the torrent is added, instead of calling these functions immediately +following adding it.

+
+
+

remove_torrent()

+
+
+void remove_torrent(torrent_handle const& h, int options = none);
+
+
+

remove_torrent() will close all peer connections associated with the torrent and tell +the tracker that we've stopped participating in the swarm. The optional second argument +options can be used to delete all the files downloaded by this torrent. To do this, pass +in the value session::delete_files. The removal of the torrent is asyncronous, there is +no guarantee that adding the same torrent immediately after it was removed will not throw +a libtorrent_exception exception. Once the torrent is deleted, a torrent_deleted_alert +is posted.

+
+
+

find_torrent() get_torrents()

+
+
+torrent_handle find_torrent(sha_hash const& ih);
+std::vector<torrent_handle> get_torrents() const;
+
+
+

find_torrent() looks for a torrent with the given info-hash. In case there +is such a torrent in the session, a torrent_handle to that torrent is returned. +In case the torrent cannot be found, an invalid torrent_handle is returned.

+

See torrent_handle::is_valid() to know if the torrent was found or not.

+

get_torrents() returns a vector of torrent_handles to all the torrents +currently in the session.

+
+
+

get_torrent_status() refresh_torrent_status()

+
+
+void get_torrent_status(std::vector<torrent_status>* ret
+        , boost::function<bool(torrent_status const&)> const& pred
+        , boost::uint32_t flags = 0) const;
+void refresh_torrent_status(std::vector<torrent_status>* ret
+        , boost::uint32_t flags = 0) const;
+
+
+
+

Note

+

these calls are potentially expensive and won't scale well +with lots of torrents. If you're concerned about performance, consider +using post_torrent_updates() instead.

+
+

get_torrent_status returns a vector of the torrent_status for every +torrent which satisfies pred, which is a predicate function which determines +if a torrent should be included in the returned set or not. Returning true means +it should be included and false means excluded. The flags argument is the same +as to torrent_handle::status(). Since pred is guaranteed to be called for +every torrent, it may be used to count the number of torrents of different categories +as well.

+

refresh_torrent_status takes a vector of torrent_status structs (for instance +the same vector that was returned by get_torrent_status()) and refreshes the +status based on the handle member. It is possible to use this function by +first setting up a vector of default constructed torrent_status objects, only +initializing the handle member, in order to request the torrent status for +multiple torrents in a single call. This can save a significant amount of time +if you have a lot of torrents.

+

Any torrent_status object whose handle member is not referring to a +valid torrent are ignored.

+
+
+

post_torrent_updates()

+
+
+void post_torrent_updates();
+
+
+

This functions instructs the session to post the state_update_alert, containing +the status of all torrents whose state changed since the last time this function +was called.

+

Only torrents who has the state subscription flag set will be included. This flag +is on by default. See add_torrent_params under async_add_torrent() add_torrent().

+
+
+

session_stats_metrics()

+
+std::vector<stats_metric> session_stats_metrics() const;
+
+

This function returns the list of available metrics exposed by libtorrent's +statistics API. Each metric has a name and a value index. The value index is +the index into the array in session_stats_alert where this metric's value +can be found when the session stats is sampled (by calling post_session_stats()).

+

The stats_metric struct has the following fields:

+
+struct stats_metric
+{
+        char const* name;
+        int value_index;
+        enum { type_counter, type_gauge };
+        int type;
+};
+
+

For more information, see the session statistics section.

+
+
+

post_session_stats()

+
+
+void post_session_stats();
+
+
+

This function will post a session_stats_alert object, containing a snapshot of +the performance counters from the internals of libtorrent. To interpret these counters, +query the session via session_stats_metrics().

+

For more information, see the session statistics section.

+
+
+

load_asnum_db() load_country_db() as_for_ip()

+
+
+void load_asnum_db(char const* file);
+void load_asnum_db(wchar_t const* file);
+void load_country_db(char const* file);
+void load_country_db(wchar_t const* file);
+int as_for_ip(address const& adr);
+
+
+

These functions are not available if TORRENT_DISABLE_GEO_IP is defined. They +expects a path to the MaxMind ASN database and MaxMind GeoIP database +respectively. This will be used to look up which AS and country peers belong to.

+

as_for_ip returns the AS number for the IP address specified. If the IP is not +in the database or the ASN database is not loaded, 0 is returned.

+

The wchar_t overloads are for wide character paths.

+
+
+

set_ip_filter()

+
+
+void set_ip_filter(ip_filter const& filter);
+
+
+

Sets a filter that will be used to reject and accept incoming as well as outgoing +connections based on their originating ip address. The default filter will allow +connections to any ip address. To build a set of rules for which addresses are +accepted and not, see ip_filter.

+

Each time a peer is blocked because of the IP filter, a peer_blocked_alert is +generated.

+
+
+

get_ip_filter()

+
+
+ip_filter get_ip_filter() const;
+
+
+

Returns the ip_filter currently in the session. See ip_filter.

+
+
+

status()

+
+
+session_status status() const;
+
+
+

status() returns session wide-statistics and status. The session_status +struct has the following members:

+
+struct dht_lookup
+{
+        char const* type;
+        int outstanding_requests;
+        int timeouts;
+        int responses;
+        int branch_factor;
+        int nodes_left;
+        int last_sent;
+        int first_timeout;
+};
+
+struct dht_routing_bucket
+{
+        int num_nodes;
+        int num_replacements;
+        int last_active;
+};
+
+struct utp_status
+{
+        int num_idle;
+        int num_syn_sent;
+        int num_connected;
+        int num_fin_sent;
+        int num_close_wait;
+};
+
+struct session_status
+{
+        bool has_incoming_connections;
+
+        int upload_rate;
+        int download_rate;
+        size_type total_download;
+        size_type total_upload;
+
+        int payload_upload_rate;
+        int payload_download_rate;
+        size_type total_payload_download;
+        size_type total_payload_upload;
+
+        int ip_overhead_upload_rate;
+        int ip_overhead_download_rate;
+        size_type total_ip_overhead_download;
+        size_type total_ip_overhead_upload;
+
+        int dht_upload_rate;
+        int dht_download_rate;
+        size_type total_dht_download;
+        size_type total_dht_upload;
+
+        int tracker_upload_rate;
+        int tracker_download_rate;
+        size_type total_tracker_download;
+        size_type total_tracker_upload;
+
+        size_type total_redundant_bytes;
+        size_type total_failed_bytes;
+
+        int num_peers;
+        int num_unchoked;
+        int allowed_upload_slots;
+
+        int up_bandwidth_queue;
+        int down_bandwidth_queue;
+
+        int up_bandwidth_bytes_queue;
+        int down_bandwidth_bytes_queue;
+
+        int optimistic_unchoke_counter;
+        int unchoke_counter;
+
+        int disk_write_queue;
+        int disk_read_queue;
+
+        int dht_nodes;
+        int dht_node_cache;
+        int dht_torrents;
+        size_type dht_global_nodes;
+        std::vector<dht_lookup> active_requests;
+        std::vector<dht_routing_table> dht_routing_table;
+        int dht_total_allocations;
+
+        utp_status utp_stats;
+
+        int num_torrents;
+        int num_paused_torrents;
+};
+
+

has_incoming_connections is false as long as no incoming connections have been +established on the listening socket. Every time you change the listen port, this will +be reset to false.

+

upload_rate, download_rate are the total download and upload rates accumulated +from all torrents. This includes bittorrent protocol, DHT and an estimated TCP/IP +protocol overhead.

+

total_download and total_upload are the total number of bytes downloaded and +uploaded to and from all torrents. This also includes all the protocol overhead.

+

payload_download_rate and payload_upload_rate is the rate of the payload +down- and upload only.

+

total_payload_download and total_payload_upload is the total transfers of payload +only. The payload does not include the bittorrent protocol overhead, but only parts of the +actual files to be downloaded.

+

ip_overhead_upload_rate, ip_overhead_download_rate, total_ip_overhead_download +and total_ip_overhead_upload is the estimated TCP/IP overhead in each direction.

+

dht_upload_rate, dht_download_rate, total_dht_download and total_dht_upload +is the DHT bandwidth usage.

+

total_redundant_bytes is the number of bytes that has been received more than once. +This can happen if a request from a peer times out and is requested from a different +peer, and then received again from the first one. To make this lower, increase the +request_timeout and the piece_timeout in the session settings.

+

total_failed_bytes is the number of bytes that was downloaded which later failed +the hash-check.

+

num_peers is the total number of peer connections this session has. This includes +incoming connections that still hasn't sent their handshake or outgoing connections +that still hasn't completed the TCP connection. This number may be slightly higher +than the sum of all peers of all torrents because the incoming connections may not +be assigned a torrent yet.

+

num_unchoked is the current number of unchoked peers. +allowed_upload_slots is the current allowed number of unchoked peers.

+

up_bandwidth_queue and down_bandwidth_queue are the number of peers that are +waiting for more bandwidth quota from the torrent rate limiter. +up_bandwidth_bytes_queue and down_bandwidth_bytes_queue count the number of +bytes the connections are waiting for to be able to send and receive.

+

optimistic_unchoke_counter and unchoke_counter tells the number of +seconds until the next optimistic unchoke change and the start of the next +unchoke interval. These numbers may be reset prematurely if a peer that is +unchoked disconnects or becomes notinterested.

+

disk_write_queue and disk_read_queue are the number of peers currently +waiting on a disk write or disk read to complete before it receives or sends +any more data on the socket. It'a a metric of how disk bound you are.

+

dht_nodes, dht_node_cache and dht_torrents are only available when +built with DHT support. They are all set to 0 if the DHT isn't running. When +the DHT is running, dht_nodes is set to the number of nodes in the routing +table. This number only includes active nodes, not cache nodes. The +dht_node_cache is set to the number of nodes in the node cache. These nodes +are used to replace the regular nodes in the routing table in case any of them +becomes unresponsive.

+

dht_torrents are the number of torrents tracked by the DHT at the moment.

+

dht_global_nodes is an estimation of the total number of nodes in the DHT +network.

+

active_requests is a vector of the currently running DHT lookups.

+

dht_routing_table contains information about every bucket in the DHT routing +table.

+

dht_total_allocations is the number of nodes allocated dynamically for a +particular DHT lookup. This represents roughly the amount of memory used +by the DHT.

+

utp_stats contains statistics on the uTP sockets.

+

num_torrents and num_paused_torrents are the number of torrents in the +session and the number of them that are currently paused, respectively.

+
+
+

get_cache_info()

+
+
+enum { disk_cache_no_pieces = 1 };
+void get_cache_info(cache_status* ret, torrent_handle h = torrent_handle(), int flags = 0) const;
+
+
+

Fills in the cache_status struct with information about the given torrent. +If flags is session::disk_cache_no_pieces the cache_status::pieces field +will not be set. This may significantly reduce the cost of this call.

+
+
+struct cached_piece_info
+{
+        int piece;
+        std::vector<bool> blocks;
+        ptime last_use;
+        enum kind_t { read_cache = 0, write_cache = 1 };
+        kind_t kind;
+};
+
+
+

piece is the piece index for this cache entry.

+

blocks has one entry for each block in this piece. true represents +the data for that block being in the disk cache and false means it's not.

+

last_use is the time when a block was last written to this piece. The older +a piece is, the more likely it is to be flushed to disk.

+

kind specifies if this piece is part of the read cache or the write cache.

+
+
+struct cache_status
+{
+        std::vector<cached_piece_info> pieces;
+        size_type blocks_written;
+        size_type writes;
+        size_type blocks_read;
+        size_type blocks_read_hit;
+        size_type reads;
+        size_type queued_bytes;
+        int write_cache_size;
+        int read_cache_size;
+        int pinned_blocks;
+        int total_used_buffers;
+        int average_read_time;
+        int average_write_time;
+        int average_hash_time;
+        int average_job_time;
+
+        boost::uint32_t cumulative_job_time;
+        boost::uint32_t cumulative_read_time;
+        boost::uint32_t cumulative_write_time;
+        boost::uint32_t cumulative_hash_time;
+
+        int total_read_back;
+        int read_queue_size;
+        int blocked_jobs;
+
+        int queued_jobs;
+        int peak_queued;
+        int pending_jobs;
+        int peak_pending;
+
+        int num_jobs;
+        int num_read_jobs;
+
+        int num_write_jobs;
+
+        int arc_mru_size;
+        int arc_mru_ghost_size;
+        int arc_mfu_size;
+        int arc_mfu_ghost_size;
+};
+
+
+

blocks_written is the total number of 16 KiB blocks written to disk +since this session was started.

+

writes is the total number of write operations performed since this +session was started.

+

The ratio (blocks_written - writes) / blocks_written represents +the number of saved write operations per total write operations. i.e. a kind +of cache hit ratio for the write cahe.

+

blocks_read is the number of blocks that were requested from the +bittorrent engine (from peers), that were served from disk or cache.

+

blocks_read_hit is the number of blocks that were served from cache.

+

The ratio blocks_read_hit / blocks_read is the cache hit ratio +for the read cache.

+

reads is the total number of read operations called this session.

+

queued_bytes is the total number of bytes queued for writing, including +bytes passed on to the operating system but have not yet completed.

+

write_cache_size is the number of 16 KiB blocks currently in the disk +write cache.

+

read_cache_size is the number of 16KiB blocks in the read cache.

+

pinned_blocks is the number of blocks that have more than 0 references +to them, forcing them to stay in RAM.

+

total_used_buffers is the total number of buffers currently in use. +This includes the read/write disk cache as well as send and receive buffers +used in peer connections. It only counts disk buffers.

+

average_queue_time is the number of microseconds an average disk I/O job +has to wait in the job queue before it get processed.

+

average_read_time is the time read jobs takes on average to complete +(not including the time in the queue), in microseconds. This only measures +read cache misses.

+

average_write_time is the time write jobs takes to complete, on average, +in microseconds. This does not include the time the job sits in the disk job +queue or in the write cache, only blocks that are flushed to disk.

+

average_hash_time is the time hash jobs takes to complete on average, in +microseconds. Hash jobs include running SHA-1 on the data (which for the most +part is done incrementally) and sometimes reading back parts of the piece. It +also includes checking files without valid resume data.

+

average_job_time is the average time it takes for any disk job to complete, +in microseconds.

+

average_sort_time is the time spent sorting disk jobs, when using synchronous +I/O.

+

average_issue_time is the time spent actually issuing the jobs to the OS. If +this is high, it might indicate a problem with the asynchronous disk API, not being +very asynchronous.

+

cumulative_job_time, cumulative_read_time, cumulative_write_time, +cumulative_hash_time, cumulative_sort_time, cumulative_issue_time +are the cumulative time, in microseconds, spent in each category of disk I/O +function.

+

total_read_back is the total number of (16 kiB) blocks read this session.

+

read_queue_size is the number of read jobs in the disk job queue.

+

blocked_jobs is the number of jobs blocked because of one or more jobs that +need exclusive access to the file storage. For instance renaming files or closing +files.

+

queued_jobs is the total number of jobs in the queue.

+

pending_jobs is the number of jobs that have been issued to the OS, but have not +yet completed.

+

num_aiocb is the number of async. disk I/O request objects currently in use.

+

peak_aiocb is the peak number of aiocb's that's ever been in use this session.

+

cumulative_completed_aiocbs is the total number of low-level AIO jobs that has +completed. This can be used as an accurate job completion rate counter, by comparing +consecutive values. Keep in mind that one low level job is typically 16 kiB, however, +some back-ends support vector I/O, in which case a job may represent a lot more than +that.

+
+
+

create_peer_class()

+
+
+int create_peer_class(char const* name);
+
+
+

Creates a new peer class (see peer classes) with the given name. The returned integer +is the new peer class' identifier. Peer classes may have the same name, so each invocation +of this function creates a new class and returns a unique identifier.

+

Identifiers are assigned from low numbers to higher. So if you plan on using certain peer +classes in a call to set_peer_class_filter(), make sure to create those early on, to get +low identifiers.

+

For more information on peer classes, see peer classes.

+
+
+

delete_peer_class()

+
+
+void delete_peer_class(int cid);
+
+
+

This call dereferences the reference count of the specified peer class. When creating a peer +class it's automatically referenced by 1. If you want to recycle a peer class, you may call +this function. You may only call this function once per peer class you create. Calling it +more than once for the same class will lead to memory corruption.

+

Since peer classes are reference counted, this function will not remove the peer class if it's +still assigned to torrents or peers. It will however remove it once the last peer and torrent +drops their references to it.

+

There is no need to call this function for custom peer classes. All peer classes will be properly +destructed when the session object destructs.

+

For more information on peer classes, see peer classes.

+
+
+

set_peer_class() get_peer_class()

+
+
+peer_class_info get_peer_class(int cid);
+void set_peer_class(int cid, peer_class_info const& pci);
+
+
+

These functions queries information from a peer class and updates the configuration +of a peer class, respectively.

+

cid must refer to an existing peer class. If it does not, the return value of +get_peer_class() is undefined.

+

set_peer_class() sets all the information in the peer_class_info object in +the specified peer class. There is no option to only update a single property.

+

The peer_class_info struct has the following fields:

+
+struct peer_class_info
+{
+        bool ignore_unchoke_slots;
+        int connection_limit_factor;
+        std::string label;
+        int upload_limit;
+        int download_limit;
+        int upload_priority;
+        int download_priority;
+};
+
+

ignore_unchoke_slots determines whether peers should always unchoke a peer, +regardless of the choking algorithm, or if it should honor the unchoke slot limits. +It's used for local peers by default. If any of the peer classes a peer belongs to +has this set to true, that peer will be unchoked at all times.

+

connection_limit_factor adjusts the connection limit (global and per torrent) that +applies to this peer class. By default, local peers are allowed to exceed the normal +connection limit for instance. This is specified as a percent factor. 100 makes +the peer class apply normally to the limit. 200 means as long as there are fewer +connections than twice the limit, we accept this peer. This factor applies both to +the global connection limit and the per-torrent limit. Note that if not used carefully +one peer class can potentially completely starve out all other over time.

+

label is not used by libtorrent. It's intended as a potentially user-facing identifier +of this peer class.

+

upload_limit and download_limit are transfer rates limits for the whole peer class. +They are specified in bytes per second and apply to the sum of all peers that are +members of this class.

+

upload_priority and download_priority are relative priorities used by the +bandwidth allocator in the rate limiter. If no rate limits are in use, the priority +is not used either. Priorities start at 1 (0 is not a valid priority) and may not +exceed 255.

+

A peer or torrent balonging to more than one class, the highest priority among any +of its classes is the one that is taken into account.

+

For more information, see peer classes.

+
+
+

set_peer_class_filter()

+
+
+void set_peer_class_filter(ip_filter const& f);
+
+
+

Sets the peer class filter for this session. All new peer connections will take this +into account and be added to the peer classes specified by this filter, based on +the peer's IP address.

+

The ip-filter essentially maps an IP -> uint32. Each bit in that 32 bit integer represents +a peer class. The least significant bit represents class 0, the next bit class 1 and so on.

+

For more info, see ip_filter.

+

For example, to make all peers in the range 200.1.1.0 - 200.1.255.255 belong to their own +peer class, apply the following filter:

+
+ip_filter f;
+int my_class = ses.create_peer_class("200.1.x.x IP range");
+f.add_rule(address_v4::from_string("200.1.1.0")
+        , address_v4::from_string("200.1.255.255")
+        , 1 << my_class);
+ses.set_peer_class_filter(f);
+
+

This setting only applies to new connections, it won't affect existing peer connections.

+

This function is limited to only peer class 0-31, since there are only 32 bits in the IP range +mapping. Only the set bits matter; no peer class will be removed from a peer as a result of +this call, peer classes are only added.

+

The peer_class argument cannot be greater than 31. The bitmasks representing +peer classes in the peer_class_filter are 32 bits.

+

For more information, see peer classes.

+
+
+

set_peer_class_type_filter() get_peer_class_type_filter()

+
+void set_peer_class_type_filter(peer_class_type_filter const& f);
+peer_class_type_filter get_peer_class_type_filter();
+
+

Sets and gets the peer class type filter. This is controls automatic peer class +assignments to peers based on what kind of socket it is.

+

It does not only support assigning peer classes, it also supports removing peer +classes based on socket type.

+

The peer_class_type_filter is a simple container for rules for adding and subtracting +peer-classes from peers. It is applied after the peer class filter is applied (which +is based on the peer's IP address). It has the following synopsis:

+
+struct peer_class_type_filter
+{
+        peer_class_type_filter();
+
+        enum socket_type_t
+        {
+                tcp_socket = 0,
+                utp_socket,
+                ssl_tcp_socket,
+                ssl_utp_socket,
+                i2p_socket,
+                num_socket_types
+        };
+        void add(socket_type_t st, int peer_class);
+        void remove(socket_type_t st, int peer_class);
+
+        void disallow(socket_type_t st, int peer_class);
+        void allow(socket_type_t st, int peer_class);
+        boost::uint32_t apply(int st, boost::uint32_t peer_class_mask);
+};
+
+

add() and remove() adds and removes a peer class to be added +to new peers based on socket type.

+

disallow and allow() adds and removes a peer class to be +removed from new peers based on socket type.

+

The peer_class argument cannot be greater than 31. The bitmasks representing +peer classes in the peer_class_type_filter are 32 bits.

+

apply() takes a bitmask of peer classes and returns a new bitmask of +peer classes after the rules have been applied, based on the socket type argument +(st).

+

The order of these rules being applied are:

+
    +
  1. peer-class IP filter
  2. +
  3. peer-class type filter, removing classes
  4. +
  5. peer-class type filter, adding classes
  6. +
+

For more information, see peer classes.

+
+
+

is_listening() listen_port() listen_on()

+
+
+bool is_listening() const;
+unsigned short listen_port() const;
+
+enum {
+        listen_reuse_address = 1,
+        listen_no_system_port = 2
+};
+
+void listen_on(
+        std::pair<int, int> const& port_range
+        , error_code& ec
+        , char const* interface = 0
+        , int flags = 0);
+
+
+

is_listening() will tell you whether or not the session has successfully +opened a listening port. If it hasn't, this function will return false, and +then you can use listen_on() to make another attempt.

+

listen_port() returns the port we ended up listening on. Since you just pass +a port-range to the constructor and to listen_on(), to know which port it +ended up using, you have to ask the session using this function.

+

listen_on() will change the listen port and/or the listen interface. If the +session is already listening on a port, this socket will be closed and a new socket +will be opened with these new settings. The port range is the ports it will try +to listen on, if the first port fails, it will continue trying the next port within +the range and so on. The interface parameter can be left as 0, in that case the +os will decide which interface to listen on, otherwise it should be the ip-address +of the interface you want the listener socket bound to. listen_on() returns the +error code of the operation in ec. If this indicates success, the session is +listening on a port within the specified range. If it fails, it will also +generate an appropriate alert (listen_failed_alert).

+

If all ports in the specified range fails to be opened for listening, libtorrent will +try to use port 0 (which tells the operating system to pick a port that's free). If +that still fails you may see a listen_failed_alert with port 0 even if you didn't +ask to listen on it.

+

It is possible to prevent libtorrent from binding to port 0 by passing in the flag +session::no_system_port in the flags argument.

+

The interface parameter can also be a hostname that will resolve to the device you +want to listen on. If you don't specify an interface, libtorrent may attempt to +listen on multiple interfaces (typically 0.0.0.0 and ::). This means that if your +IPv6 interface doesn't work, you may still see a listen_failed_alert, even though +the IPv4 port succeeded.

+

The flags parameter can either be 0 or session::listen_reuse_address, which +will set the reuse address socket option on the listen socket(s). By default, the +listen socket does not use reuse address. If you're running a service that needs +to run on a specific port no matter if it's in use, set this flag.

+

If you're also starting the DHT, it is a good idea to do that after you've called +listen_on(), since the default listen port for the DHT is the same as the tcp +listen socket. If you start the DHT first, it will assume the tcp port is free and +open the udp socket on that port, then later, when listen_on() is called, it +may turn out that the tcp port is in use. That results in the DHT and the bittorrent +socket listening on different ports. If the DHT is active when listen_on is +called, the udp port will be rebound to the new port, if it was configured to use +the same port as the tcp socket, and if the listen_on call failed to bind to the +same port that the udp uses.

+

If you want the OS to pick a port for you, pass in 0 as both first and second.

+

The reason why it's a good idea to run the DHT and the bittorrent socket on the same +port is because that is an assumption that may be used to increase performance. One +way to accelerate the connecting of peers on windows may be to first ping all peers +with a DHT ping packet, and connect to those that responds first. On windows one +can only connect to a few peers at a time because of a built in limitation (in XP +Service pack 2).

+
+
+

use_interfaces()

+
+
+void use_interfaces(char const* net_interface) const;
+
+
+

use_interfaces() sets the network interface this torrent will use when it opens outgoing +connections. By default, it binds outgoing connections to INADDR_ANY and port 0 (i.e. let the +OS decide). Ths parameter must be a string containing one or more, comma separated, ip-address +(either an IPv4 or IPv6 address). When specifying multiple interfaces, they will be assigned +in round-robin order. This may be useful for clients that are multi-homed.

+
+
+

set_alert_mask()

+
+
+void set_alert_mask(int m);
+
+
+

Changes the mask of which alerts to receive. By default only errors are reported. +m is a bitmask where each bit represents a category of alerts.

+

See alerts for mor information on the alert categories.

+
+
+

pop_alerts() pop_alert() wait_for_alert()

+
+
+std::auto_ptr<alert> pop_alert();
+void pop_alerts(std::deque<alert*>* alerts);
+alert const* wait_for_alert(time_duration max_wait);
+
+
+

pop_alert() is used to ask the session if any errors or events has occurred. With +set_alert_mask() you can filter which alerts to receive through pop_alert(). +For information about the alert categories, see alerts.

+

pop_alerts() pops all pending alerts in a single call. In high performance environments +with a very high alert churn rate, this can save significant amount of time compared to +popping alerts one at a time. Each call requires one round-trip to the network thread. If +alerts are produced in a higher rate than they can be popped (when popped one at a time) +it's easy to get stuck in an infinite loop, trying to drain the alert queue. Popping the entire +queue at once avoids this problem.

+

However, the pop_alerts function comes with significantly more responsibility. You pass +in an empty std::dequeue<alert*> to it. If it's not empty, all elements in it will +be deleted and then cleared. All currently pending alerts are returned by being swapped +into the passed in container. The responsibility of deleting the alerts is transferred +to the caller. This means you need to call delete for each item in the returned dequeue. +It's probably a good idea to delete the alerts as you handle them, to save one extra +pass over the dequeue.

+

Alternatively, you can pass in the same container the next time you call pop_alerts.

+

wait_for_alert blocks until an alert is available, or for no more than max_wait +time. If wait_for_alert returns because of the time-out, and no alerts are available, +it returns 0. If at least one alert was generated, a pointer to that alert is returned. +The alert is not popped, any subsequent calls to wait_for_alert will return the +same pointer until the alert is popped by calling pop_alert. This is useful for +leaving any alert dispatching mechanism independent of this blocking call, the dispatcher +can be called and it can pop the alert independently.

+

In the python binding, wait_for_alert takes the number of milliseconds to wait as an integer.

+

To control the max number of alerts that's queued by the session, see +alert_queue_size.

+

save_resume_data_alert and save_resume_data_failed_alert are always posted, regardelss +of the alert mask.

+
+
+

set_alert_dispatch()

+
+
+void set_alert_dispatch(boost::function<void(std::auto_ptr<alert>)> const& fun);
+
+
+

This sets a function to be called (from within libtorrent's netowrk thread) every time an alert +is posted. Since the function (fun) is run in libtorrent's internal thread, it may not call +any of libtorrent's external API functions. Doing so results in a dead lock.

+

The main intention with this function is to support integration with platform-dependent message +queues or signalling systems. For instance, on windows, one could post a message to an HNWD or +on linux, write to a pipe or an eventfd.

+
+
+

add_feed()

+
+
+feed_handle add_feed(feed_settings const& feed);
+
+
+

This adds an RSS feed to the session. The feed will be refreshed +regularly and optionally add all torrents from the feed, as they +appear. The feed is defined by the feed_settings object:

+
+struct feed_settings
+{
+        feed_settings();
+
+std::string url;
+        bool auto_download;
+        bool auto_map_handles;
+        int default_ttl;
+        add_torrent_params add_args;
+};
+
+

By default auto_download is true, which means all torrents in +the feed will be downloaded. Set this to false in order to manually +add torrents to the session. You may react to the rss_alert when +a feed has been updated to poll it for the new items in the feed +when adding torrents manually. When torrents are added automatically, +an add_torrent_alert is posted which includes the torrent handle +as well as the error code if it failed to be added. You may also call +session::get_torrents() to get the handles to the new torrents.

+

Before adding the feed, you must set the url field to the +feed's url. It may point to an RSS or an atom feed.

+

auto_map_handles defaults to true and determines whether or +not to set the handle field in the feed_item, returned +as the feed status. If auto-download is enabled, this setting +is ignored. If auto-download is not set, setting this to false +will save one pass through all the feed items trying to find +corresponding torrents in the session.

+

The default_ttl is the default interval for refreshing a feed. +This may be overridden by the feed itself (by specifying the <ttl> +tag) and defaults to 30 minutes. The field specifies the number of +minutes between refreshes.

+

If torrents are added automatically, you may want to set the +add_args to appropriate values for download directory etc. +This object is used as a template for adding torrents from feeds, +but some torrent specific fields will be overridden by the +individual torrent being added. For more information on the +add_torrent_params, see async_add_torrent() add_torrent().

+

The returned feed_handle is a handle which is used to interact +with the feed, things like forcing a refresh or querying for +information about the items in the feed. For more information, +see feed_handle.

+
+
+

remove_feed()

+
+
+void remove_feed(feed_handle h);
+
+
+

Removes a feed from being watched by the session. When this +call returns, the feed handle is invalid and won't refer +to any feed.

+
+
+

get_feeds()

+
+
+void get_feeds(std::vector<feed_handle>& f) const;
+
+
+

Returns a list of all RSS feeds that are being watched by the session.

+
+
+

add_extension()

+
+
+void add_extension(boost::function<
+        boost::shared_ptr<torrent_plugin>(torrent*, void*)> ext);
+
+
+

This function adds an extension to this session. The argument is a function +object that is called with a torrent* and which should return a +boost::shared_ptr<torrent_plugin>. To write custom plugins, see +libtorrent plugins. For the typical bittorrent client all of these +extensions should be added. The main plugins implemented in libtorrent are:

+
+
metadata extension
+
Allows peers to download the metadata (.torren files) from the swarm +directly. Makes it possible to join a swarm with just a tracker and +info-hash.
+
+
+#include <libtorrent/extensions/metadata_transfer.hpp>
+ses.add_extension(&libtorrent::create_metadata_plugin);
+
+
+
uTorrent metadata
+
Same as metadata extension but compatible with uTorrent.
+
+
+#include <libtorrent/extensions/ut_metadata.hpp>
+ses.add_extension(&libtorrent::create_ut_metadata_plugin);
+
+
+
uTorrent peer exchange
+
Exchanges peers between clients.
+
+
+#include <libtorrent/extensions/ut_pex.hpp>
+ses.add_extension(&libtorrent::create_ut_pex_plugin);
+
+
+
smart ban plugin
+
A plugin that, with a small overhead, can ban peers +that sends bad data with very high accuracy. Should +eliminate most problems on poisoned torrents.
+
+
+#include <libtorrent/extensions/smart_ban.hpp>
+ses.add_extension(&libtorrent::create_smart_ban_plugin);
+
+
+
+

set_settings() set_pe_settings()

+
+
+void set_settings(session_settings const& settings);
+void set_pe_settings(pe_settings const& settings);
+
+
+

Sets the session settings and the packet encryption settings respectively. +See pe_settings for more information on available options.

+

set_settings() is deprecated. Use apply_settings() and the settings_pack +instead.

+
+
+

apply_settings()

+
+
+void apply_settings(settings_pack const& s);
+
+
+

Applies the settings specified by the settings_pack s. This is an +asynchronous operation that will return immediately and actually apply +the settings to the main thread of libtorrent some time later.

+
+
+

set_proxy() proxy()

+
+
+void set_proxy(proxy_settings const& s);
+proxy_setting proxy() const;
+
+
+

These functions sets and queries the proxy settings to be used for the session.

+

For more information on what settings are available for proxies, see +proxy_settings.

+
+
+

set_i2p_proxy() i2p_proxy()

+
+
+void set_i2p_proxy(proxy_settings const&);
+proxy_settings const& i2p_proxy();
+
+
+

set_i2p_proxy sets the i2p proxy, and tries to open a persistant +connection to it. The only used fields in the proxy settings structs +are hostname and port.

+

i2p_proxy returns the current i2p proxy in use.

+
+
+

start_dht() stop_dht() set_dht_settings() dht_state() is_dht_running()

+
+
+void start_dht(entry const& startup_state);
+void stop_dht();
+void set_dht_settings(dht_settings const& settings);
+entry dht_state() const;
+bool is_dht_running() const;
+
+
+

These functions are not available in case TORRENT_DISABLE_DHT is +defined. start_dht starts the dht node and makes the trackerless service +available to torrents. The startup state is optional and can contain nodes +and the node id from the previous session. The dht node state is a bencoded +dictionary with the following entries:

+
+
nodes
+
A list of strings, where each string is a node endpoint encoded in binary. If +the string is 6 bytes long, it is an IPv4 address of 4 bytes, encoded in +network byte order (big endian), followed by a 2 byte port number (also +network byte order). If the string is 18 bytes long, it is 16 bytes of IPv6 +address followed by a 2 bytes port number (also network byte order).
+
node-id
+
The node id written as a readable string as a hexadecimal number.
+
+

dht_state will return the current state of the dht node, this can be used +to start up the node again, passing this entry to start_dht. It is a good +idea to save this to disk when the session is closed, and read it up again +when starting.

+

If the port the DHT is supposed to listen on is already in use, and exception +is thrown, asio::error.

+

stop_dht stops the dht node.

+

add_dht_node adds a node to the routing table. This can be used if your +client has its own source of bootstrapping nodes.

+

set_dht_settings sets some parameters availavle to the dht node. The +struct has the following members:

+
+struct dht_settings
+{
+        int max_peers_reply;
+        int search_branching;
+        int max_fail_count;
+        int max_torrents;
+        bool restrict_routing_ips;
+        bool restrict_search_ips;
+        bool extended_routing_table;
+        bool aggressive_lookups;
+};
+
+

max_peers_reply is the maximum number of peers the node will send in +response to a get_peers message from another node.

+

search_branching is the number of concurrent search request the node will +send when announcing and refreshing the routing table. This parameter is +called alpha in the kademlia paper.

+

max_fail_count is the maximum number of failed tries to contact a node +before it is removed from the routing table. If there are known working nodes +that are ready to replace a failing node, it will be replaced immediately, +this limit is only used to clear out nodes that don't have any node that can +replace them.

+

max_torrents is the total number of torrents to track from the DHT. This +is simply an upper limit to make sure malicious DHT nodes cannot make us allocate +an unbounded amount of memory.

+

max_feed_items is the total number of feed items to store from the DHT. This +is simply an upper limit to make sure malicious DHT nodes cannot make us allocate +an unbounded amount of memory.

+

restrict_routing_ips determines if the routing table entries should restrict +entries to one per IP. This defaults to true, which helps mitigate some attacks +on the DHT. It prevents adding multiple nodes with IPs with a very close CIDR +distance.

+

restrict_search_ips determines if DHT searches should prevent adding nodes +with IPs with very close CIDR distance. This also defaults to true and helps +mitigate certain attacks on the DHT.

+

extended_routing_table makes the first buckets in the DHT routing +table fit 128, 64, 32 and 16 nodes respectively, as opposed to the +standard size of 8. All other buckets have size 8 still.

+

The dht_settings struct used to contain a service_port member to control +which port the DHT would listen on and send messages from. This field is deprecated +and ignored. libtorrent always tries to open the UDP socket on the same port +as the TCP socket.

+

aggressive_lookups slightly changes the lookup behavior in terms of how +many outstanding requests we keep. Instead of having branch factor be a hard +limit, we always keep branch factor outstanding requests to the closest nodes. +i.e. every time we get results back with closer nodes, we query them right away. +It lowers the lookup times at the cost of more outstanding queries.

+

is_dht_running() returns true if the DHT support has been started and false +otherwise.

+
+
+

add_dht_node() add_dht_router()

+
+
+void add_dht_node(std::pair<std::string, int> const& node);
+void add_dht_router(std::pair<std::string, int> const& node);
+
+
+

add_dht_node takes a host name and port pair. That endpoint will be +pinged, and if a valid DHT reply is received, the node will be added to +the routing table.

+

add_dht_router adds the given endpoint to a list of DHT router nodes. +If a search is ever made while the routing table is empty, those nodes will +be used as backups. Nodes in the router node list will also never be added +to the regular routing table, which effectively means they are only used +for bootstrapping, to keep the load off them.

+

An example routing node that you could typically add is +router.bittorrent.com.

+
+
+

start_lsd() stop_lsd()

+
+
+void start_lsd();
+void stop_lsd();
+
+
+

Starts and stops Local Service Discovery. This service will broadcast +the infohashes of all the non-private torrents on the local network to +look for peers on the same swarm within multicast reach.

+

It is turned off by default.

+
+
+

start_upnp() stop_upnp()

+
+
+upnp* start_upnp();
+void stop_upnp();
+
+
+

Starts and stops the UPnP service. When started, the listen port and the DHT +port are attempted to be forwarded on local UPnP router devices.

+

The upnp object returned by start_upnp() can be used to add and remove +arbitrary port mappings. Mapping status is returned through the +portmap_alert and the portmap_error_alert. The object will be valid until +stop_upnp() is called. See UPnP and NAT-PMP.

+

It is off by default.

+
+
+

start_natpmp() stop_natpmp()

+
+
+natpmp* start_natpmp();
+void stop_natpmp();
+
+
+

Starts and stops the NAT-PMP service. When started, the listen port and the DHT +port are attempted to be forwarded on the router through NAT-PMP.

+

The natpmp object returned by start_natpmp() can be used to add and remove +arbitrary port mappings. Mapping status is returned through the +portmap_alert and the portmap_error_alert. The object will be valid until +stop_natpmp() is called. See UPnP and NAT-PMP.

+

It is off by default.

+
+
+
+

entry

+

The entry class represents one node in a bencoded hierarchy. It works as a +variant type, it can be either a list, a dictionary (std::map), an integer +or a string. This is its synopsis:

+
+class entry
+{
+public:
+
+        typedef std::map<std::string, entry> dictionary_type;
+        typedef std::string string_type;
+        typedef std::list<entry> list_type;
+        typedef size_type integer_type;
+
+        enum data_type
+        {
+                int_t,
+                string_t,
+                list_t,
+                dictionary_t,
+                undefined_t
+        };
+
+        data_type type() const;
+
+        entry(dictionary_type const&);
+        entry(string_type const&);
+        entry(list_type const&);
+        entry(integer_type const&);
+
+        entry();
+        entry(data_type t);
+        entry(entry const& e);
+        ~entry();
+
+        void operator=(entry const& e);
+        void operator=(dictionary_type const&);
+        void operator=(string_type const&);
+        void operator=(list_type const&);
+        void operator=(integer_type const&);
+
+        integer_type& integer();
+        integer_type const& integer() const;
+        string_type& string();
+        string_type const& string() const;
+        list_type& list();
+        list_type const& list() const;
+        dictionary_type& dict();
+        dictionary_type const& dict() const;
+
+        // these functions requires that the entry
+        // is a dictionary, otherwise they will throw
+        entry& operator[](char const* key);
+        entry& operator[](std::string const& key);
+        entry const& operator[](char const* key) const;
+        entry const& operator[](std::string const& key) const;
+        entry* find_key(char const* key);
+        entry const* find_key(char const* key) const;
+
+        void print(std::ostream& os, int indent = 0) const;
+};
+
+

TODO: finish documentation of entry.

+
+

integer() string() list() dict() type()

+
+
+integer_type& integer();
+integer_type const& integer() const;
+string_type& string();
+string_type const& string() const;
+list_type& list();
+list_type const& list() const;
+dictionary_type& dict();
+dictionary_type const& dict() const;
+
+
+

The integer(), string(), list() and dict() functions +are accessors that return the respective type. If the entry object isn't of the +type you request, the accessor will throw libtorrent_exception (which derives from +std::runtime_error). You can ask an entry for its type through the +type() function.

+

The print() function is there for debug purposes only.

+

If you want to create an entry you give it the type you want it to have in its +constructor, and then use one of the non-const accessors to get a reference which you then +can assign the value you want it to have.

+

The typical code to get info from a torrent file will then look like this:

+
+entry torrent_file;
+// ...
+
+// throws if this is not a dictionary
+entry::dictionary_type const& dict = torrent_file.dict();
+entry::dictionary_type::const_iterator i;
+i = dict.find("announce");
+if (i != dict.end())
+{
+        std::string tracker_url = i->second.string();
+        std::cout << tracker_url << "\n";
+}
+
+

The following code is equivalent, but a little bit shorter:

+
+entry torrent_file;
+// ...
+
+// throws if this is not a dictionary
+if (entry* i = torrent_file.find_key("announce"))
+{
+        std::string tracker_url = i->string();
+        std::cout << tracker_url << "\n";
+}
+
+

To make it easier to extract information from a torrent file, the class torrent_info +exists.

+
+
+

operator[]

+
+
+entry& operator[](char const* key);
+entry& operator[](std::string const& key);
+entry const& operator[](char const* key) const;
+entry const& operator[](std::string const& key) const;
+
+
+

All of these functions requires the entry to be a dictionary, if it isn't they +will throw libtorrent::type_error.

+

The non-const versions of the operator[] will return a reference to either +the existing element at the given key or, if there is no element with the +given key, a reference to a newly inserted element at that key.

+

The const version of operator[] will only return a reference to an +existing element at the given key. If the key is not found, it will throw +libtorrent::type_error.

+
+
+

find_key()

+
+
+entry* find_key(char const* key);
+entry const* find_key(char const* key) const;
+
+
+

These functions requires the entry to be a dictionary, if it isn't they +will throw libtorrent::type_error.

+

They will look for an element at the given key in the dictionary, if the +element cannot be found, they will return 0. If an element with the given +key is found, the return a pointer to it.

+
+
+
+

torrent_info

+

In previous versions of libtorrent, this class was also used for creating +torrent files. This functionality has been moved to create_torrent, see +make_torrent.

+

The torrent_info has the following synopsis:

+
+class torrent_info
+{
+public:
+
+        // these constructors throws exceptions on error
+        torrent_info(sha1_hash const& info_hash, int flags = 0);
+        torrent_info(lazy_entry const& torrent_file, int flags = 0);
+        torrent_info(char const* buffer, int size, int flags = 0);
+        torrent_info(std::string const& filename, int flags = 0);
+        torrent_info(std::wstring const& filename, int flags = 0);
+
+        // these constructors sets the error code on error
+        torrent_info(sha1_hash const& info_hash, error_code& ec, int flags = 0);
+        torrent_info(lazy_entry const& torrent_file, error_code& ec, int flags = 0);
+        torrent_info(char const* buffer, int size, error_code& ec, int flags = 0);
+        torrent_info(fs::path const& filename, error_code& ec, int flags = 0);
+        torrent_info(fs::wpath const& filename, error_code& ec, int flags = 0);
+
+        void add_tracker(std::string const& url, int tier = 0);
+        std::vector<announce_entry> const& trackers() const;
+
+        file_storage const& files() const;
+        file_storage const& orig_files() const;
+
+        void remap_files(file_storage const& f);
+
+        void rename_file(int index, std::string const& new_filename);
+        void rename_file(int index, std::wstring const& new_filename);
+
+        typedef file_storage::iterator file_iterator;
+        typedef file_storage::reverse_iterator reverse_file_iterator;
+
+        file_iterator begin_files() const;
+        file_iterator end_files() const;
+        reverse_file_iterator rbegin_files() const;
+        reverse_file_iterator rend_files() const;
+
+        int num_files() const;
+        file_entry const& file_at(int index) const;
+
+        std::vector<file_slice> map_block(int piece, size_type offset
+                , int size) const;
+        peer_request map_file(int file_index, size_type file_offset
+                , int size) const;
+
+        bool priv() const;
+
+        void add_url_seed(std::string const& url);
+        void add_http_seed(std::string const& url);
+        std::vector<web_seed_entry> const& web_seeds() const;
+
+        size_type total_size() const;
+        int piece_length() const;
+        int num_pieces() const;
+        sha1_hash const& info_hash() const;
+        std::string const& name() const;
+        std::string const& comment() const;
+        std::string const& creator() const;
+
+        std::vector<std::pair<std::string, int> > const& nodes() const;
+        void add_node(std::pair<std::string, int> const& node);
+
+        boost::optional<time_t> creation_date() const;
+
+        int piece_size(unsigned int index) const;
+        sha1_hash const& hash_for_piece(unsigned int index) const;
+        char const* hash_for_piece_ptr(unsigned int index) const;
+
+        std::vector<sha1_hash> const& merkle_tree() const;
+        void set_merkle_tree(std::vector<sha1_hash>& h);
+
+        boost::shared_array<char> metadata() const;
+        int metadata_size() const;
+};
+
+
+

torrent_info()

+
+
+torrent_info(sha1_hash const& info_hash, int flags = 0);
+torrent_info(lazy_entry const& torrent_file, int flags = 0);
+torrent_info(char const* buffer, int size, int flags = 0);
+torrent_info(std::string const& filename, int flags = 0);
+torrent_info(std::wstring const& filename, int flags = 0);
+
+torrent_info(sha1_hash const& info_hash, error_code& ec, int flags = 0);
+torrent_info(lazy_entry const& torrent_file, error_code& ec, int flags = 0);
+torrent_info(char const* buffer, int size, error_code& ec, int flags = 0);
+torrent_info(fs::path const& filename, error_code& ec, int flags = 0);
+torrent_info(fs::wpath const& filename, error_code& ec, int flags = 0);
+
+
+

The constructor that takes an info-hash will initialize the info-hash to the given value, +but leave all other fields empty. This is used internally when downloading torrents without +the metadata. The metadata will be created by libtorrent as soon as it has been downloaded +from the swarm.

+

The constructor that takes a lazy_entry will create a torrent_info object from the +information found in the given torrent_file. The lazy_entry represents a tree node in +an bencoded file. To load an ordinary .torrent file +into a lazy_entry, use lazy_bdecode().

+

The version that takes a buffer pointer and a size will decode it as a .torrent file and +initialize the torrent_info object for you.

+

The version that takes a filename will simply load the torrent file and decode it inside +the constructor, for convenience. This might not be the most suitable for applications that +want to be able to report detailed errors on what might go wrong.

+

The overloads that takes an error_code const& never throws if an error occur, they +will simply set the error code to describe what went wrong and not fully initialize the +torrent_info object. The overloads that do not take the extra error_code parameter will +always throw if an error occurs. These overloads are not available when building without +exception support.

+

The flags argument is currently unused.

+
+
+

add_tracker()

+
+
+void add_tracker(std::string const& url, int tier = 0);
+
+
+

add_tracker() adds a tracker to the announce-list. The tier determines the order in +which the trackers are to be tried. For more information see trackers().

+
+
+

files() orig_files()

+
+
+file_storage const& files() const;
+file_storage const& orig_files() const;
+
+
+

The file_storage object contains the information on how to map the pieces to +files. It is separated from the torrent_info object because when creating torrents +a storage object needs to be created without having a torrent file. When renaming files +in a storage, the storage needs to make its own copy of the file_storage in order +to make its mapping differ from the one in the torrent file.

+

orig_files() returns the original (unmodified) file storage for this torrent. This +is used by the web server connection, which needs to request files with the original +names. Filename may be chaged using torrent_info::rename_file().

+

For more information on the file_storage object, see the separate document on how +to create torrents.

+
+
+

remap_files()

+
+
+void remap_files(file_storage const& f);
+
+
+

Remaps the file storage to a new file layout. This can be used to, for instance, +download all data in a torrent to a single file, or to a number of fixed size +sector aligned files, regardless of the number and sizes of the files in the torrent.

+

The new specified file_storage must have the exact same size as the current one.

+
+
+

rename_file()

+
+
+void rename_file(int index, std::string const& new_filename);
+void rename_file(int index, std::wstring const& new_filename);
+
+
+

Renames a the file with the specified index to the new name. The new filename is +reflected by the file_storage returned by files() but not by the one +returned by orig_files().

+

If you want to rename the base name of the torrent (for a multifile torrent), you +can copy the file_storage (see files() orig_files()), change the name, and +then use remap_files().

+

The new_filename can both be a relative path, in which case the file name +is relative to the save_path of the torrent. If the new_filename is +an absolute path (i.e. is_complete(new_filename) == true), then the file +is detached from the save_path of the torrent. In this case the file is +not moved when move_storage_ is invoked.

+
+
+

begin_files() end_files() rbegin_files() rend_files()

+
+
+file_iterator begin_files() const;
+file_iterator end_files() const;
+reverse_file_iterator rbegin_files() const;
+reverse_file_iterator rend_files() const;
+
+
+

This class will need some explanation. First of all, to get a list of all files +in the torrent, you can use begin_files(), end_files(), +rbegin_files() and rend_files(). These will give you standard vector +iterators with the type internal_file_entry, which is an internal type.

+

You can resolve it into the public representation of a file (file_entry) +using the file_storage::at function, which takes an index and an iterator;

+
+struct file_entry
+{
+        std::string path;
+        size_type offset;
+        size_type size;
+        size_type file_base;
+        time_t mtime;
+        sha1_hash filehash;
+        bool pad_file:1;
+        bool hidden_attribute:1;
+        bool executable_attribute:1;
+        bool symlink_attribute:1;
+};
+
+

The path is the full path of this file. The paths are unicode strings +encoded in UTF-8.

+

size is the size of the file (in bytes) and offset is the byte offset +of the file within the torrent. i.e. the sum of all the sizes of the files +before it in the list.

+

file_base is the offset in the file where the storage should start. The normal +case is to have this set to 0, so that the storage starts saving data at the start +if the file. In cases where multiple files are mapped into the same file though, +the file_base should be set to an offset so that the different regions do +not overlap. This is used when mapping "unselected" files into a so-called part +file.

+

mtime is the modification time of this file specified in posix time.

+

symlink_path is the path which this is a symlink to, or empty if this is +not a symlink. This field is only used if the symlink_attribute is set.

+

filehash is a sha-1 hash of the content of the file, or zeroes, if no +file hash was present in the torrent file. It can be used to potentially +find alternative sources for the file.

+

pad_file is set to true for files that are not part of the data of the torrent. +They are just there to make sure the next file is aligned to a particular byte offset +or piece boundry. These files should typically be hidden from an end user. They are +not written to disk.

+

hidden_attribute is true if the file was marked as hidden (on windows).

+

executable_attribute is true if the file was marked as executable (posix)

+

symlink_attribute is true if the file was a symlink. If this is the case +the symlink_index refers to a string which specifies the original location +where the data for this file was found.

+
+
+

num_files() file_at()

+
+
+int num_files() const;
+file_entry const& file_at(int index) const;
+
+
+

If you need index-access to files you can use the num_files() and file_at() +to access files using indices.

+
+
+

map_block()

+
+
+std::vector<file_slice> map_block(int piece, size_type offset
+        , int size) const;
+
+
+

This function will map a piece index, a byte offset within that piece and +a size (in bytes) into the corresponding files with offsets where that data +for that piece is supposed to be stored.

+

The file slice struct looks like this:

+
+struct file_slice
+{
+        int file_index;
+        size_type offset;
+        size_type size;
+};
+
+

The file_index refers to the index of the file (in the torrent_info). +To get the path and filename, use file_at() and give the file_index +as argument. The offset is the byte offset in the file where the range +starts, and size is the number of bytes this range is. The size + offset +will never be greater than the file size.

+
+
+

map_file()

+
+
+peer_request map_file(int file_index, size_type file_offset
+        , int size) const;
+
+
+

This function will map a range in a specific file into a range in the torrent. +The file_offset parameter is the offset in the file, given in bytes, where +0 is the start of the file. +The peer_request structure looks like this:

+
+struct peer_request
+{
+        int piece;
+        int start;
+        int length;
+        bool operator==(peer_request const& r) const;
+};
+
+

piece is the index of the piece in which the range starts. +start is the offset within that piece where the range starts. +length is the size of the range, in bytes.

+

The input range is assumed to be valid within the torrent. file_offset ++ size is not allowed to be greater than the file size. file_index +must refer to a valid file, i.e. it cannot be >= num_files().

+
+
+

add_url_seed() add_http_seed()

+
+
+void add_url_seed(std::string const& url
+        , std::string const& extern_auth = std::string()
+        , web_seed_entry::headers_t const& extra_headers = web_seed_entry::headers_t());
+void add_http_seed(std::string const& url
+        , std::string const& extern_auth = std::string()
+        , web_seed_entry::headers_t const& extra_headers = web_seed_entry::headers_t());
+std::vector<web_seed_entry> const& web_seeds() const;
+
+
+

web_seeds() returns all url seeds and http seeds in the torrent. Each entry +is a web_seed_entry and may refer to either a url seed or http seed.

+

add_url_seed() and add_http_seed() adds one url to the list of +url/http seeds. Currently, the only transport protocol supported for the url +is http.

+

The extern_auth argument can be used for other athorization schemese than +basic HTTP authorization. If set, it will override any username and password +found in the URL itself. The string will be sent as the HTTP authorization header's +value (without specifying "Basic").

+

The extra_headers argument defaults to an empty list, but can be used to +insert custom HTTP headers in the requests to a specific web seed.

+

See HTTP seeding for more information.

+

The web_seed_entry has the following members:

+
+struct web_seed_entry
+{
+        enum type_t { url_seed, http_seed };
+
+        typedef std::vector<std::pair<std::string, std::string> > headers_t;
+
+        web_seed_entry(std::string const& url_, type_t type_
+                , std::string const& auth_ = std::string()
+                , headers_t const& extra_headers_ = headers_t());
+
+        bool operator==(web_seed_entry const& e) const;
+        bool operator<(web_seed_entry const& e) const;
+
+        std::string url;
+        type_t type;
+        std::string auth;
+        headers_t extra_headers;
+
+        // ...
+};
+
+
+
+

trackers()

+
+
+std::vector<announce_entry> const& trackers() const;
+
+
+

The trackers() function will return a sorted vector of announce_entry. +Each announce entry contains a string, which is the tracker url, and a tier index. The +tier index is the high-level priority. No matter which trackers that works or not, the +ones with lower tier will always be tried before the one with higher tier number.

+
+struct announce_entry
+{
+        announce_entry(std::string const& url);
+        std::string url;
+
+        int next_announce_in() const;
+        int min_announce_in() const;
+
+        int scrape_incomplete;
+        int scrape_complete;
+        int scrape_downloaded;
+
+        error_code last_error;
+
+        std::string message;
+
+        boost::uint8_t tier;
+        boost::uint8_t fail_limit;
+        boost::uint8_t fails;
+
+        enum tracker_source
+        {
+                source_torrent = 1,
+                source_client = 2,
+                source_magnet_link = 4,
+                source_tex = 8
+        };
+        boost::uint8_t source;
+
+        bool verified:1;
+        bool updating:1;
+        bool start_sent:1;
+        bool complete_sent:1;
+};
+
+

next_announce_in() returns the number of seconds to the next announce on +this tracker. min_announce_in() returns the number of seconds until we are +allowed to force another tracker update with this tracker.

+

If the last time this tracker was contacted failed, last_error is the error +code describing what error occurred.

+

scrape_incomplete, scrape_complete and scrape_downloaded are either +-1 or the scrape information this tracker last responded with. incomplete is +the current number of downloaders in the swarm, complete is the current number +of seeds in the swarm and downloaded is the cumulative number of completed +downloads of this torrent, since the beginning of time (from this tracker's point +of view).

+

If the last time this tracker was contacted, the tracker returned a warning +or error message, message contains that message.

+

fail_limit is the max number of failures to announce to this tracker in +a row, before this tracker is not used anymore.

+

fails is the number of times in a row we have failed to announce to this +tracker.

+

source is a bitmask specifying which sources we got this tracker from.

+

verified is set to true the first time we receive a valid response +from this tracker.

+

updating is true while we're waiting for a response from the tracker.

+

start_sent is set to true when we get a valid response from an announce +with event=started. If it is set, we won't send start in the subsequent +announces.

+

complete_sent is set to true when we send a event=completed.

+
+
+

total_size() piece_length() piece_size() num_pieces()

+
+
+size_type total_size() const;
+int piece_length() const;
+int piece_size(unsigned int index) const;
+int num_pieces() const;
+
+
+

total_size(), piece_length() and num_pieces() returns the total +number of bytes the torrent-file represents (all the files in it), the number of byte for +each piece and the total number of pieces, respectively. The difference between +piece_size() and piece_length() is that piece_size() takes +the piece index as argument and gives you the exact size of that piece. It will always +be the same as piece_length() except in the case of the last piece, which may +be smaller.

+
+
+

hash_for_piece() hash_for_piece_ptr() info_hash()

+
+
+size_type piece_size(unsigned int index) const;
+sha1_hash const& hash_for_piece(unsigned int index) const;
+char const* hash_for_piece_ptr(unsigned int index) const;
+
+
+

hash_for_piece() takes a piece-index and returns the 20-bytes sha1-hash for that +piece and info_hash() returns the 20-bytes sha1-hash for the info-section of the +torrent file. For more information on the sha1_hash, see the big_number class. +hash_for_piece_ptr() returns a pointer to the 20 byte sha1 digest for the piece. +Note that the string is not null-terminated.

+
+
+

merkle_tree() set_merkle_tree()

+
+
+std::vector<sha1_hash> const& merkle_tree() const;
+void set_merkle_tree(std::vector<sha1_hash>& h);
+
+
+

merkle_tree() returns a reference to the merkle tree for this torrent, if any.

+

set_merkle_tree() moves the passed in merkle tree into the torrent_info object. +i.e. h will not be identical after the call. You need to set the merkle tree for +a torrent that you've just created (as a merkle torrent). The merkle tree is retrieved +from the create_torrent::merkle_tree() function, and need to be saved separately +from the torrent file itself. Once it's added to libtorrent, the merkle tree will be +persisted in the resume data.

+
+
+

name() comment() creation_date() creator()

+
+
+std::string const& name() const;
+std::string const& comment() const;
+std::string const& creator() const;
+boost::optional<time_t> creation_date() const;
+
+
+

name() returns the name of the torrent.

+

comment() returns the comment associated with the torrent. If there's no comment, +it will return an empty string. creation_date() returns the creation date of +the torrent as time_t (posix time). If there's no time stamp in the torrent file, +the optional object will be uninitialized.

+

Both the name and the comment is UTF-8 encoded strings.

+

creator() returns the creator string in the torrent. If there is no creator string +it will return an empty string.

+
+
+

priv()

+
+
+bool priv() const;
+
+
+

priv() returns true if this torrent is private. i.e., it should not be +distributed on the trackerless network (the kademlia DHT).

+
+
+

nodes()

+
+
+std::vector<std::pair<std::string, int> > const& nodes() const;
+
+
+

If this torrent contains any DHT nodes, they are put in this vector in their original +form (host name and port number).

+
+
+

add_node()

+
+
+void add_node(std::pair<std::string, int> const& node);
+
+
+

This is used when creating torrent. Use this to add a known DHT node. It may +be used, by the client, to bootstrap into the DHT network.

+
+
+

metadata() metadata_size()

+
+
+boost::shared_array<char> metadata() const;
+int metadata_size() const;
+
+
+

metadata() returns a the raw info section of the torrent file. The size +of the metadata is returned by metadata_size().

+
+
+
+

torrent_handle

+

You will usually have to store your torrent handles somewhere, since it's the +object through which you retrieve information about the torrent and aborts the torrent.

+
+

Warning

+

Any member function that returns a value or fills in a value has to +be made synchronously. This means it has to wait for the main thread +to complete the query before it can return. This might potentially be +expensive if done from within a GUI thread that needs to stay responsive. +Try to avoid quering for information you don't need, and try to do it +in as few calls as possible. You can get most of the interesting information +about a torrent from the torrent_handle::status() call.

+
+

Its declaration looks like this:

+
+struct torrent_handle
+{
+        torrent_handle();
+
+        enum status_flags_t
+        {
+                query_distributed_copies = 1,
+                query_accurate_download_counters = 2,
+                query_last_seen_complete = 4,
+                query_pieces = 8,
+                query_verified_pieces = 16,
+                query_torrent_file = 32,
+                query_name = 64,
+                query_save_path = 128,
+        };
+
+        torrent_status status(boost::uint32_t flags = 0xffffffff);
+        void file_progress(std::vector<size_type>& fp, int flags = 0);
+        void file_status(std::vector<pool_file_status>& status);
+        void get_download_queue(std::vector<partial_piece_info>& queue) const;
+        void get_peer_info(std::vector<peer_info>& v) const;
+        boost::intrusive_ptr<torrent_info> torrent_file() const;
+        bool is_valid() const;
+
+        enum save_resume_flags_t { flush_disk_cache = 1, save_info_dict = 2 };
+        void save_resume_data(int flags = 0) const;
+        bool need_save_resume_data() const;
+        void force_reannounce() const;
+        void force_dht_announce() const;
+        void force_reannounce(boost::posix_time::time_duration) const;
+        void scrape_tracker() const;
+        void connect_peer(asio::ip::tcp::endpoint const& adr, int source = 0, int flags = 0) const;
+
+        void set_tracker_login(std::string const& username
+                , std::string const& password) const;
+
+        std::vector<announce_entry> trackers() const;
+        void replace_trackers(std::vector<announce_entry> const&);
+        void add_tracker(announce_entry const& url);
+
+        void add_url_seed(std::string const& url);
+        void remove_url_seed(std::string const& url);
+        std::set<std::string> url_seeds() const;
+
+        void add_http_seed(std::string const& url);
+        void remove_http_seed(std::string const& url);
+        std::set<std::string> http_seeds() const;
+
+        int max_uploads() const;
+        void set_max_uploads(int max_uploads) const;
+        void set_max_connections(int max_connections) const;
+        int max_connections() const;
+        void set_upload_limit(int limit) const;
+        int upload_limit() const;
+        void set_download_limit(int limit) const;
+        int download_limit() const;
+        void set_sequential_download(bool sd) const;
+        bool is_sequential_download() const;
+        void set_pinned(bool p) const;
+
+        int queue_position() const;
+        void queue_position_up() const;
+        void queue_position_down() const;
+        void queue_position_top() const;
+        void queue_position_bottom() const;
+
+        void set_priority(int prio) const;
+
+        enum pause_flags_t { graceful_pause = 1 };
+        void pause(int flags = 0) const;
+        void resume() const;
+        bool is_paused() const;
+
+        void set_load_function(boost::function<void(sha1_hash const&
+                , std::vector<char>&, error_code& ec)> fun);
+
+        bool is_seed() const;
+        void force_recheck() const;
+        void clear_error() const;
+        void set_upload_mode(bool m) const;
+        void set_share_mode(bool m) const;
+
+        void apply_ip_filter(bool b) const;
+
+        void flush_cache() const;
+
+        void resolve_countries(bool r);
+        bool resolve_countries() const;
+
+        enum deadline_flags { alert_when_available = 1 };
+        void set_piece_deadline(int index, int deadline, int flags = 0) const;
+        void reset_piece_deadline(int index) const;
+
+        void piece_availability(std::vector<int>& avail) const;
+        void piece_priority(int index, int priority) const;
+        int piece_priority(int index) const;
+        void prioritize_pieces(std::vector<int> const& pieces) const;
+        void prioritize_pieces(std::vector<std::pair<int, int> > const& pieces) const;
+        std::vector<int> piece_priorities() const;
+
+        void file_priority(int index, int priority) const;
+        int file_priority(int index) const;
+        void prioritize_files(std::vector<int> const& files) const;
+        std::vector<int> file_priorities() const;
+
+        void auto_managed(bool m) const;
+
+        bool set_metadata(char const* buf, int size) const;
+
+        void move_storage(std::string const& save_path, int flags = 0) const;
+        void move_storage(std::wstring const& save_path, int flags = 0) const;
+        void rename_file(int index, std::string) const;
+        void rename_file(int index, std::wstring) const;
+        storage_interface* get_storage_impl() const;
+
+        void super_seeding(bool on) const;
+
+        enum flags_t { overwrite_existing = 1 };
+        void add_piece(int piece, char const* data, int flags = 0) const;
+        void read_piece(int piece) const;
+        bool have_piece(int piece) const;
+
+        sha1_hash info_hash() const;
+
+        void set_ssl_certificate(std::string const& cert
+                , std::string const& private_key
+                , std::string const& dh_params
+                , std::string const& passphrase = "");
+
+        bool operator==(torrent_handle const&) const;
+        bool operator!=(torrent_handle const&) const;
+        bool operator<(torrent_handle const&) const;
+
+        boost::shared_ptr<torrent> native_handle() const;
+};
+
+

The default constructor will initialize the handle to an invalid state. Which +means you cannot perform any operation on it, unless you first assign it a +valid handle. If you try to perform any operation on an uninitialized handle, +it will throw invalid_handle.

+
+

Warning

+

All operations on a torrent_handle may throw libtorrent_exception +exception, in case the handle is no longer refering to a torrent. There is +one exception is_valid() will never throw. +Since the torrents are processed by a background thread, there is no +guarantee that a handle will remain valid between two calls.

+
+
+

set_piece_deadline() reset_piece_deadline()

+
+
+enum deadline_flags { alert_when_available = 1 };
+void set_piece_deadline(int index, int deadline, int flags = 0) const;
+void reset_piece_deadline(int index) const;
+
+
+

This function sets or resets the deadline associated with a specific piece +index (index). libtorrent will attempt to download this entire piece before +the deadline expires. This is not necessarily possible, but pieces with a more +recent deadline will always be prioritized over pieces with a deadline further +ahead in time. The deadline (and flags) of a piece can be changed by calling this +function again.

+

The flags parameter can be used to ask libtorrent to send an alert once the +piece has been downloaded, by passing alert_when_available. When set, the +read_piece_alert alert will be delivered, with the piece data, when it's downloaded.

+

If the piece is already downloaded when this call is made, nothing happens, unless +the alert_when_available flag is set, in which case it will do the same thing +as calling read_piece() for index.

+

deadline is the number of milliseconds until this piece should be completed.

+

reset_piece_deadline removes the deadline from the piece. If it hasn't already +been downloaded, it will no longer be considered a priority.

+
+
+

piece_availability()

+
+
+void piece_availability(std::vector<int>& avail) const;
+
+
+

Fills the specified std::vector<int> with the availability for each +piece in this torrent. libtorrent does not keep track of availability for +seeds, so if the torrent is seeding the availability for all pieces is +reported as 0.

+

The piece availability is the number of peers that we are connected that has +advertized having a particular piece. This is the information that libtorrent +uses in order to prefer picking rare pieces.

+
+
+

piece_priority() prioritize_pieces() piece_priorities()

+
+
+void piece_priority(int index, int priority) const;
+int piece_priority(int index) const;
+void prioritize_pieces(std::vector<int> const& pieces) const;
+void prioritize_pieces(std::vector<std::pair<int, int> > const& pieces) const;
+std::vector<int> piece_priorities() const;
+
+
+

These functions are used to set and get the prioritiy of individual pieces. +By default all pieces have priority 1. That means that the random rarest +first algorithm is effectively active for all pieces. You may however +change the priority of individual pieces. There are 8 different priority +levels:

+
+
    +
  1. piece is not downloaded at all
  2. +
  3. normal priority. Download order is dependent on availability
  4. +
  5. higher than normal priority. Pieces are preferred over pieces with +the same availability, but not over pieces with lower availability
  6. +
  7. pieces are as likely to be picked as partial pieces.
  8. +
  9. pieces are preferred over partial pieces, but not over pieces with +lower availability
  10. +
  11. currently the same as 4
  12. +
  13. piece is as likely to be picked as any piece with availability 1
  14. +
  15. maximum priority, availability is disregarded, the piece is preferred +over any other piece with lower priority
  16. +
+
+

The exact definitions of these priorities are implementation details, and +subject to change. The interface guarantees that higher number means higher +priority, and that 0 means do not download.

+

piece_priority sets or gets the priority for an individual piece, +specified by index.

+

prioritize_pieces takes a vector of integers, one integer per piece in +the torrent. All the piece priorities will be updated with the priorities +in the vector.

+

The second overload of prioritize_pieces that takes a vector of pairs +will update the priorities of only select pieces, and leave all other +unaffected. Each pair is (piece, priority). That is, the first item is +the piece index and the second item is the priority of that piece. +Invalid entries, where the piece index or priority is out of range, are +not allowed.

+

piece_priorities returns a vector with one element for each piece in the +torrent. Each element is the current priority of that piece.

+
+
+

file_priority() prioritize_files() file_priorities()

+
+
+void file_priority(int index, int priority) const;
+int file_priority(int index) const;
+void prioritize_files(std::vector<int> const& files) const;
+std::vector<int> file_priorities() const;
+
+
+

index must be in the range [0, number_of_files).

+

file_priority queries or sets the priority of file index.

+

prioritize_files takes a vector that has at as many elements as there are +files in the torrent. Each entry is the priority of that file. The function +sets the priorities of all the pieces in the torrent based on the vector.

+

file_priorities returns a vector with the priorities of all files.

+

The priority values are the same as for piece_priority.

+

Whenever a file priority is changed, all other piece priorities are reset +to match the file priorities. In order to maintain sepcial priorities for +particular pieces, piece_priority has to be called again for those pieces.

+

You cannot set the file priorities on a torrent that does not yet +have metadata or a torrent that is a seed. file_priority(int, int) and +prioritize_files() are both no-ops for such torrents.

+
+
+

file_progress()

+
+
+void file_progress(std::vector<size_type>& fp, int flags = 0);
+
+
+

This function fills in the supplied vector with the the number of bytes downloaded +of each file in this torrent. The progress values are ordered the same as the files +in the torrent_info. This operation is not very cheap. Its complexity is O(n + mj). +Where n is the number of files, m is the number of downloading pieces and j +is the number of blocks in a piece.

+

The flags parameter can be used to specify the granularity of the file progress. If +left at the default value of 0, the progress will be as accurate as possible, but also +more expensive to calculate. If torrent_handle::piece_granularity is specified, +the progress will be specified in piece granularity. i.e. only pieces that have been +fully downloaded and passed the hash check count. When specifying piece granularity, +the operation is a lot cheaper, since libtorrent already keeps track of this internally +and no calculation is required.

+
+<<<<<<< .working +
+

file_status()

+
+
+void file_status(std::vector<pool_file_status>& status);
+
+
+

This function fills in the passed in vector with status about files that are open +for this torrent. Any file that is not open in this torrent, will not be reported +in the vector, i.e. it's possible that the vector is empty when returning, if none +of the files in the torrent are currently open.

+

The pool_file_status is defined as:

+
+struct pool_file_status
+{
+        int file_index;
+        ptime last_use;
+        int open_mode;
+};
+
+

file_index is the index of the file this entry refers to into the file_storage +file list of this torrent. This starts indexing at 0.

+

last_use is a (high precision) timestamp of when the file was last used.

+

open_mode is a bitmask of the file flags this file is currently opened with. These +are the flags used in the file::open() function. This enum is defined as a member +of the file class.

+
+enum
+{
+        read_only = 0,
+        write_only = 1,
+        read_write = 2,
+        rw_mask = 3,
+        no_buffer = 4,
+        sparse = 8,
+        no_atime = 16,
+        random_access = 32,
+        lock_file = 64,
+};
+
+

Note that the read/write mode is not a bitmask. The two least significant bits are used +to represent the read/write mode. Those bits can be masked out using the rw_mask constant.

+
+
+

save_path()

+
+
+std::string save_path() const;
+
+
+

save_path() returns the path that was given to async_add_torrent() add_torrent() when this torrent +was started.

+
+======= +>>>>>>> .merge-right.r8585 +
+

move_storage()

+
+
+void move_storage(std::string const& save_path, int flags = 0) const;
+void move_storage(std::wstring const& save_path, int flags = 0) const;
+
+
+

Moves the file(s) that this torrent are currently seeding from or downloading to. If +the given save_path is not located on the same drive as the original save path, +the files will be copied to the new drive and removed from their original location. +This will block all other disk IO, and other torrents download and upload rates may +drop while copying the file.

+

Since disk IO is performed in a separate thread, this operation is also asynchronous. +Once the operation completes, the storage_moved_alert is generated, with the new +path as the message. If the move fails for some reason, storage_moved_failed_alert +is generated instead, containing the error message.

+

The flags argument determines the behavior of the copying/moving of the files +in the torrent. They are defined in include/libtorrent/storage.hpp:

+
+
    +
  • always_replace_files = 0
  • +
  • fail_if_exist = 1
  • +
  • dont_replace = 2
  • +
+
+

always_replace_files is the default and replaces any file that exist in both the +source directory and the target directory.

+

fail_if_exist first check to see that none of the copy operations would cause an +overwrite. If it would, it will fail. Otherwise it will proceed as if it was in +always_replace_files mode. Note that there is an inherent race condition here. +If the files in the target directory appear after the check but before the copy +or move completes, they will be overwritten. When failing because of files already +existing in the target path, the error of move_storage_failed_alert is set +to boost::system::errc::file_exists.

+

The intention is that a client may use this as a probe, and if it fails, ask the user +which mode to use. The client may then re-issue the move_storage call with one +of the other modes.

+

dont_replace always takes the existing file in the target directory, if there is +one. The source files will still be removed in that case.

+

Files that have been renamed to have absolute pahts are not moved by this function. +Keep in mind that files that don't belong to the torrent but are stored in the torrent's +directory may be moved as well. This goes for files that have been renamed to +absolute paths that still end up inside the save path.

+
+
+

rename_file()

+
+
+void rename_file(int index, std::string) const;
+void rename_file(int index, std::wstring) const;
+
+
+

Renames the file with the given index asynchronously. The rename operation is complete +when either a file_renamed_alert or file_rename_failed_alert is posted.

+
+
+

get_storage_impl()

+
+
+storage_interface* get_storage_impl() const;
+
+
+

Returns the storage implementation for this torrent. This depends on the +storage contructor function that was passed to session::add_torrent.

+
+
+

super_seeding()

+
+
+void super_seeding(bool on) const;
+
+
+

Enables or disabled super seeding/initial seeding for this torrent. The torrent +needs to be a seed for this to take effect.

+
+
+

add_piece()

+
+
+enum flags_t { overwrite_existing = 1 };
+void add_piece(int piece, char const* data, int flags = 0) const;
+
+
+

This function will write data to the storage as piece piece, as if it had +been downloaded from a peer. data is expected to point to a buffer of as many +bytes as the size of the specified piece. The data in the buffer is copied and +passed on to the disk IO thread to be written at a later point.

+

By default, data that's already been downloaded is not overwritten by this buffer. If +you trust this data to be correct (and pass the piece hash check) you may pass the +overwrite_existing flag. This will instruct libtorrent to overwrite any data that +may already have been downloaded with this data.

+

Since the data is written asynchronously, you may know that is passed or failed the +hash check by waiting for piece_finished_alert or has_failed_alert.

+
+
+

read_piece()

+
+
+void read_piece(int piece) const;
+
+
+

This function starts an asynchronous read operation of the specified piece from +this torrent. You must have completed the download of the specified piece before +calling this function.

+

When the read operation is completed, it is passed back through an alert, +read_piece_alert. Since this alert is a reponse to an explicit call, it will +always be posted, regardless of the alert mask.

+

Note that if you read multiple pieces, the read operations are not guaranteed to +finish in the same order as you initiated them.

+
+
+

have_piece()

+
+
+bool have_piece(int piece) const;
+
+
+

Returns true if this piece has been completely downloaded, and false otherwise.

+
+
+

force_reannounce() force_dht_announce()

+
+
+void force_reannounce() const;
+void force_reannounce(boost::posix_time::time_duration) const;
+void force_dht_announce() const;
+
+
+

force_reannounce() will force this torrent to do another tracker request, to receive new +peers. The second overload of force_reannounce that takes a time_duration as +argument will schedule a reannounce in that amount of time from now.

+

If the tracker's min_interval has not passed since the last announce, the forced +announce will be scheduled to happen immediately as the min_interval expires. This is +to honor trackers minimum re-announce interval settings.

+

force_dht_announce will announce the torrent to the DHT immediately.

+
+
+

scrape_tracker()

+
+
+void scrape_tracker() const;
+
+
+

scrape_tracker() will send a scrape request to the tracker. A scrape request queries the +tracker for statistics such as total number of incomplete peers, complete peers, number of +downloads etc.

+

This request will specifically update the num_complete and num_incomplete fields in +the torrent_status struct once it completes. When it completes, it will generate a +scrape_reply_alert. If it fails, it will generate a scrape_failed_alert.

+
+
+

connect_peer()

+
+
+void connect_peer(asio::ip::tcp::endpoint const& adr, int source = 0, int flags = 0) const;
+
+
+

connect_peer() is a way to manually connect to peers that one believe is a part of the +torrent. If the peer does not respond, or is not a member of this torrent, it will simply +be disconnected. No harm can be done by using this other than an unnecessary connection +attempt is made. If the torrent is uninitialized or in queued or checking mode, this +will throw libtorrent_exception. The second (optional) argument will be bitwised ORed into +the source mask of this peer. Typically this is one of the source flags in peer_info. +i.e. tracker, pex, dht etc.

+

flags are the same flags that are passed along with the ut_pex extension.

+ ++++ + + + + + + + + + + + + + + +
0x01peer supports encryption
0x02peer is a seed
0x04supports uTP. This is only a positive flags +passing 0 doesn't mean the peer doesn't +support uTP
0x08supports holepunching protocol. If this +flag is received from a peer, it can be +used as a rendezvous point in case direct +connections to the peer fail
+
+
+

set_upload_limit() set_download_limit() upload_limit() download_limit()

+
+
+void set_upload_limit(int limit) const;
+void set_download_limit(int limit) const;
+int upload_limit() const;
+int download_limit() const;
+
+
+

set_upload_limit will limit the upload bandwidth used by this particular torrent to the +limit you set. It is given as the number of bytes per second the torrent is allowed to upload. +set_download_limit works the same way but for download bandwidth instead of upload bandwidth. +Note that setting a higher limit on a torrent then the global limit (session_settings::upload_rate_limit) +will not override the global rate limit. The torrent can never upload more than the global rate +limit.

+

upload_limit and download_limit will return the current limit setting, for upload and +download, respectively.

+
+
+

set_sequential_download()

+
+
+void set_sequential_download(bool sd);
+
+
+

set_sequential_download() enables or disables sequential download. When enabled, the piece +picker will pick pieces in sequence instead of rarest first.

+

Enabling sequential download will affect the piece distribution negatively in the swarm. It should be +used sparingly.

+
+
+

set_pinned()

+
+
+void set_pinned(bool p) const;
+
+
+

A pinned torrent may not be unloaded by libtorrent. When the dynamic loading and unloading of +torrents is enabled (by setting a load function on the session), this can be used to exempt +certain torrents from the unloading logic.

+

Magnet links, and other torrents that start out without having metadata are pinned automatically. +This is to give the client a chance to get the metadata and save it before it's unloaded. In this +case, it may be useful to un-pin the torrent once its metadata has been saved to disk.

+

For more information about dynamically loading and unloading torrents, see +dynamic loading of torrent files.

+
+
+

pause() resume()

+
+
+enum pause_flags_t { graceful_pause = 1 };
+void pause(int flags) const;
+void resume() const;
+
+
+

pause(), and resume() will disconnect all peers and reconnect all peers respectively. +When a torrent is paused, it will however remember all share ratios to all peers and remember +all potential (not connected) peers. Torrents may be paused automatically if there is a file +error (e.g. disk full) or something similar. See file_error_alert.

+

To know if a torrent is paused or not, call torrent_handle::status() and inspect +torrent_status::paused.

+

The flags argument to pause can be set to torrent_handle::graceful_pause which will +delay the disconnect of peers that we're still downloading outstanding requests from. The torrent +will not accept any more requests and will disconnect all idle peers. As soon as a peer is +done transferring the blocks that were requested from it, it is disconnected. This is a graceful +shut down of the torrent in the sense that no downloaded bytes are wasted.

+

torrents that are auto-managed may be automatically resumed again. It does not make sense to +pause an auto-managed torrent without making it not automanaged first. Torrents are auto-managed +by default when added to the session. For more information, see queuing.

+
+
+

flush_cache()

+
+
+void flush_cache() const;
+
+
+

Instructs libtorrent to flush all the disk caches for this torrent and close all +file handles. This is done asynchronously and you will be notified that it's complete +through cache_flushed_alert.

+

Note that by the time you get the alert, libtorrent may have cached more data for the +torrent, but you are guaranteed that whatever cached data libtorrent had by the time +you called torrent_handle::flush_cache() has been written to disk.

+
+
+

force_recheck()

+
+
+void force_recheck() const;
+
+
+

force_recheck puts the torrent back in a state where it assumes to have no resume data. +All peers will be disconnected and the torrent will stop announcing to the tracker. The torrent +will be added to the checking queue, and will be checked (all the files will be read and +compared to the piece hashes). Once the check is complete, the torrent will start connecting +to peers again, as normal.

+
+
+

clear_error()

+
+
+void clear_error() const;
+
+
+

If the torrent is in an error state (i.e. torrent_status::error is non-empty), this +will clear the error and start the torrent again.

+
+
+

set_upload_mode()

+
+void set_upload_mode(bool m) const;
+
+

Explicitly sets the upload mode of the torrent. In upload mode, the torrent will not +request any pieces. If the torrent is auto managed, it will automatically be taken out +of upload mode periodically (see optimistic_disk_retry). Torrents +are automatically put in upload mode whenever they encounter a disk write error.

+

m should be true to enter upload mode, and false to leave it.

+

To test if a torrent is in upload mode, call torrent_handle::status() and inspect +torrent_status::upload_mode.

+
+
+

set_share_mode()

+
+
+void set_share_mode(bool m) const;
+
+
+

Enable or disable share mode for this torrent. When in share mode, the torrent will +not necessarily be downloaded, especially not the whole of it. Only parts that are likely +to be distributed to more than 2 other peers are downloaded, and only if the previous +prediction was correct.

+
+
+

apply_ip_filter()

+
+void apply_ip_filter(bool b) const;
+
+

Set to true to apply the session global IP filter to this torrent (which is the +default). Set to false to make this torrent ignore the IP filter.

+
+
+

resolve_countries()

+
+
+void resolve_countries(bool r);
+bool resolve_countries() const;
+
+
+

Sets or gets the flag that derermines if countries should be resolved for the peers of this +torrent. It defaults to false. If it is set to true, the peer_info structure for the peers +in this torrent will have their country member set. See peer_info for more information +on how to interpret this field.

+
+
+

is_seed()

+
+
+bool is_seed() const;
+
+
+

Returns true if the torrent is in seed mode (i.e. if it has finished downloading).

+
+
+

auto_managed()

+
+
+void auto_managed(bool m) const;
+
+
+

auto_managed() changes whether the torrent is auto managed or not. For more info, +see queuing.

+
+
+

set_metadata()

+
+
+bool set_metadata(char const* buf, int size) const;
+
+
+

set_metadata expects the info section of metadata. i.e. The buffer passed in will be +hashed and verified against the info-hash. If it fails, a metadata_failed_alert will be +generated. If it passes, a metadata_received_alert is generated. The function returns +true if the metadata is successfully set on the torrent, and false otherwise. If the torrent +already has metadata, this function will not affect the torrent, and false will be returned.

+
+
+

set_tracker_login()

+
+
+void set_tracker_login(std::string const& username
+        , std::string const& password) const;
+
+
+

set_tracker_login() sets a username and password that will be sent along in the HTTP-request +of the tracker announce. Set this if the tracker requires authorization.

+
+
+

trackers() replace_trackers() add_tracker()

+
+
+std::vector<announce_entry> trackers() const;
+void replace_trackers(std::vector<announce_entry> const&) const;
+void add_tracker(announc_entry const& url);
+
+
+

trackers() will return the list of trackers for this torrent. The +announce entry contains both a string url which specify the announce url +for the tracker as well as an int tier, which is specifies the order in +which this tracker is tried. If you want libtorrent to use another list of +trackers for this torrent, you can use replace_trackers() which takes +a list of the same form as the one returned from trackers() and will +replace it. If you want an immediate effect, you have to call +force_reannounce() force_dht_announce(). See trackers() for the definition of announce_entry.

+

add_tracker() will look if the specified tracker is already in the set. +If it is, it doesn't do anything. If it's not in the current set of trackers, +it will insert it in the tier specified in the announce_entry.

+

The updated set of trackers will be saved in the resume data, and when a torrent +is started with resume data, the trackers from the resume data will replace the +original ones.

+
+
+

add_url_seed() remove_url_seed() url_seeds()

+
+
+void add_url_seed(std::string const& url);
+void remove_url_seed(std::string const& url);
+std::set<std::string> url_seeds() const;
+
+
+

add_url_seed() adds another url to the torrent's list of url seeds. If the +given url already exists in that list, the call has no effect. The torrent +will connect to the server and try to download pieces from it, unless it's +paused, queued, checking or seeding. remove_url_seed() removes the given +url if it exists already. url_seeds() return a set of the url seeds +currently in this torrent. Note that urls that fails may be removed +automatically from the list.

+

See HTTP seeding for more information.

+
+
+

add_http_seed() remove_http_seed() http_seeds()

+
+
+void add_http_seed(std::string const& url);
+void remove_http_seed(std::string const& url);
+std::set<std::string> http_seeds() const;
+
+
+

These functions are identical as the *_url_seed() variants, but they +operate on BEP 17 web seeds instead of BEP 19.

+

See HTTP seeding for more information.

+
+
+

queue_position() queue_position_up() queue_position_down() queue_position_top() queue_position_bottom()

+
+
+int queue_position() const;
+void queue_position_up() const;
+void queue_position_down() const;
+void queue_position_top() const;
+void queue_position_bottom() const;
+
+
+

Every torrent that is added is assigned a queue position exactly one greater than +the greatest queue position of all existing torrents. Torrents that are being +seeded have -1 as their queue position, since they're no longer in line to be downloaded.

+

When a torrent is removed or turns into a seed, all torrents with greater queue positions +have their positions decreased to fill in the space in the sequence.

+

queue_position() returns the torrent's position in the download queue. The torrents +with the smallest numbers are the ones that are being downloaded. The smaller number, +the closer the torrent is to the front of the line to be started.

+

The queue position is also available in the torrent_status.

+

The queue_position_*() functions adjust the torrents position in the queue. Up means +closer to the front and down means closer to the back of the queue. Top and bottom refers +to the front and the back of the queue respectively.

+
+
+

set_priority()

+
+
+void set_priority(int prio) const;
+
+
+

This sets the bandwidth priority of this torrent. The priority of a torrent determines +how much bandwidth its peers are assigned when distributing upload and download rate quotas. +A high number gives more bandwidth. The priority must be within the range [0, 255].

+

The default priority is 0, which is the lowest priority.

+

To query the priority of a torrent, use the torrent_handle::status() call.

+

Torrents with higher priority will not nececcarily get as much bandwidth as they can +consume, even if there's is more quota. Other peers will still be weighed in when +bandwidth is being distributed. With other words, bandwidth is not distributed strictly +in order of priority, but the priority is used as a weight.

+

Peers whose Torrent has a higher priority will take precedence when distributing unchoke slots. +This is a strict prioritization where every interested peer on a high priority torrent will +be unchoked before any other, lower priority, torrents have any peers unchoked.

+
+
+

info_hash()

+
+
+sha1_hash info_hash() const;
+
+
+

info_hash() returns the info-hash for the torrent.

+
+
+

set_max_uploads() max_uploads()

+
+
+void set_max_uploads(int max_uploads) const;
+int max_uploads() const;
+
+
+

set_max_uploads() sets the maximum number of peers that's unchoked at the same time on this +torrent. If you set this to -1, there will be no limit. This defaults to infinite. The primary +setting controlling this is the global unchoke slots limit, set by unchoke_slots_limit +in session_settings.

+

max_uploads() returns the current settings.

+
+
+

set_max_connections() max_connections()

+
+
+void set_max_connections(int max_connections) const;
+int max_connections() const;
+
+
+

set_max_connections() sets the maximum number of connection this torrent will open. If all +connections are used up, incoming connections may be refused or poor connections may be closed. +This must be at least 2. The default is unlimited number of connections. If -1 is given to the +function, it means unlimited. There is also a global limit of the number of connections, set +by connections_limit in session_settings.

+

max_connections() returns the current settings.

+
+
+

save_resume_data()

+
+
+enum save_resume_flags_t { flush_disk_cache = 1, save_info_dict = 2 };
+void save_resume_data(int flags = 0) const;
+
+
+

save_resume_data() generates fast-resume data and returns it as an entry. This entry +is suitable for being bencoded. For more information about how fast-resume works, see fast resume.

+

The flags argument is a bitmask of flags ORed together. If the flag torrent_handle::flush_cache +is set, the disk cache will be flushed before creating the resume data. This avoids a problem with +file timestamps in the resume data in case the cache hasn't been flushed yet.

+

If the flag torrent_handle::save_info_dict is set, the resume data will contain the metadata +from the torrent file as well. This is default for any torrent that's added without a torrent +file (such as a magnet link or a URL).

+

This operation is asynchronous, save_resume_data will return immediately. The resume data +is delivered when it's done through an save_resume_data_alert.

+

The fast resume data will be empty in the following cases:

+
+
    +
  1. The torrent handle is invalid.
  2. +
  3. The torrent is checking (or is queued for checking) its storage, it will obviously +not be ready to write resume data.
  4. +
  5. The torrent hasn't received valid metadata and was started without metadata +(see libtorrent's metadata from peers extension)
  6. +
+
+

Note that by the time you receive the fast resume data, it may already be invalid if the torrent +is still downloading! The recommended practice is to first pause the session, then generate the +fast resume data, and then close it down. Make sure to not remove_torrent() before you receive +the save_resume_data_alert though. There's no need to pause when saving intermittent resume data.

+
+

Warning

+

If you pause every torrent individually instead of pausing the session, every torrent +will have its paused state saved in the resume data!

+
+
+

Warning

+

The resume data contains the modification timestamps for all files. If one file has +been modified when the torrent is added again, the will be rechecked. When shutting down, make +sure to flush the disk cache before saving the resume data. This will make sure that the file +timestamps are up to date and won't be modified after saving the resume data. The recommended way +to do this is to pause the torrent, which will flush the cache and disconnect all peers.

+
+
+

Note

+

It is typically a good idea to save resume data whenever a torrent is completed or paused. In those +cases you don't need to pause the torrent or the session, since the torrent will do no more writing +to its files. If you save resume data for torrents when they are paused, you can accelerate the +shutdown process by not saving resume data again for paused torrents. Completed torrents should +have their resume data saved when they complete and on exit, since their statistics might be updated.

+

In full allocation mode the reume data is never invalidated by subsequent +writes to the files, since pieces won't move around. This means that you don't need to +pause before writing resume data in full or sparse mode. If you don't, however, any data written to +disk after you saved resume data and before the session closed is lost.

+
+

It also means that if the resume data is out dated, libtorrent will not re-check the files, but assume +that it is fairly recent. The assumption is that it's better to loose a little bit than to re-check +the entire file.

+

It is still a good idea to save resume data periodically during download as well as when +closing down.

+

Example code to pause and save resume data for all torrents and wait for the alerts:

+
+extern int outstanding_resume_data; // global counter of outstanding resume data
+std::vector<torrent_handle> handles = ses.get_torrents();
+ses.pause();
+for (std::vector<torrent_handle>::iterator i = handles.begin();
+        i != handles.end(); ++i)
+{
+        torrent_handle& h = *i;
+        if (!h.is_valid()) continue;
+        torrent_status s = h.status();
+        if (!s.has_metadata) continue;
+        if (!s.need_save_resume_data()) continue;
+
+        h.save_resume_data();
+        ++outstanding_resume_data;
+}
+
+while (outstanding_resume_data > 0)
+{
+        alert const* a = ses.wait_for_alert(seconds(10));
+
+        // if we don't get an alert within 10 seconds, abort
+        if (a == 0) break;
+
+        std::auto_ptr<alert> holder = ses.pop_alert();
+
+        if (alert_cast<save_resume_data_failed_alert>(a))
+        {
+                process_alert(a);
+                --outstanding_resume_data;
+                continue;
+        }
+
+        save_resume_data_alert const* rd = alert_cast<save_resume_data_alert>(a);
+        if (rd == 0)
+        {
+                process_alert(a);
+                continue;
+        }
+
+        torrent_handle h = rd->handle;
+        torrent_status st = h.status(torrent_handle::query_save_path | torrent_handle::query_name);
+        std::ofstream out((st.save_path
+                + "/" + st.name + ".fastresume").c_str()
+                , std::ios_base::binary);
+        out.unsetf(std::ios_base::skipws);
+        bencode(std::ostream_iterator<char>(out), *rd->resume_data);
+        --outstanding_resume_data;
+}
+
+
+

Note

+

Note how outstanding_resume_data is a global counter in this example. +This is deliberate, otherwise there is a race condition for torrents that +was just asked to save their resume data, they posted the alert, but it has +not been received yet. Those torrents would report that they don't need to +save resume data again, and skipped by the initial loop, and thwart the counter +otherwise.

+
+
+
+

need_save_resume_data()

+
+
+bool need_save_resume_data() const;
+
+
+

This function returns true if any whole chunk has been downloaded since the +torrent was first loaded or since the last time the resume data was saved. When +saving resume data periodically, it makes sense to skip any torrent which hasn't +downloaded anything since the last time.

+
+

Note

+

A torrent's resume data is considered saved as soon as the alert +is posted. It is important to make sure this alert is received and handled +in order for this function to be meaningful.

+
+
+
+

status()

+
+
+torrent_status status(boost::uint32_t flags = 0xffffffff) const;
+
+
+

status() will return a structure with information about the status of this +torrent. If the torrent_handle is invalid, it will throw libtorrent_exception exception. +See torrent_status. The flags argument filters what information is returned +in the torrent_status. Some information in there is relatively expensive to calculate, and +if you're not interested in it (and see performance issues), you can filter them out.

+

By default everything is included. The flags you can use to decide what to include are:

+
    +
  • +
    query_distributed_copies
    +

    calculates distributed_copies, distributed_full_copies and distributed_fraction.

    +
    +
    +
  • +
  • +
    query_accurate_download_counters
    +

    includes partial downloaded blocks in total_done and total_wanted_done.

    +
    +
    +
  • +
  • +
    query_last_seen_complete
    +

    includes last_seen_complete.

    +
    +
    +
  • +
  • +
    query_pieces
    +

    includes pieces.

    +
    +
    +
  • +
  • +
    query_verified_pieces
    +

    includes verified_pieces (only applies to torrents in seed mode).

    +
    +
    +
  • +
  • +
    query_torrent_file
    +

    includes torrent_file, which is all the static information from the .torrent file.

    +
    +
    +
  • +
  • +
    query_name
    +

    includes name, the name of the torrent. This is either derived from the .torrent +file, or from the &dn= magnet link argument or possibly some other source. If the +name of the torrent is not known, this is an empty string.

    +
    +
    +
  • +
  • +
    query_save_path
    +

    includes save_path, the path to the directory the files of the torrent are saved to.

    +
    +
    +
  • +
+
+
+

get_download_queue()

+
+
+void get_download_queue(std::vector<partial_piece_info>& queue) const;
+
+
+

get_download_queue() takes a non-const reference to a vector which it will fill with +information about pieces that are partially downloaded or not downloaded at all but partially +requested. The entry in the vector (partial_piece_info) looks like this:

+
+struct partial_piece_info
+{
+        int piece_index;
+        int blocks_in_piece;
+        enum state_t { none, slow, medium, fast };
+        state_t piece_state;
+        block_info* blocks;
+};
+
+

piece_index is the index of the piece in question. blocks_in_piece is the +number of blocks in this particular piece. This number will be the same for most pieces, but +the last piece may have fewer blocks than the standard pieces.

+

piece_state is set to either fast, medium, slow or none. It tells which +download rate category the peers downloading this piece falls into. none means that no +peer is currently downloading any part of the piece. Peers prefer picking pieces from +the same category as themselves. The reason for this is to keep the number of partially +downloaded pieces down. Pieces set to none can be converted into any of fast, +medium or slow as soon as a peer want to download from it.

+
+struct block_info
+{
+        enum block_state_t
+        { none, requested, writing, finished };
+
+        void set_peer(tcp::endpoint const& ep);
+        tcp::endpoint peer() const;
+
+        unsigned bytes_progress:15;
+        unsigned block_size:15;
+        unsigned state:2;
+        unsigned num_peers:14;
+};
+
+

The blocks field points to an array of blocks_in_piece elements. This pointer is +only valid until the next call to get_download_queue() for any torrent in the same session. +They all share the storaga for the block arrays in their session object.

+

The block_info array contains data for each individual block in the piece. Each block has +a state (state) which is any of:

+
    +
  • none - This block has not been downloaded or requested form any peer.
  • +
  • requested - The block has been requested, but not completely downloaded yet.
  • +
  • writing - The block has been downloaded and is currently queued for being written to disk.
  • +
  • finished - The block has been written to disk.
  • +
+

The peer field is the ip address of the peer this block was downloaded from. +num_peers is the number of peers that is currently requesting this block. Typically this +is 0 or 1, but at the end of the torrent blocks may be requested by more peers in parallel to +speed things up. +bytes_progress is the number of bytes that have been received for this block, and +block_size is the total number of bytes in this block.

+
+
+

get_peer_info()

+
+
+void get_peer_info(std::vector<peer_info>&) const;
+
+
+

get_peer_info() takes a reference to a vector that will be cleared and filled +with one entry for each peer connected to this torrent, given the handle is valid. If the +torrent_handle is invalid, it will throw libtorrent_exception exception. Each entry in +the vector contains information about that particular peer. See peer_info.

+
+
+

torrent_file()

+
+
+boost::intrusive_ptr<torrent_info> torrent_file() const;
+
+
+

Returns a pointer to the torrent_info object associated with this torrent. The +torrent_info object is a copy of the internal object. If the torrent doesn't +have metadata, the object being returned will not be fully filled in. +The torrent may be in a state without metadata only if +it was started without a .torrent file, e.g. by using the libtorrent extension of +just supplying a tracker and info-hash.

+
+
+

is_valid()

+
+
+bool is_valid() const;
+
+
+

Returns true if this handle refers to a valid torrent and false if it hasn't been initialized +or if the torrent it refers to has been aborted. Note that a handle may become invalid after +it has been added to the session. Usually this is because the storage for the torrent is +somehow invalid or if the filenames are not allowed (and hence cannot be opened/created) on +your filesystem. If such an error occurs, a file_error_alert is generated and all handles +that refers to that torrent will become invalid.

+
+
+

set_ssl_certificate()

+
+
+void set_ssl_certificate(std::string const& cert, std::string const& private_key
+        , std::string const& dh_params, std::string const& passphrase = "");
+
+
+

For SSL torrents, use this to specify a path to a .pem file to use as this client's certificate. +The certificate must be signed by the certificate in the .torrent file to be valid.

+

cert is a path to the (signed) certificate in .pem format corresponding to this torrent.

+

private_key is a path to the private key for the specified certificate. This must be in .pem +format.

+

dh_params is a path to the Diffie-Hellman parameter file, which needs to be in .pem format. +You can generate this file using the openssl command like this: +openssl dhparam -outform PEM -out dhparams.pem 512.

+

passphrase may be specified if the private key is encrypted and requires a passphrase to +be decrypted.

+

Note that when a torrent first starts up, and it needs a certificate, it will suspend connecting +to any peers until it has one. It's typically desirable to resume the torrent after setting the +ssl certificate.

+

If you receive a torrent_need_cert_alert, you need to call this to provide a valid cert. If you +don't have a cert you won't be allowed to connect to any peers.

+
+
+

native_handle()

+
+
+boost::shared_ptr<torrent> native_handle() const;
+
+
+

This function is intended only for use by plugins and the alert dispatch function. Any code +that runs in libtorrent's network thread may not use the public API of torrent_handle. +Doing so results in a dead-lock. For such routines, the native_handle gives access to the +underlying type representing the torrent. This type does not have a stable API and should +be relied on as little as possible.

+
+
+
+

torrent_status

+

It contains the following fields:

+
+struct torrent_status
+{
+        enum state_t
+        {
+                queued_for_checking, // deprecated
+                checking_files,
+                downloading_metadata,
+                downloading,
+                finished,
+                seeding,
+                allocating,
+                checking_resume_data
+        };
+
+        torrent_handle handle;
+
+        state_t state;
+        bool paused;
+        bool auto_managed;
+        bool sequential_download;
+        bool is_seeding;
+        bool is_finished;
+        bool is_loaded;
+        float progress;
+        int progress_ppm;
+        std::string error;
+        std::string save_path;
+        std::string name;
+
+        boost::intrusive_ptr<const torrent_info> torrent_file;
+
+        boost::posix_time::time_duration next_announce;
+        boost::posix_time::time_duration announce_interval;
+
+        std::string current_tracker;
+
+        size_type total_download;
+        size_type total_upload;
+
+        size_type total_payload_download;
+        size_type total_payload_upload;
+
+        size_type total_failed_bytes;
+        size_type total_redundant_bytes;
+
+        int download_rate;
+        int upload_rate;
+
+        int download_payload_rate;
+        int upload_payload_rate;
+
+        int num_peers;
+
+        int num_complete;
+        int num_incomplete;
+
+        int list_seeds;
+        int list_peers;
+
+        int connect_candidates;
+
+        bitfield pieces;
+        bitfield verified_pieces;
+
+        int num_pieces;
+
+        size_type total_done;
+        size_type total_wanted_done;
+        size_type total_wanted;
+
+        int num_seeds;
+
+        int distributed_full_copies;
+        int distributed_fraction;
+
+        float distributed_copies;
+
+        int block_size;
+
+        int num_uploads;
+        int num_connections;
+        int uploads_limit;
+        int connections_limit;
+
+        storage_mode_t storage_mode;
+
+        int up_bandwidth_queue;
+        int down_bandwidth_queue;
+
+        size_type all_time_upload;
+        size_type all_time_download;
+
+        int active_time;
+        int finished_time;
+        int seeding_time;
+
+        int seed_rank;
+
+        int last_scrape;
+
+        bool has_incoming;
+
+        int sparse_regions;
+
+        bool seed_mode;
+        bool upload_mode;
+        bool share_mode;
+        bool super_seeding;
+
+        int priority;
+
+        time_t added_time;
+        time_t completed_time;
+        time_t last_seen_complete;
+
+        int time_since_upload;
+        int time_since_download;
+
+        int queue_position;
+        bool need_save_resume;
+        bool ip_filter_applies;
+
+        sha1_hash info_hash;
+
+        int listen_port;
+};
+
+

handle is a handle to the torrent whose status the object represents.

+

progress is a value in the range [0, 1], that represents the progress of the +torrent's current task. It may be checking files or downloading.

+

progress_ppm reflects the same value as progress, but instead in a range +[0, 1000000] (ppm = parts per million). When floating point operations are disabled, +this is the only alternative to the floating point value in progress.

+

The torrent's current task is in the state member, it will be one of the following:

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + +
checking_resume_dataThe torrent is currently checking the fastresume data and +comparing it to the files on disk. This is typically +completed in a fraction of a second, but if you add a +large number of torrents at once, they will queue up.
queued_for_checking

THIS STATE IS DEPRECATED +A torrent that is queued for checking is now in the +checking_files state, and paused and auto-managed.

+

The previous semantic for this state was: +The torrent is in the queue for being checked. But there +currently is another torrent that are being checked. +This torrent will wait for its turn.

+
checking_filesThe torrent has not started its download yet, and is +currently checking existing files.
downloading_metadataThe torrent is trying to download metadata from peers. +This assumes the metadata_transfer extension is in use.
downloadingThe torrent is being downloaded. This is the state +most torrents will be in most of the time. The progress +meter will tell how much of the files that has been +downloaded.
finishedIn this state the torrent has finished downloading but +still doesn't have the entire torrent. i.e. some pieces +are filtered and won't get downloaded.
seedingIn this state the torrent has finished downloading and +is a pure seeder.
allocatingIf the torrent was started in full allocation mode, this +indicates that the (disk) storage for the torrent is +allocated.
+

When downloading, the progress is total_wanted_done / total_wanted. This takes +into account files whose priority have been set to 0. They are not considered.

+

paused is set to true if the torrent is paused and false otherwise. It's only true +if the torrent itself is paused. If the torrent is not running because the session is +paused, this is still false. To know if a torrent is active or not, you need to inspect +both torrent_status::paused and session::is_paused().

+

auto_managed is set to true if the torrent is auto managed, i.e. libtorrent is +responsible for determining whether it should be started or queued. For more info +see queuing

+

sequential_download is true when the torrent is in sequential download mode. In +this mode pieces are downloaded in order rather than rarest first.

+

is_seeding is true if all pieces have been downloaded.

+

is_finished is true if all pieces that have a priority > 0 are downloaded. There is +only a distinction between finished and seeding if some pieces or files have been +set to priority 0, i.e. are not downloaded.

+

is_loaded is true if this torrent is loaded into RAM. A torrent can be started +and still not loaded into RAM, in case it has not had any peers interested in it +yet. Torrents are loaded on demand.

+

has_metadata is true if this torrent has metadata (either it was started from a +.torrent file or the metadata has been downloaded). The only scenario where this can be +false is when the torrent was started torrent-less (i.e. with just an info-hash and tracker +ip, a magnet link for instance).

+

error may be set to an error message describing why the torrent was paused, in +case it was paused by an error. If the torrent is not paused or if it's paused but +not because of an error, this string is empty.

+

save_path is the path to the directory where this torrent's files are stored. +It's typically the path as was given to async_add_torrent() add_torrent() when this torrent +was started. This field is only included if the torrent status is queried with +torrent_handle::query_save_path.

+

name is the name of the torrent. Typically this is derived from the .torrent file. +In case the torrent was started without metadata, and hasn't completely received it yet, +it returns the name given to it when added to the session. See session::add_torrent. +This field is only included if the torrent status is queried with torrent_handle::query_name.

+

torrent_file is set to point to the torrent_info object for this torrent. It's +only included if the torrent status is queried with torrent_handle::query_torrent_file.

+

next_announce is the time until the torrent will announce itself to the tracker. And +announce_interval is the time the tracker want us to wait until we announce ourself +again the next time.

+

current_tracker is the URL of the last working tracker. If no tracker request has +been successful yet, it's set to an empty string.

+

total_download and total_upload is the number of bytes downloaded and +uploaded to all peers, accumulated, this session only. The session is considered +to restart when a torrent is paused and restarted again. When a torrent is paused, +these counters are reset to 0. If you want complete, persistent, stats, see +all_time_upload and all_time_download.

+

total_payload_download and total_payload_upload counts the amount of bytes +send and received this session, but only the actual payload data (i.e the interesting +data), these counters ignore any protocol overhead.

+

total_failed_bytes is the number of bytes that has been downloaded and that +has failed the piece hash test. In other words, this is just how much crap that +has been downloaded.

+

total_redundant_bytes is the number of bytes that has been downloaded even +though that data already was downloaded. The reason for this is that in some +situations the same data can be downloaded by mistake. When libtorrent sends +requests to a peer, and the peer doesn't send a response within a certain +timeout, libtorrent will re-request that block. Another situation when +libtorrent may re-request blocks is when the requests it sends out are not +replied in FIFO-order (it will re-request blocks that are skipped by an out of +order block). This is supposed to be as low as possible.

+

pieces is the bitmask that represents which pieces we have (set to true) and +the pieces we don't have. It's a pointer and may be set to 0 if the torrent isn't +downloading or seeding.

+

verified_pieces is a bitmask representing which pieces has had their hash +checked. This only applies to torrents in seed mode. If the torrent is not +in seed mode, this bitmask may be empty.

+

num_pieces is the number of pieces that has been downloaded. It is equivalent +to: std::accumulate(pieces->begin(), pieces->end()). So you don't have to +count yourself. This can be used to see if anything has updated since last time +if you want to keep a graph of the pieces up to date.

+

download_rate and upload_rate are the total rates for all peers for this +torrent. These will usually have better precision than summing the rates from +all peers. The rates are given as the number of bytes per second. The +download_payload_rate and upload_payload_rate respectively is the +total transfer rate of payload only, not counting protocol chatter. This might +be slightly smaller than the other rates, but if projected over a long time +(e.g. when calculating ETA:s) the difference may be noticeable.

+

num_peers is the number of peers this torrent currently is connected to. +Peer connections that are in the half-open state (is attempting to connect) +or are queued for later connection attempt do not count. Although they are +visible in the peer list when you call get_peer_info().

+

num_complete and num_incomplete are set to -1 if the tracker did not +send any scrape data in its announce reply. This data is optional and may +not be available from all trackers. If these are not -1, they are the total +number of peers that are seeding (complete) and the total number of peers +that are still downloading (incomplete) this torrent.

+

list_seeds and list_peers are the number of seeds in our peer list +and the total number of peers (including seeds) respectively. We are not +necessarily connected to all the peers in our peer list. This is the number +of peers we know of in total, including banned peers and peers that we have +failed to connect to.

+

connect_candidates is the number of peers in this torrent's peer list +that is a candidate to be connected to. i.e. It has fewer connect attempts +than the max fail count, it is not a seed if we are a seed, it is not banned +etc. If this is 0, it means we don't know of any more peers that we can try.

+

total_done is the total number of bytes of the file(s) that we have. All +this does not necessarily has to be downloaded during this session (that's +total_payload_download).

+

total_wanted_done is the number of bytes we have downloaded, only counting the +pieces that we actually want to download. i.e. excluding any pieces that we have but +have priority 0 (i.e. not wanted).

+

total_wanted is the total number of bytes we want to download. This is also +excluding pieces whose priorities have been set to 0.

+

num_seeds is the number of peers that are seeding that this client is +currently connected to.

+

distributed_full_copies is the number of distributed copies of the torrent. +Note that one copy may be spread out among many peers. It tells how many copies +there are currently of the rarest piece(s) among the peers this client is +connected to.

+

distributed_fraction tells the share of pieces that have more copies than +the rarest piece(s). Divide this number by 1000 to get the fraction.

+

For example, if distributed_full_copies is 2 and distrbuted_fraction +is 500, it means that the rarest pieces have only 2 copies among the peers +this torrent is connected to, and that 50% of all the pieces have more than +two copies.

+

If we are a seed, the piece picker is deallocated as an optimization, and +piece availability is no longer tracked. In this case the distributed +copies members are set to -1.

+

distributed_copies is a floating point representation of the +distributed_full_copies as the integer part and distributed_fraction +/ 1000 as the fraction part. If floating point operations are disabled +this value is always -1.

+

block_size is the size of a block, in bytes. A block is a sub piece, it +is the number of bytes that each piece request asks for and the number of +bytes that each bit in the partial_piece_info's bitset represents +(see get_download_queue()). This is typically 16 kB, but it may be +larger if the pieces are larger.

+

num_uploads is the number of unchoked peers in this torrent.

+

num_connections is the number of peer connections this torrent has, including +half-open connections that hasn't completed the bittorrent handshake yet. This is +always >= num_peers.

+

uploads_limit is the set limit of upload slots (unchoked peers) for this torrent.

+

connections_limit is the set limit of number of connections for this torrent.

+

storage_mode is one of storage_mode_allocate, storage_mode_sparse or +storage_mode_compact. Identifies which storage mode this torrent is being saved +with. See Storage allocation.

+

up_bandwidth_queue and down_bandwidth_queue are the number of peers in this +torrent that are waiting for more bandwidth quota from the torrent rate limiter. +This can determine if the rate you get from this torrent is bound by the torrents +limit or not. If there is no limit set on this torrent, the peers might still be +waiting for bandwidth quota from the global limiter, but then they are counted in +the session_status object.

+

all_time_upload and all_time_download are accumulated upload and download +payload byte counters. They are saved in and restored from resume data to keep totals +across sessions.

+

active_time, finished_time and seeding_time are second counters. +They keep track of the number of seconds this torrent has been active (not +paused) and the number of seconds it has been active while being finished and +active while being a seed. seeding_time should be <= finished_time which +should be <= active_time. They are all saved in and restored from resume data, +to keep totals across sessions.

+

seed_rank is a rank of how important it is to seed the torrent, it is used +to determine which torrents to seed and which to queue. It is based on the peer +to seed ratio from the tracker scrape. For more information, see queuing.

+

last_scrape is the number of seconds since this torrent acquired scrape data. +If it has never done that, this value is -1.

+

has_incoming is true if there has ever been an incoming connection attempt +to this torrent.'

+

sparse_regions the number of regions of non-downloaded pieces in the +torrent. This is an interesting metric on windows vista, since there is +a limit on the number of sparse regions in a single file there.

+

seed_mode is true if the torrent is in seed_mode. If the torrent was +started in seed mode, it will leave seed mode once all pieces have been +checked or as soon as one piece fails the hash check.

+

upload_mode is true if the torrent is blocked from downloading. This +typically happens when a disk write operation fails. If the torrent is +auto-managed, it will periodically be taken out of this state, in the +hope that the disk condition (be it disk full or permission errors) has +been resolved. If the torrent is not auto-managed, you have to explicitly +take it out of the upload mode by calling set_upload_mode() on the +torrent_handle.

+

share_mode is true if the torrent is currently in share-mode, i.e. +not downloading the torrent, but just helping the swarm out.

+

super_seeding is true if the torrent is in super seeding mode.

+

added_time is the posix-time when this torrent was added. i.e. what +time(NULL) returned at the time.

+

completed_time is the posix-time when this torrent was finished. If +the torrent is not yet finished, this is 0.

+

last_seen_complete is the time when we, or one of our peers, last +saw a complete copy of this torrent.

+

time_since_upload and time_since_download are the number of +seconds since any peer last uploaded from this torrent and the last +time a downloaded piece passed the hash check, respectively.

+

queue_position is the position this torrent has in the download +queue. If the torrent is a seed or finished, this is -1.

+

need_save_resume is true if this torrent has unsaved changes +to its download state and statistics since the last resume data +was saved.

+

ip_filter_applies is true if the session global IP filter applies +to this torrent. This defaults to true.

+

info_hash is the info-hash of the torrent.

+

listen_port is the listen port this torrent is listening on for new +connections, if the torrent has its own listen socket. Only SSL torrents +have their own listen sockets. If the torrent doesn't have one, and is +accepting connections on the single listen socket, this is 0.

+
+
+

peer_info

+

It contains the following fields:

+
+struct peer_info
+{
+        enum
+        {
+                interesting = 0x1,
+                choked = 0x2,
+                remote_interested = 0x4,
+                remote_choked = 0x8,
+                supports_extensions = 0x10,
+                local_connection = 0x20,
+                handshake = 0x40,
+                connecting = 0x80,
+                queued = 0x100,
+                on_parole = 0x200,
+                seed = 0x400,
+                optimistic_unchoke = 0x800,
+                snubbed = 0x1000,
+                upload_only = 0x2000,
+                endgame_mode = 0x4000,
+                holepunched = 0x8000,
+                rc4_encrypted = 0x100000,
+                plaintext_encrypted = 0x200000
+        };
+
+        unsigned int flags;
+
+        enum peer_source_flags
+        {
+                tracker = 0x1,
+                dht = 0x2,
+                pex = 0x4,
+                lsd = 0x8
+        };
+
+        int source;
+
+        // bitmask representing socket state
+        enum bw_state { bw_idle = 0, bw_limit = 1, bw_network = 2, bw_disk = 4 };
+
+        char read_state;
+        char write_state;
+
+        asio::ip::tcp::endpoint ip;
+        int up_speed;
+        int down_speed;
+        int payload_up_speed;
+        int payload_down_speed;
+        size_type total_download;
+        size_type total_upload;
+        peer_id pid;
+        bitfield pieces;
+        int upload_limit;
+        int download_limit;
+
+        time_duration last_request;
+        time_duration last_active;
+        int request_timeout;
+
+        int send_buffer_size;
+        int used_send_buffer;
+
+        int receive_buffer_size;
+        int used_receive_buffer;
+
+        int num_hashfails;
+
+        char country[2];
+
+        std::string inet_as_name;
+        int inet_as;
+
+        int requests_in_buffer;
+        int download_queue_length;
+        int upload_queue_length;
+
+        int failcount;
+
+        int downloading_piece_index;
+        int downloading_block_index;
+        int downloading_progress;
+        int downloading_total;
+
+        std::string client;
+
+        enum
+        {
+                standard_bittorrent = 0,
+                web_seed = 1
+        };
+        int connection_type;
+
+        int remote_dl_rate;
+
+        int pending_disk_bytes;
+
+        int send_quota;
+        int receive_quota;
+
+        int rtt;
+
+        int num_pieces;
+
+        int download_rate_peak;
+        int upload_rate_peak;
+
+        float progress;
+        int progress_ppm;
+
+        tcp::endpoint local_endpoint;
+};
+
+

The flags attribute tells you in which state the peer is. It is set to +any combination of the enums above. The following table describes each flag:

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
interestingwe are interested in pieces from this peer.
chokedwe have choked this peer.
remote_interestedthe peer is interested in us
remote_chokedthe peer has choked us.
support_extensionsmeans that this peer supports the +extension protocol.
local_connectionThe connection was initiated by us, the peer has a +listen port open, and that port is the same as in the +address of this peer. If this flag is not set, this +peer connection was opened by this peer connecting to +us.
handshakeThe connection is opened, and waiting for the +handshake. Until the handshake is done, the peer +cannot be identified.
connectingThe connection is in a half-open state (i.e. it is +being connected).
queuedThe connection is currently queued for a connection +attempt. This may happen if there is a limit set on +the number of half-open TCP connections.
on_paroleThe peer has participated in a piece that failed the +hash check, and is now "on parole", which means we're +only requesting whole pieces from this peer until +it either fails that piece or proves that it doesn't +send bad data.
seedThis peer is a seed (it has all the pieces).
optimistic_unchokeThis peer is subject to an optimistic unchoke. It has +been unchoked for a while to see if it might unchoke +us in return an earn an upload/unchoke slot. If it +doesn't within some period of time, it will be choked +and another peer will be optimistically unchoked.
snubbedThis peer has recently failed to send a block within +the request timeout from when the request was sent. +We're currently picking one block at a time from this +peer.
upload_onlyThis peer has either explicitly (with an extension) +or implicitly (by becoming a seed) told us that it +will not downloading anything more, regardless of +which pieces we have.
endgame_modeThis means the last time this peer picket a piece, +it could not pick as many as it wanted because there +were not enough free ones. i.e. all pieces this peer +has were already requested from other peers.
holepunchedThis flag is set if the peer was in holepunch mode +when the connection succeeded. This typically only +happens if both peers are behind a NAT and the peers +connect via the NAT holepunch mechanism.
+

source is a combination of flags describing from which sources this peer +was received. The flags are:

+ ++++ + + + + + + + + + + + + + + + + + +
trackerThe peer was received from the tracker.
dhtThe peer was received from the kademlia DHT.
pexThe peer was received from the peer exchange +extension.
lsdThe peer was received from the local service +discovery (The peer is on the local network).
resume_dataThe peer was added from the fast resume data.
+

read_state and write_state are bitmasks indicating what state this peer +is in with regards to sending and receiving data. The states are declared in the +bw_state enum and defines as follows:

+ ++++ + + + + + + + + + + + + + + +
bw_idleThe peer is not waiting for any external events to +send or receive data.
bw_limitThe peer is waiting for the rate limiter.
bw_networkThe peer has quota and is currently waiting for a +network read or write operation to complete. This is +the state all peers are in if there are no bandwidth +limits.
bw_diskThe peer is waiting for the disk I/O thread to catch +up writing buffers to disk before downloading more.
+

Note that read_state and write_state are bitmasks. A peer may be waiting +on disk and on the network at the same time. bw_idle does not represent a bit, +but is simply a name for no bit being set in the bitmask.

+

The ip field is the IP-address to this peer. The type is an asio endpoint. For +more info, see the asio documentation.

+

up_speed and down_speed contains the current upload and download speed +we have to and from this peer (including any protocol messages). The transfer rates +of payload data only are found in payload_up_speed and payload_down_speed. +These figures are updated approximately once every second.

+

total_download and total_upload are the total number of bytes downloaded +from and uploaded to this peer. These numbers do not include the protocol chatter, but only +the payload data.

+

pid is the peer's id as used in the bit torrent protocol. This id can be used to +extract 'fingerprints' from the peer. Sometimes it can tell you which client the peer +is using. See identify_client()_

+

pieces is a bitfield, with one bit per piece in the torrent. +Each bit tells you if the peer has that piece (if it's set to 1) +or if the peer miss that piece (set to 0).

+

seed is true if this peer is a seed.

+

upload_limit is the number of bytes per second we are allowed to send to this +peer every second. It may be -1 if there's no local limit on the peer. The global +limit and the torrent limit is always enforced anyway.

+

download_limit is the number of bytes per second this peer is allowed to +receive. -1 means it's unlimited.

+

last_request and last_active is the time since we last sent a request +to this peer and since any transfer occurred with this peer, respectively.

+

request_timeout is the number of seconds until the current front piece request +will time out. This timeout can be adjusted through request_timeout. +-1 means that there is not outstanding request.

+

send_buffer_size and used_send_buffer is the number of bytes allocated +and used for the peer's send buffer, respectively.

+

receive_buffer_size and used_receive_buffer are the number of bytes +allocated and used as receive buffer, respectively.

+

num_hashfails is the number of pieces this peer has participated in +sending us that turned out to fail the hash check.

+

country is the two letter ISO 3166 country code for the country the peer +is connected from. If the country hasn't been resolved yet, both chars are set +to 0. If the resolution failed for some reason, the field is set to "--". If the +resolution service returns an invalid country code, it is set to "!!". +The countries.nerd.dk service is used to look up countries. This field will +remain set to 0 unless the torrent is set to resolve countries, see resolve_countries().

+

inet_as_name is the name of the AS this peer is located in. This might be +an empty string if there is no name in the geo ip database.

+

inet_as is the AS number the peer is located in.

+

requests_in_buffer is the number of requests messages that are currently in the +send buffer waiting to be sent.

+

download_queue_length is the number of piece-requests we have sent to this peer +that hasn't been answered with a piece yet.

+

upload_queue_length is the number of piece-requests we have received from this peer +that we haven't answered with a piece yet.

+

failcount is the number of times this peer has "failed". i.e. failed to connect +or disconnected us. The failcount is decremented when we see this peer in a tracker +response or peer exchange message.

+

You can know which piece, and which part of that piece, that is currently being +downloaded from a specific peer by looking at the next four members. +downloading_piece_index is the index of the piece that is currently being downloaded. +This may be set to -1 if there's currently no piece downloading from this peer. If it is +>= 0, the other three members are valid. downloading_block_index is the index of the +block (or sub-piece) that is being downloaded. downloading_progress is the number +of bytes of this block we have received from the peer, and downloading_total is +the total number of bytes in this block.

+

client is a string describing the software at the other end of the connection. +In some cases this information is not available, then it will contain a string +that may give away something about which software is running in the other end. +In the case of a web seed, the server type and version will be a part of this +string.

+

connection_type can currently be one of:

+ ++++ + + + + + + + + + + + + + + + + + + + +
typemeaning
peer_info::standard_bittorrentRegular bittorrent connection over TCP
peer_info::bittorrent_utpBittorrent connection over uTP
peer_info::web_sesedHTTP connection using the BEP 19 protocol
peer_info::http_seedHTTP connection using the BEP 17 protocol
+

remote_dl_rate is an estimate of the rate this peer is downloading at, in +bytes per second.

+

pending_disk_bytes is the number of bytes this peer has pending in the +disk-io thread. Downloaded and waiting to be written to disk.

+

send_quota and receive_quota are the number of bytes this peer has been +assigned to be allowed to send and receive until it has to request more quota +from the bandwidth manager.

+

rtt is an estimated round trip time to this peer, in milliseconds. It is +estimated by timing the the tcp connect(). It may be 0 for incoming connections.

+

num_pieces is the number of pieces this peer has.

+

download_rate_peak and upload_rate_peak are the highest download and upload +rates seen on this connection. They are given in bytes per second. This number is +reset to 0 on reconnect.

+

progress is the progress of the peer in the range [0, 1]. This is always 0 when +floating point operations are diabled, instead use progress_ppm.

+

progress_ppm indicates the download progress of the peer in the range [0, 1000000] +(parts per million).

+

local_endpoint is the IP and port pair the socket is bound to locally. i.e. the IP +address of the interface it's going out over. This may be useful for multi-homed +clients with multiple interfaces to the internet.

+
+
+

feed_handle

+

The feed_handle refers to a specific RSS feed which is watched by the session. +The feed_item struct is defined in <libtorrent/rss.hpp>. It has the following +functions:

+
+struct feed_handle
+{
+        feed_handle();
+        void update_feed();
+        feed_status get_feed_status() const;
+        void set_settings(feed_settings const& s);
+        feed_settings settings() const;
+};
+
+
+

update_feed()

+
+
+void update_feed();
+
+
+

Forces an update/refresh of the feed. Regular updates of the feed is managed +by libtorrent, be careful to not call this too frequently since it may +overload the RSS server.

+
+
+

get_feed_status()

+
+
+feed_status get_feed_status() const;
+
+
+

Queries the RSS feed for information, including all the items in the feed. +The feed_status object has the following fields:

+
+struct feed_status
+{
+        std::string url;
+        std::string title;
+        std::string description;
+        time_t last_update;
+        int next_update;
+        bool updating;
+        std::vector<feed_item> items;
+        error_code error;
+        int ttl;
+};
+
+

url is the URL of the feed.

+

title is the name of the feed (as specified by the feed itself). This +may be empty if we have not recevied a response from the RSS server yet, +or if the feed does not specify a title.

+

description is the feed description (as specified by the feed itself). +This may be empty if we have not received a response from the RSS server +yet, or if the feed does not specify a description.

+

last_update is the posix time of the last successful response from the feed.

+

next_update is the number of seconds, from now, when the feed will be +updated again.

+

updating is true if the feed is currently being updated (i.e. waiting for +DNS resolution, connecting to the server or waiting for the response to the +HTTP request, or receiving the response).

+

items is a vector of all items that we have received from the feed. See +feed_item for more information.

+

error is set to the appropriate error code if the feed encountered an +error.

+

ttl is the current refresh time (in minutes). It's either the configured +default ttl, or the ttl specified by the feed.

+
+
+

set_settings() settings()

+
+
+void set_settings(feed_settings const& s);
+feed_settings settings() const;
+
+
+

Sets and gets settings for this feed. For more information on the +available settings, see add_feed().

+
+
+
+

feed_item

+

The feed_item struct is defined in <libtorrent/rss.hpp>.

+
+
+struct feed_item
+{
+        feed_item();
+        std::string url;
+        std::string uuid;
+        std::string title;
+        std::string description;
+        std::string comment;
+        std::string category;
+        size_type size;
+        torrent_handle handle;
+        sha1_hash info_hash;
+};
+
+
+

size is the total size of the content the torrent refers to, or -1 +if no size was specified by the feed.

+

handle is the handle to the torrent, if the session is already downloading +this torrent.

+

info_hash is the info-hash of the torrent, or cleared (i.e. all zeroes) if +the feed does not specify the info-hash.

+

All the strings are self explanatory and may be empty if the feed does not specify +those fields.

+
+
+

session customization

+

You have some control over session configuration through the session::apply_settings() +member function. To change one or more configuration options, create a settings_pack. +object and fill it with the settings to be set and pass it in to session::apply_settings().

+

see apply_settings().

+

You have control over proxy and authorization settings and also the user-agent +that will be sent to the tracker. The user-agent will also be used to identify the +client with other peers.

+
+

presets

+

The default values of the session settings are set for a regular bittorrent client running +on a desktop system. There are functions that can set the session settings to pre set +settings for other environments. These can be used for the basis, and should be tweaked to +fit your needs better.

+
+void min_memory_usage(settings_pack& p);
+void high_performance_seed(settings_pack& p);
+
+

min_memory_usage returns settings that will use the minimal amount of RAM, at the +potential expense of upload and download performance. It adjusts the socket buffer sizes, +disables the disk cache, lowers the send buffer watermarks so that each connection only has +at most one block in use at any one time. It lowers the outstanding blocks send to the disk +I/O thread so that connections only have one block waiting to be flushed to disk at any given +time. It lowers the max number of peers in the peer list for torrents. It performs multiple +smaller reads when it hashes pieces, instead of reading it all into memory before hashing.

+

This configuration is inteded to be the starting point for embedded devices. It will +significantly reduce memory usage.

+

high_performance_seed returns settings optimized for a seed box, serving many peers +and that doesn't do any downloading. It has a 128 MB disk cache and has a limit of 400 files +in its file pool. It support fast upload rates by allowing large send buffers.

+
+
+

settings_pack

+
+struct settings_pack
+{
+        void set_str(int name, std::string val);
+        void set_int(int name, int val);
+        void set_bool(int name, bool val);
+
+        std::string get_str(int name);
+        int get_int(int name);
+        bool get_bool(int name);
+
+        void clear();
+
+        all settings enums, see below
+
+        enum { no_piece_suggestions = 0, suggest_read_cache = 1 };
+
+        enum choking_algorithm_t
+        {
+                fixed_slots_choker,
+                auto_expand_choker,
+                rate_based_choker,
+                bittyrant_choker
+        };
+
+        enum seed_choking_algorithm_t
+        {
+                round_robin,
+                fastest_upload,
+                anti_leech
+        };
+
+        enum io_buffer_mode_t
+        {
+                enable_os_cache = 0,
+                disable_os_cache_for_aligned_files = 1,
+                disable_os_cache = 2
+        };
+
+        enum disk_cache_algo_t
+        { lru, largest_contiguous, avoid_readback };
+
+<<<<<<< .working
+=======
+        disk_cache_algo_t disk_cache_algorithm;
+
+        int read_cache_line_size;
+        int write_cache_line_size;
+
+        int optimistic_disk_retry;
+        bool disable_hash_checks;
+
+        int max_suggest_pieces;
+
+        bool drop_skipped_requests;
+
+        bool low_prio_disk;
+        int local_service_announce_interval;
+        int dht_announce_interval;
+
+        int udp_tracker_token_expiry;
+        bool volatile_read_cache;
+        bool guided_read_cache;
+        bool default_cache_min_age;
+
+        int num_optimistic_unchoke_slots;
+        bool no_atime_storage;
+        int default_est_reciprocation_rate;
+        int increase_est_reciprocation_rate;
+        int decrease_est_reciprocation_rate;
+        bool incoming_starts_queued_torrents;
+        bool report_true_downloaded;
+        bool strict_end_game_mode;
+
+        bool broadcast_lsd;
+
+        bool enable_outgoing_utp;
+        bool enable_incoming_utp;
+        bool enable_outgoing_tcp;
+        bool enable_incoming_tcp;
+        int max_pex_peers;
+        bool ignore_resume_timestamps;
+        bool no_recheck_incomplete_resume;
+        bool anonymous_mode;
+        bool force_proxy;
+        int tick_interval;
+        int share_mode_target;
+
+        int upload_rate_limit;
+        int download_rate_limit;
+        int local_upload_rate_limit;
+        int local_download_rate_limit;
+        int dht_upload_rate_limit;
+        int unchoke_slots_limit;
+        int half_open_limit;
+        int connections_limit;
+
+        int utp_target_delay;
+        int utp_gain_factor;
+        int utp_min_timeout;
+        int utp_syn_resends;
+        int utp_num_resends;
+        int utp_connect_timeout;
+        bool utp_dynamic_sock_buf;
+        int utp_loss_multiplier;
+
+>>>>>>> .merge-right.r8585
+        enum bandwidth_mixed_algo_t
+        {
+                prefer_tcp = 0,
+                peer_proportional = 1
+        };
+        int max_http_recv_buffer_size;
+
+        bool support_share_mode;
+        bool support_merkle_torrents;
+        bool report_redundant_bytes;
+        std::string handshake_client_version;
+        bool use_disk_cache_pool;
+};
+
+

The settings_pack struct, contains the names of all settings as +enum values. These values are passed in to the set_str(), +set_int(), set_bool() functions, to specify the setting to +change.

+

These are the available settings:

+ +++++ + + + + + + + + + + + + +
nametypedefault
user_agentstring"libtorrent/"LIBTORRENT_VERSION
+

this is the client identification to the tracker. +The recommended format of this string is: +"ClientName/ClientVersion libtorrent/libtorrentVersion". +This name will not only be used when making HTTP requests, but also when +sending extended headers to peers that support that extension. +It may not contain r or n

+ +++++ + + + + + + + + + + + + +
nametypedefault
announce_ipstring0
+

announce_ip is the ip address passed along to trackers as the &ip= parameter. +If left as the default, that parameter is omitted.

+ +++++ + + + + + + + + + + + + +
nametypedefault
mmap_cachestring0
+

mmap_cache may be set to a filename where the disk cache will be mmapped +to. This could be useful, for instance, to map the disk cache from regular +rotating hard drives onto an SSD drive. Doing that effectively introduces +a second layer of caching, allowing the disk cache to be as big as can +fit on an SSD drive (probably about one order of magnitude more than the +available RAM). The intention of this setting is to set it up once at the +start up and not change it while running. The setting may not be changed +as long as there are any disk buffers in use. This default to the empty +string, which means use regular RAM allocations for the disk cache. The file +specified will be created and truncated to the disk cache size (cache_size). +Any existing file with the same name will be replaced.

+

Since this setting sets a hard upper limit on cache usage, it cannot be combined +with session_settings::contiguous_recv_buffer, since that feature treats the +cache_size setting as a soft (but still pretty hard) limit. The result of combining +the two is peers being disconnected after failing to allocate more disk buffers.

+

This feature requires the mmap system call, on systems that don't have mmap +this setting is ignored.

+ +++++ + + + + + + + + + + + + +
nametypedefault
allow_multiple_connections_per_ipboolfalse
+

determines if connections from the same IP address as +existing connections should be rejected or not. Multiple +connections from the same IP address is not allowed by +default, to prevent abusive behavior by peers. It may +be useful to allow such connections in cases where +simulations are run on the same machie, and all peers +in a swarm has the same IP address.

+ +++++ + + + + + + + + + + + + +
nametypedefault
send_redundant_havebooltrue
+

if set to true, upload, download and unchoke limits +are ignored for peers on the local network. +This option is DEPRECATED, please use set_peer_class_filter() instead. +send_redundant_have controls if have messages will be sent +to peers that already have the piece. This is typically not necessary, +but it might be necessary for collecting statistics in some cases. +Default is false.

+ +++++ + + + + + + + + + + + + +
nametypedefault
lazy_bitfieldsbooltrue
+

if this is true, outgoing bitfields will never be fuil. If the +client is seed, a few bits will be set to 0, and later filled +in with have messages. This is to prevent certain ISPs +from stopping people from seeding.

+ +++++ + + + + + + + + + + + + +
nametypedefault
use_dht_as_fallbackboolfalse
+

use_dht_as_fallback determines how the DHT is used. If this is true, +the DHT will only be used for torrents where all trackers in its tracker +list has failed. Either by an explicit error message or a time out. This +is false by default, which means the DHT is used by default regardless of

+ +++++ + + + + + + + + + + + + +
nametypedefault
upnp_ignore_nonroutersboolfalse
+

upnp_ignore_nonrouters indicates whether or not the UPnP implementation +should ignore any broadcast response from a device whose address is not the +configured router for this machine. i.e. it's a way to not talk to other +people's routers by mistake.

+ +++++ + + + + + + + + + + + + +
nametypedefault
use_parole_modebooltrue
+

use_parole_mode specifies if parole mode should be used. Parole mode means +that peers that participate in pieces that fail the hash check are put in a mode +where they are only allowed to download whole pieces. If the whole piece a peer +in parole mode fails the hash check, it is banned. If a peer participates in a +piece that passes the hash check, it is taken out of parole mode.

+ +++++ + + + + + + + + + + + + + + + + +
nametypedefault
use_read_cachebooltrue
use_write_cachebooltrue
+

enable and disable caching of read blocks and +blocks to be written to disk respsectively. +the purpose of the read cache is partly read-ahead of requests +but also to avoid reading blocks back from the disk multiple +times for popular pieces. +the write cache purpose is to hold off writing blocks to disk until +they have been hashed, to avoid having to read them back in again.

+ +++++ + + + + + + + + + + + + +
nametypedefault
dont_flush_write_cacheboolfalse
+

this will make the disk cache never flush a write +piece if it would cause is to have to re-read it +once we want to calculate the piece hash

+ +++++ + + + + + + + + + + + + +
nametypedefault
explicit_read_cacheboolfalse
+

explicit_read_cache defaults to 0. If set to something greater than 0, the +disk read cache will not be evicted by cache misses and will explicitly be +controlled based on the rarity of pieces. Rare pieces are more likely to be +cached. This would typically be used together with suggest_mode set to +suggest_read_cache. The value is the number of pieces to keep in the read +cache. If the actual read cache can't fit as many, it will essentially be clamped.

+ +++++ + + + + + + + + + + + + + + + + +
nametypedefault
coalesce_readsboolfalse
coalesce_writesboolfalse
+

allocate separate, contiguous, buffers for read and +write calls. Only used where writev/readv cannot be used +will use more RAM but may improve performance

+ +++++ + + + + + + + + + + + + +
nametypedefault
auto_manage_prefer_seedsboolfalse
+

prefer seeding torrents when determining which torrents to give +active slots to, the default is false which gives preference to +downloading torrents

+ +++++ + + + + + + + + + + + + +
nametypedefault
dont_count_slow_torrentsbooltrue
+

if dont_count_slow_torrents is true, torrents without any payload transfers are +not subject to the active_seeds and active_downloads limits. This is intended +to make it more likely to utilize all available bandwidth, and avoid having torrents +that don't transfer anything block the active slots.

+ +++++ + + + + + + + + + + + + +
nametypedefault
close_redundant_connectionsbooltrue
+

close_redundant_connections specifies whether libtorrent should close +connections where both ends have no utility in keeping the connection open. +For instance if both ends have completed their downloads, there's no point +in keeping it open.

+ +++++ + + + + + + + + + + + + +
nametypedefault
prioritize_partial_piecesboolfalse
+

If prioritize_partial_pieces is true, partial pieces are picked +before pieces that are more rare. If false, rare pieces are always +prioritized, unless the number of partial pieces is growing out of +proportion.

+ +++++ + + + + + + + + + + + + +
nametypedefault
rate_limit_ip_overheadbooltrue
+

if set to true, the estimated TCP/IP overhead is +drained from the rate limiters, to avoid exceeding +the limits with the total traffic

+ +++++ + + + + + + + + + + + + + + + + +
nametypedefault
announce_to_all_tiersboolfalse
announce_to_all_trackersboolfalse
+

announce_to_all_trackers controls how multi tracker torrents are +treated. If this is set to true, all trackers in the same tier are +announced to in parallel. If all trackers in tier 0 fails, all trackers +in tier 1 are announced as well. If it's set to false, the behavior is as +defined by the multi tracker specification. It defaults to false, which +is the same behavior previous versions of libtorrent has had as well.

+

announce_to_all_tiers also controls how multi tracker torrents are +treated. When this is set to true, one tracker from each tier is announced +to. This is the uTorrent behavior. This is false by default in order +to comply with the multi-tracker specification.

+ +++++ + + + + + + + + + + + + +
nametypedefault
prefer_udp_trackersbooltrue
+

prefer_udp_trackers is true by default. It means that trackers may +be rearranged in a way that udp trackers are always tried before http +trackers for the same hostname. Setting this to fails means that the +trackers' tier is respected and there's no preference of one protocol +over another.

+ +++++ + + + + + + + + + + + + +
nametypedefault
strict_super_seedingboolfalse
+

strict_super_seeding when this is set to true, a piece has to +have been forwarded to a third peer before another one is handed out. +This is the traditional definition of super seeding.

+ +++++ + + + + + + + + + + + + +
nametypedefault
lock_disk_cacheboolfalse
+

if this is set to true, the memory allocated for the +disk cache will be locked in physical RAM, never to +be swapped out

+ +++++ + + + + + + + + + + + + +
nametypedefault
optimize_hashing_for_speedbooltrue
+

optimize_hashing_for_speed chooses between two ways of reading back +piece data from disk when its complete and needs to be verified against +the piece hash. This happens if some blocks were flushed to the disk +out of order. Everything that is flushed in order is hashed as it goes +along. Optimizing for speed will allocate space to fit all the the +remaingin, unhashed, part of the piece, reads the data into it in a single +call and hashes it. This is the default. If optimizing_hashing_for_speed +is false, a single block will be allocated (16 kB), and the unhashed parts +of the piece are read, one at a time, and hashed in this single block. This +is appropriate on systems that are memory constrained.

+ +++++ + + + + + + + + + + + + +
nametypedefault
disable_hash_checksboolfalse
+

when set to true, all data downloaded from +peers will be assumed to be correct, and not +tested to match the hashes in the torrent +this is only useful for simulation and +testing purposes (typically combined with +disabled_storage)

+ +++++ + + + + + + + + + + + + +
nametypedefault
allow_reordered_disk_operationsbooltrue
+

if this is true, disk read operations are +sorted by their physical offset on disk before +issued to the operating system. This is useful +if async I/O is not supported. It defaults to +true if async I/O is not supported and fals +otherwise. +disk I/O operations are likely to be reordered +regardless of this setting when async I/O +is supported by the OS.

+ +++++ + + + + + + + + + + + + +
nametypedefault
allow_i2p_mixedboolfalse
+

if this is true, i2p torrents are allowed +to also get peers from other sources than +the tracker, and connect to regular IPs, +not providing any anonymization. This may +be useful if the user is not interested in +the anonymization of i2p, but still wants to +be able to connect to i2p peers.

+ +++++ + + + + + + + + + + + + +
nametypedefault
drop_skipped_requestsboolfalse
+

If drop_skipped_requests is set to true (it defaults to false), piece +requests that have been skipped enough times when piece messages +are received, will be considered lost. Requests are considered skipped +when the returned piece messages are re-ordered compared to the order +of the requests. This was an attempt to get out of dead-locks caused by +BitComet peers silently ignoring some requests. It may cause problems +at high rates, and high level of reordering in the uploading peer, that's +why it's disabled by default.

+ +++++ + + + + + + + + + + + + +
nametypedefault
low_prio_diskbooltrue
+

low_prio_disk determines if the disk I/O should use a normal +or low priority policy. This defaults to true, which means that +it's low priority by default. Other processes doing disk I/O will +normally take priority in this mode. This is meant to improve the +overall responsiveness of the system while downloading in the +background. For high-performance server setups, this might not +be desirable.

+ +++++ + + + + + + + + + + + + +
nametypedefault
volatile_read_cacheboolfalse
+

volatile_read_cache, if this is set to true, read cache blocks +that are hit by peer read requests are removed from the disk cache +to free up more space. This is useful if you don't expect the disk +cache to create any cache hits from other peers than the one who +triggered the cache line to be read into the cache in the first place.

+ +++++ + + + + + + + + + + + + +
nametypedefault
guided_read_cacheboolfalse
+

guided_read_cache enables the disk cache to adjust the size +of a cache line generated by peers to depend on the upload rate +you are sending to that peer. The intention is to optimize the RAM +usage of the cache, to read ahead further for peers that you're +sending faster to.

+ +++++ + + + + + + + + + + + + +
nametypedefault
no_atime_storagebooltrue
+

no_atime_storage this is a linux-only option and passes in the +O_NOATIME to open() when opening files. This may lead to +some disk performance improvements.

+ +++++ + + + + + + + + + + + + +
nametypedefault
incoming_starts_queued_torrentsboolfalse
+

incoming_starts_queued_torrents defaults to false. If a torrent +has been paused by the auto managed feature in libtorrent, i.e. +the torrent is paused and auto managed, this feature affects whether +or not it is automatically started on an incoming connection. The +main reason to queue torrents, is not to make them unavailable, but +to save on the overhead of announcing to the trackers, the DHT and to +avoid spreading one's unchoke slots too thin. If a peer managed to +find us, even though we're no in the torrent anymore, this setting +can make us start the torrent and serve it.

+ +++++ + + + + + + + + + + + + +
nametypedefault
report_true_downloadedboolfalse
+

when set to true, the downloaded counter sent to trackers +will include the actual number of payload bytes donwnloaded +including redundant bytes. If set to false, it will not include +any redundany bytes

+ +++++ + + + + + + + + + + + + +
nametypedefault
strict_end_game_modebooltrue
+

strict_end_game_mode defaults to true, and controls when a block +may be requested twice. If this is true, a block may only be requested +twice when there's ay least one request to every piece that's left to +download in the torrent. This may slow down progress on some pieces +sometimes, but it may also avoid downloading a lot of redundant bytes. +If this is false, libtorrent attempts to use each peer connection +to its max, by always requesting something, even if it means requesting +something that has been requested from another peer already.

+ +++++ + + + + + + + + + + + + +
nametypedefault
broadcast_lsdbooltrue
+

if broadcast_lsd is set to true, the local peer discovery +(or Local Service Discovery) will not only use IP multicast, but also +broadcast its messages. This can be useful when running on networks +that don't support multicast. Since broadcast messages might be +expensive and disruptive on networks, only every 8th announce uses +broadcast.

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
nametypedefault
enable_outgoing_utpbooltrue
enable_incoming_utpbooltrue
enable_outgoing_tcpbooltrue
enable_incoming_tcpbooltrue
+

when set to true, libtorrent will try to make outgoing utp connections +controls whether libtorrent will accept incoming connections or make +outgoing connections of specific type.

+ +++++ + + + + + + + + + + + + +
nametypedefault
ignore_resume_timestampsboolfalse
+

ignore_resume_timestamps determines if the storage, when loading +resume data files, should verify that the file modification time +with the timestamps in the resume data. This defaults to false, which +means timestamps are taken into account, and resume data is less likely +to accepted (torrents are more likely to be fully checked when loaded). +It might be useful to set this to true if your network is faster than your +disk, and it would be faster to redownload potentially missed pieces than +to go through the whole storage to look for them.

+ +++++ + + + + + + + + + + + + +
nametypedefault
no_recheck_incomplete_resumeboolfalse
+

no_recheck_incomplete_resume determines if the storage should check +the whole files when resume data is incomplete or missing or whether +it should simply assume we don't have any of the data. By default, this +is determined by the existance of any of the files. By setting this setting +to true, the files won't be checked, but will go straight to download +mode.

+ +++++ + + + + + + + + + + + + +
nametypedefault
anonymous_modeboolfalse
+

anonymous_mode defaults to false. When set to true, the client tries +to hide its identity to a certain degree. The peer-ID will no longer +include the client's fingerprint. The user-agent will be reset to an +empty string. Trackers will only be used if they are using a proxy +server. The listen sockets are closed, and incoming connections will +only be accepted through a SOCKS5 or I2P proxy (if a peer proxy is set up and +is run on the same machine as the tracker proxy). Since no incoming connections +are accepted, NAT-PMP, UPnP, DHT and local peer discovery are all turned off +when this setting is enabled.

+

If you're using I2P, it might make sense to enable anonymous mode as well.

+ +++++ + + + + + + + + + + + + +
nametypedefault
report_web_seed_downloadsbooltrue
+

specifies whether downloads from web seeds is reported to the +tracker or not. Defaults to on

+ +++++ + + + + + + + + + + + + +
nametypedefault
utp_dynamic_sock_bufbooltrue
+

controls if the uTP socket manager is allowed to increase +the socket buffer if a network interface with a large MTU is used (such as loopback +or ethernet jumbo frames). This defaults to true and might improve uTP throughput. +For RAM constrained systems, disabling this typically saves around 30kB in user space +and probably around 400kB in kernel socket buffers (it adjusts the send and receive +buffer size on the kernel socket, both for IPv4 and IPv6).

+ +++++ + + + + + + + + + + + + +
nametypedefault
announce_double_natboolfalse
+

set to true if uTP connections should be rate limited +This option is DEPRECATED, please use set_peer_class_filter() instead. +if this is true, the &ip= argument in tracker requests +(unless otherwise specified) will be set to the intermediate +IP address if the user is double NATed. If ther user is not +double NATed, this option does not have an affect

+ +++++ + + + + + + + + + + + + +
nametypedefault
seeding_outgoing_connectionsbooltrue
+

seeding_outgoing_connections determines if seeding (and finished) torrents +should attempt to make outgoing connections or not. By default this is true. It +may be set to false in very specific applications where the cost of making +outgoing connections is high, and there are no or small benefits of doing so. +For instance, if no nodes are behind a firewall or a NAT, seeds don't need to +make outgoing connections.

+ +++++ + + + + + + + + + + + + +
nametypedefault
no_connect_privileged_portsbooltrue
+

when this is true, libtorrent will not attempt to make outgoing +connections to peers whose port is < 1024. This is a safety +precaution to avoid being part of a DDoS attack

+ +++++ + + + + + + + + + + + + +
nametypedefault
smooth_connectsbooltrue
+

smooth_connects is true by default, which means the number of connection +attempts per second may be limited to below the connection_speed, in case +we're close to bump up against the limit of number of connections. The intention +of this setting is to more evenly distribute our connection attempts over time, +instead of attempting to connectin in batches, and timing them out in batches.

+ +++++ + + + + + + + + + + + + +
nametypedefault
always_send_user_agentboolfalse
+

always send user-agent in every web seed request. If false, only +the first request per http connection will include the user agent

+ +++++ + + + + + + + + + + + + +
nametypedefault
apply_ip_filter_to_trackersbooltrue
+

apply_ip_filter_to_trackers defaults to true. It determines whether the +IP filter applies to trackers as well as peers. If this is set to false, +trackers are exempt from the IP filter (if there is one). If no IP filter +is set, this setting is irrelevant.

+ +++++ + + + + + + + + + + + + +
nametypedefault
use_disk_read_aheadbooltrue
+

use_disk_read_ahead defaults to true and will attempt to optimize disk reads +by giving the operating system heads up of disk read requests as they are queued +in the disk job queue.

+ +++++ + + + + + + + + + + + + +
nametypedefault
lock_filesboolfalse
+

lock_files determines whether or not to lock files which libtorrent is downloading +to or seeding from. This is implemented using fcntl(F_SETLK) on unix systems and +by not passing in SHARE_READ and SHARE_WRITE on windows. This might prevent +3rd party processes from corrupting the files under libtorrent's feet.

+ +++++ + + + + + + + + + + + + +
nametypedefault
contiguous_recv_bufferbooltrue
+

contiguous_recv_buffer determines whether or not libtorrent should receive +data from peers into a contiguous intermediate buffer, to then copy blocks into +disk buffers from, or to make many smaller calls to read(), each time passing +in the specific buffer the data belongs in. When downloading at high rates, the latter +may save some time copying data. When seeding at high rates, all incoming traffic +consists of a very large number of tiny packets, and enabling contiguous_recv_buffer +will provide higher performance. When this is enabled, it will only be used when +seeding to peers, since that's when it provides performance improvements.

+ +++++ + + + + + + + + + + + + +
nametypedefault
ban_web_seedsbooltrue
+

when true, web seeds sending bad data will be banned

+ +++++ + + + + + + + + + + + + +
nametypedefault
allow_partial_disk_writesbooltrue
+

when set to false, the write_cache_line_size will apply across piece boundaries. +this is a bad idea unless the piece picker also is configured to have an affinity +to pick pieces belonging to the same write cache line as is configured in the +disk cache.

+ +++++ + + + + + + + + + + + + +
nametypedefault
tracker_completion_timeoutint60
+

tracker_completion_timeout is the number of seconds the tracker +connection will wait from when it sent the request until it considers the +tracker to have timed-out. Default value is 60 seconds.

+ +++++ + + + + + + + + + + + + +
nametypedefault
tracker_receive_timeoutint40
+

tracker_receive_timeout is the number of seconds to wait to receive +any data from the tracker. If no data is received for this number of +seconds, the tracker will be considered as having timed out. If a tracker +is down, this is the kind of timeout that will occur.

+ +++++ + + + + + + + + + + + + +
nametypedefault
stop_tracker_timeoutint5
+

the time to wait when sending a stopped message +before considering a tracker to have timed out. +this is usually shorter, to make the client quit +faster

+ +++++ + + + + + + + + + + + + +
nametypedefault
tracker_maximum_response_lengthint1024*1024
+

this is the maximum number of bytes in a tracker +response. If a response size passes this number +of bytes it will be rejected and the connection +will be closed. On gzipped responses this size is +measured on the uncompressed data. So, if you get +20 bytes of gzip response that'll expand to 2 megabytes, +it will be interrupted before the entire response +has been uncompressed (assuming the limit is lower +than 2 megs).

+ +++++ + + + + + + + + + + + + +
nametypedefault
piece_timeoutint20
+

the number of seconds from a request is sent until +it times out if no piece response is returned.

+ +++++ + + + + + + + + + + + + +
nametypedefault
request_timeoutint50
+

the number of seconds one block (16kB) is expected +to be received within. If it's not, the block is +requested from a different peer

+ +++++ + + + + + + + + + + + + +
nametypedefault
request_queue_timeint3
+

the length of the request queue given in the number +of seconds it should take for the other end to send +all the pieces. i.e. the actual number of requests +depends on the download rate and this number.

+ +++++ + + + + + + + + + + + + +
nametypedefault
max_allowed_in_request_queueint250
+

the number of outstanding block requests a peer is +allowed to queue up in the client. If a peer sends +more requests than this (before the first one has +been sent) the last request will be dropped. +the higher this is, the faster upload speeds the +client can get to a single peer.

+ +++++ + + + + + + + + + + + + +
nametypedefault
max_out_request_queueint200
+

max_out_request_queue is the maximum number of outstanding requests to +send to a peer. This limit takes precedence over request_queue_time. i.e. +no matter the download speed, the number of outstanding requests will never +exceed this limit.

+ +++++ + + + + + + + + + + + + +
nametypedefault
whole_pieces_thresholdint20
+

if a whole piece can be downloaded in this number +of seconds, or less, the peer_connection will prefer +to request whole pieces at a time from this peer. +The benefit of this is to better utilize disk caches by +doing localized accesses and also to make it easier +to identify bad peers if a piece fails the hash check.

+ +++++ + + + + + + + + + + + + +
nametypedefault
peer_timeoutint120
+

peer_timeout is the number of seconds the peer connection should +wait (for any activity on the peer connection) before closing it due +to time out. This defaults to 120 seconds, since that's what's specified +in the protocol specification. After half the time out, a keep alive message +is sent.

+ +++++ + + + + + + + + + + + + +
nametypedefault
urlseed_timeoutint20
+

same as peer_timeout, but only applies to url-seeds. +this is usually set lower, because web servers are +expected to be more reliable.

+ +++++ + + + + + + + + + + + + +
nametypedefault
urlseed_pipeline_sizeint5
+

controls the pipelining size of url-seeds. i.e. the number +of HTTP request to keep outstanding before waiting for +the first one to complete. It's common for web servers +to limit this to a relatively low number, like 5

+ +++++ + + + + + + + + + + + + +
nametypedefault
urlseed_wait_retryint30
+

time to wait until a new retry of a web seed takes place

+ +++++ + + + + + + + + + + + + +
nametypedefault
file_pool_sizeint40
+

sets the upper limit on the total number of files this +session will keep open. The reason why files are +left open at all is that some anti virus software +hooks on every file close, and scans the file for +viruses. deferring the closing of the files will +be the difference between a usable system and +a completely hogged down system. Most operating +systems also has a limit on the total number of +file descriptors a process may have open. It is +usually a good idea to find this limit and set the +number of connections and the number of files +limits so their sum is slightly below it.

+ +++++ + + + + + + + + + + + + +
nametypedefault
max_failcountint3
+

max_failcount is the maximum times we try to connect to a peer before +stop connecting again. If a peer succeeds, the failcounter is reset. If +a peer is retrieved from a peer source (other than DHT) the failcount is +decremented by one, allowing another try.

+ +++++ + + + + + + + + + + + + +
nametypedefault
min_reconnect_timeint60
+

the number of seconds to wait to reconnect to a peer. +this time is multiplied with the failcount.

+ +++++ + + + + + + + + + + + + +
nametypedefault
peer_connect_timeoutint15
+

peer_connect_timeout the number of seconds to wait after a connection +attempt is initiated to a peer until it is considered as having timed out. +This setting is especially important in case the number of half-open +connections are limited, since stale half-open +connection may delay the connection of other peers considerably.

+ +++++ + + + + + + + + + + + + +
nametypedefault
connection_speedint6
+

connection_speed is the number of connection attempts that +are made per second. If a number < 0 is specified, it will default to +200 connections per second. If 0 is specified, it means don't make +outgoing connections at all.

+ +++++ + + + + + + + + + + + + +
nametypedefault
inactivity_timeoutint600
+

if a peer is uninteresting and uninterested for longer +than this number of seconds, it will be disconnected. +default is 10 minutes

+ +++++ + + + + + + + + + + + + +
nametypedefault
unchoke_intervalint15
+

unchoke_interval is the number of seconds between chokes/unchokes. +On this interval, peers are re-evaluated for being choked/unchoked. This +is defined as 30 seconds in the protocol, and it should be significantly +longer than what it takes for TCP to ramp up to it's max rate.

+ +++++ + + + + + + + + + + + + +
nametypedefault
optimistic_unchoke_intervalint30
+

optimistic_unchoke_interval is the number of seconds between +each optimistic unchoke. On this timer, the currently optimistically +unchoked peer will change.

+ +++++ + + + + + + + + + + + + +
nametypedefault
num_wantint200
+

num_want is the number of peers we want from each tracker request. It defines +what is sent as the &num_want= parameter to the tracker.

+ +++++ + + + + + + + + + + + + +
nametypedefault
initial_picker_thresholdint4
+

initial_picker_threshold specifies the number of pieces we need before we +switch to rarest first picking. This defaults to 4, which means the 4 first +pieces in any torrent are picked at random, the following pieces are picked +in rarest first order.

+ +++++ + + + + + + + + + + + + +
nametypedefault
allowed_fast_set_sizeint10
+

the number of allowed pieces to send to peers +that supports the fast extensions

+ +++++ + + + + + + + + + + + + +
nametypedefault
suggest_modeintsettings_pack::no_piece_suggestions
+

suggest_mode controls whether or not libtorrent will send out suggest +messages to create a bias of its peers to request certain pieces. The modes +are:

+
    +
  • no_piece_suggestsions which is the default and will not send out suggest +messages.
  • +
  • suggest_read_cache which will send out suggest messages for the most +recent pieces that are in the read cache.
  • +
+ +++++ + + + + + + + + + + + + +
nametypedefault
max_queued_disk_bytesint1024 * 1024
+

max_queued_disk_bytes is the number maximum number of bytes, to be +written to disk, that can wait in the disk I/O thread queue. This queue +is only for waiting for the disk I/O thread to receive the job and either +write it to disk or insert it in the write cache. When this limit is reached, +the peer connections will stop reading data from their sockets, until the disk +thread catches up. Setting this too low will severly limit your download rate.

+ +++++ + + + + + + + + + + + + +
nametypedefault
handshake_timeoutint10
+

the number of seconds to wait for a handshake +response from a peer. If no response is received +within this time, the peer is disconnected.

+ +++++ + + + + + + + + + + + + + + + + + + + + +
nametypedefault
send_buffer_low_watermarkint512
send_buffer_watermarkint500 * 1024
send_buffer_watermark_factorint50
+

send_buffer_low_watermark the minimum send buffer target +size (send buffer includes bytes pending being read from disk). +For good and snappy seeding performance, set this fairly high, to +at least fit a few blocks. This is essentially the initial +window size which will determine how fast we can ramp up +the send rate

+

if the send buffer has fewer bytes than send_buffer_watermark, +we'll read another 16kB block onto it. If set too small, +upload rate capacity will suffer. If set too high, +memory will be wasted. +The actual watermark may be lower than this in case +the upload rate is low, this is the upper limit.

+

the current upload rate to a peer is multiplied by +this factor to get the send buffer watermark. The +factor is specified as a percentage. i.e. 50 -> 0.5 +This product is clamped to the send_buffer_watermark +setting to not exceed the max. For high speed +upload, this should be set to a greater value than +100. For high capacity connections, setting this +higher can improve upload performance and disk throughput. Setting it too +high may waste RAM and create a bias towards read jobs over write jobs.

+ +++++ + + + + + + + + + + + + + + + + +
nametypedefault
choking_algorithmintsettings_pack::fixed_slots_choker
seed_choking_algorithmintsettings_pack::round_robin
+

choking_algorithm specifies which algorithm to use to determine which peers +to unchoke.

+

The options for choking algorithms are:

+
    +
  • fixed_slots_choker is the traditional choker with a fixed number of unchoke +slots (as specified by session::set_max_uploads()).
  • +
  • auto_expand_choker opens at least the number of slots as specified by +session::set_max_uploads() but opens up more slots if the upload capacity +is not saturated. This unchoker will work just like the fixed_slots_choker +if there's no global upload rate limit set.
  • +
  • rate_based_choker opens up unchoke slots based on the upload rate +achieved to peers. The more slots that are opened, the marginal upload +rate required to open up another slot increases.
  • +
  • bittyrant_choker attempts to optimize download rate by finding the +reciprocation rate of each peer individually and prefers peers that gives +the highest return on investment. It still allocates all upload capacity, +but shuffles it around to the best peers first. For this choker to be +efficient, you need to set a global upload rate limit +(session_settings::upload_rate_limit). For more information about this +choker, see the paper.
  • +
+

seed_choking_algorithm controls the seeding unchoke behavior. The available +options are:

+
    +
  • round_robin which round-robins the peers that are unchoked when seeding. This +distributes the upload bandwidht uniformly and fairly. It minimizes the ability +for a peer to download everything without redistributing it.
  • +
  • fastest_upload unchokes the peers we can send to the fastest. This might be +a bit more reliable in utilizing all available capacity.
  • +
  • anti_leech prioritizes peers who have just started or are just about to finish +the download. The intention is to force peers in the middle of the download to +trade with each other.
  • +
+ +++++ + + + + + + + + + + + + + + + + + + + + +
nametypedefault
cache_sizeint1024
cache_buffer_chunk_sizeint0
cache_expiryint300
+

cache_size is the disk write and read cache. It is specified in units of +16 KiB blocks. Buffers that are part of a peer's send or receive buffer also +count against this limit. Send and receive buffers will never be denied to be +allocated, but they will cause the actual cached blocks to be flushed or evicted. +If this is set to -1, the cache size is automatically set to the amount +of physical RAM available in the machine divided by 8. If the amount of physical +RAM cannot be determined, it's set to 1024 (= 16 MiB).

+

Disk buffers are allocated using a pool allocator, the number of blocks that +are allocated at a time when the pool needs to grow can be specified in +cache_buffer_chunk_size. Lower numbers saves memory at the expense of more +heap allocations. If it is set to 0, the effective chunk size is proportional +to the total cache size, attempting to strike a good balance between performance +and memory usage. It defaults to 0. +cache_expiry is the number of seconds from the last cached write to a piece +in the write cache, to when it's forcefully flushed to disk. Default is 60 second.

+ +++++ + + + + + + + + + + + + +
nametypedefault
explicit_cache_intervalint30
+

explicit_cache_interval is the number of seconds in between each refresh of +a part of the explicit read cache. Torrents take turns in refreshing and this +is the time in between each torrent refresh. Refreshing a torrent's explicit +read cache means scanning all pieces and picking a random set of the rarest ones. +There is an affinity to pick pieces that are already in the cache, so that +subsequent refreshes only swaps in pieces that are rarer than whatever is in +the cache at the time.

+ +++++ + + + + + + + + + + + + + + + + +
nametypedefault
disk_io_write_modeintsettings_pack::enable_os_cache
disk_io_read_modeintsettings_pack::enable_os_cache
+

determines how files are opened when they're in read only mode versus +read and write mode. The options are:

+
    +
  • +
    enable_os_cache
    +

    This is the default and files are opened normally, with the OS caching +reads and writes.

    +
    +
    +
  • +
  • +
    disable_os_cache_for_aligned_files
    +

    This will open files in unbuffered mode for files where every read and +write would be sector aligned. Using aligned disk offsets is a requirement +on some operating systems.

    +
    +
    +
  • +
  • +
    disable_os_cache
    +

    This opens all files in unbuffered mode (if allowed by the operating system). +Linux and Windows, for instance, require disk offsets to be sector aligned, +and in those cases, this option is the same as disable_os_caches_for_aligned_files.

    +
    +
    +
  • +
+

One reason to disable caching is that it may help the operating system from growing +its file cache indefinitely. Since some OSes only allow aligned files to be opened +in unbuffered mode, It is recommended to make the largest file in a torrent the first +file (with offset 0) or use pad files to align all files to piece boundries.

+ +++++ + + + + + + + + + + + + + + + + +
nametypedefault
outgoing_portint0
num_outgoing_portsint0
+

this is the first port to use for binding +outgoing connections to. This is useful +for users that have routers that +allow QoS settings based on local port. +when binding outgoing connections to specific +ports, num_outgoing_ports is the size of +the range. It should be more than a few

+
+

Warning

+

setting outgoing ports will limit the ability to keep multiple +connections to the same client, even for different torrents. It is not +recommended to change this setting. Its main purpose is to use as an +escape hatch for cheap routers with QoS capability but can only classify +flows based on port numbers.

+
+

It is a range instead of a single port because of the problems with failing to reconnect +to peers if a previous socket to that peer and port is in TIME_WAIT state.

+ +++++ + + + + + + + + + + + + +
nametypedefault
peer_tosint0
+

peer_tos determines the TOS byte set in the IP header of every packet +sent to peers (including web seeds). The default value for this is 0x0 +(no marking). One potentially useful TOS mark is 0x20, this represents +the QBone scavenger service. For more details, see QBSS.

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
nametypedefault
active_downloadsint3
active_seedsint5
active_dht_limitint88
active_tracker_limitint1600
active_lsd_limitint60
active_limitint15
active_loaded_limitint0
+

for auto managed torrents, these are the limits +they are subject to. If there are too many torrents +some of the auto managed ones will be paused until +some slots free up. +active_downloads and active_seeds controls how many active seeding and +downloading torrents the queuing mechanism allows. The target number of active +torrents is min(active_downloads + active_seeds, active_limit). +active_downloads and active_seeds are upper limits on the number of +downloading torrents and seeding torrents respectively. Setting the value to +-1 means unlimited.

+

For example if there are 10 seeding torrents and 10 downloading torrents, and +active_downloads is 4 and active_seeds is 4, there will be 4 seeds +active and 4 downloading torrents. If the settings are active_downloads = 2 +and active_seeds = 4, then there will be 2 downloading torrents and 4 seeding +torrents active. Torrents that are not auto managed are also counted against these +limits. If there are non-auto managed torrents that use up all the slots, no +auto managed torrent will be activated.

+

active_limit is a hard limit on the number of active torrents. This applies even to +slow torrents.

+

active_dht_limit is the max number of torrents to announce to the DHT. By default +this is set to 88, which is no more than one DHT announce every 10 seconds.

+

active_tracker_limit is the max number of torrents to announce to their trackers. +By default this is 360, which is no more than one announce every 5 seconds.

+

active_lsd_limit is the max number of torrents to announce to the local network +over the local service discovery protocol. By default this is 80, which is no more +than one announce every 5 seconds (assuming the default announce interval of 5 minutes).

+

You can have more torrents active, even though they are not announced to the DHT, +lsd or their tracker. If some peer knows about you for any reason and tries to connect, +it will still be accepted, unless the torrent is paused, which means it won't accept +any connections.

+

active_loaded_limit is the number of torrents that are allowed to be loaded +at any given time. Note that a torrent can be active even though it's not loaded. +if an unloaded torrents finds a peer that wants to access it, the torrent will be +loaded on demand, using a user-supplied callback function. If the feature of unloading +torrents is not enabled, this setting have no effect. If this limit is set to 0, it +means unlimited. For more information, see dynamic loading of torrent files.

+ +++++ + + + + + + + + + + + + +
nametypedefault
auto_manage_intervalint30
+

auto_manage_interval is the number of seconds between the torrent queue +is updated, and rotated.

+ +++++ + + + + + + + + + + + + +
nametypedefault
seed_time_limitint24 * 60 * 60
+

this is the limit on the time a torrent has been an active seed +(specified in seconds) before it is considered having met the seed limit criteria. +See queuing.

+ +++++ + + + + + + + + + + + + + + + + +
nametypedefault
auto_scrape_intervalint1800
auto_scrape_min_intervalint300
+

auto_scrape_interval is the number of seconds between scrapes of +queued torrents (auto managed and paused torrents). Auto managed +torrents that are paused, are scraped regularly in order to keep +track of their downloader/seed ratio. This ratio is used to determine +which torrents to seed and which to pause.

+

auto_scrape_min_interval is the minimum number of seconds between any +automatic scrape (regardless of torrent). In case there are a large number +of paused auto managed torrents, this puts a limit on how often a scrape +request is sent.

+ +++++ + + + + + + + + + + + + + + + + +
nametypedefault
max_peerlist_sizeint3000
max_paused_peerlist_sizeint1000
+

max_peerlist_size is the maximum number of peers in the list of +known peers. These peers are not necessarily connected, so this number +should be much greater than the maximum number of connected peers. +Peers are evicted from the cache when the list grows passed 90% of +this limit, and once the size hits the limit, peers are no longer +added to the list. If this limit is set to 0, there is no limit on +how many peers we'll keep in the peer list.

+

max_paused_peerlist_size is the max peer list size used for torrents +that are paused. This default to the same as max_peerlist_size, but +can be used to save memory for paused torrents, since it's not as +important for them to keep a large peer list.

+ +++++ + + + + + + + + + + + + +
nametypedefault
min_announce_intervalint5 * 60
+

this is the minimum allowed announce interval for a tracker. This +is specified in seconds and is used as a sanity check on what is +returned from a tracker. It mitigates hammering misconfigured trackers.

+ +++++ + + + + + + + + + + + + +
nametypedefault
auto_manage_startupint120
+

this is the number of seconds a torrent is considered +active after it was started, regardless of upload and download speed. This +is so that newly started torrents are not considered inactive until they +have a fair chance to start downloading.

+ +++++ + + + + + + + + + + + + +
nametypedefault
seeding_piece_quotaint20
+

seeding_piece_quota is the number of pieces to send to a peer, +when seeding, before rotating in another peer to the unchoke set. +It defaults to 3 pieces, which means that when seeding, any peer we've +sent more than this number of pieces to will be unchoked in favour of +a choked peer.

+ +++++ + + + + + + + + + + + + +
nametypedefault
max_sparse_regionsint0
+

max_sparse_regions is a limit of the number of sparse regions in +a torrent. A sparse region is defined as a hole of pieces we have not +yet downloaded, in between pieces that have been downloaded. This is +used as a hack for windows vista which has a bug where you cannot +write files with more than a certain number of sparse regions. This +limit is not hard, it will be exceeded. Once it's exceeded, pieces +that will maintain or decrease the number of sparse regions are +prioritized. To disable this functionality, set this to 0. It defaults +to 0 on all platforms except windows.

+ +++++ + + + + + + + + + + + + +
nametypedefault
max_rejectsint50
+

max_rejects is the number of piece requests we will reject in a row +while a peer is choked before the peer is considered abusive and is +disconnected.

+ +++++ + + + + + + + + + + + + + + + + +
nametypedefault
recv_socket_buffer_sizeint0
send_socket_buffer_sizeint0
+

recv_socket_buffer_size and send_socket_buffer_size specifies +the buffer sizes set on peer sockets. 0 (which is the default) means +the OS default (i.e. don't change the buffer sizes). The socket buffer +sizes are changed using setsockopt() with SOL_SOCKET/SO_RCVBUF and +SO_SNDBUFFER.

+ +++++ + + + + + + + + + + + + +
nametypedefault
file_checks_delay_per_blockint0
+

file_checks_delay_per_block is the number of milliseconds to sleep +in between disk read operations when checking torrents. This defaults +to 0, but can be set to higher numbers to slow down the rate at which +data is read from the disk while checking. This may be useful for +background tasks that doesn't matter if they take a bit longer, as long +as they leave disk I/O time for other processes.

+ +++++ + + + + + + + + + + + + + + + + +
nametypedefault
read_cache_line_sizeint32
write_cache_line_sizeint16
+

read_cache_line_size is the number of blocks to read into the read +cache when a read cache miss occurs. Setting this to 0 is essentially +the same thing as disabling read cache. The number of blocks read +into the read cache is always capped by the piece boundry.

+

When a piece in the write cache has write_cache_line_size contiguous +blocks in it, they will be flushed. Setting this to 1 effectively +disables the write cache.

+ +++++ + + + + + + + + + + + + +
nametypedefault
optimistic_disk_retryint10 * 60
+

optimistic_disk_retry is the number of seconds from a disk write +errors occur on a torrent until libtorrent will take it out of the +upload mode, to test if the error condition has been fixed.

+

libtorrent will only do this automatically for auto managed torrents.

+

You can explicitly take a torrent out of upload only mode using +set_upload_mode().

+<<<<<<< .working + +++++ + + + + + + + + + + + + +
nametypedefault
max_suggest_piecesint10
+======= +

disable_hash_checks controls if downloaded pieces are verified against +the piece hashes in the torrent file or not. The default is false, i.e. +to verify all downloaded data. It may be useful to turn this off for performance +profiling and simulation scenarios. Do not disable the hash check for regular +bittorrent clients.

+>>>>>>> .merge-right.r8585 +

max_suggest_pieces is the max number of suggested piece indices received +from a peer that's remembered. If a peer floods suggest messages, this limit +prevents libtorrent from using too much RAM. It defaults to 10.

+ +++++ + + + + + + + + + + + + +
nametypedefault
local_service_announce_intervalint5 * 60
+

local_service_announce_interval is the time between local +network announces for a torrent. By default, when local service +discovery is enabled a torrent announces itself every 5 minutes. +This interval is specified in seconds.

+ +++++ + + + + + + + + + + + + +
nametypedefault
dht_announce_intervalint15 * 60
+

dht_announce_interval is the number of seconds between announcing +torrents to the distributed hash table (DHT).

+ +++++ + + + + + + + + + + + + +
nametypedefault
udp_tracker_token_expiryint60
+

udp_tracker_token_expiry is the number of seconds libtorrent +will keep UDP tracker connection tokens around for. This is specified +to be 60 seconds, and defaults to that. The higher this value is, the +fewer packets have to be sent to the UDP tracker. In order for higher +values to work, the tracker needs to be configured to match the +expiration time for tokens.

+<<<<<<< .working + +++++ + + + + + + + + + + + + +
nametypedefault
default_cache_min_ageint1
+

default_cache_min_age is the minimum number of seconds any read +======= +

volatile_read_cache, if this is set to true, read cache blocks +that are hit by peer read requests are removed from the disk cache +to free up more space. This is useful if you don't expect the disk +cache to create any cache hits from other peers than the one who +triggered the cache line to be read into the cache in the first place.

+

guided_read_cache enables the disk cache to adjust the size +of a cache line generated by peers to depend on the upload rate +you are sending to that peer. The intention is to optimize the RAM +usage of the cache, to read ahead further for peers that you're +sending faster to.

+

default_cache_min_age is the minimum number of seconds any read +>>>>>>> .merge-right.r8585 +cache line is kept in the cache. This defaults to one second but +may be greater if guided_read_cache is enabled. Having a lower +bound on the time a cache line stays in the cache is an attempt +to avoid swapping the same pieces in and out of the cache in case +there is a shortage of spare cache space.

+ +++++ + + + + + + + + + + + + +
nametypedefault
num_optimistic_unchoke_slotsint0
+

num_optimistic_unchoke_slots is the number of optimistic unchoke +slots to use. It defaults to 0, which means automatic. Having a higher +number of optimistic unchoke slots mean you will find the good peers +faster but with the trade-off to use up more bandwidth. When this is +set to 0, libtorrent opens up 20% of your allowed upload slots as +optimistic unchoke slots.

+ +++++ + + + + + + + + + + + + + + + + + + + + +
nametypedefault
default_est_reciprocation_rateint16000
increase_est_reciprocation_rateint20
decrease_est_reciprocation_rateint3
+

default_est_reciprocation_rate is the assumed reciprocation rate +from peers when using the BitTyrant choker. This defaults to 14 kiB/s. +If set too high, you will over-estimate your peers and be more altruistic +while finding the true reciprocation rate, if it's set too low, you'll +be too stingy and waste finding the true reciprocation rate.

+

increase_est_reciprocation_rate specifies how many percent the +extimated reciprocation rate should be increased by each unchoke +interval a peer is still choking us back. This defaults to 20%. +This only applies to the BitTyrant choker.

+

decrease_est_reciprocation_rate specifies how many percent the +estimated reciprocation rate should be decreased by each unchoke +interval a peer unchokes us. This default to 3%. +This only applies to the BitTyrant choker.

+<<<<<<< .working + +++++ + + + + + + + + + + + + +
nametypedefault
max_pex_peersint50
+

the max number of peers we accept from pex messages from a single peer. +this limits the number of concurrent peers any of our peers claims to +be connected to. If they clain to be connected to more than this, we'll +ignore any peer that exceeds this limit

+ +++++ + + + + + + + + + + + + +
nametypedefault
tick_intervalint100
+======= +

incoming_starts_queued_torrents defaults to false. If a torrent +has been paused by the auto managed feature in libtorrent, i.e. +the torrent is paused and auto managed, this feature affects whether +or not it is automatically started on an incoming connection. The +main reason to queue torrents, is not to make them unavailable, but +to save on the overhead of announcing to the trackers, the DHT and to +avoid spreading one's unchoke slots too thin. If a peer managed to +find us, even though we're no in the torrent anymore, this setting +can make us start the torrent and serve it.

+

When report_true_downloaded is true, the &downloaded= argument +sent to trackers will include redundant downloaded bytes. It defaults +to false, which means redundant bytes are not reported to the tracker.

+

strict_end_game_mode defaults to true, and controls when a block +may be requested twice. If this is true, a block may only be requested +twice when there's ay least one request to every piece that's left to +download in the torrent. This may slow down progress on some pieces +sometimes, but it may also avoid downloading a lot of redundant bytes. +If this is false, libtorrent attempts to use each peer connection +to its max, by always requesting something, even if it means requesting +something that has been requested from another peer already.

+

if broadcast_lsd is set to true, the local peer discovery +(or Local Service Discovery) will not only use IP multicast, but also +broadcast its messages. This can be useful when running on networks +that don't support multicast. Since broadcast messages might be +expensive and disruptive on networks, only every 8th announce uses +broadcast.

+

enable_outgoing_utp, enable_incoming_utp, enable_outgoing_tcp, +enable_incoming_tcp all determines if libtorrent should attempt to make +outgoing connections of the specific type, or allow incoming connection. By +default all of them are enabled.

+

ignore_resume_timestamps determines if the storage, when loading +resume data files, should verify that the file modification time +with the timestamps in the resume data. This defaults to false, which +means timestamps are taken into account, and resume data is less likely +to accepted (torrents are more likely to be fully checked when loaded). +It might be useful to set this to true if your network is faster than your +disk, and it would be faster to redownload potentially missed pieces than +to go through the whole storage to look for them.

+

no_recheck_incomplete_resume determines if the storage should check +the whole files when resume data is incomplete or missing or whether +it should simply assume we don't have any of the data. By default, this +is determined by the existance of any of the files. By setting this setting +to true, the files won't be checked, but will go straight to download +mode.

+

anonymous_mode defaults to false. When set to true, the client tries +to hide its identity to a certain degree. The peer-ID will no longer +include the client's fingerprint. The user-agent will be reset to an +empty string.

+

If you're using I2P, it might make sense to enable anonymous mode.

+

force_proxy disables any communication that's not going over a proxy. +Enabling this requires a proxy to be configured as well, see set_proxy_settings. +The listen sockets are closed, and incoming connections will +only be accepted through a SOCKS5 or I2P proxy (if a peer proxy is set up and +is run on the same machine as the tracker proxy). This setting also +disabled peer country lookups, since those are done via DNS lookups that +aren't supported by proxies.

+>>>>>>> .merge-right.r8585 +

tick_interval specifies the number of milliseconds between internal +ticks. This is the frequency with which bandwidth quota is distributed to +peers. It should not be more than one second (i.e. 1000 ms). Setting this +to a low value (around 100) means higher resolution bandwidth quota distribution, +setting it to a higher value saves CPU cycles.

+ +++++ + + + + + + + + + + + + +
nametypedefault
share_mode_targetint3
+

share_mode_target specifies the target share ratio for share mode torrents. +This defaults to 3, meaning we'll try to upload 3 times as much as we download. +Setting this very high, will make it very conservative and you might end up +not downloading anything ever (and not affecting your share ratio). It does +not make any sense to set this any lower than 2. For instance, if only 3 peers +need to download the rarest piece, it's impossible to download a single piece +and upload it more than 3 times. If the share_mode_target is set to more than 3, +nothing is downloaded.

+ +++++ + + + + + + + + + + + + + + + + +
nametypedefault
upload_rate_limitint0
download_rate_limitint0
+

upload_rate_limit, download_rate_limit, local_upload_rate_limit +and local_download_rate_limit sets the session-global limits of upload +and download rate limits, in bytes per second. The local rates refer to peers +on the local network. By default peers on the local network are not rate limited.

+

These rate limits are only used for local peers (peers within the same subnet as +the client itself) and it is only used when ignore_limits_on_local_network +is set to true (which it is by default). These rate limits default to unthrottled, +but can be useful in case you want to treat local peers preferentially, but not +quite unthrottled.

+

A value of 0 means unlimited.

+ +++++ + + + + + + + + + + + + +
nametypedefault
dht_upload_rate_limitint4000
+

dht_upload_rate_limit sets the rate limit on the DHT. This is specified in +bytes per second and defaults to 4000. For busy boxes with lots of torrents +that requires more DHT traffic, this should be raised.

+<<<<<<< .working + +++++ + + + + + + + + + + + + +
nametypedefault
unchoke_slots_limitint8
+

unchoke_slots_limit is the max number of unchoked peers in the session. +The number of unchoke slots may be ignored depending on what +======= +

unchoke_slots_limit is the max number of unchoked peers in the session.

+

The number of unchoke slots may be ignored depending on what +>>>>>>> .merge-right.r8585 +choking_algorithm is set to.

+ +++++ + + + + + + + + + + + + +
nametypedefault
half_open_limitint0
+

half_open_limit sets the maximum number of half-open connections +libtorrent will have when connecting to peers. A half-open connection is one +where connect() has been called, but the connection still hasn't been established +(nor failed). Windows XP Service Pack 2 sets a default, system wide, limit of +the number of half-open connections to 10. So, this limit can be used to work +nicer together with other network applications on that system. The default is +to have no limit, and passing -1 as the limit, means to have no limit. When +limiting the number of simultaneous connection attempts, peers will be put in +a queue waiting for their turn to get connected.

+ +++++ + + + + + + + + + + + + +
nametypedefault
connections_limitint200
+

connections_limit sets a global limit on the number of connections +opened. The number of connections is set to a hard minimum of at least two per +torrent, so if you set a too low connections limit, and open too many torrents, +the limit will not be met.

+ +++++ + + + + + + + + + + + + +
nametypedefault
connections_slackint10
+

connections_slack is the the number of incoming connections exceeding the +connection limit to accept in order to potentially replace existing ones.

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
nametypedefault
utp_target_delayint100
utp_gain_factorint1500
utp_min_timeoutint500
utp_syn_resendsint2
utp_fin_resendsint2
utp_num_resendsint6
utp_connect_timeoutint3000
utp_loss_multiplierint50
+

utp_target_delay is the target delay for uTP sockets in milliseconds. A high +value will make uTP connections more aggressive and cause longer queues in the upload +bottleneck. It cannot be too low, since the noise in the measurements would cause +it to send too slow. The default is 50 milliseconds. +utp_gain_factor is the number of bytes the uTP congestion window can increase +at the most in one RTT. This defaults to 300 bytes. If this is set too high, +the congestion controller reacts too hard to noise and will not be stable, if it's +set too low, it will react slow to congestion and not back off as fast. +utp_min_timeout is the shortest allowed uTP socket timeout, specified in milliseconds. +This defaults to 500 milliseconds. The timeout depends on the RTT of the connection, but +is never smaller than this value. A connection times out when every packet in a window +is lost, or when a packet is lost twice in a row (i.e. the resent packet is lost as well).

+

The shorter the timeout is, the faster the connection will recover from this situation, +assuming the RTT is low enough. +utp_syn_resends is the number of SYN packets that are sent (and timed out) before +giving up and closing the socket. +utp_num_resends is the number of times a packet is sent (and lossed or timed out) +before giving up and closing the connection. +utp_connect_timeout is the number of milliseconds of timeout for the initial SYN +packet for uTP connections. For each timed out packet (in a row), the timeout is doubled. +utp_loss_multiplier controls how the congestion window is changed when a packet +loss is experienced. It's specified as a percentage multiplier for cwnd. By default +it's set to 50 (i.e. cut in half). Do not change this value unless you know what +you're doing. Never set it higher than 100.

+ +++++ + + + + + + + + + + + + +
nametypedefault
mixed_mode_algorithmintsettings_pack::peer_proportional
+

The mixed_mode_algorithm determines how to treat TCP connections when there are +uTP connections. Since uTP is designed to yield to TCP, there's an inherent problem +when using swarms that have both TCP and uTP connections. If nothing is done, uTP +connections would often be starved out for bandwidth by the TCP connections. This mode +is prefer_tcp. The peer_proportional mode simply looks at the current throughput +and rate limits all TCP connections to their proportional share based on how many of +the connections are TCP. This works best if uTP connections are not rate limited by +the global rate limiter (which they aren't by default).

+ +++++ + + + + + + + + + + + + +
nametypedefault
listen_queue_sizeint5
+

listen_queue_size is the value passed in to listen() for the listen socket. +It is the number of outstanding incoming connections to queue up while we're not +actively waiting for a connection to be accepted. The default is 5 which should +be sufficient for any normal client. If this is a high performance server which +expects to receive a lot of connections, or used in a simulator or test, it +might make sense to raise this number. It will not take affect until listen_on() +is called again (or for the first time).

+ +++++ + + + + + + + + + + + + +
nametypedefault
torrent_connect_boostint10
+

torrent_connect_boost is the number of peers to try to connect to immediately +when the first tracker response is received for a torrent. This is a boost to +given to new torrents to accelerate them starting up. The normal connect scheduler +is run once every second, this allows peers to be connected immediately instead +of waiting for the session tick to trigger connections.

+ +++++ + + + + + + + + + + + + +
nametypedefault
alert_queue_sizeint1000
+

alert_queue_size is the maximum number of alerts queued up internally. If +alerts are not popped, the queue will eventually fill up to this level.

+ +++++ + + + + + + + + + + + + +
nametypedefault
max_metadata_sizeint3 * 1024 * 10240
+

max_metadata_size is the maximum allowed size (in bytes) to be received +by the metadata extension, i.e. magnet links. It defaults to 1 MiB.

+ +++++ + + + + + + + + + + + + +
nametypedefault
read_job_everyint10
+

read_job_every is used to avoid starvation of read jobs in the disk I/O +thread. By default, read jobs are deferred, sorted by physical disk location +and serviced once all write jobs have been issued. In scenarios where the +download rate is enough to saturate the disk, there's a risk the read jobs will +never be serviced. With this setting, every x write job, issued in a row, will +instead pick one read job off of the sorted queue, where x is read_job_every.

+ +++++ + + + + + + + + + + + + +
nametypedefault
hashing_threadsint1
+

hashing_threads is the number of threads to use for piece hash verification. It +defaults to 1. For very high download rates, on machines with multiple cores, this +could be incremented. Setting it higher than the number of CPU cores would presumably +not provide any benefit of setting it to the number of cores. If it's set to 0, +hashing is done in the disk thread.

+ +++++ + + + + + + + + + + + + +
nametypedefault
checking_mem_usageint256
+

the number of blocks to keep outstanding at any given time when +checking torrents. Higher numbers give faster re-checks but uses +more memory. Specified in number of 16 kiB blocks

+ +++++ + + + + + + + + + + + + +
nametypedefault
predictive_piece_announceint0
+

if set to > 0, pieces will be announced to other peers before they +are fully downloaded (and before they are hash checked). The intention +is to gain 1.5 potential round trip times per downloaded piece. When +non-zero, this indicates how many milliseconds in advance pieces +should be announced, before they are expected to be completed.

+ +++++ + + + + + + + + + + + + + + + + +
nametypedefault
aio_threadsint4
aio_maxint300
+

for some aio back-ends, aio_threads specifies the number of +io-threads to use, and aio_max the max number of outstanding jobs.

+ +++++ + + + + + + + + + + + + +
nametypedefault
network_threadsint0
+

network_threads is the number of threads to use to call async_write_some +(i.e. send) on peer connection sockets. When seeding at extremely high rates, +this may become a bottleneck, and setting this to 2 or more may parallelize +that cost. When using SSL torrents, all encryption for outgoing traffic is +done withint the socket send functions, and this will help parallelizing the +cost of SSL encryption as well.

+ +++++ + + + + + + + + + + + + +
nametypedefault
ssl_listenint4433
+

ssl_listen sets the listen port for SSL connections. If this is set to 0, +no SSL listen port is opened. Otherwise a socket is opened on this port. This +setting is only taken into account when opening the regular listen port, and +won't re-open the listen socket simply by changing this setting.

+ +++++ + + + + + + + + + + + + +
nametypedefault
tracker_backoffint250
+

tracker_backoff determines how aggressively to back off from retrying +failing trackers. This value determines x in the following formula, determining +the number of seconds to wait until the next retry:

+
+delay = 5 + 5 * x / 100 * fails^2
+

This setting may be useful to make libtorrent more or less aggressive in hitting +trackers.

+ +++++ + + + + + + + + + + + + + + + + +
nametypedefault
share_ratio_limitint200
seed_time_ratio_limitint700
+

when a seeding torrent reaches eaither the share ratio +(bytes up / bytes down) or the seed time ratio +(seconds as seed / seconds as downloader) or the seed +time limit (seconds as seed) it is considered +done, and it will leave room for other torrents +these are specified as percentages

+ +++++ + + + + + + + + + + + + + + + + + + + + +
nametypedefault
peer_turnoverint4
peer_turnover_cutoffint90
peer_turnover_intervalint300
+

peer_turnover is the percentage of peers to disconnect +every turnover peer_turnover_interval (if we're at +the peer limit), this is specified in percent +when we are connected to more than +limit * peer_turnover_cutoff peers +disconnect peer_turnover fraction +of the peers. It is specified in percent +peer_turnover_interval is the interval (in seconds) +between optimistic disconnects +if the disconnects happen and how many peers are disconnected +is controlled by peer_turnover and peer_turnover_cutoff

+ +++++ + + + + + + + + + + + + +
nametypedefault
connect_seed_every_n_downloadint10
+

this setting controls the priority of downloading torrents +over seeding or finished torrents when it comes to making +peer connections. Peer connections are throttled by the +connection_speed and the half-open connection limit. This +makes peer connections a limited resource. Torrents that +still have pieces to download are prioritized by default, +to avoid having many seeding torrents use most of the connection +attempts and only give one peer every now and then to the +downloading torrent. libtorrent will loop over the downloading +torrents to connect a peer each, and every n:th connection +attempt, a finished torrent is picked to be allowed to connect +to a peer. This setting controls n.

+ +++++ + + + + + + + + + + + + +
nametypedefault
max_http_recv_buffer_sizeint2*1024*204
+

the max number of bytes to allow an HTTP response to be when +announcing to trackers or downloading .torrent files via +the url provided in add_torrent_params.

+

max_http_recv_buffer_size specifies the max number of bytes to receive into +RAM buffers when downloading stuff over HTTP. Specifically when specifying a +URL to a .torrent file when adding a torrent or when announcing to an HTTP +tracker. The default is 2 MiB.

+

support_share_mode enables or disables the share mode extension. This is +enabled by default.

+

support_merkle_torrents enables or disables the merkle tree torrent support. +This is enabled by default.

+

report_redundant_bytes enables or disables reporting redundant bytes to the tracker. +This is enabled by default.

+

handshake_client_version is the client name advertized in the peer handshake. If +set to an empty string, the user_agent string is used.

+

use_disk_cache_pool enables using a pool allocator for disk cache blocks. This is +disabled by default. Enabling it makes the cache perform better at high throughput. +It also makes the cache less likely and slower at returning memory back to the system +once allocated.

+
+
+
+

pe_settings

+

The pe_settings structure is used to control the settings related +to peer protocol encryption:

+
+struct pe_settings
+{
+        pe_settings();
+
+        enum enc_policy
+        {
+                forced,
+                enabled,
+                disabled
+        };
+
+        enum enc_level
+        {
+                plaintext,
+                rc4,
+                both
+        };
+
+        enc_policy out_enc_policy;
+        enc_policy in_enc_policy;
+        enc_level allowed_enc_level;
+        bool prefer_rc4;
+};
+
+

in_enc_policy and out_enc_policy control the settings for incoming +and outgoing connections respectively. The settings for these are:

+
+
    +
  • forced - Only encrypted connections are allowed. Incoming connections +that are not encrypted are closed and if the encrypted outgoing connection +fails, a non-encrypted retry will not be made.
  • +
  • enabled - encrypted connections are enabled, but non-encrypted +connections are allowed. An incoming non-encrypted connection will +be accepted, and if an outgoing encrypted connection fails, a non- +encrypted connection will be tried.
  • +
  • disabled - only non-encrypted connections are allowed.
  • +
+
+

allowed_enc_level determines the encryption level of the +connections. This setting will adjust which encryption scheme is +offered to the other peer, as well as which encryption scheme is +selected by the client. The settings are:

+
+
    +
  • plaintext - only the handshake is encrypted, the bulk of the traffic +remains unchanged.
  • +
  • rc4 - the entire stream is encrypted with RC4
  • +
  • both - both RC4 and plaintext connections are allowed.
  • +
+
+

prefer_rc4 can be set to true if you want to prefer the RC4 encrypted stream.

+
+
+

proxy_settings

+

The proxy_settings structs contains the information needed to +direct certain traffic to a proxy.

+
+
+struct proxy_settings
+{
+        proxy_settings();
+
+        std::string hostname;
+        int port;
+
+        std::string username;
+        std::string password;
+
+        enum proxy_type
+        {
+                none,
+                socks4,
+                socks5,
+                socks5_pw,
+                http,
+                http_pw
+        };
+
+        proxy_type type;
+        bool proxy_hostnames;
+        bool proxy_peer_connections;
+};
+
+
+

hostname is the name or IP of the proxy server. port is the +port number the proxy listens to. If required, username and password +can be set to authenticate with the proxy.

+

The type tells libtorrent what kind of proxy server it is. The following +options are available:

+
+
    +
  • none - This is the default, no proxy server is used, all other fields +are ignored.
  • +
  • socks4 - The server is assumed to be a SOCKS4 server that +requires a username.
  • +
  • socks5 - The server is assumed to be a SOCKS5 server (RFC 1928) that +does not require any authentication. The username and password are ignored.
  • +
  • socks5_pw - The server is assumed to be a SOCKS5 server that supports +plain text username and password authentication (RFC 1929). The username +and password specified may be sent to the proxy if it requires.
  • +
  • http - The server is assumed to be an HTTP proxy. If the transport used +for the connection is non-HTTP, the server is assumed to support the +CONNECT method. i.e. for web seeds and HTTP trackers, a plain proxy will +suffice. The proxy is assumed to not require authorization. The username +and password will not be used.
  • +
  • http_pw - The server is assumed to be an HTTP proxy that requires +user authorization. The username and password will be sent to the proxy.
  • +
+
+

proxy_hostnames defaults to true. It means that hostnames should be +attempted to be resolved through the proxy instead of using the local DNS +service. This is only supported by SOCKS5 and HTTP.

+

proxy_peer_connections determines whether or not to excempt peer and +web seed connections from using the proxy. This defaults to true, i.e. peer +connections are proxied by default.

+
+
+

ip_filter

+

The ip_filter class is a set of rules that uniquely categorizes all +ip addresses as allowed or disallowed. The default constructor creates +a single rule that allows all addresses (0.0.0.0 - 255.255.255.255 for +the IPv4 range, and the equivalent range covering all addresses for the +IPv6 range).

+
+
+template <class Addr>
+struct ip_range
+{
+        Addr first;
+        Addr last;
+        int flags;
+};
+
+class ip_filter
+{
+public:
+        enum access_flags { blocked = 1 };
+
+        ip_filter();
+        void add_rule(address first, address last, int flags);
+        int access(address const& addr) const;
+
+        typedef boost::tuple<std::vector<ip_range<address_v4> >
+                , std::vector<ip_range<address_v6> > > filter_tuple_t;
+
+        filter_tuple_t export_filter() const;
+};
+
+
+
+

ip_filter()

+
+
+ip_filter()
+
+
+

Creates a default filter that doesn't filter any address.

+

postcondition: +access(x) == 0 for every x

+
+
+

add_rule()

+
+
+void add_rule(address first, address last, int flags);
+
+
+

Adds a rule to the filter. first and last defines a range of +ip addresses that will be marked with the given flags. The flags +can currently be 0, which means allowed, or ip_filter::blocked, which +means disallowed.

+

precondition: +first.is_v4() == last.is_v4() && first.is_v6() == last.is_v6()

+

postcondition: +access(x) == flags for every x in the range [first, last]

+

This means that in a case of overlapping ranges, the last one applied takes +precedence.

+
+
+

access()

+
+
+int access(address const& addr) const;
+
+
+

Returns the access permissions for the given address (addr). The permission +can currently be 0 or ip_filter::blocked. The complexity of this operation +is O(log n), where n is the minimum number of non-overlapping ranges to describe +the current filter.

+
+
+

export_filter()

+
+
+boost::tuple<std::vector<ip_range<address_v4> >
+        , std::vector<ip_range<address_v6> > > export_filter() const;
+
+
+

This function will return the current state of the filter in the minimum number of +ranges possible. They are sorted from ranges in low addresses to high addresses. Each +entry in the returned vector is a range with the access control specified in its +flags field.

+

The return value is a tuple containing two range-lists. One for IPv4 addresses +and one for IPv6 addresses.

+
+
+
+

big_number

+

Both the peer_id and sha1_hash types are typedefs of the class +big_number. It represents 20 bytes of data. Its synopsis follows:

+
+class big_number
+{
+public:
+        bool operator==(const big_number& n) const;
+        bool operator!=(const big_number& n) const;
+        bool operator<(const big_number& n) const;
+
+        const unsigned char* begin() const;
+        const unsigned char* end() const;
+
+        unsigned char* begin();
+        unsigned char* end();
+};
+
+

The iterators gives you access to individual bytes.

+
+
+

bitfield

+

The bitfiled type stores any number of bits as a bitfield in an array.

+
+class bitfield
+{
+        bitfield();
+        bitfield(int bits);
+        bitfield(int bits, bool val);
+        bitfield(char const* bytes, int bits);
+        bitfield(bitfield const& rhs);
+
+        void borrow_bytes(char* bytes, int bits);
+        ~bitfield();
+
+        void assign(char const* bytes, int bits);
+
+        bool operator[](int index) const;
+
+        bool get_bit(int index) const;
+
+        void clear_bit(int index);
+        void set_bit(int index);
+
+        std::size_t size() const;
+        bool empty() const;
+
+        char const* bytes() const;
+
+        bitfield& operator=(bitfield const& rhs);
+
+        int count() const;
+
+        typedef const_iterator;
+        const_iterator begin() const;
+        const_iterator end() const;
+
+        void resize(int bits, bool val);
+        void set_all();
+        void clear_all();
+        void resize(int bits);
+};
+
+
+
+

hasher

+

This class creates sha1-hashes. Its declaration looks like this:

+
+class hasher
+{
+public:
+        hasher();
+        hasher(char const* data, unsigned int len);
+
+        void update(char const* data, unsigned int len);
+        sha1_hash final();
+        void reset();
+};
+
+

You use it by first instantiating it, then call update() to feed it +with data. i.e. you don't have to keep the entire buffer of which you want to +create the hash in memory. You can feed the hasher parts of it at a time. When +You have fed the hasher with all the data, you call final() and it +will return the sha1-hash of the data.

+

The constructor that takes a char const* and an integer will construct the +sha1 context and feed it the data passed in.

+

If you want to reuse the hasher object once you have created a hash, you have to +call reset() to reinitialize it.

+

The sha1-algorithm used was implemented by Steve Reid and released as public domain. +For more info, see src/sha1.cpp.

+
+
+

fingerprint

+

The fingerprint class represents information about a client and its version. It is used +to encode this information into the client's peer id.

+

This is the class declaration:

+
+struct fingerprint
+{
+        fingerprint(const char* id_string, int major, int minor
+                , int revision, int tag);
+
+        std::string to_string() const;
+
+        char name[2];
+        char major_version;
+        char minor_version;
+        char revision_version;
+        char tag_version;
+
+};
+
+

The constructor takes a char const* that should point to a string constant containing +exactly two characters. These are the characters that should be unique for your client. Make +sure not to clash with anybody else. Here are some taken id's:

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
id charsclient
'AZ'Azureus
'LT'libtorrent (default)
'BX'BittorrentX
'MT'Moonlight Torrent
'TS'Torrent Storm
'SS'Swarm Scope
'XT'Xan Torrent
+

There's currently an informal directory of client id's here.

+

The major, minor, revision and tag parameters are used to identify the +version of your client. All these numbers must be within the range [0, 9].

+

to_string() will generate the actual string put in the peer-id, and return it.

+
+
+

UPnP and NAT-PMP

+

The upnp and natpmp classes contains the state for all UPnP and NAT-PMP mappings, +by default 1 or two mappings are made by libtorrent, one for the listen port and one +for the DHT port (UDP).

+
+class upnp
+{
+public:
+
+        enum protocol_type { none = 0, udp = 1, tcp = 2 };
+        int add_mapping(protocol_type p, int external_port, int local_port);
+        void delete_mapping(int mapping_index);
+
+        void discover_device();
+        void close();
+
+        std::string router_model();
+};
+
+class natpmp
+{
+public:
+
+        enum protocol_type { none = 0, udp = 1, tcp = 2 };
+        int add_mapping(protocol_type p, int external_port, int local_port);
+        void delete_mapping(int mapping_index);
+
+        void close();
+        void rebind(address const& listen_interface);
+};
+
+

discover_device(), close() and rebind() are for internal uses and should +not be called directly by clients.

+
+

add_mapping()

+
+
+int add_mapping(protocol_type p, int external_port, int local_port);
+
+
+

Attempts to add a port mapping for the specified protocol. Valid protocols are +upnp::tcp and upnp::udp for the UPnP class and natpmp::tcp and +natpmp::udp for the NAT-PMP class.

+

external_port is the port on the external address that will be mapped. This +is a hint, you are not guaranteed that this port will be available, and it may +end up being something else. In the portmap_alert notification, the actual +external port is reported.

+

local_port is the port in the local machine that the mapping should forward +to.

+

The return value is an index that identifies this port mapping. This is used +to refer to mappings that fails or succeeds in the portmap_error_alert and +portmap_alert respectively. If The mapping fails immediately, the return value +is -1, which means failure. There will not be any error alert notification for +mappings that fail with a -1 return value.

+
+
+

delete_mapping()

+
+
+void delete_mapping(int mapping_index);
+
+
+

This function removes a port mapping. mapping_index is the index that refers +to the mapping you want to remove, which was returned from add_mapping().

+
+
+

router_model()

+
+
+std::string router_model();
+
+
+

This is only available for UPnP routers. If the model is advertized by +the router, it can be queried through this function.

+
+
+
+

free functions

+
+

identify_client()

+
+
+std::string identify_client(peer_id const& id);
+
+
+

This function is declared in the header <libtorrent/identify_client.hpp>. It can can be used +to extract a string describing a client version from its peer-id. It will recognize most clients +that have this kind of identification in the peer-id.

+
+
+

client_fingerprint()

+
+
+boost::optional<fingerprint> client_fingerprint(peer_id const& p);
+
+
+

Returns an optional fingerprint if any can be identified from the peer id. This can be used +to automate the identification of clients. It will not be able to identify peers with non- +standard encodings. Only Azureus style, Shadow's style and Mainline style. This function is +declared in the header <libtorrent/identify_client.hpp>.

+
+
+

lazy_bdecode()

+
+
+int lazy_bdecode(char const* start, char const* end, lazy_entry& ret
+        , error_code& ec, int* error_pos = 0, int depth_limit = 1000
+        , int item_limit = 1000000);
+
+
+

This function decodes bencoded data.

+

Whenever possible, lazy_bdecode() should be preferred over bdecode(). +It is more efficient and more secure. It supports having constraints on the +amount of memory is consumed by the parser.

+

lazy refers to the fact that it doesn't copy any actual data out of the +bencoded buffer. It builds a tree of lazy_entry which has pointers into +the bencoded buffer. This makes it very fast and efficient. On top of that, +it is not recursive, which saves a lot of stack space when parsing deeply +nested trees. However, in order to protect against potential attacks, the +depth_limit and item_limit control how many levels deep the tree is +allowed to get. With recursive parser, a few thousand levels would be enough +to exhaust the threads stack and terminate the process. The item_limit +protects against very large structures, not necessarily deep. Each bencoded +item in the structure causes the parser to allocate some amount of memory, +this memory is constant regardless of how much data actually is stored in +the item. One potential attack is to create a bencoded list of hundreds of +thousands empty strings, which would cause the parser to allocate a significant +amount of memory, perhaps more than is available on the machine, and effectively +provide a denial of service. The default item limit is set as a reasonable +upper limit for desktop computers. Very few torrents have more items in them. +The limit corresponds to about 25 MB, which might be a bit much for embedded +systems.

+

start and end defines the bencoded buffer to be decoded. ret is +the lazy_entry which is filled in with the whole decoded tree. ec +is a reference to an error_code which is set to describe the error encountered +in case the function fails. error_pos is an optional pointer to an int, +which will be set to the byte offset into the buffer where an error occurred, +in case the function fails.

+
+
+

bdecode() bencode()

+
+
+template<class InIt> entry bdecode(InIt start, InIt end);
+template<class OutIt> void bencode(OutIt out, const entry& e);
+
+
+

These functions will encode data to bencoded or decode bencoded data.

+

If possible, lazy_bdecode() should be preferred over bdecode().

+

The entry class is the internal representation of the bencoded data +and it can be used to retrieve information, an entry can also be build by +the program and given to bencode() to encode it into the OutIt +iterator.

+

The OutIt and InIt are iterators +(InputIterator and OutputIterator respectively). They +are templates and are usually instantiated as ostream_iterator, +back_insert_iterator or istream_iterator. These +functions will assume that the iterator refers to a character +(char). So, if you want to encode entry e into a buffer +in memory, you can do it like this:

+
+std::vector<char> buffer;
+bencode(std::back_inserter(buf), e);
+
+

If you want to decode a torrent file from a buffer in memory, you can do it like this:

+
+std::vector<char> buffer;
+// ...
+entry e = bdecode(buf.begin(), buf.end());
+
+

Or, if you have a raw char buffer:

+
+const char* buf;
+// ...
+entry e = bdecode(buf, buf + data_size);
+
+

Now we just need to know how to retrieve information from the entry.

+

If bdecode() encounters invalid encoded data in the range given to it +it will throw libtorrent_exception.

+
+
+

add_magnet_uri()

+

deprecated

+
+
+torrent_handle add_magnet_uri(session& ses, std::string const& uri
+        add_torrent_params p);
+torrent_handle add_magnet_uri(session& ses, std::string const& uri
+        add_torrent_params p, error_code& ec);
+
+
+

This function parses the magnet URI (uri) as a bittorrent magnet link, +and adds the torrent to the specified session (ses). It returns the +handle to the newly added torrent, or an invalid handle in case parsing +failed. To control some initial settings of the torrent, sepcify those in +the add_torrent_params, p. See async_add_torrent() add_torrent().

+

The overload that does not take an error_code throws an exception on +error and is not available when building without exception support.

+

A simpler way to add a magnet link to a session is to pass in the +link through add_torrent_params::url argument to session::add_torrent().

+

For more information about magnet links, see magnet links.

+
+
+

parse_magnet_uri()

+
+
+void parse_magnet_uri(std::string const& uri, add_torrent_params& p, error_code& ec);
+
+
+

This function parses out information from the magnet link and populates the +add_torrent_params object.

+
+
+

make_magnet_uri()

+
+
+std::string make_magnet_uri(torrent_handle const& handle);
+
+
+

Generates a magnet URI from the specified torrent. If the torrent +handle is invalid, an empty string is returned.

+

For more information about magnet links, see magnet links.

+
+
+
+

alerts

+

The pop_alert() function on session is the interface for retrieving +alerts, warnings, messages and errors from libtorrent. If no alerts have +been posted by libtorrent pop_alert() will return a default initialized +auto_ptr object. If there is an alert in libtorrent's queue, the alert +from the front of the queue is popped and returned. +You can then use the alert object and query

+

By default, only errors are reported. set_alert_mask() can be +used to specify which kinds of events should be reported. The alert mask +is a bitmask with the following bits:

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
error_notification

Enables alerts that report an error. This includes:

+
    +
  • tracker errors
  • +
  • tracker warnings
  • +
  • file errors
  • +
  • resume data failures
  • +
  • web seed errors
  • +
  • .torrent files errors
  • +
  • listen socket errors
  • +
  • port mapping errors
  • +
+
peer_notificationEnables alerts when peers send invalid requests, get banned or +snubbed.
port_mapping_notificationEnables alerts for port mapping events. For NAT-PMP and UPnP.
storage_notificationEnables alerts for events related to the storage. File errors and +synchronization events for moving the storage, renaming files etc.
tracker_notificationEnables all tracker events. Includes announcing to trackers, +receiving responses, warnings and errors.
debug_notificationLow level alerts for when peers are connected and disconnected.
status_notificationEnables alerts for when a torrent or the session changes state.
progress_notificationAlerts for when blocks are requested and completed. Also when +pieces are completed.
ip_block_notificationAlerts when a peer is blocked by the ip blocker or port blocker.
performance_warningAlerts when some limit is reached that might limit the download +or upload rate.
stats_notificationIf you enable these alerts, you will receive a stats_alert +approximately once every second, for every active torrent. +These alerts contain all statistics counters for the interval since +the lasts stats alert.
dht_notificationAlerts on events in the DHT node. For incoming searches or +bootstrapping being done etc.
rss_notificationAlerts on RSS related events, like feeds being updated, feed error +conditions and successful RSS feed updates. Enabling this categoty +will make you receive rss_alert alerts.
all_categoriesThe full bitmask, representing all available categories.
+

Every alert belongs to one or more category. There is a small cost involved in posting alerts. Only +alerts that belong to an enabled category are posted. Setting the alert bitmask to 0 will disable +all alerts

+

When you get an alert, you can use alert_cast<> to attempt to cast the pointer to a +more specific alert type, to be queried for more information about the alert. alert_cast +has the followinf signature:

+
+template <T> T* alert_cast(alert* a);
+template <T> T const* alert_cast(alert const* a);
+
+

You can also use a alert dispatcher mechanism that's available in libtorrent.

+

All alert types are defined in the <libtorrent/alert_types.hpp> header file.

+

The alert class is the base class that specific messages are derived from. This +is its synopsis:

+
+class alert
+{
+public:
+
+        enum category_t
+        {
+                error_notification = implementation defined,
+                peer_notification = implementation defined,
+                port_mapping_notification = implementation defined,
+                storage_notification = implementation defined,
+                tracker_notification = implementation defined,
+                debug_notification = implementation defined,
+                status_notification = implementation defined,
+                progress_notification = implementation defined,
+                ip_block_notification = implementation defined,
+                performance_warning = implementation defined,
+                dht_notification = implementation defined,
+                stats_notification = implementation defined,
+                rss_notification = implementation defined,
+
+                all_categories = implementation defined
+        };
+
+        ptime timestamp() const;
+
+        virtual ~alert();
+
+        virtual int type() const = 0;
+        virtual std::string message() const = 0;
+        virtual char const* what() const = 0;
+        virtual int category() const = 0;
+        virtual bool discardable() const;
+        virtual std::auto_ptr<alert> clone() const = 0;
+};
+
+

type() returns an integer that is unique to this alert type. It can be +compared against a specific alert by querying a static constant called alert_type +in the alert. It can be used to determine the run-time type of an alert* in +order to cast to that alert type and access specific members.

+

e.g:

+
+std::auto_ptr<alert> a = ses.pop_alert();
+switch (a->type())
+{
+        case read_piece_alert::alert_type:
+        {
+                read_piece_alert* p = (read_piece_alert*)a.get();
+                if (p->ec) {
+                        // read_piece failed
+                        break;
+                }
+                // use p
+                break;
+        }
+        case file_renamed_alert::alert_type:
+        {
+                // etc...
+        }
+}
+
+

what() returns a string literal describing the type of the alert. It does +not include any information that might be bundled with the alert.

+

category() returns a bitmask specifying which categories this alert belong to.

+

clone() returns a pointer to a copy of the alert.

+

discardable() determines whether or not an alert is allowed to be discarded +when the alert queue is full. There are a few alerts which may not be discared, +since they would break the user contract, such as save_resume_data_alert.

+

message() generate a string describing the alert and the information bundled +with it. This is mainly intended for debug and development use. It is not suitable +to use this for applications that may be localized. Instead, handle each alert +type individually and extract and render the information from the alert depending +on the locale.

+

There's another alert base class that most alerts derives from, all the +alerts that are generated for a specific torrent are derived from:

+
+struct torrent_alert: alert
+{
+        // ...
+        torrent_handle handle;
+};
+
+

There's also a base class for all alerts referring to tracker events:

+
+struct tracker_alert: torrent_alert
+{
+        // ...
+        std::string url;
+};
+
+

The specific alerts are:

+
+

torrent_added_alert

+

The torrent_added_alert is posted once every time a torrent is successfully +added. It doesn't contain any members of its own, but inherits the torrent handle +from its base class. +It's posted when the status_notification bit is set in the alert mask.

+
+struct torrent_added_alert: torrent_alert
+{
+        // ...
+};
+
+
+
+

add_torrent_alert

+

This alert is always posted when a torrent was attempted to be added +and contains the return status of the add operation. The torrent handle of the new +torrent can be found in the base class' handle member. If adding +the torrent failed, error contains the error code.

+
+struct add_torrent_alert: torrent_alert
+{
+        // ...
+        add_torrent_params params;
+        error_code error;
+};
+
+

params is a copy of the parameters used when adding the torrent, it can be used +to identify which invocation to async_add_torrent() caused this alert.

+

error is set to the error, if one occurred while adding the torrent.

+
+
+

torrent_removed_alert

+

The torrent_removed_alert is posted whenever a torrent is removed. Since +the torrent handle in its baseclass will always be invalid (since the torrent +is already removed) it has the info hash as a member, to identify it. +It's posted when the status_notification bit is set in the alert mask.

+

Even though the handle member doesn't point to an existing torrent anymore, +it is still useful for comparing to other handles, which may also no +longer point to existing torrents, but to the same non-existing torrents.

+

The torrent_handle acts as a weak_ptr, even though its object no +longer exists, it can still compare equal to another weak pointer which +points to the same non-existent object.

+
+struct torrent_removed_alert: torrent_alert
+{
+        // ...
+        sha1_hash info_hash;
+};
+
+
+
+

read_piece_alert

+

This alert is posted when the asynchronous read operation initiated by +a call to read_piece() is completed. If the read failed, the torrent +is paused and an error state is set and the buffer member of the alert +is 0. If successful, buffer points to a buffer containing all the data +of the piece. piece is the piece index that was read. size is the +number of bytes that was read.

+

If the operation fails, ec will indicat what went wrong.

+
+struct read_piece_alert: torrent_alert
+{
+        // ...
+        error_code ec;
+        boost::shared_ptr<char> buffer;
+        int piece;
+        int size;
+};
+
+
+
+

external_ip_alert

+

Whenever libtorrent learns about the machines external IP, this alert is +generated. The external IP address can be acquired from the tracker (if it +supports that) or from peers that supports the extension protocol. +The address can be accessed through the external_address member.

+
+struct external_ip_alert: alert
+{
+        // ...
+        address external_address;
+};
+
+
+
+

listen_failed_alert

+

This alert is generated when none of the ports, given in the port range, to +session can be opened for listening. The endpoint member is the +interface and port that failed, error is the error code describing +the failure.

+

libtorrent may sometimes try to listen on port 0, if all other ports failed. +Port 0 asks the operating system to pick a port that's free). If that fails +you may see a listen_failed_alert with port 0 even if you didn't ask to +listen on it.

+
+struct listen_failed_alert: alert
+{
+        // ...
+        tcp::endpoint endpoint;
+        error_code error;
+};
+
+
+
+

listen_succeeded_alert

+

This alert is posted when the listen port succeeds to be opened on a +particular interface. endpoint is the endpoint that successfully +was opened for listening.

+
+struct listen_succeeded_alert: alert
+{
+        // ...
+        tcp::endpoint endpoint;
+};
+
+
+
+

portmap_error_alert

+

This alert is generated when a NAT router was successfully found but some +part of the port mapping request failed. It contains a text message that +may help the user figure out what is wrong. This alert is not generated in +case it appears the client is not running on a NAT:ed network or if it +appears there is no NAT router that can be remote controlled to add port +mappings.

+

mapping refers to the mapping index of the port map that failed, i.e. +the index returned from add_mapping().

+

map_type is 0 for NAT-PMP and 1 for UPnP.

+

error tells you what failed.

+
+struct portmap_error_alert: alert
+{
+        // ...
+        int mapping;
+        int type;
+        error_code error;
+};
+
+
+
+

portmap_alert

+

This alert is generated when a NAT router was successfully found and +a port was successfully mapped on it. On a NAT:ed network with a NAT-PMP +capable router, this is typically generated once when mapping the TCP +port and, if DHT is enabled, when the UDP port is mapped.

+

mapping refers to the mapping index of the port map that failed, i.e. +the index returned from add_mapping().

+

external_port is the external port allocated for the mapping.

+

type is 0 for NAT-PMP and 1 for UPnP.

+
+struct portmap_alert: alert
+{
+        // ...
+        int mapping;
+        int external_port;
+        int map_type;
+};
+
+
+
+

portmap_log_alert

+

This alert is generated to log informational events related to either +UPnP or NAT-PMP. They contain a log line and the type (0 = NAT-PMP +and 1 = UPnP). Displaying these messages to an end user is only useful +for debugging the UPnP or NAT-PMP implementation.

+
+struct portmap_log_alert: alert
+{
+        //...
+        int map_type;
+        std::string msg;
+};
+
+
+
+

file_error_alert

+

If the storage fails to read or write files that it needs access to, this alert is +generated and the torrent is paused.

+

file is the path to the file that was accessed when the error occurred.

+

error is the error code describing the error.

+

operation is a NULL-terminated string of the low-level operation that failed, or NULL if +there was no low level disk operation.

+
+struct file_error_alert: torrent_alert
+{
+        // ...
+        std::string file;
+        error_code error;
+        char const* operation;
+};
+
+
+
+

torrent_error_alert

+

This is posted whenever a torrent is transitioned into the error state.

+
+struct torrent_error_alert: torrent_alert
+{
+        // ...
+        error_code error;
+        std::string error_file;
+};
+
+

The error specifies which error the torrent encountered.

+

error_file is the filename (or object) the error occurred on.

+
+
+

file_renamed_alert

+

This is posted as a response to a torrent_handle::rename_file call, if the rename +operation succeeds.

+
+struct file_renamed_alert: torrent_alert
+{
+        // ...
+        std::string name;
+        int index;
+};
+
+

The index member refers to the index of the file that was renamed, +name is the new name of the file.

+
+
+

file_rename_failed_alert

+

This is posted as a response to a torrent_handle::rename_file call, if the rename +operation failed.

+
+struct file_rename_failed_alert: torrent_alert
+{
+        // ...
+        int index;
+        error_code error;
+};
+
+

The index member refers to the index of the file that was supposed to be renamed, +error is the error code returned from the filesystem.

+
+
+

tracker_announce_alert

+

This alert is generated each time a tracker announce is sent (or attempted to be sent). +There are no extra data members in this alert. The url can be found in the base class +however.

+
+struct tracker_announce_alert: tracker_alert
+{
+        // ...
+        int event;
+};
+
+

Event specifies what event was sent to the tracker. It is defined as:

+
    +
  1. None
  2. +
  3. Completed
  4. +
  5. Started
  6. +
  7. Stopped
  8. +
+
+
+

tracker_error_alert

+

This alert is generated on tracker time outs, premature disconnects, invalid response or +a HTTP response other than "200 OK". From the alert you can get the handle to the torrent +the tracker belongs to.

+

The times_in_row member says how many times in a row this tracker has failed. +status_code is the code returned from the HTTP server. 401 means the tracker needs +authentication, 404 means not found etc. If the tracker timed out, the code will be set +to 0.

+
+struct tracker_error_alert: tracker_alert
+{
+        // ...
+        int times_in_row;
+        int status_code;
+};
+
+
+
+

tracker_reply_alert

+

This alert is only for informational purpose. It is generated when a tracker announce +succeeds. It is generated regardless what kind of tracker was used, be it UDP, HTTP or +the DHT.

+
+struct tracker_reply_alert: tracker_alert
+{
+        // ...
+        int num_peers;
+};
+
+

The num_peers tells how many peers the tracker returned in this response. This is +not expected to be more thant the num_want settings. These are not necessarily +all new peers, some of them may already be connected.

+
+
+

tracker_warning_alert

+

This alert is triggered if the tracker reply contains a warning field. Usually this +means that the tracker announce was successful, but the tracker has a message to +the client. The msg string in the alert contains the warning message from +the tracker.

+
+struct tracker_warning_alert: tracker_alert
+{
+        // ...
+        std::string msg;
+};
+
+
+
+

scrape_reply_alert

+

This alert is generated when a scrape request succeeds. incomplete +and complete is the data returned in the scrape response. These numbers +may be -1 if the reponse was malformed.

+
+struct scrape_reply_alert: tracker_alert
+{
+        // ...
+        int incomplete;
+        int complete;
+};
+
+
+
+

scrape_failed_alert

+

If a scrape request fails, this alert is generated. This might be due +to the tracker timing out, refusing connection or returning an http response +code indicating an error. msg contains a message describing the error.

+
+struct scrape_failed_alert: tracker_alert
+{
+        // ...
+        std::string msg;
+};
+
+
+
+

url_seed_alert

+

This alert is generated when a HTTP seed name lookup fails.

+

It contains url to the HTTP seed that failed along with an error message.

+
+struct url_seed_alert: torrent_alert
+{
+        // ...
+        std::string url;
+};
+
+
+
+

hash_failed_alert

+

This alert is generated when a finished piece fails its hash check. You can get the handle +to the torrent which got the failed piece and the index of the piece itself from the alert.

+
+struct hash_failed_alert: torrent_alert
+{
+        // ...
+        int piece_index;
+};
+
+
+
+

peer_alert

+

The peer alert is a base class for alerts that refer to a specific peer. It includes all +the information to identify the peer. i.e. ip and peer-id.

+
+struct peer_alert: torrent_alert
+{
+        // ...
+        tcp::endpoint ip;
+        peer_id pid;
+};
+
+
+
+

peer_connect_alert

+

This alert is posted every time an outgoing peer connect attempts succeeds.

+
+struct peer_connect_alert: peer_alert
+{
+        // ...
+};
+
+
+
+

peer_ban_alert

+

This alert is generated when a peer is banned because it has sent too many corrupt pieces +to us. ip is the endpoint to the peer that was banned.

+
+struct peer_ban_alert: peer_alert
+{
+        // ...
+};
+
+
+
+

peer_snubbed_alert

+

This alert is generated when a peer is snubbed, when it stops sending data when we request +it.

+
+struct peer_snubbed_alert: peer_alert
+{
+        // ...
+};
+
+
+
+

peer_unsnubbed_alert

+

This alert is generated when a peer is unsnubbed. Essentially when it was snubbed for stalling +sending data, and now it started sending data again.

+
+struct peer_unsnubbed_alert: peer_alert
+{
+        // ...
+};
+
+
+
+

peer_error_alert

+

This alert is generated when a peer sends invalid data over the peer-peer protocol. The peer +will be disconnected, but you get its ip address from the alert, to identify it.

+

The error_code tells you what error caused this alert.

+
+struct peer_error_alert: peer_alert
+{
+        // ...
+        error_code error;
+};
+
+
+
+

peer_connected_alert

+

This alert is generated when a peer is connected.

+
+struct peer_connected_alert: peer_alert
+{
+        // ...
+};
+
+
+
+

peer_disconnected_alert

+

This alert is generated when a peer is disconnected for any reason (other than the ones +covered by peer_error_alert).

+

The error_code tells you what error caused peer to disconnect.

+
+struct peer_disconnected_alert: peer_alert
+{
+        // ...
+        error_code error;
+};
+
+
+
+

invalid_request_alert

+

This is a debug alert that is generated by an incoming invalid piece request. +ip is the address of the peer and the request is the actual incoming +request from the peer.

+
+struct invalid_request_alert: peer_alert
+{
+        // ...
+        peer_request request;
+};
+
+
+struct peer_request
+{
+        int piece;
+        int start;
+        int length;
+        bool operator==(peer_request const& r) const;
+};
+
+

The peer_request contains the values the client sent in its request message. piece is +the index of the piece it want data from, start is the offset within the piece where the data +should be read, and length is the amount of data it wants.

+
+
+

request_dropped_alert

+

This alert is generated when a peer rejects or ignores a piece request.

+
+struct request_dropped_alert: peer_alert
+{
+        // ...
+        int block_index;
+        int piece_index;
+};
+
+
+
+

block_timeout_alert

+

This alert is generated when a block request times out.

+
+struct block_timeout_alert: peer_alert
+{
+        // ...
+        int block_index;
+        int piece_index;
+};
+
+
+
+

block_finished_alert

+

This alert is generated when a block request receives a response.

+
+struct block_finished_alert: peer_alert
+{
+        // ...
+        int block_index;
+        int piece_index;
+};
+
+
+
+

lsd_peer_alert

+

This alert is generated when we receive a local service discovery message from a peer +for a torrent we're currently participating in.

+
+struct lsd_peer_alert: peer_alert
+{
+        // ...
+};
+
+
+
+

file_completed_alert

+

This is posted whenever an individual file completes its download. i.e. +All pieces overlapping this file have passed their hash check.

+
+struct file_completed_alert: torrent_alert
+{
+        // ...
+        int index;
+};
+
+

The index member refers to the index of the file that completed.

+
+
+

block_downloading_alert

+

This alert is generated when a block request is sent to a peer.

+
+struct block_downloading_alert: peer_alert
+{
+        // ...
+        int block_index;
+        int piece_index;
+};
+
+
+
+

unwanted_block_alert

+

This alert is generated when a block is received that was not requested or +whose request timed out.

+
+struct unwanted_block_alert: peer_alert
+{
+        // ...
+        int block_index;
+        int piece_index;
+};
+
+
+
+

torrent_delete_failed_alert

+

This alert is generated when a request to delete the files of a torrent fails.

+

The error_code tells you why it failed.

+
+struct torrent_delete_failed_alert: torrent_alert
+{
+        // ...
+        error_code error;
+};
+
+
+
+

torrent_deleted_alert

+

This alert is generated when a request to delete the files of a torrent complete.

+

The info_hash is the info-hash of the torrent that was just deleted. Most of +the time the torrent_handle in the torrent_alert will be invalid by the time +this alert arrives, since the torrent is being deleted. The info_hash member +is hence the main way of identifying which torrent just completed the delete.

+

This alert is posted in the storage_notification category, and that bit +needs to be set in the alert mask.

+
+struct torrent_deleted_alert: torrent_alert
+{
+        // ...
+        sha1_hash info_hash;
+};
+
+
+
+

torrent_finished_alert

+

This alert is generated when a torrent switches from being a downloader to a seed. +It will only be generated once per torrent. It contains a torrent_handle to the +torrent in question.

+

There are no additional data members in this alert.

+
+
+

performance_alert

+

This alert is generated when a limit is reached that might have a negative impact on +upload or download rate performance.

+
+struct performance_alert: torrent_alert
+{
+        // ...
+
+        enum performance_warning_t
+        {
+                outstanding_disk_buffer_limit_reached,
+                outstanding_request_limit_reached,
+                upload_limit_too_low,
+                download_limit_too_low,
+                send_buffer_watermark_too_low,
+                too_many_optimistic_unchoke_slots,
+                too_high_disk_queue_limit,
+                too_few_outgoing_ports
+        };
+
+        performance_warning_t warning_code;
+};
+
+
+
outstanding_disk_buffer_limit_reached
+
This warning means that the number of bytes queued to be written to disk +exceeds the max disk byte queue setting (settings_pack::max_queued_disk_bytes). +This might restrict the download rate, by not queuing up enough write jobs +to the disk I/O thread. When this alert is posted, peer connections are +temporarily stopped from downloading, until the queued disk bytes have fallen +below the limit again. Unless your max_queued_disk_bytes setting is already +high, you might want to increase it to get better performance.
+
outstanding_request_limit_reached
+
This is posted when libtorrent would like to send more requests to a peer, +but it's limited by max_out_request_queue. The queue length +libtorrent is trying to achieve is determined by the download rate and the +assumed round-trip-time (request_queue_time). The assumed +rount-trip-time is not limited to just the network RTT, but also the remote disk +access time and message handling time. It defaults to 3 seconds. The target number +of outstanding requests is set to fill the bandwidth-delay product (assumed RTT +times download rate divided by number of bytes per request). When this alert +is posted, there is a risk that the number of outstanding requests is too low +and limits the download rate. You might want to increase the max_out_request_queue +setting.
+
upload_limit_too_low
+
This warning is posted when the amount of TCP/IP overhead is greater than the +upload rate limit. When this happens, the TCP/IP overhead is caused by a much +faster download rate, triggering TCP ACK packets. These packets eat into the +rate limit specified to libtorrent. When the overhead traffic is greater than +the rate limit, libtorrent will not be able to send any actual payload, such +as piece requests. This means the download rate will suffer, and new requests +can be sent again. There will be an equilibrium where the download rate, on +average, is about 20 times the upload rate limit. If you want to maximize the +download rate, increase the upload rate limit above 5% of your download capacity.
+
download_limit_too_low
+
This is the same warning as upload_limit_too_low but referring to the download +limit instead of upload. This suggests that your download rate limit is mcuh lower +than your upload capacity. Your upload rate will suffer. To maximize upload rate, +make sure your download rate limit is above 5% of your upload capacity.
+
send_buffer_watermark_too_low
+

We're stalled on the disk. We want to write to the socket, and we can write +but our send buffer is empty, waiting to be refilled from the disk. +This either means the disk is slower than the network connection +or that our send buffer watermark is too small, because we can +send it all before the disk gets back to us. +The number of bytes that we keep outstanding, requested from the disk, is calculated +as follows:

+
+min(512, max(upload_rate * send_buffer_watermark_factor / 100, send_buffer_watermark))
+
+

If you receive this alert, you migth want to either increase your send_buffer_watermark +or send_buffer_watermark_factor.

+
+
too_many_optimistic_unchoke_slots
+
If the half (or more) of all upload slots are set as optimistic unchoke slots, this +warning is issued. You probably want more regular (rate based) unchoke slots.
+
too_high_disk_queue_limit
+
If the disk write queue ever grows larger than half of the cache size, this warning +is posted. The disk write queue eats into the total disk cache and leaves very little +left for the actual cache. This causes the disk cache to oscillate in evicting large +portions of the cache before allowing peers to download any more, onto the disk write +queue. Either lower max_queued_disk_bytes or increase cache_size.
+
too_few_outgoing_ports
+
This is generated if outgoing peer connections are failing because of address in use +errors, indicating that num_outgoing_ports is set and is too small of +a range. Consider not using the num_outgoing_ports setting at all, +or widen the range to include more ports.
+
+
+
+

state_changed_alert

+

Generated whenever a torrent changes its state.

+
+struct state_changed_alert: torrent_alert
+{
+        // ...
+
+        torrent_status::state_t state;
+        torrent_status::state_t prev_state;
+};
+
+

state is the new state of the torrent. prev_state is the previous state.

+
+
+

metadata_failed_alert

+

This alert is generated when the metadata has been completely received and the info-hash +failed to match it. i.e. the metadata that was received was corrupt. libtorrent will +automatically retry to fetch it in this case. This is only relevant when running a +torrent-less download, with the metadata extension provided by libtorrent.

+
+struct metadata_failed_alert: torrent_alert
+{
+        // ...
+
+        error_code error;
+};
+
+

The error member indicates what failed when parsing the metadata. This error is +what's returned from lazy_bdecode().

+
+
+

metadata_received_alert

+

This alert is generated when the metadata has been completely received and the torrent +can start downloading. It is not generated on torrents that are started with metadata, but +only those that needs to download it from peers (when utilizing the libtorrent extension).

+

There are no additional data members in this alert.

+

Typically, when receiving this alert, you would want to save the torrent file in order +to load it back up again when the session is restarted. Here's an example snippet of +code to do that:

+
+torrent_handle h = alert->handle();
+if (h.is_valid()) {
+        boost::intrusive_ptr<torrent_info const> ti = h.torrent_file();
+        create_torrent ct(*ti);
+        entry te = ct.generate();
+        std::vector<char> buffer;
+        bencode(std::back_inserter(buffer), te);
+        FILE* f = fopen((to_hex(ti->info_hash().to_string()) + ".torrent").c_str(), "wb+");
+        if (f) {
+                fwrite(&buffer[0], 1, buffer.size(), f);
+                fclose(f);
+        }
+}
+
+
+
+

fastresume_rejected_alert

+

This alert is generated when a fastresume file has been passed to add_torrent but the +files on disk did not match the fastresume file. The error_code explains the reason why the +resume file was rejected.

+

If the error happend to a specific file, file is the path to it. If the error happened +in a disk operation, operation is a NULL-terminated string of the name of that operation. +operation is NULL otherwise.

+
+struct fastresume_rejected_alert: torrent_alert
+{
+        // ...
+        error_code error;
+        std::string file;
+        char const* operation;
+};
+
+
+
+

peer_blocked_alert

+

This alert is posted when an incoming peer connection, or a peer that's about to be added +to our peer list, is blocked for some reason. This could be any of:

+
    +
  • the IP filter
  • +
  • i2p mixed mode restrictions (a normal peer is not allowed on an i2p swarm)
  • +
  • the port filter
  • +
  • the peer has a low port and no_connect_privileged_ports is enabled
  • +
  • the protocol of the peer is blocked (uTP/TCP blocking)
  • +
+

The ip member is the address that was blocked.

+
+struct peer_blocked_alert: torrent_alert
+{
+        // ...
+        address ip;
+};
+
+
+
+

storage_moved_alert

+

The storage_moved_alert is generated when all the disk IO has completed and the +files have been moved, as an effect of a call to torrent_handle::move_storage. This +is useful to synchronize with the actual disk. The path member is the new path of +the storage.

+
+struct storage_moved_alert: torrent_alert
+{
+        // ...
+        std::string path;
+};
+
+
+
+

storage_moved_failed_alert

+

The storage_moved_failed_alert is generated when an attempt to move the storage +(via torrent_handle::move_storage()) fails.

+

If the error happened for a speific file, file is its path. If the error +happened in a specific disk operation, operation is a NULL terminated string +naming which one, otherwise it's NULL.

+
+struct storage_moved_failed_alert: torrent_alert
+{
+        // ...
+        error_code error;
+        std::string file;
+        char const* operation;
+};
+
+
+
+

torrent_paused_alert

+

This alert is generated as a response to a torrent_handle::pause request. It is +generated once all disk IO is complete and the files in the torrent have been closed. +This is useful for synchronizing with the disk.

+

There are no additional data members in this alert.

+
+
+

torrent_resumed_alert

+

This alert is generated as a response to a torrent_handle::resume request. It is +generated when a torrent goes from a paused state to an active state.

+

There are no additional data members in this alert.

+
+
+

save_resume_data_alert

+

This alert is generated as a response to a torrent_handle::save_resume_data request. +It is generated once the disk IO thread is done writing the state for this torrent. +The resume_data member points to the resume data.

+
+struct save_resume_data_alert: torrent_alert
+{
+        // ...
+        boost::shared_ptr<entry> resume_data;
+};
+
+
+
+

save_resume_data_failed_alert

+

This alert is generated instead of save_resume_data_alert if there was an error +generating the resume data. error describes what went wrong.

+
+struct save_resume_data_failed_alert: torrent_alert
+{
+        // ...
+        error_code error;
+};
+
+
+
+

stats_alert

+

This alert is posted approximately once every second, and it contains +byte counters of most statistics that's tracked for torrents. Each active +torrent posts these alerts regularly.

+
+struct stats_alert: torrent_alert
+{
+        // ...
+        enum stats_channel
+        {
+                upload_payload,
+                upload_protocol,
+                upload_ip_protocol,
+                upload_dht_protocol,
+                upload_tracker_protocol,
+                download_payload,
+                download_protocol,
+                download_ip_protocol,
+                download_dht_protocol,
+                download_tracker_protocol,
+                num_channels
+        };
+
+        int transferred[num_channels];
+        int interval;
+};
+
+

transferred this is an array of samples. The enum describes what each +sample is a measurement of. All of these are raw, and not smoothing is performed.

+

interval the number of milliseconds during which these stats +were collected. This is typically just above 1000, but if CPU is +limited, it may be higher than that.

+
+
+

cache_flushed_alert

+

This alert is posted when the disk cache has been flushed for a specific torrent +as a result of a call to flush_cache(). This alert belongs to the +storage_notification category, which must be enabled to let this alert through. +The alert is also posted when removing a torrent from the session, once the outstanding +cache flush is complete and the torrent does no longer have any files open.

+
+struct flush_cached_alert: torrent_alert
+{
+        // ...
+};
+
+
+
+

torrent_need_cert_alert

+

This is always posted for SSL torrents. This is a reminder to the client that +the torrent won't work unless torrent_handle::set_ssl_certificate() is called with +a valid certificate. Valid certificates MUST be signed by the SSL certificate +in the .torrent file.

+
+struct torrent_need_cert_alert: tracker_alert
+{
+        // ...
+};
+
+
+
+

dht_announce_alert

+

This alert is generated when a DHT node announces to an info-hash on our DHT node. It belongs +to the dht_notification category.

+
+struct dht_announce_alert: alert
+{
+        // ...
+        address ip;
+        int port;
+        sha1_hash info_hash;
+};
+
+
+
+

dht_get_peers_alert

+

This alert is generated when a DHT node sends a get_peers message to our DHT node. +It belongs to the dht_notification category.

+
+struct dht_get_peers_alert: alert
+{
+        // ...
+        sha1_hash info_hash;
+};
+
+
+
+

dht_reply_alert

+

This alert is generated each time the DHT receives peers from a node. num_peers +is the number of peers we received in this packet. Typically these packets are +received from multiple DHT nodes, and so the alerts are typically generated +a few at a time.

+
+struct dht_reply_alert: tracker_alert
+{
+        // ...
+        int num_peers;
+};
+
+
+
+

dht_bootstrap_alert

+

This alert is posted when the initial DHT bootstrap is done. There's no any other +relevant information associated with this alert.

+
+struct dht_bootstrap_alert: alert
+{
+        // ...
+};
+
+
+
+

anonymous_mode_alert

+

This alert is posted when a bittorrent feature is blocked because of the +anonymous mode. For instance, if the tracker proxy is not set up, no +trackers will be used, because trackers can only be used through proxies +when in anonymous mode.

+
+struct anonymous_mode_alert: tracker_alert
+{
+        // ...
+        enum kind_t
+        {
+                tracker_not_anonymous = 1
+        };
+        int kind;
+        std::string str;
+};
+
+

kind specifies what error this is, it's one of:

+

tracker_not_anonymous means that there's no proxy set up for tracker +communication and the tracker will not be contacted. The tracker which +this failed for is specified in the str member.

+
+
+

rss_alert

+

This alert is posted on RSS feed events such as start of RSS feed updates, +successful completed updates and errors during updates.

+

This alert is only posted if the rss_notifications category is enabled +in the alert mask.

+
+struct rss_alert: alert
+{
+        // ..
+        virtual std::string message() const;
+
+        enum state_t
+        {
+                state_updating, state_updated, state_error
+        };
+
+        feed_handle handle;
+        std::string url;
+        int state;
+        error_code error;
+};
+
+

handle is the handle to the feed which generated this alert.

+

url is a short cut to access the url of the feed, without +having to call get_settings().

+

state is one of:

+
+
rss_alert::state_updating
+
An update of this feed was just initiated, it will either succeed +or fail soon.
+
rss_alert::state_updated
+
The feed just completed a successful update, there may be new items +in it. If you're adding torrents manually, you may want to request +the feed status of the feed and look through the items vector.
+
rss_akert::state_error
+
An error just occurred. See the error field for information on +what went wrong.
+
+

error is an error code used for when an error occurs on the feed.

+
+
+

rss_item_alert

+

This alert is posted every time a new RSS item (i.e. torrent) is received +from an RSS feed.

+

It is only posted if the rss_notifications category is enabled in the +alert mask.

+
+struct rss_alert : alert
+{
+        // ...
+        virtual std::string message() const;
+
+        feed_handle handle;
+        feed_item item;
+};
+
+
+
+

incoming_connection_alert

+

The incoming connection alert is posted every time we successfully accept +an incoming connection, through any mean. The most straigh-forward ways +of accepting incoming connections are through the TCP listen socket and +the UDP listen socket for uTP sockets. However, connections may also be +accepted ofer a Socks5 or i2p listen socket, or via a torrent specific +listen socket for SSL torrents.

+
+struct incoming_connection_alert: alert
+{
+        // ...
+        virtual std::string message() const;
+
+        int socket_type;
+        tcp::endpoint ip;
+};
+
+

socket_type tells you what kind of socket the connection was accepted +as:

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
valuetype
0none (no socket instantiated)
1TCP
2Socks5
3HTTP
4uTP
5i2p
6SSL/TCP
7SSL/Socks5
8HTTPS (SSL/HTTP)
9SSL/uTP
+

ip is the IP address and port the connection came from.

+
+
+

state_update_alert

+

This alert is only posted when requested by the user, by calling post_torrent_updates() +on the session. It contains the torrent status of all torrents that changed +since last time this message was posted. Its category is status_notification, but +it's not subject to filtering, since it's only manually posted anyway.

+
+struct state_update_alert: alert
+{
+        // ...
+        std::vector<torrent_status> status;
+};
+
+

status contains the torrent status of all torrents that changed since last time +this message was posted. Note that you can map a torrent status to a specific torrent +via its handle member. The receiving end is suggested to have all torrents sorted +by the torrent_handle or hashed by it, for efficient updates.

+
+<<<<<<< .working +
+

session_stats_alert

+

The session_stats_alert is posted when the user requests session statistics by +calling post_session_stats() on the session object. Its category is +status_notification, but it is not subject to filtering, since it's only +manually posted anyway.

+

The resulting alert contains:

+
+struct session_stats_alert: alert
+{
+        // ...
+        boost::uint64_t timestamp;
+        std::vector<boost::uint64_t> values;
+};
+
+

The values in the values array are a mix of counters and gauges, which +meanings can be queries via the session_stats_metrics() function on the session. +The mapping from a specific metric to an index into this array is constant for a +specific version of libtorrent, but may differ for other versions. The intended +usage is to request the mapping (i.e. call session_stats_metrics()) once +on startup, and then use that mapping to interpret these values throughout +the process' runtime.

+

The timestamp field is the number of microseconds since the session was +started. It represent the time when the snapshot of values was taken. When +the network thread is under heavy load, the latency between calling +post_session_stats() and receiving this alert may be significant, and +the timestamp may help provide higher accuracy in measurements.

+

For more information, see the session statistics section.

+======= +
+

torrent_update_alert

+

When a torrent changes its info-hash, this alert is posted. This only happens in very +specific cases. For instance, when a torrent is downloaded from a URL, the true info +hash is not known immediately. First the .torrent file must be downloaded and parsed.

+

Once this download completes, the torrent_update_alert is posted to notify the client +of the info-hash changing.

+
+struct torrent_update_alert: torrent_alert
+{
+        // ...
+        sha1_hash old_ih;
+        sha1_hash new_ih;
+};
+
+

old_ih and new_ih are the previous and new info-hash for the torrent, respectively.

+>>>>>>> .merge-right.r8585 +
+
+
+

alert dispatcher

+

The handle_alert class is defined in <libtorrent/alert.hpp>.

+

Examples usage:

+
+struct my_handler
+{
+        void operator()(portmap_error_alert const& a) const
+        {
+                std::cout << "Portmapper: " << a.msg << std::endl;
+        }
+
+        void operator()(tracker_warning_alert const& a) const
+        {
+                std::cout << "Tracker warning: " << a.msg << std::endl;
+        }
+
+        void operator()(torrent_finished_alert const& a) const
+        {
+                // write fast resume data
+                // ...
+
+                std::cout << a.handle.torrent_file()->name() << "completed"
+                        << std::endl;
+        }
+};
+
+
+std::auto_ptr<alert> a;
+a = ses.pop_alert();
+my_handler h;
+while (a.get())
+{
+        handle_alert<portmap_error_alert
+                , tracker_warning_alert
+                , torrent_finished_alert
+        >::handle_alert(h, a);
+        a = ses.pop_alert();
+}
+
+

In this example 3 alert types are used. You can use any number of template +parameters to select between more types. If the number of types are more than +15, you can define TORRENT_MAX_ALERT_TYPES to a greater number before +including <libtorrent/alert.hpp>.

+
+
+

exceptions

+

Many functions in libtorrent have two versions, one that throws exceptions on +errors and one that takes an error_code reference which is filled with the +error code on errors.

+

There is one exception class that is used for errors in libtorrent, it is based +on boost.system's error_code class to carry the error code.

+
+

libtorrent_exception

+
+struct libtorrent_exception: std::exception
+{
+        libtorrent_exception(error_code const& s);
+        virtual const char* what() const throw();
+        virtual ~libtorrent_exception() throw() {}
+        boost::system::error_code error() const;
+};
+
+
+
+
+

error_code

+

libtorrent uses boost.system's error_code class to represent errors. libtorrent has +its own error category (libtorrent::get_libtorrent_category()) whith the following error +codes:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
codesymboldescription
0no_errorNot an error
1file_collisionTwo torrents has files which end up overwriting each other
2failed_hash_checkA piece did not match its piece hash
3torrent_is_no_dictThe .torrent file does not contain a bencoded dictionary at +its top level
4torrent_missing_infoThe .torrent file does not have an info dictionary
5torrent_info_no_dictThe .torrent file's info entry is not a dictionary
6torrent_missing_piece_lengthThe .torrent file does not have a piece length entry
7torrent_missing_nameThe .torrent file does not have a name entry
8torrent_invalid_nameThe .torrent file's name entry is invalid
9torrent_invalid_lengthThe length of a file, or of the whole .torrent file is invalid. +Either negative or not an integer
10torrent_file_parse_failedFailed to parse a file entry in the .torrent
11torrent_missing_piecesThe pieces field is missing or invalid in the .torrent file
12torrent_invalid_hashesThe pieces string has incorrect length
13too_many_pieces_in_torrentThe .torrent file has more pieces than is supported by libtorrent
14invalid_swarm_metadataThe metadata (.torrent file) that was received from the swarm +matched the info-hash, but failed to be parsed
15invalid_bencodingThe file or buffer is not correctly bencoded
16no_files_in_torrentThe .torrent file does not contain any files
17invalid_escaped_stringThe string was not properly url-encoded as expected
18session_is_closingOperation is not permitted since the session is shutting down
19duplicate_torrentThere's already a torrent with that info-hash added to the +session
20invalid_torrent_handleThe supplied torrent_handle is not referring to a valid torrent
21invalid_entry_typeThe type requested from the entry did not match its type
22missing_info_hash_in_uriThe specified URI does not contain a valid info-hash
23file_too_shortOne of the files in the torrent was unexpectadly small. This +might be caused by files being changed by an external process
24unsupported_url_protocolThe URL used an unknown protocol. Currently http and +https (if built with openssl support) are recognized. For +trackers udp is recognized as well.
25url_parse_errorThe URL did not conform to URL syntax and failed to be parsed
26peer_sent_empty_pieceThe peer sent a 'piece' message of length 0
27parse_failedA bencoded structure was currupt and failed to be parsed
28invalid_file_tagThe fast resume file was missing or had an invalid file version +tag
29missing_info_hashThe fast resume file was missing or had an invalid info-hash
30mismatching_info_hashThe info-hash in the resume file did not match the torrent
31invalid_hostnameThe URL contained an invalid hostname
32invalid_portThe URL had an invalid port
33port_blockedThe port is blocked by the port-filter, and prevented the +connection
34expected_close_bracket_in_addressThe IPv6 address was expected to end with ']'
35destructing_torrentThe torrent is being destructed, preventing the operation to +succeed
36timed_outThe connection timed out
37upload_upload_connectionThe peer is upload only, and we are upload only. There's no point +in keeping the connection
38uninteresting_upload_peerThe peer is upload only, and we're not interested in it. There's +no point in keeping the connection
39invalid_info_hashThe peer sent an unknown info-hash
40torrent_pausedThe torrent is paused, preventing the operation from succeeding
41invalid_haveThe peer sent an invalid have message, either wrong size or +referring to a piece that doesn't exist in the torrent
42invalid_bitfield_sizeThe bitfield message had the incorrect size
43too_many_requests_when_chokedThe peer kept requesting pieces after it was choked, possible +abuse attempt.
44invalid_pieceThe peer sent a piece message that does not correspond to a +piece request sent by the client
45no_memorymemory allocation failed
46torrent_abortedThe torrent is aborted, preventing the operation to succeed
47self_connectionThe peer is a connection to ourself, no point in keeping it
48invalid_piece_sizeThe peer sent a piece message with invalid size, either negative +or greater than one block
49timed_out_no_interestThe peer has not been interesting or interested in us for too +long, no point in keeping it around
50timed_out_inactivityThe peer has not said anything in a long time, possibly dead
51timed_out_no_handshakeThe peer did not send a handshake within a reasonable amount of +time, it might not be a bittorrent peer
52timed_out_no_requestThe peer has been unchoked for too long without requesting any +data. It might be lying about its interest in us
53invalid_chokeThe peer sent an invalid choke message
54invalid_unchokeThe peer send an invalid unchoke message
55invalid_interestedThe peer sent an invalid interested message
56invalid_not_interestedThe peer sent an invalid not-interested message
57invalid_requestThe peer sent an invalid piece request message
58invalid_hash_listThe peer sent an invalid hash-list message (this is part of the +merkle-torrent extension)
59invalid_hash_pieceThe peer sent an invalid hash-piece message (this is part of the +merkle-torrent extension)
60invalid_cancelThe peer sent an invalid cancel message
61invalid_dht_portThe peer sent an invalid DHT port-message
62invalid_suggestThe peer sent an invalid suggest piece-message
63invalid_have_allThe peer sent an invalid have all-message
64invalid_have_noneThe peer sent an invalid have none-message
65invalid_rejectThe peer sent an invalid reject message
66invalid_allow_fastThe peer sent an invalid allow fast-message
67invalid_extendedThe peer sent an invalid extesion message ID
68invalid_messageThe peer sent an invalid message ID
69sync_hash_not_foundThe synchronization hash was not found in the encrypted handshake
70invalid_encryption_constantThe encryption constant in the handshake is invalid
71no_plaintext_modeThe peer does not support plaintext, which is the selected mode
72no_rc4_modeThe peer does not support rc4, which is the selected mode
73unsupported_encryption_modeThe peer does not support any of the encryption modes that the +client supports
74unsupported_encryption_mode_selectedThe peer selected an encryption mode that the client did not +advertise and does not support
75invalid_pad_sizeThe pad size used in the encryption handshake is of invalid size
76invalid_encrypt_handshakeThe encryption handshake is invalid
77no_incoming_encryptedThe client is set to not support incoming encrypted connections +and this is an encrypted connection
78no_incoming_regularThe client is set to not support incoming regular bittorrent +connections, and this is a regular connection
79duplicate_peer_idThe client is already connected to this peer-ID
80torrent_removedTorrent was removed
81packet_too_largeThe packet size exceeded the upper sanity check-limit
82reserved 
83http_errorThe web server responded with an error
84missing_locationThe web server response is missing a location header
85invalid_redirectionThe web seed redirected to a path that no longer matches the +.torrent directory structure
86redirectingThe connection was closed becaused it redirected to a different +URL
87invalid_rangeThe HTTP range header is invalid
88no_content_lengthThe HTTP response did not have a content length
89banned_by_ip_filterThe IP is blocked by the IP filter
90too_many_connectionsAt the connection limit
91peer_bannedThe peer is marked as banned
92stopping_torrentThe torrent is stopping, causing the operation to fail
93too_many_corrupt_piecesThe peer has sent too many corrupt pieces and is banned
94torrent_not_readyThe torrent is not ready to receive peers
95peer_not_constructedThe peer is not completely constructed yet
96session_closingThe session is closing, causing the operation to fail
97optimistic_disconnectThe peer was disconnected in order to leave room for a +potentially better peer
98torrent_finishedThe torrent is finished
99no_routerNo UPnP router found
100metadata_too_largeThe metadata message says the metadata exceeds the limit
101invalid_metadata_requestThe peer sent an invalid metadata request message
102invalid_metadata_sizeThe peer advertised an invalid metadata size
103invalid_metadata_offsetThe peer sent a message with an invalid metadata offset
104invalid_metadata_messageThe peer sent an invalid metadata message
105pex_message_too_largeThe peer sent a peer exchange message that was too large
106invalid_pex_messageThe peer sent an invalid peer exchange message
107invalid_lt_tracker_messageThe peer sent an invalid tracker exchange message
108too_frequent_pexThe peer sent an pex messages too often. This is a possible +attempt of and attack
109no_metadataThe operation failed because it requires the torrent to have +the metadata (.torrent file) and it doesn't have it yet. +This happens for magnet links before they have downloaded the +metadata, and also torrents added by URL.
110invalid_dont_haveThe peer sent an invalid dont_have message. The dont have +message is an extension to allow peers to advertise that the +no longer has a piece they previously had.
111requires_ssl_connectionThe peer tried to connect to an SSL torrent without connecting +over SSL.
112invalid_ssl_certThe peer tried to connect to a torrent with a certificate +for a different torrent.
+

NAT-PMP errors:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
codesymboldescription
120unsupported_protocol_versionThe NAT-PMP router responded with an unsupported protocol version
121natpmp_not_authorizedYou are not authorized to map ports on this NAT-PMP router
122network_failureThe NAT-PMP router failed because of a network failure
123no_resourcesThe NAT-PMP router failed because of lack of resources
124unsupported_opcodeThe NAT-PMP router failed because an unsupported opcode was sent
+

fastresume data errors:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
codesymboldescription
130missing_file_sizesThe resume data file is missing the 'file sizes' entry
131no_files_in_resume_dataThe resume data file 'file sizes' entry is empty
132missing_piecesThe resume data file is missing the 'pieces' and 'slots' entry
133mismatching_number_of_filesThe number of files in the resume data does not match the number +of files in the torrent
134mismatching_files_sizeOne of the files on disk has a different size than in the fast +resume file
135mismatching_file_timestampOne of the files on disk has a different timestamp than in the +fast resume file
136not_a_dictionaryThe resume data file is not a dictionary
137invalid_blocks_per_pieceThe 'blocks per piece' entry is invalid in the resume data file
138missing_slotsThe resume file is missing the 'slots' entry, which is required +for torrents with compact allocation
139too_many_slotsThe resume file contains more slots than the torrent
140invalid_slot_listThe 'slot' entry is invalid in the resume data
141invalid_piece_indexOne index in the 'slot' list is invalid
142pieces_need_reorderThe pieces on disk needs to be re-ordered for the specified +allocation mode. This happens if you specify sparse allocation +and the files on disk are using compact storage. The pieces needs +to be moved to their right position
+

HTTP errors:

+ +++++ + + + + + + + + + + + + + + +
150http_parse_errorThe HTTP header was not correctly formatted
151http_missing_locationThe HTTP response was in the 300-399 range but lacked a location +header
152http_failed_decompressThe HTTP response was encoded with gzip or deflate but +decompressing it failed
+

I2P errors:

+ +++++ + + + + + + +
160no_i2p_routerThe URL specified an i2p address, but no i2p router is configured
+

tracker errors:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
170scrape_not_availableThe tracker URL doesn't support transforming it into a scrape +URL. i.e. it doesn't contain "announce.
171invalid_tracker_responseinvalid tracker response
172invalid_peer_dictinvalid peer dictionary entry. Not a dictionary
173tracker_failuretracker sent a failure message
174invalid_files_entrymissing or invalid 'files' entry
175invalid_hash_entrymissing or invalid 'hash' entry
176invalid_peers_entrymissing or invalid 'peers' and 'peers6' entry
177invalid_tracker_response_lengthudp tracker response packet has invalid size
178invalid_tracker_transaction_idinvalid transaction id in udp tracker response
179invalid_tracker_actioninvalid action field in udp tracker response
190expected_stringexpected string in bencoded string
191expected_colonexpected colon in bencoded string
192unexpected_eofunexpected end of file in bencoded string
193expected_valueexpected value (list, dict, int or string) in bencoded string
194depth_exceededbencoded recursion depth limit exceeded
195item_limit_exceededbencoded item count limit exceeded
+

The names of these error codes are declared in then libtorrent::errors namespace.

+

There is also another error category, libtorrent::upnp_category, defining errors +retrned by UPnP routers. Here's a (possibly incomplete) list of UPnP error codes:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
codesymboldescription
0no_errorNo error
402invalid_argumentOne of the arguments in the request is invalid
501action_failedThe request failed
714value_not_in_arrayThe specified value does not exist in the array
715source_ip_cannot_be_wildcardedThe source IP address cannot be wild-carded, but +must be fully specified
716external_port_cannot_be_wildcardedThe external port cannot be wildcarded, but must +be specified
718port_mapping_conflictThe port mapping entry specified conflicts with a +mapping assigned previously to another client
724internal_port_must_match_externalInternal and external port value must be the same
725only_permanent_leases_supportedThe NAT implementation only supports permanent +lease times on port mappings
726remote_host_must_be_wildcardRemoteHost must be a wildcard and cannot be a +specific IP addres or DNS name
727external_port_must_be_wildcardExternalPort must be a wildcard and cannot be a +specific port
+

The UPnP errors are declared in the libtorrent::upnp_errors namespace.

+

HTTP errors are reported in the libtorrent::http_category, with error code enums in +the libtorrent::errors namespace.

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
codesymbol
100cont
200ok
201created
202accepted
204no_content
300multiple_choices
301moved_permanently
302moved_temporarily
304not_modified
400bad_request
401unauthorized
403forbidden
404not_found
500internal_server_error
501not_implemented
502bad_gateway
503service_unavailable
+
+

translating error codes

+

The error_code::message() function will typically return a localized error string, +for system errors. That is, errors that belong to the generic or system category.

+

Errors that belong to the libtorrent error category are not localized however, they +are only available in english. In order to translate libtorrent errors, compare the +error category of the error_code object against libtorrent::get_libtorrent_category(), +and if matches, you know the error code refers to the list above. You can provide +your own mapping from error code to string, which is localized. In this case, you +cannot rely on error_code::message() to generate your strings.

+

The numeric values of the errors are part of the API and will stay the same, although +new error codes may be appended at the end.

+

Here's a simple example of how to translate error codes:

+
+std::string error_code_to_string(boost::system::error_code const& ec)
+{
+        if (ec.category() != libtorrent::get_libtorrent_category())
+        {
+                return ec.message();
+        }
+        // the error is a libtorrent error
+
+        int code = ec.value();
+        static const char const* swedish[] =
+        {
+                "inget fel",
+                "en fil i torrenten kolliderar med en fil fran en annan torrent",
+                "hash check misslyckades",
+                "torrent filen ar inte en dictionary",
+                "'info'-nyckeln saknas eller ar korrupt i torrentfilen",
+                "'info'-faltet ar inte en dictionary",
+                "'piece length' faltet saknas eller ar korrupt i torrentfilen",
+                "torrentfilen saknar namnfaltet",
+                "ogiltigt namn i torrentfilen (kan vara en attack)",
+                // ... more strings here
+        };
+
+        // use the default error string in case we don't have it
+        // in our translated list
+        if (code < 0 || code >= sizeof(swedish)/sizeof(swedish[0]))
+                return ec.message();
+
+        return swedish[code];
+}
+
+
+
+
+

storage_interface

+

The storage interface is a pure virtual class that can be implemented to +customize how and where data for a torrent is stored. The default storage +implementation uses regular files in the filesystem, mapping the files in the +torrent in the way one would assume a torrent is saved to disk. Implementing +your own storage interface makes it possible to store all data in RAM, or in +some optimized order on disk (the order the pieces are received for instance), +or saving multifile torrents in a single file in order to be able to take +advantage of optimized disk-I/O.

+

It is also possible to write a thin class that uses the default storage but +modifies some particular behavior, for instance encrypting the data before +it's written to disk, and decrypting it when it's read again.

+

The storage interface is based on slots, each slot is 'piece_size' number +of bytes. All access is done by writing and reading whole or partial +slots. One slot is one piece in the torrent, but the data in the slot +does not necessarily correspond to the piece with the same index (in +compact allocation mode it won't).

+

libtorrent comes with two built-in storage implementations; default_storage +and disabled_storage. Their constructor functions are called default_storage_constructor +and disabled_storage_constructor respectively. The disabled storage does +just what it sounds like. It throws away data that's written, and it +reads garbage. It's useful mostly for benchmarking and profiling purpose.

+

The interface looks like this:

+
+struct storage_params
+{
+        file_storage const* files;
+        file_storage const* mapped_files; // optional
+        std::string path;
+        file_pool* pool;
+        storage_mode_t mode;
+        std::vector<boost::uint8_t> const* priorities; // optional
+        torrent_info const* info;
+};
+
+struct storage_interface
+{
+        virtual void initialize(storage_error& ec) = 0;
+        virtual int readv(file::iovec_t const* bufs, int num_bufs
+                , int piece, int offset, int flags, storage_error& ec) = 0;
+        virtual int writev(file::iovec_t const* bufs, int num_bufs
+                , int piece, int offset, int flags, storage_error& ec) = 0;
+        virtual bool has_any_file(storage_error& ec) = 0;
+        virtual void move_storage(std::string const& save_path, storage_error& ec) = 0;
+        virtual bool verify_resume_data(lazy_entry const& rd, storage_error& ec) = 0;
+        virtual void write_resume_data(entry& rd, storage_error& ec) const = 0;
+        virtual void release_files(storage_error& ec) = 0;
+        virtual void rename_file(int index, std::string const& new_filenamem, storage_error& ec) = 0;
+        virtual void delete_files(storage_error& ec) = 0;
+        virtual void finalize_file(int, storage_error&);
+        virtual ~storage_interface();
+
+        // non virtual functions
+
+        aux::session_settings const& settings() const { return *m_settings; }
+};
+
+struct storage_error
+{
+        // the actual error code
+        error_code ec;
+
+        // the index of the file the error occurred on
+        int32_t file:24;
+
+        // the operation that failed
+        int32_t operation:8;
+
+        enum {
+                none,
+                stat,
+                mkdir,
+                open,
+                rename,
+                remove,
+                copy,
+                read,
+                write,
+                fallocate,
+                alloc_cache_piece
+        };
+
+        storage_error();
+        operator bool() const;
+
+        // turn the operation ID into a string
+        char const* operation_str() const;
+};
+
+
+

initialize()

+
+
+virtual void initialize(storage_error& ec) = 0;
+
+
+

This function is called when the storage is to be initialized. The default storage +will create directories and empty files at this point.

+

If an error occurs, storage_error should be set to reflect it.

+
+
+

has_any_file()

+
+
+virtual bool has_any_file(storage_error& ec) = 0;
+
+
+

This function is called when first checking (or re-checking) the storage for a torrent. +It should return true if any of the files that is used in this storage exists on disk. +If so, the storage will be checked for existing pieces before starting the download.

+

If an error occurs, storage_error should be set to reflect it.

+
+
+

readv() writev()

+
+
+virtual int readv(file::iovec_t const* bufs, int num_bufs
+        , int piece, int offset, int flags, storage_error& ec) = 0;
+virtual int writev(file::iovec_t const* bufs, int num_bufs
+        , int piece, int offset, int flags, storage_error& ec) = 0;
+
+
+

These functions should read and write the data in or to the given piece at +the given offset. It should read or write num_bufs buffers sequentially, +where the size of each buffer is specified in the buffer array bufs. The +file::iovec_t type has the following members:

+
+struct iovec_t
+{
+        void* iov_base;
+        size_t iov_len;
+};
+
+

These functions may be called simultaneously from multiple threads. Make sure they +are thread safe. The file in libtorrent is thread safe when it can fall back +to pread, preadv or the windows equivalents. On targets where read operations +cannot be thread safe (i.e one has to seek first and then read), only one disk thread +is used.

+

Every buffer in bufs can be assumed to be page aligned and be of a page aligned size, +except for the last buffer of the torrent. The allocated buffer can be assumed to fit a +fully page aligned number of bytes though.

+

The offset is aligned to 16 kiB boundries most of the time, but there are rare +exceptions when it's not. Specifically if the read cache is disabled/or full and a +peer requests unaligned data. Most clients request aligned data.

+

The number of bytes read or written should be returned, or -1 on error. If there's +an error, the storage_error must be filled out to represent the error that occurred.

+
+
+

move_storage()

+
+
+void move_storage(std::string const& save_path, storage_error& ec) = 0;
+
+
+

This function should move all the files belonging to the storage to the new save_path. +The default storage moves the single file or the directory of the torrent.

+

Before moving the files, any open file handles may have to be closed, like +release_files().

+

If an error occurs, storage_error should be set to reflect it.

+
+
+

verify_resume_data()

+
+
+bool verify_resume_data(lazy_entry const& rd, storage_error& error) = 0;
+
+
+

This function should verify the resume data rd with the files +on disk. If the resume data seems to be up-to-date, return true. If +not, set error to a description of what mismatched and return false.

+

The default storage may compare file sizes and time stamps of the files.

+

If an error occurs, storage_error should be set to reflect it.

+
+
+

write_resume_data()

+
+
+bool write_resume_data(entry& rd, storage_error& ec) const = 0;
+
+
+

This function should fill in resume data, the current state of the +storage, in rd. The default storage adds file timestamps and +sizes.

+

Returning true indicates an error occurred.

+

If an error occurs, storage_error should be set to reflect it.

+
+
+

rename_file()

+
+
+void rename_file(int file, std::string const& new_name, storage_error& ec) = 0;
+
+
+

Rename file with index file to the thame new_name.

+

If an error occurs, storage_error should be set to reflect it.

+
+
+

release_files()

+
+
+void release_files(storage_error& ec) = 0;
+
+
+

This function should release all the file handles that it keeps open to files +belonging to this storage. The default implementation just calls +file_pool::release_files(this).

+

If an error occurs, storage_error should be set to reflect it.

+
+
+

delete_files()

+
+
+void delete_files(storage_error& ec) = 0;
+
+
+

This function should delete all files and directories belonging to this storage.

+

If an error occurs, storage_error should be set to reflect it.

+

The disk_buffer_pool is used to allocate and free disk buffers. It has the +following members:

+
+struct disk_buffer_pool : boost::noncopyable
+{
+        char* allocate_buffer(char const* category);
+        void free_buffer(char* buf);
+
+        char* allocate_buffers(int blocks, char const* category);
+        void free_buffers(char* buf, int blocks);
+
+        int block_size() const { return m_block_size; }
+
+        void release_memory();
+};
+
+
+
+

finalize_file()

+
+
+virtual void finalize_file(int index, storage_error& ec);
+
+
+

This function is called each time a file is completely downloaded. The +storage implementation can perform last operations on a file. The file will +not be opened for writing after this.

+

index is the index of the file that completed.

+

On windows the default storage implementation clears the sparse file flag +on the specified file.

+

If an error occurs, storage_error should be set to reflect it.

+
+
+

example

+

This is an example storage implementation that stores all pieces in a std::map, +i.e. in RAM. It's not necessarily very useful in practice, but illustrates the +basics of implementing a custom storage.

+
+struct temp_storage : storage_interface
+{
+        temp_storage(file_storage const& fs) : m_files(fs) {}
+        virtual bool initialize(storage_error& se) { return false; }
+        virtual bool has_any_file() { return false; }
+        virtual int read(char* buf, int slot, int offset, int size)
+        {
+                std::map<int, std::vector<char> >::const_iterator i = m_file_data.find(slot);
+                if (i == m_file_data.end()) return 0;
+                int available = i->second.size() - offset;
+                if (available <= 0) return 0;
+                if (available > size) available = size;
+                memcpy(buf, &i->second[offset], available);
+                return available;
+        }
+        virtual int write(const char* buf, int slot, int offset, int size)
+        {
+                std::vector<char>& data = m_file_data[slot];
+                if (data.size() < offset + size) data.resize(offset + size);
+                std::memcpy(&data[offset], buf, size);
+                return size;
+        }
+        virtual bool rename_file(int file, std::string const& new_name)
+        { assert(false); return false; }
+        virtual bool move_storage(std::string const& save_path) { return false; }
+        virtual bool verify_resume_data(lazy_entry const& rd, storage_error& error) { return false; }
+        virtual bool write_resume_data(entry& rd) const { return false; }
+        virtual size_type physical_offset(int slot, int offset)
+        { return slot * m_files.piece_length() + offset; };
+        virtual sha1_hash hash_for_slot(int slot, partial_hash& ph, int piece_size)
+        {
+                int left = piece_size - ph.offset;
+                assert(left >= 0);
+                if (left > 0)
+                {
+                        std::vector<char>& data = m_file_data[slot];
+                        // if there are padding files, those blocks will be considered
+                        // completed even though they haven't been written to the storage.
+                        // in this case, just extend the piece buffer to its full size
+                        // and fill it with zeroes.
+                        if (data.size() < piece_size) data.resize(piece_size, 0);
+                        ph.h.update(&data[ph.offset], left);
+                }
+                return ph.h.final();
+        }
+        virtual bool release_files() { return false; }
+        virtual bool delete_files() { return false; }
+
+        std::map<int, std::vector<char> > m_file_data;
+        file_storage m_files;
+};
+
+storage_interface* temp_storage_constructor(storage_params const& params)
+{
+        return new temp_storage(*params.files);
+}
+
+
+
+ +
+

queuing

+

libtorrent supports queuing. Which means it makes sure that a limited number of +torrents are being downloaded at any given time, and once a torrent is completely +downloaded, the next in line is started.

+

Torrents that are auto managed are subject to the queuing and the active torrents +limits. To make a torrent auto managed, set auto_managed to true when adding the +torrent (see async_add_torrent() add_torrent()).

+

The limits of the number of downloading and seeding torrents are controlled via +active_downloads, active_seeds and active_limit settings. +These limits takes non auto managed torrents into account as well. If there are +more non-auto managed torrents being downloaded than the active_downloads +setting, any auto managed torrents will be queued until torrents are removed so +that the number drops below the limit.

+

The default values are 8 active downloads and 5 active seeds.

+

At a regular interval, torrents are checked if there needs to be any re-ordering of +which torrents are active and which are queued. This interval can be controlled via +auto_manage_interval setting.

+

For queuing to work, resume data needs to be saved and restored for all torrents. +See save_resume_data().

+
+

downloading

+

Torrents that are currently being downloaded or incomplete (with bytes still to download) +are queued. The torrents in the front of the queue are started to be actively downloaded +and the rest are ordered with regards to their queue position. Any newly added torrent +is placed at the end of the queue. Once a torrent is removed or turns into a seed, its +queue position is -1 and all torrents that used to be after it in the queue, decreases their +position in order to fill the gap.

+

The queue positions are always in a sequence without any gaps.

+

Lower queue position means closer to the front of the queue, and will be started sooner than +torrents with higher queue positions.

+

To query a torrent for its position in the queue, or change its position, see: +queue_position() queue_position_up() queue_position_down() queue_position_top() queue_position_bottom().

+
+
+

seeding

+

Auto managed seeding torrents are rotated, so that all of them are allocated a fair +amount of seeding. Torrents with fewer completed seed cycles are prioritized for +seeding. A seed cycle is completed when a torrent meets either the share ratio limit +(uploaded bytes / downloaded bytes), the share time ratio (time seeding / time +downloaing) or seed time limit (time seeded).

+

The relevant settings to control these limits are share_ratio_limit, +seed_time_ratio_limit and seed_time_limit.

+
+
+
+

fast resume

+

The fast resume mechanism is a way to remember which pieces are downloaded +and where they are put between sessions. You can generate fast resume data by +calling save_resume_data() on torrent_handle. You can +then save this data to disk and use it when resuming the torrent. libtorrent +will not check the piece hashes then, and rely on the information given in the +fast-resume data. The fast-resume data also contains information about which +blocks, in the unfinished pieces, were downloaded, so it will not have to +start from scratch on the partially downloaded pieces.

+

To use the fast-resume data you simply give it to async_add_torrent() add_torrent(), and it +will skip the time consuming checks. It may have to do the checking anyway, if +the fast-resume data is corrupt or doesn't fit the storage for that torrent, +then it will not trust the fast-resume data and just do the checking.

+
+

file format

+

The file format is a bencoded dictionary containing the following fields:

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
file-formatstring: "libtorrent resume file"
file-versioninteger: 1
info-hashstring, the info hash of the torrent this data is saved for.
blocks per pieceinteger, the number of blocks per piece. Must be: piece_size +/ (16 * 1024). Clamped to be within the range [1, 256]. It +is the number of blocks per (normal sized) piece. Usually +each block is 16 * 1024 bytes in size. But if piece size is +greater than 4 megabytes, the block size will increase.
piecesA string with piece flags, one character per piece. +Bit 1 means we have that piece. +Bit 2 means we have verified that this piece is correct. +This only applies when the torrent is in seed_mode.
slots

list of integers. The list maps slots to piece indices. It +tells which piece is on which slot. If piece index is -2 it +means it is free, that there's no piece there. If it is -1, +means the slot isn't allocated on disk yet. The pieces have +to meet the following requirement:

+

If there's a slot at the position of the piece index, +the piece must be located in that slot.

+
total_uploadedinteger. The number of bytes that have been uploaded in +total for this torrent.
total_downloadedinteger. The number of bytes that have been downloaded in +total for this torrent.
active_timeinteger. The number of seconds this torrent has been active. +i.e. not paused.
seeding_timeinteger. The number of seconds this torrent has been active +and seeding.
num_seedsinteger. An estimate of the number of seeds on this torrent +when the resume data was saved. This is scrape data or based +on the peer list if scrape data is unavailable.
num_downloadersinteger. An estimate of the number of downloaders on this +torrent when the resume data was last saved. This is used as +an initial estimate until we acquire up-to-date scrape info.
upload_rate_limitinteger. In case this torrent has a per-torrent upload rate +limit, this is that limit. In bytes per second.
download_rate_limitinteger. The download rate limit for this torrent in case +one is set, in bytes per second.
max_connectionsinteger. The max number of peer connections this torrent +may have, if a limit is set.
max_uploadsinteger. The max number of unchoked peers this torrent may +have, if a limit is set.
seed_modeinteger. 1 if the torrent is in seed mode, 0 otherwise.
file_prioritylist of integers. One entry per file in the torrent. Each +entry is the priority of the file with the same index.
piece_prioritystring of bytes. Each byte is interpreted as an integer and +is the priority of that piece.
auto_managedinteger. 1 if the torrent is auto managed, otherwise 0.
sequential_downloadinteger. 1 if the torrent is in sequential download mode, +0 otherwise.
pausedinteger. 1 if the torrent is paused, 0 otherwise.
trackerslist of lists of strings. The top level list lists all +tracker tiers. Each second level list is one tier of +trackers.
mapped_fileslist of strings. If any file in the torrent has been +renamed, this entry contains a list of all the filenames. +In the same order as in the torrent file.
url-listlist of strings. List of url-seed URLs used by this torrent. +The urls are expected to be properly encoded and not contain +any illegal url characters.
httpseedslist of strings. List of httpseed URLs used by this torrent. +The urls are expected to be properly encoded and not contain +any illegal url characters.
merkle treestring. In case this torrent is a merkle torrent, this is a +string containing the entire merkle tree, all nodes, +including the root and all leaves. The tree is not +necessarily complete, but complete enough to be able to send +any piece that we have, indicated by the have bitmask.
peers

list of dictionaries. Each dictionary has the following +layout:

+ ++++ + + + + + + + + +
ipstring, the ip address of the peer. This is +not a binary representation of the ip +address, but the string representation. It +may be an IPv6 string or an IPv4 string.
portinteger, the listen port of the peer
+

These are the local peers we were connected to when this +fast-resume data was saved.

+
unfinished

list of dictionaries. Each dictionary represents an +piece, and has the following layout:

+ ++++ + + + + + + + + + + + +
pieceinteger, the index of the piece this entry +refers to.
bitmaskstring, a binary bitmask representing the +blocks that have been downloaded in this +piece.
adler32The adler32 checksum of the data in the +blocks specified by bitmask.
+
file sizeslist where each entry corresponds to a file in the file list +in the metadata. Each entry has a list of two values, the +first value is the size of the file in bytes, the second +is the time stamp when the last time someone wrote to it. +This information is used to compare with the files on disk. +All the files must match exactly this information in order +to consider the resume data as current. Otherwise a full +re-check is issued.
allocationThe allocation mode for the storage. Can be either full +or compact. If this is full, the file sizes and +timestamps are disregarded. Pieces are assumed not to have +moved around even if the files have been modified after the +last resume data checkpoint.
+
+
+
+

storage allocation

+

There are two modes in which storage (files on disk) are allocated in libtorrent.

+
    +
  1. The traditional full allocation mode, where the entire files are filled up with +zeros before anything is downloaded. libtorrent will look for sparse files support +in the filesystem that is used for storage, and use sparse files or file system +zero fill support if present. This means that on NTFS, full allocation mode will +only allocate storage for the downloaded pieces.
  2. +
  3. The sparse allocation, sparse files are used, and pieces are downloaded directly +to where they belong. This is the recommended (and default) mode.
  4. +
+

In previous versions of libtorrent, a 3rd mode was supported, compact allocation. +Support for this is deprecated and will be removed in future versions of libtorrent. +It's still described in here for completeness.

+

The allocation mode is selected when a torrent is started. It is passed as an +argument to session::add_torrent() (see async_add_torrent() add_torrent()).

+

The decision to use full allocation or compact allocation typically depends on whether +any files have priority 0 and if the filesystem supports sparse files.

+
+

sparse allocation

+

On filesystems that supports sparse files, this allocation mode will only use +as much space as has been downloaded.

+
+
    +
  • It does not require an allocation pass on startup.
  • +
  • It supports skipping files (setting prioirty to 0 to not download).
  • +
  • Fast resume data will remain valid even when file time stamps are out of date.
  • +
+
+
+
+

full allocation

+

When a torrent is started in full allocation mode, the disk-io thread +will make sure that the entire storage is allocated, and fill any gaps with zeros. +This will be skipped if the filesystem supports sparse files or automatic zero filling. +It will of course still check for existing pieces and fast resume data. The main +drawbacks of this mode are:

+
+
    +
  • It may take longer to start the torrent, since it will need to fill the files +with zeros on some systems. This delay is linearly dependent on the size of +the download.
  • +
  • The download may occupy unnecessary disk space between download sessions. In case +sparse files are not supported.
  • +
  • Disk caches usually perform extremely poorly with random access to large files +and may slow down a download considerably.
  • +
+
+

The benefits of this mode are:

+
+
    +
  • Downloaded pieces are written directly to their final place in the files and the +total number of disk operations will be fewer and may also play nicer to +filesystems' file allocation, and reduce fragmentation.
  • +
  • No risk of a download failing because of a full disk during download. Unless +sparse files are being used.
  • +
  • The fast resume data will be more likely to be usable, regardless of crashes or +out of date data, since pieces won't move around.
  • +
  • Can be used with prioritizing files to 0.
  • +
+
+
+
+

compact allocation

+
+

Note

+

Support for compact allocation is deprecated in libttorrent, and will +be removed in future versions.

+
+

The compact allocation will only allocate as much storage as it needs to keep the +pieces downloaded so far. This means that pieces will be moved around to be placed +at their final position in the files while downloading (to make sure the completed +download has all its pieces in the correct place). So, the main drawbacks are:

+
+
    +
  • More disk operations while downloading since pieces are moved around.
  • +
  • Potentially more fragmentation in the filesystem.
  • +
  • Cannot be used while having files with priority 0.
  • +
+
+

The benefits though, are:

+
+
    +
  • No startup delay, since the files don't need allocating.
  • +
  • The download will not use unnecessary disk space.
  • +
  • Disk caches perform much better than in full allocation and raises the download +speed limit imposed by the disk.
  • +
  • Works well on filesystems that don't support sparse files.
  • +
+
+

The algorithm that is used when allocating pieces and slots isn't very complicated. +For the interested, a description follows.

+

storing a piece:

+
    +
  1. let A be a newly downloaded piece, with index n.
  2. +
  3. let s be the number of slots allocated in the file we're +downloading to. (the number of pieces it has room for).
  4. +
  5. if n >= s then allocate a new slot and put the piece there.
  6. +
  7. if n < s then allocate a new slot, move the data at +slot n to the new slot and put A in slot n.
  8. +
+

allocating a new slot:

+
    +
  1. if there's an unassigned slot (a slot that doesn't +contain any piece), return that slot index.
  2. +
  3. append the new slot at the end of the file (or find an unused slot).
  4. +
  5. let i be the index of newly allocated slot
  6. +
  7. if we have downloaded piece index i already (to slot j) then
      +
    1. move the data at slot j to slot i.
    2. +
    3. return slot index j as the newly allocated free slot.
    4. +
    +
  8. +
  9. return i as the newly allocated slot.
  10. +
+
+
+
+

extensions

+

These extensions all operates within the extension protocol. The +name of the extension is the name used in the extension-list packets, +and the payload is the data in the extended message (not counting the +length-prefix, message-id nor extension-id).

+

Note that since this protocol relies on one of the reserved bits in the +handshake, it may be incompatible with future versions of the mainline +bittorrent client.

+

These are the extensions that are currently implemented.

+
+

metadata from peers

+

Extension name: "LT_metadata"

+
+

Note

+

This extension is deprecated in favor of the more widely supported +ut_metadata extension, see BEP 9.

+
+

The point with this extension is that you don't have to distribute the +metadata (.torrent-file) separately. The metadata can be distributed +through the bittorrent swarm. The only thing you need to download such +a torrent is the tracker url and the info-hash of the torrent.

+

It works by assuming that the initial seeder has the metadata and that +the metadata will propagate through the network as more peers join.

+

There are three kinds of messages in the metadata extension. These packets +are put as payload to the extension message. The three packets are:

+
+
    +
  • request metadata
  • +
  • metadata
  • +
  • don't have metadata
  • +
+
+

request metadata:

+ +++++ + + + + + + + + + + + + + + + + + + + + +
sizenamedescription
uint8_tmsg_typeDetermines the kind of message this is +0 means 'request metadata'
uint8_tstartThe start of the metadata block that +is requested. It is given in 256:ths +of the total size of the metadata, +since the requesting client don't know +the size of the metadata.
uint8_tsizeThe size of the metadata block that is +requested. This is also given in +256:ths of the total size of the +metadata. The size is given as size-1. +That means that if this field is set +0, the request wants one 256:th of the +metadata.
+

metadata:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
sizenamedescription
uint8_tmsg_type1 means 'metadata'
int32_ttotal_sizeThe total size of the metadata, given +in number of bytes.
int32_toffsetThe offset of where the metadata block +in this message belongs in the final +metadata. This is given in bytes.
uint8_t[]metadataThe actual metadata block. The size of +this part is given implicit by the +length prefix in the bittorrent +protocol packet.
+

Don't have metadata:

+ +++++ + + + + + + + + + + + + +
sizenamedescription
uint8_tmsg_type2 means 'I don't have metadata'. +This message is sent as a reply to a +metadata request if the the client +doesn't have any metadata.
+
+
+

dont_have

+

Extension name: "lt_dont_have"

+

The dont_have extension message is used to tell peers that the client no longer +has a specific piece. The extension message should be advertised in the m dictionary +as lt_dont_have. The message format mimics the regular HAVE bittorrent message.

+

Just like all extension messages, the first 2 bytes in the mssage itself are 20 (the +bittorrent extension message) and the message ID assigned to this extension in the m +dictionary in the handshake.

+ +++++ + + + + + + + + + + + + +
sizenamedescription
uint32_tpieceindex of the piece the peer no longer +has.
+

The length of this message (including the extension message prefix) is +6 bytes, i.e. one byte longer than the normal HAVE message, because +of the extension message wrapping.

+
+
+

HTTP seeding

+

There are two kinds of HTTP seeding. One with that assumes a smart +(and polite) client and one that assumes a smart server. These +are specified in BEP 19 and BEP 17 respectively.

+

libtorrent supports both. In the libtorrent source code and API, +BEP 19 urls are typically referred to as url seeds and BEP 17 +urls are typically referred to as HTTP seeds.

+

The libtorrent implementation of BEP 19 assumes that, if the URL ends with a slash +('/'), the filename should be appended to it in order to request pieces from +that file. The way this works is that if the torrent is a single-file torrent, +only that filename is appended. If the torrent is a multi-file torrent, the +torrent's name '/' the file name is appended. This is the same directory +structure that libtorrent will download torrents into.

+
+
+
+

dynamic loading of torrent files

+

libtorrent has a feature that can unload idle torrents from memory. The purpose +of this is to support being active on many more torrents than the RAM permits. +This is useful for both embedded devices that have limited RAM and servers +seeding tens of thousands of torrents.

+

The most significant parts of loaded torrents that use RAM are the piece +hashes (20 bytes per piece) and the file list. The entire info-dictionary +of the .torrent file is kept in RAM.

+

In order to activate the dynamic loading of torrent files, set the load +function on the session. See set_load_function().

+

When a load function is set on the session, the dynamic load/unload +feature is enabled. Torrents are kept in an LRU. Every time an operation +is performed, on a torrent or from a peer, that requires the metadata of +the torrent to be loaded, the torrent is bumped up in the LRU. When a torrent +is paused or queued, it is demoted to the least recently used torrent in +the LRU, since it's a good candidate for eviction.

+

To configure how many torrents are allowed to be loaded at the same time, +set settings_pack::active_loaded_limit on the session.

+

Torrents can be exempt from being unloaded by being pinned. Pinned torrents +still count against the limit, but are never considered for eviction. +You can either pin a torrent when adding it, in add_torrent_params +(see async_add_torrent() add_torrent()), or after ading it with the +set_pinned() function on torrent_handle.

+

Torrents that start out without metadata (e.g. magnet links or http downloads) +are automatically pinned. This is important in order to give the client a +chance to save the metadata to disk once it's received (see metadata_received_alert).

+

Once the metadata is saved to disk, it might make sense to unpin the torrent.

+
+
+

piece picker

+

The piece picker in libtorrent has the following features:

+
    +
  • rarest first
  • +
  • sequential download
  • +
  • random pick
  • +
  • reverse order picking
  • +
  • parole mode
  • +
  • prioritize partial pieces
  • +
  • prefer whole pieces
  • +
  • piece affinity by speed category
  • +
  • piece priorities
  • +
+
+

internal representation

+

It is optimized by, at all times, keeping a list of pieces ordered +by rarity, randomly shuffled within each rarity class. This list +is organized as a single vector of contigous memory in RAM, for +optimal memory locality and to eliminate heap allocations and frees +when updating rarity of pieces.

+

Expensive events, like a peer joining or leaving, are evaluated +lazily, since it's cheaper to rebuild the whole list rather than +updating every single piece in it. This means as long as no blocks +are picked, peers joining and leaving is no more costly than a single +peer joining or leaving. Of course the special cases of peers that have +all or no pieces are optimized to not require rebuilding the list.

+
+
+

picker strategy

+

The normal mode of the picker is of course rarest first, meaning +pieces that few peers have are preferred to be downloaded over pieces +that more peers have. This is a fundamental algorithm that is the +basis of the performance of bittorrent. However, the user may set the +piece picker into sequential download mode. This mode simply picks +pieces sequentially, always preferring lower piece indices.

+

When a torrent starts out, picking the rarest pieces means increased +risk that pieces won't be completed early (since there are only a few +peers they can be downloaded from), leading to a delay of having any +piece to offer to other peers. This lack of pieces to trade, delays +the client from getting started into the normal tit-for-tat mode of +bittorrent, and will result in a long ramp-up time. The heuristic to +mitigate this problem is to, for the first few pieces, pick random pieces +rather than rare pieces. The threshold for when to leave this initial +picker mode is determined by initial_picker_threshold.

+
+
+

reverse order

+

An orthogonal setting is reverse order, which is used for snubbed +peers. Snubbed peers are peers that appear very slow, and might have timed +out a piece request. The idea behind this is to make all snubbed peers +more likely to be able to do download blocks from the same piece, +concentrating slow peers on as few pieces as possible. The reverse order +means that the most common pieces are picked, instead of the rarest pieces +(or in the case of sequential download, the last pieces, intead of the first).

+
+
+

parole mode

+

Peers that have participated in a piece that failed the hash check, may be +put in parole mode. This means we prefer downloading a full piece from this +peer, in order to distinguish which peer is sending corrupt data. Whether to +do this is or not is controlled by use_parole_mode.

+

In parole mode, the piece picker prefers picking one whole piece at a time for +a given peer, avoiding picking any blocks from a piece any other peer has +contributed to (since that would defeat the purpose of parole mode).

+
+
+

prioritize partial pieces

+

This setting determines if partially downloaded or requested pieces should always +be preferred over other pieces. The benefit of doing this is that the number of +partial pieces is minimized (and hence the turn-around time for downloading a block +until it can be uploaded to others is minimized). It also puts less stress on the +disk cache, since fewer partial pieces need to be kept in the cache. Whether or +not to enable this is controlled by prioritize_partial_pieces.

+

The main benefit of not prioritizing partial pieces is that the rarest first +algorithm gets to have more influence on which pieces are picked. The picker is +more likely to truly pick the rarest piece, and hence improving the performance +of the swarm.

+

This setting is turned on automatically whenever the number of partial pieces +in the piece picker exceeds the number of peers we're connected to times 1.5. +This is in order to keep the waste of partial pieces to a minimum, but still +prefer rarest pieces.

+
+
+

prefer whole pieces

+

The prefer whole pieces setting makes the piece picker prefer picking entire +pieces at a time. This is used by web connections (both http seeding +standards), in order to be able to coalesce the small bittorrent requests +to larger HTTP requests. This significantly improves performance when +downloading over HTTP.

+

It is also used by peers that are downloading faster than a certain +threshold. The main advantage is that these peers will better utilize the +other peer's disk cache, by requesting all blocks in a single piece, from +the same peer.

+

This threshold is controlled by the whole_pieces_threshold setting.

+

TODO: piece affinity by speed category +TODO: piece priorities

+
+
+
+

predictive piece announce

+

In order to improve performance, libtorrent supports a feature called +predictive piece announce. When enabled, it will make libtorrent announce +that we have pieces to peers, before we truly have them. The most important +case is to announce a piece as soon as it has been downloaded and passed +the hash check, but not yet been written to disk. In this case, there is +a risk the piece will fail to be written to disk, in which case we won't have +the piece anymore, even though we announced it to peers.

+

The other case is when we're very close to completing the download of a piece +and assume it will pass the hash check, we can announce it to peers to make +it available one round-trip sooner than otherwise. This lets libtorrent start +uploading the piece to interested peers immediately when the piece complete, instead +of waiting one round-trip for the peers to request it.

+

This makes for the implementation slightly more complicated, since piece will have +more states and more complicated transitions. For instance, a piece could be:

+
    +
  1. hashed but not fully written to disk
  2. +
  3. fully written to disk but not hashed
  4. +
  5. not fully downloaded
  6. +
  7. downloaded and hash checked
  8. +
+

Once a piece is fully downloaded, the hash check could complete before any of +the write operations or it could complete after all write operations are complete.

+
+
+

peer classes

+

The peer classes feature in libtorrent allows a client to define custom groups of peers +and rate limit them individually. Each such group is called a peer class. There are a few +default peer classes that are always created:

+
    +
  • global - all peers belong to this class, except peers on the local network
  • +
  • local peers - all peers on the local network belongs to this class
  • +
  • TCP peers - all peers connected over TCP belong to this class
  • +
+

The TCP peers class is used by the uTP/TCP balancing logic, if it's enabled, to throttle TCP +peers. The global and local classes are used to adjust the global rate limits.

+

When the rate limits are adjusted for a specific torrent, a class is created implicitly for +that torrent.

+

The default peer class IDs are defined as enums in the session class:

+
+enum {
+        global_peer_class_id,
+        tcp_peer_class_id,
+        local_peer_class_id
+};
+
+

A peer class can be considered a more general form of lables that some clients have. Peer +classes however are not just applied to torrents, but ultimately the peers.

+

Peer classes can be created with the create_peer_class() call (on the session object), and +deleted with the delete_peer_class() call.

+

Peer classes are configured with the set_peer_class() get_peer_class() calls.

+

Custom peer classes can be assigned to torrents, with the ??? call, in which case all its +peers will belong to the class. They can also be assigned based on the peer's IP address. +See set_peer_class_filter() for more information.

+
+
+

SSL torrents

+

Torrents may have an SSL root (CA) certificate embedded in them. Such torrents +are called SSL torrents. An SSL torrent talks to all bittorrent peers over SSL. +The protocols are layered like this:

+
++-----------------------+
+| BitTorrent protocol   |
++-----------------------+
+| SSL                   |
++-----------+-----------+
+| TCP       | uTP       |
+|           +-----------+
+|           | UDP       |
++-----------+-----------+
+
+

During the SSL handshake, both peers need to authenticate by providing a certificate +that is signed by the CA certificate found in the .torrent file. These peer +certificates are expected to be privided to peers through some other means than +bittorrent. Typically by a peer generating a certificate request which is sent to +the publisher of the torrent, and the publisher returning a signed certificate.

+

In libtorrent, set_ssl_certificate() in torrent_handle is used to tell libtorrent where +to find the peer certificate and the private key for it. When an SSL torrent is loaded, +the torrent_need_cert_alert is posted to remind the user to provide a certificate.

+

A peer connecting to an SSL torrent MUST provide the SNI TLS extension (server name +indication). The server name is the hex encoded info-hash of the torrent to connect to. +This is required for the client accepting the connection to know which certificate to +present.

+

SSL connections are accepted on a separate socket from normal bittorrent connections. To +pick which port the SSL socket should bind to, set ssl_listen to a +different port. It defaults to port 4433. This setting is only taken into account when the +normal listen socket is opened (i.e. just changing this setting won't necessarily close +and re-open the SSL socket). To not listen on an SSL socket at all, set ssl_listen to 0.

+

This feature is only available if libtorrent is build with openssl support (TORRENT_USE_OPENSSL) +and requires at least openSSL version 1.0, since it needs SNI support.

+

Peer certificates must have at least one SubjectAltName field of type dNSName. At least +one of the fields must exactly match the name of the torrent. This is a byte-by-byte comparison, +the UTF-8 encoding must be identical (i.e. there's no unicode normalization going on). This is +the recommended way of verifying certificates for HTTPS servers according to RFC 2818. Note +the difference that for torrents only dNSName fields are taken into account (not IP address fields). +The most specific (i.e. last) Common Name field is also taken into account if no SubjectAltName +did not match.

+

If any of these fields contain a single asterisk ("*"), the certificate is considered covering +any torrent, allowing it to be reused for any torrent.

+

The purpose of matching the torrent name with the fields in the peer certificate is to allow +a publisher to have a single root certificate for all torrents it distributes, and issue +separate peer certificates for each torrent. A peer receiving a certificate will not necessarily +be able to access all torrents published by this root certificate (only if it has a "star cert").

+
+

testing

+

To test incoming SSL connections to an SSL torrent, one can use the following openssl command:

+
+openssl s_client -cert <peer-certificate>.pem -key <peer-private-key>.pem -CAfile <torrent-cert>.pem -debug -connect 127.0.0.1:4433 -tls1 -servername <info-hash>
+
+

To create a root certificate, the Distinguished Name (DN) is not taken into account +by bittorrent peers. You still need to specify something, but from libtorrent's point of +view, it doesn't matter what it is. libtorrent only makes sure the peer certificates are +signed by the correct root certificate.

+

One way to create the certificates is to use the CA.sh script that comes with openssl, like thisi (don't forget to enter a common Name for the certificate):

+
+CA.sh -newca
+CA.sh -newreq
+CA.sh -sign
+
+

The torrent certificate is located in ./demoCA/private/demoCA/cacert.pem, this is +the pem file to include in the .torrent file.

+

The peer's certificate is located in ./newcert.pem and the certificate's +private key in ./newkey.pem.

+
+
+<<<<<<< .working +
+

session statistics

+

libtorrent provides a mechanism to query performance and statistics counters from its +internals. This is primarily useful for troubleshooting of production systems and performance +tuning.

+

The statistics consists of two fundamental types. counters and gauges. A counter is a +monotonically increasing value, incremented every time some event occurs. For example, +every time the network thread wakes up because a socket became readable will increment a +counter. Another example is every time a socket receives n bytes, a counter is incremented +by n.

+

Counters are the most flexible of metrics. It allows the program to sample the counter at +any interval, and calculate average rates of increments to the counter. Some events may be +rare and need to be sampled over a longer period in order to get userful rates, where other +events may be more frequent and evenly distributed that sampling it frequently yields useful +values. Counters also provides accurate overall counts. For example, converting samples of +a download rate into a total transfer count is not accurate and takes more samples. Converting +an increasing counter into a rate is easy and flexible.

+

Gauges measure the instantaneous state of some kind. This is used for metrics that are not +counting events or flows, but states that can fluctuate. For example, the number of torrents +that are currenly being downloaded.

+

It's important to know whether a value is a counter or a gauge in order to interpret it correctly. +In order to query libtorrent for which counters and gauges are available, call +session_stats_metrics(). This will return metadata about the values available for inspection +in libtorrent. It will include whether a value is a counter or a gauge. The key information +it includes is the index used to extract the actual measurements for a specific counter or +gauge.

+

In order to take a sample, call post_session_stats() in the session object. This will result +in a session_stats_alert being posted. In this alert object, there is an array of values, +these values make up the sample. The value index in the stats metric indicates which index the +metric's value is stored in.

+

The mapping between metric and value is not stable across versions of libtorrent. Always query +the metrics first, to find out the index at which the value is stored, before interpreting the +values array in the session_stats_alert. The mapping will not change during the runtime of +your process though, it's tied to a specific libtorrent version. You only have to query the +mapping once on startup (or every time libtorrent.so is loaded, if it's done dynamically).

+

The available stats metrics are:

+ ++++ + + + + + + + + + + + + + +
nametype
peer.error_peerscounter
peer.disconnected_peerscounter
+

error_peers is the total number of peer disconnects +caused by an error (not initiated by this client) and +disconnected initiated by this client (disconnected_peers).

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
nametype
peer.eof_peerscounter
peer.connreset_peerscounter
peer.connrefused_peerscounter
peer.connaborted_peerscounter
peer.perm_peerscounter
peer.buffer_peerscounter
peer.unreachable_peerscounter
peer.broken_pipe_peerscounter
peer.addrinuse_peerscounter
peer.no_access_peerscounter
peer.invalid_arg_peerscounter
peer.aborted_peerscounter
+

these counters break down the peer errors into more specific +categories. These errors are what the underlying transport +reported (i.e. TCP or uTP)

+ ++++ + + + + + + + + + + + + + +
nametype
peer.error_incoming_peerscounter
peer.error_outgoing_peerscounter
+

these counters break down the peer errors into +whether they happen on incoming or outgoing peers.

+ ++++ + + + + + + + + + + + + + +
nametype
peer.error_rc4_peerscounter
peer.error_encrypted_peerscounter
+

these counters break down the peer errors into +whether they happen on encrypted peers (just +encrypted handshake) and rc4 peers (full stream +encryption). These can indicate whether encrypted +peers are more or less likely to fail

+ ++++ + + + + + + + + + + + + + +
nametype
peer.error_tcp_peerscounter
peer.error_utp_peerscounter
+

these counters break down the peer errors into +whether they happen on uTP peers or TCP peers. +these may indicate whether one protocol is +more error prone

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
nametype
peer.connect_timeoutscounter
peer.uninteresting_peerscounter
peer.timeout_peerscounter
peer.no_memory_peerscounter
peer.too_many_peerscounter
peer.transport_timeout_peerscounter
peer.num_banned_peerscounter
peer.connection_attemptscounter
peer.banned_for_hash_failurecounter
+

these counters break down the reasons to +disconnect peers.

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
nametype
peer.num_tcp_peersgauge
peer.num_socks5_peersgauge
peer.num_http_proxy_peersgauge
peer.num_utp_peersgauge
peer.num_i2p_peersgauge
peer.num_ssl_peersgauge
peer.num_ssl_socks5_peersgauge
peer.num_ssl_http_proxy_peersgauge
peer.num_ssl_utp_peersgauge
+

the number of peer connections for each kind of socket. +these counts include half-open (connecting) peers.

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
nametype
net.on_read_countercounter
net.on_write_countercounter
net.on_tick_countercounter
net.on_lsd_countercounter
net.on_lsd_peer_countercounter
net.on_udp_countercounter
net.on_accept_countercounter
net.on_disk_countercounter
+

These counters count the number of times the +network thread wakes up for each respective +reason. If these counters are very large, it +may indicate a performance issue, causing the +network thread to wake up too ofte, wasting CPU. +mitigate it by increasing buffers and limits +for the specific trigger that wakes up the +thread.

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
nametype
ses.num_checking_torrentsgauge
ses.num_stopped_torrentsgauge
ses.num_upload_only_torrentsgauge
ses.num_downloading_torrentsgauge
ses.num_seeding_torrentsgauge
ses.num_queued_seeding_torrentsgauge
ses.num_queued_download_torrentsgauge
ses.num_error_torrentsgauge
+

these gauges count the number of torrents in +different states. Each torrent only belongs to +one of these states. For torrents that could +belong to multiple of these, the most prominent +in picked. For instance, a torrent with an error +counts as an error-torrent, regardless of its other +state.

+ ++++ + + + + + + + + + + +
nametype
ses.torrent_evicted_countercounter
+

this counts the number of times a torrent has been +evicted (only applies when dynamic loading of torrent files +is enabled).

+ ++++ + + + + + + + + + + +
nametype
picker.piece_pickscounter
+

counts the number of times the piece picker has been invoked

+ ++++ + + + + + + + + + + +
nametype
picker.piece_picker_loopscounter
+

the number of pieces considered while picking pieces

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
nametype
picker.end_game_piece_picker_blockscounter
picker.piece_picker_blockscounter
picker.reject_piece_pickscounter
picker.unchoke_piece_pickscounter
picker.incoming_redundant_piece_pickscounter
picker.incoming_piece_pickscounter
picker.end_game_piece_pickscounter
picker.snubbed_piece_pickscounter
+

This breaks down the piece picks into the event that +triggered it

+
+======= +
+

Docutils System Messages

+
+

System Message: ERROR/3 (manual.rst, line 1901); backlink

+Unknown target name: "move_storage".
+
+>>>>>>> .merge-right.r8585 +
+ +
+ + +
+ + diff --git a/docs/manual.rst b/docs/manual.rst index 24418334e..2cbc549eb 100644 --- a/docs/manual.rst +++ b/docs/manual.rst @@ -3,7 +3,7 @@ libtorrent API Documentation ============================ :Author: Arvid Norberg, arvid@rasterbar.com -:Version: 1.0.0 +:Version: 1.1.0 .. contents:: Table of contents :depth: 1 @@ -177,22 +177,19 @@ torrents are being downloaded at any given time, and once a torrent is completel downloaded, the next in line is started. Torrents that are *auto managed* are subject to the queuing and the active -torrents limits. To make a torrent auto managed, set ``auto_managed`` to true +torrents limits. To make a torrent auto managed, set add_torrent_params::flag_auto_managed when adding the torrent (see async_add_torrent() and add_torrent()). The limits of the number of downloading and seeding torrents are controlled via -``active_downloads``, ``active_seeds`` and ``active_limit`` in -session_settings. These limits takes non auto managed torrents into account as +settings_pack::active_downloads, settings_pack::active_seeds and settings_pack::active_limit in +settings_pack. These limits takes non auto managed torrents into account as well. If there are more non-auto managed torrents being downloaded than the -``active_downloads`` setting, any auto managed torrents will be queued until +settings_pack::active_downloads setting, any auto managed torrents will be queued until torrents are removed so that the number drops below the limit. -The default values are 8 active downloads and 5 active seeds. - At a regular interval, torrents are checked if there needs to be any re-ordering of which torrents are active and which are queued. This interval -can be controlled via ``auto_manage_interval`` in session_settings. It defaults -to every 30 seconds. +can be controlled via settings_pack::auto_manage_interval. For queuing to work, resume data needs to be saved and restored for all torrents. See save_resume_data(). @@ -226,9 +223,9 @@ prioritized for seeding. A seed cycle is completed when a torrent meets either the share ratio limit (uploaded bytes / downloaded bytes), the share time ratio (time seeding / time downloaing) or seed time limit (time seeded). -The relevant settings to control these limits are ``share_ratio_limit``, -``seed_time_ratio_limit`` and ``seed_time_limit`` in session_settings. - +The relevant settings to control these limits are +settings_pack::share_ratio_limit, settings_pack::seed_time_ratio_limit and +settings_pack::seed_time_limit. fast resume =========== @@ -469,8 +466,7 @@ compact allocation ------------------ .. note:: - Note that support for compact allocation is deprecated in libttorrent, and will - be removed in future versions. + Support for compact allocation has been removed from libttorrent The compact allocation will only allocate as much storage as it needs to keep the pieces downloaded so far. This means that pieces will be moved around to be @@ -541,12 +537,14 @@ metadata from peers Extension name: "LT_metadata" -This extension is deprecated in favor of the more widely supported -``ut_metadata`` extension, see `BEP 9`_. The point with this extension is that -you don't have to distribute the metadata (.torrent-file) separately. The -metadata can be distributed through the bittorrent swarm. The only thing you -need to download such a torrent is the tracker url and the info-hash of the -torrent. +.. note:: + This extension is deprecated in favor of the more widely supported + ``ut_metadata`` extension, see `BEP 9`_. + +The point with this extension is that you don't have to distribute the +metadata (.torrent-file) separately. The metadata can be distributed +through the bittorrent swarm. The only thing you need to download such +a torrent is the tracker url and the info-hash of the torrent. It works by assuming that the initial seeder has the metadata and that the metadata will propagate through the network as more peers join. @@ -660,6 +658,43 @@ directory structure that libtorrent will download torrents into. .. _`BEP 17`: http://bittorrent.org/beps/bep_0017.html .. _`BEP 19`: http://bittorrent.org/beps/bep_0019.html +dynamic loading of torrent files +================================ + +libtorrent has a feature that can unload idle torrents from memory. The purpose +of this is to support being active on many more torrents than the RAM permits. +This is useful for both embedded devices that have limited RAM and servers +seeding tens of thousands of torrents. + +The most significant parts of loaded torrents that use RAM are the piece +hashes (20 bytes per piece) and the file list. The entire info-dictionary +of the .torrent file is kept in RAM. + +In order to activate the dynamic loading of torrent files, set the load +function on the session. See set_load_function(). + +When a load function is set on the session, the dynamic load/unload +feature is enabled. Torrents are kept in an LRU. Every time an operation +is performed, on a torrent or from a peer, that requires the metadata of +the torrent to be loaded, the torrent is bumped up in the LRU. When a torrent +is paused or queued, it is demoted to the least recently used torrent in +the LRU, since it's a good candidate for eviction. + +To configure how many torrents are allowed to be loaded at the same time, +set settings_pack::active_loaded_limit on the session. + +Torrents can be exempt from being unloaded by being *pinned*. Pinned torrents +still count against the limit, but are never considered for eviction. +You can either pin a torrent when adding it, in ``add_torrent_params`` +(see async_add_torrent() and add_torrent()), or after ading it with the +set_pinned() function on torrent_handle. + +Torrents that start out without metadata (e.g. magnet links or http downloads) +are automatically pinned. This is important in order to give the client a +chance to save the metadata to disk once it's received (see metadata_received_alert). + +Once the metadata is saved to disk, it might make sense to unpin the torrent. + piece picker ============ @@ -708,7 +743,7 @@ into the normal tit-for-tat mode of bittorrent, and will result in a long ramp-up time. The heuristic to mitigate this problem is to, for the first few pieces, pick random pieces rather than rare pieces. The threshold for when to leave this initial picker mode is determined by -session_settings::initial_picker_threshold. +settings_pack::initial_picker_threshold. reverse order ------------- @@ -727,7 +762,7 @@ parole mode Peers that have participated in a piece that failed the hash check, may be put in *parole mode*. This means we prefer downloading a full piece from this peer, in order to distinguish which peer is sending corrupt data. Whether to do -this is or not is controlled by session_settings::use_parole_mode. +this is or not is controlled by settings_pack::use_parole_mode. In parole mode, the piece picker prefers picking one whole piece at a time for a given peer, avoiding picking any blocks from a piece any other peer has @@ -742,7 +777,7 @@ number of partial pieces is minimized (and hence the turn-around time for downloading a block until it can be uploaded to others is minimized). It also puts less stress on the disk cache, since fewer partial pieces need to be kept in the cache. Whether or not to enable this is controlled by -session_settings::prioritize_partial_pieces. +setting_pack::prioritize_partial_pieces. The main benefit of not prioritizing partial pieces is that the rarest first algorithm gets to have more influence on which pieces are picked. The picker is @@ -767,11 +802,81 @@ It is also used by peers that are downloading faster than a certain threshold. The main advantage is that these peers will better utilize the other peer's disk cache, by requesting all blocks in a single piece, from the same peer. -This threshold is controlled by session_settings::whole_pieces_threshold. +This threshold is controlled by the settings_pack::whole_pieces_threshold +setting. *TODO: piece affinity by speed category* *TODO: piece priorities* +predictive piece announce +========================= + +In order to improve performance, libtorrent supports a feature called +``predictive piece announce``. When enabled, it will make libtorrent announce +that we have pieces to peers, before we truly have them. The most important +case is to announce a piece as soon as it has been downloaded and passed the +hash check, but not yet been written to disk. In this case, there is a risk the +piece will fail to be written to disk, in which case we won't have the piece +anymore, even though we announced it to peers. + +The other case is when we're very close to completing the download of a piece +and assume it will pass the hash check, we can announce it to peers to make it +available one round-trip sooner than otherwise. This lets libtorrent start +uploading the piece to interested peers immediately when the piece complete, +instead of waiting one round-trip for the peers to request it. + +This makes for the implementation slightly more complicated, since piece will +have more states and more complicated transitions. For instance, a piece could +be: + +1. hashed but not fully written to disk +2. fully written to disk but not hashed +3. not fully downloaded +4. downloaded and hash checked + +Once a piece is fully downloaded, the hash check could complete before any of +the write operations or it could complete after all write operations are +complete. + +peer classes +============ + +The peer classes feature in libtorrent allows a client to define custom groups +of peers and rate limit them individually. Each such group is called a *peer +class*. There are a few default peer classes that are always created: + +* global - all peers belong to this class, except peers on the local network +* local peers - all peers on the local network belongs to this class TCP peers +* - all peers connected over TCP belong to this class + +The TCP peers class is used by the uTP/TCP balancing logic, if it's enabled, to +throttle TCP peers. The global and local classes are used to adjust the global +rate limits. + +When the rate limits are adjusted for a specific torrent, a class is created +implicitly for that torrent. + +The default peer class IDs are defined as enums in the ``session`` class:: + + enum { + global_peer_class_id, + tcp_peer_class_id, + local_peer_class_id + }; + +A peer class can be considered a more general form of *lables* that some +clients have. Peer classes however are not just applied to torrents, but +ultimately the peers. + +Peer classes can be created with the create_peer_class() call (on the session +object), and deleted with the delete_peer_class() call. + +Peer classes are configured with the set_peer_class() get_peer_class() calls. + +Custom peer classes can be assigned to torrents, with the ??? call, in which +case all its peers will belong to the class. They can also be assigned based on +the peer's IP address. See set_peer_class_filter() for more information. + SSL torrents ============ @@ -808,7 +913,7 @@ to know which certificate to present. SSL connections are accepted on a separate socket from normal bittorrent connections. To pick which port the SSL socket should bind to, set -session_settings::ssl_listen to a different port. It defaults to port 4433. +settings_pack::ssl_listen to a different port. It defaults to port 4433. This setting is only taken into account when the normal listen socket is opened (i.e. just changing this setting won't necessarily close and re-open the SSL socket). To not listen on an SSL socket at all, set ``ssl_listen`` to 0. @@ -865,3 +970,53 @@ this is the pem file to include in the .torrent file. The peer's certificate is located in ``./newcert.pem`` and the certificate's private key in ``./newkey.pem``. +session statistics +================== + +libtorrent provides a mechanism to query performance and statistics counters +from its internals. This is primarily useful for troubleshooting of production +systems and performance tuning. + +The statistics consists of two fundamental types. *counters* and *gauges*. A +counter is a monotonically increasing value, incremented every time some event +occurs. For example, every time the network thread wakes up because a socket +became readable will increment a counter. Another example is every time a +socket receives *n* bytes, a counter is incremented by *n*. + +*Counters* are the most flexible of metrics. It allows the program to sample +the counter at any interval, and calculate average rates of increments to the +counter. Some events may be rare and need to be sampled over a longer period in +order to get userful rates, where other events may be more frequent and evenly +distributed that sampling it frequently yields useful values. Counters also +provides accurate overall counts. For example, converting samples of a download +rate into a total transfer count is not accurate and takes more samples. +Converting an increasing counter into a rate is easy and flexible. + +*Gauges* measure the instantaneous state of some kind. This is used for metrics +that are not counting events or flows, but states that can fluctuate. For +example, the number of torrents that are currenly being downloaded. + +It's important to know whether a value is a counter or a gauge in order to +interpret it correctly. In order to query libtorrent for which counters and +gauges are available, call session_stats_metrics(). This will return metadata +about the values available for inspection in libtorrent. It will include +whether a value is a counter or a gauge. The key information it includes is the +index used to extract the actual measurements for a specific counter or gauge. + +In order to take a sample, call post_session_stats() in the session object. +This will result in a session_stats_alert being posted. In this alert object, +there is an array of values, these values make up the sample. The value index +in the stats metric indicates which index the metric's value is stored in. + +The mapping between metric and value is not stable across versions of +libtorrent. Always query the metrics first, to find out the index at which the +value is stored, before interpreting the values array in the +session_stats_alert. The mapping will *not* change during the runtime of your +process though, it's tied to a specific libtorrent version. You only have to +query the mapping once on startup (or every time ``libtorrent.so`` is loaded, +if it's done dynamically). + +The available stats metrics are: + +.. include:: stats_counters.rst + diff --git a/docs/read_disk_buffers.png b/docs/read_disk_buffers.png index 119cb97f272ac630311112fc6e625483dfc7a4fb..4b146b0ef1eb97f2a46bb655e5e0c789b5731290 100644 GIT binary patch literal 14183 zcmch8bySt@wl9i`0m1?mBm|LA8j&uAr6?&<(g+AhFS)GD7ZF0Dot*xSElmnULnA|bfqWuu6}dEQt0X;D#=pAKZ0y~1!366ZYo3W- zyIkzhqkP4|hT4#^n^Uow#p9gAd2mg1jDYnREMrdQ6cUY`H!qVi4bwb|L(zFa*yTib)j_-0MUD3pAL+WWx3z+Cim1i0-& zTlkyGYC_jZ`3}oZ4$6D{&M#tfacQ9Qh1{p3vm1Snaj7Us(WkihV6i_tjpX9bk4}8w zN#j<|+oDG7cg)Sr<+-nBh|JC+FWE6vw91sw3-bjFf4F{Ktv%L%o^iJLr=OX*`P9@@ zOAP8TLa>({Ok z#U6f0wTj-p7rBlm%RbsmIoi|H)upk7vtKvckawUoLGYNGaq z5g9!@J3B3{-pcRNwY9adu&|34FTQ{OK0atB+EyHOT<1p;!Dqkk%~FiT z_?FYnVds2>G6x0}h^&LYmzF9gzS??={?4d$>lUIVlvyxMc4eeI#)C>UluTH1F)7Aj ze#2s>)gB-6%-WqO)Q!; zw>EM}$AU02xP~S!xWr{=v)sFvkd@Ur@ILe=F788<$>(}P%mjU8FVbX1zTB@u4UN}$ z3knGAo5&}ue5O~No|@XKzvus&`BH{d-iXvzly79x(d8r_C-XN9Z$^G76@Cg1o|>6S z>egK;KRMptxB0B1tjudVEsBkeohBO*9dV8k@5n{ygg*4OJn1wVH9b!Buw0dou_GLLqIt3Y-6t95#7P zP0iTY*qu9f+W1d+V(L#Yo;q&m84VRw9EWmS{%8nN>5gbWK00V>Y`kMLdsosI%Y?RD z=aDx)BcIjkXuv%o%^nKOhi_**8r(F8=;-rsJbT_|CEV=$PEM{Uzlnn8X3;UBiwC+i_wEV4zJS=;+%zVZQ&JK|1_T7?-?Y8K$jci=|2Zf~ zG?eKHm%`XdNZTINDCFeyG?h}~tK{9M-cQ2m>NT7F`HwjeQGE8{69r8njChg|+eoe$ zMJQwqS#Z!E-+uh?qyPH#YvV|*dK?w4yE4vw8k%tq@$_C1f34kAI5G}D+WYs(;u`{~ zy?Y2x=vkhkN;9ulE}TPJtV zlR6#-Ocd;G%*;$r3k&HTX6hOSuU>QTej!i$rF!quUzsY!%G@s%$z zrwN4IqKnuu+rw)&FEZKf8k2V}Y?)Nz85kcQT9dDDZLt>iaL{@`@LE{P8#GK9(c<8N zJ6`Bb4=j-+ap(Jf^0A~$q@Pp$$KBgT4GlVXanrmVMx^BF#P`jzLtVa^1%~D1HpX#~KHOhOM^4Vp-luauNy^CgE-EhG(CbZ=7er=j zwG#`#RwgI)4AHr+Xw4SjQzAvpgS;5C#HLwduigaj$V3Q&ZD>@v4qTGNC%bvIG77FUm<{UOfI2 z?e?5PK183^KKqzm3(!%Hk8V^)bw)*hjO=`>$IZ0XmrS5ot>4P{n(GP zjLLj7xpiGfw0O6ENtKnAK@XbyL@Dx+g=KH>^?_Wv^9@7|F%HgSIUDhYrmJ=3Dq3b!pC>a;dK~0SH7!Mn-X4UUlTJP%@}bR)qSPqY{3ri<@&@cJVj|HHk*$ zN1r{YB%+&>iVF+VUS~Rcl3SJd-YUQJlRdAVtLNVobB%4@8XFx8~u51J0)kj zlU}|2;=(B`KZd@8y?Lvl%uZH8q45VH%eiyspk)x(LDBUm=6sdZ6~$wn zm7M$zcKgYbCzZ6V`UjSPz@UM5bab?~wnE9Pj2s*oaKk3$v)$I`?D#aAEc7M;mo?)y zixy4?yf)u#?iJ^sq6C19(6_du16AdQ#ci<&-BMa!J`7oe(kIu=wb__%x#ThyD8Y9( z{$1DChLLAlaq(z=B-~$RQ_Q)BA;lps-sSZ`>rrwg^mX&N3c!ny5Tcy?-o13y^44@; zKR+rsp7_d2fzQFK(O452_=)#_u(RPS6kDV*b*16UU_BV7pnKX zR&kv|!2`K~yCprn9o#h+c5gVZf56S{(sKR`X`bP*E)zjkc6PtwOFcckmVXHHq2%*_ z6|l|kVF0=ONw|CZ`>n^TJ%fXT?e<~e_zX(lx^;(#hqYV6WfT=@X`DUS3tIGXH*bg?=J0FR$gWxtyI{N#V@&S>fM1EBu;*pA0`w?=u7Z7?QfZQjej< ztQANhjQ`eTb+qE+M;r->wx`~=x_f$B!&vG77)><>PwQ4G4SNy9B;CUPA~&4t8?$bQ zB0xBw*Ws*X3d!JfX&0q;7$D&?d%o)@phz}@N0_=CqUx}=z3p(c)q4;fJTALGpqKtG zcL1W|h1QeVgnjQc`)c7$jh-cm)ZYkK6h$&CE96qhsuUav7H`ay&UM3*z3T+rDeRsQKGw zfu1~^S;I{sO)I!cg^)_Lu;%q*UFu*=pSV%a&gnv;q{>K@i!JCG7?zvESk^5wdtxt= z45zi+n!#m?rQj>&JKmkB@Imr5le&eM<#)ojySlpCu9UJvRC01!J2_C*Y-gh23qC)G z&(^;SfzTci_Dhvi6!2v~0B#=J?ZwT_P21(dnOlZ4QBhI1Z{LO#?YUk}g<0X(`nB&R zz2n~j`i(UB4uw)1b7|=t2q<)kU%psbS^3=I-477D!^|0AZfyMWs8$Ij_d}_nv2lH4 zqf0k;XT;Ib-kui$uouwPr3)80U}?6tjIT3dGczUCZGU^>QO74F*xTEeAMfkEu>mS; zVv?1TqWAJ;JNHUSLaYMXghyVq^B@wZW5<>s0Ymq>gyT!3;pVg{NM z_+a|_dtS_2w{Q3N^>H{HT9p)%E><#@A3}RsA9E)a4|_;SMm8Fz9UX8?g6ka=HA;nn zSvwOXtOlGEd4q_EmdCp-l5?X|gz>|N4CRE1pO8xj8g*f9@L<4E$xwl;PGOh_3- z8OFzv>D{#n-CQneYWLFk!9HIib~7$z9UUEc`P;VWWW^y2R8+N1O({Qq{76glJsN6* zaG}hbxz^IsGF)uQh!6{V_GwPp4P5aWCFQ=5|0MI@#(9FDg&|He#K3tw1T*7jW@wOf6!lCI7F zEYEhwutQ;@{O#9J?hC+4@|yqRBspd`Q6NBE(jO}5XlU@{U+W(MOe4X!H_3mzhCTM# zBs?VxZ)nJ=VmA`_*;{@*_-c#1mF^Tdh{&DA{;FGh&L*a&(AprmJZ6LCRK~EMv=-8p zS3MDO_Y=RZ7$%b8sUazes6*M5s>I6!??6Z@E0c&wh>6`a=<4nc{`4seV1(y5w5=Db z#cXHPLOcg6VqY&;VQy(p0P7Mu!rI1Wos$PL3Td{#`Oa^GLA6X2+3D}UaXUWj{APd- z!y7~Wj;^kIN&XLem!3yXq!vU+N9*QE9e8+UheJEiZXyt3k^G8WE?#7vjllJQmT0z5jqy86{$yl8N}g6GP-dJ*-9%+8r*Bbz00;zHY5T$!^znp|J5{*LHA%3|Yja64 zcbL_yp?84>aIdKKDN=%Gt-3Xen^C!lVr|p-_3PK>=D8JW&g)cHZ8mdD3C z-=xXpB_*gs>E;4G+1QcvAz&6TqYVlP=?$ip`;sD%`08k({CHusJPT`7_LY>>1~i=) z`mr54fXnNAZnHQX?w%a4oP1tRXg24@ohit`!!y^pRm_bTy97(UeDx~O2?Jf-hbpuk zrr{z1)|XAbNq?sl`Rqf)-fAVIrS-J|@f(n2Uq{FLrA&KAgA;K_R@5{>9uC2JN3ela!z`{rV;eoSN5s9_YEWg2Di-=oY<#rU`zj z$_nU32S=#Kzo^52+Y#nP$C{V%1=G`7k~Z4fGg)t&l$4e6>0TR;S6?Z6X*%eA9`8fT zwyvIDw$*Mt=J3$arDJ|`Zy7pRo|{?X%4Y@yW@l$dI!}J7 z=efXHZ#UB2*{N=VFCihJ@4s|lX?uKeZF*I35)wYV`Ezdw-q5(y`+ThyLh^7{y`B%( z@Xw>84-O8tESYRfi;4!?(HEu;KhCMHCi2=op3J# zNXPxI806rMn>P_1AP#-VM|uxA94sM&Vq)5EMMOl5*sk#UO@I{j3<)$3|E8{?t$3Dw zHr_f&Vbn2|3{t{^EEi|FO?Nn$fv(X6(?E~%W&hn*Vx8TLjXPl|G)^2cdz;R}lDrbD z5C0uVJXF0jyB@)C+_5X?Q&=qh@es+~y1vg6I&?k-2KM`7zt;cJC~h2ve(5*h5$Fb< zIuN>!CkG=>^*$6Ul~@HhIQj<~GrVCK_-(;4JNqRrfklgU1*L9u{&+gVsJ=7!D|83E zfTX0EJcB`x*}hDTc&ueW34MKi+1hPccb#8&dU_&r?Dp2Zyu94p+%`AOw!gclDdyu! z%F5nCTuF}%A5jy}ex~MHaA{!_Kg!1n<_3;-{~} z3k`+(IT^}}S+X`k3w3{oY_oUjN|@XlDxjSxcK2Ey2P(vpQXtf{VwTOW3q!BK|V4Vc$D|V+M09l zYf+I+?+<0<9X@yIi z4TUDw)`ia!P-bRB8E>k9YdrHMl#!Ly%A~n0rQ*bgrxKzj`<{dD%?(vO65_X?Of-2TIdU zPrjw48SJi(!RfK7)L2-<<}<345Ri~8EiOXJwa9{4>*7&%uw0xd5+aPGJ#7cn7$BMF zJq#J;Fg(~^a&T|}S`8XCCN_3kLql?Es%FaU&K>*Bj`sGKmX)t8Tr zOlxlbovGM8!N9(QdF9I5=B84Pj--LXQY5D-4-b#*52bg8whslZcH`pW;2;C3#6GbE zjJAuO+ehaQF*Y?-%+cup#MeB$dH<|c7;Cknym?&!hNS){*{GxZC$bUehU!=^e&E0| zW}8pngA1+W2c&G~=L zT?sU!F;(mu6tV4?{`2S0U%Y_0k32;lQN**eKK+fhkxuS%7tl79O-(nvZqlA+2?z&+ zhQ(s)BAb#&7wz$14};!20tEqb>SRvB@sm}^z5dsxAONo7K__W*JnpiRF&cSz0lnp` zf}d~3Y7sgbn(ZX4@VD&>I1y+B(t!APZQCry{u^s>63AO$WCd^JXFCE9FX2^kj)DOk z1Ufd4+LAHajY_3clSeHBP*h=25x}m*jEwe468S@kHHKU0np?P_`*nb*3UKem?4zchV^!{Y zkHYKj*y_h#D|)N*C>$P8l40d(%}~}+!;t6fscoJ57RpT@f%s~9? zb4porrg5RHooo*p6&DwO@B~rdWVeo=L8(A3?c3Q)7CYL*MJ8uv^d1pTnE5^njEUjj zT|0ZGFFlSmq`sx)rgD%Pzz>k_F!=ABJ#)b7I*rT9%9d7wW7X}5j8nJK7Jn9bXHsQGAS3&+&ncSw|1p*ND)BRQ~0n@ z38a$jyDV)ZDw4<>r+hG3@NT?h%K>njmsS_LHH92a1=YQb#$3ROgqmz>;#5ze*TX0#cPB_ME&1ST`+QTfs}5C-7)vd!=058Rax2Xc0uebMDL#^ z$d_2HRk&b)E~);ICRo@Ul6t#tsL(WV0-DuVcXxN$6woej{x*}Ak=X*2^h`)-VQU{8 z6>Mz(Z{NN(Ha3E-0j4dGcvpA#7!H(x1KJ-W@S$8Fm0mND!w%^5Sy@?!hle=|O8|9% zW*e=wxBfHRU0Pn&I=As}XHimX3fclD3hr$&p$l9Jhm-!sOF=AJ%{UHu;lzoTg|2rc zNsQzhk;dE{Y_gh+Rr{sMplvF6IS!T;6m$bt2}TwFdi}jhfkaa#cC#@~4vtk;s%9y( z5-OLO{+6HboEUxj7%cn)WmDd6eH8t66=nhOaZYCeqM}MQ8$Qj>>Hw2dE}C3fiTV7w zXK3iK@){!!BI04~KiE?ZsG!BYN4@$8n(_NIlM-62aGPuI{;iQzC8c{rOvc=M^x zhkQ`^)RdJidYrULaTf_N2nnxTNBEx;Zd)~8+$i}7RgEcxsbjzYLrGp9TL%O1a!`d$&CQiF)XtrJIah8Ee%hdDXdK8Q zh$HasYUQt-c-PWhpW^g01w)T53(U1(4`LFMmL>AizCkos?`d7pdQF)O65yRriFW4O z4S$!sKti2A8~~Dw(~&Bl4QK#|fS^7`!Kda=xc4cIPq#!^4xum*P=|>y-GGw-s3+$F zgbEc4XmPqqDa}CBxRSsw*4Ksrmn_M^``X@6TKk(_HilzFe&NhKT?<2W|IW{Li01%c>hJ zjNZL_$}-58ts2db(gFbHGT z@QD(pT@DSLMa$2c8GUm@!#j8oxfye^7Zbs56b&3kN`m((60VQaPxjLt_d*=UVgg>) zPS(e%J`*b?6cCN#+WM6&_29t+AwS}V`udN7E`@Q|R=@`S5F+*0pvu^dld;HY2vDVvGcyNxd}dTtk(2~p^)Wj77?kCYi-H=Ky7o{Bk&mp` zHTj0YtEut2iQKHgSIsyHdHLr?(_oaMslT6-pZ}zVedKU(5W`Sq}M#z72*FMJu2IAGB^qn&; zmJ}rCh}iVWDiT;~eG(puzcVzv!)fwGJPC{kO)04^*nJmgXV;KYWbt#xMocHiR4@80 z{2QSu%y$E2Bu~x6M&>_e;yU&cthdx^P2J2a2jBxpaZEE{-~&Zyi{>5NZKsp{gK3;c zz8@^8o(MY&S&`Z0w5bOrYWkQ?}JcFOV93mbXo}0Tnv_`hQ&0Y*UJaMxk0@90%T|rDY7Op6sdOAnM9U%_i^KjMltlm{p7P zNpYSi0Ff6)&_H{Zh~x-Je>5jJf!>$%E)b3!?pZX?C`x<>BtcJH`m;@XNfjv)$z$Ew z-Muy05U9TfrvNJW;uYl%hT4Q19WURP{(MA10dUyWKn-FD5JqJ7CXN8X82CYigoN|U z%h8dMhtLu>Qofinahf6Stau>q`3T8V`S)^I-7O~Iz}hOTCcClO#8_@+YrByyIl-}< z+p7Y74$x~CS&*TWPl^DuX*St!n3LKKiMoQ-e_EnG3UY)H*b%T^>96_O@LtUchN|qr zN`d@@S*JMnI|Bu%382P+g0F4Mrr&)keG^@-Jy)+68t`^AOPfchyU8hd>f`eXcU@E; zb=6MCfB~^efU3$bUp_`Yefl&pF>x{JmOI%S8BEvEP=a9L#BN<+Q2p2om$BAPmylt9 z`t`>@U(_5OUrLYDI6mAv)%T2+9>?}lL?1nukQgbpG*VI;0@t+qHvtwP^z~%v?hx zx+6JIy z-eOSVs9$rkulOTE%tCARzD1Awd!m|%&&~>f+^G8niwkE1olbWR#wtIMkdVX!k*TZk z#!r!^NpZcf{paqT?k)jeJgy==CKFywgHQ(=A4o)D9hw z=V`KFWr9XuIe>Ui^5;!xIcDIGrO@#c%^Lja4o*0V=b(=s zK#~9a@uS+(IiHRKWO~J;HN$Psg>IiTN^l%0ShKh;`FpyZo&bu_k z<#R(QSpp$M-hW6*NqH(IF+06y19|{G|5R0iKz@zR{55M0r9g_o3CuuVUfyZzS@-2#nVT#zh<6klDpW>11higqJ7?MLk$*v4Av31~4we2YoRUH3yr% zpr8O&zo`MGz$UV%?n$enQR!18u(Ovb-7tbjeF_FVUa5T?vp$<gs0N;{+=Y#k44wz_Vm5-F)@>bsw-W4GoRbl9Fh!sh5`x^YU_XK(%HiBl`)n zE`x)%Py^sYPiLoQn!{k6xU{r1gh9Cx_a+jEPB4HO@W;|*ZBLGNdV70CZh(35-(2Pe zKG|!q4V#c}%EH24;0uEVLTWvU=-AZ;(*gRULK+(@Yi?1ILP5eNHDALWDX|zn`;jtx zB*j1{1rc%5nwPgXOi1wZ@I*TvTk}a0q@gBhg6BajSoAM6oe3A6>pmxNGI{|j56Buk zt+)<#umvgI^Dt8dgEV>b|K!E`PjG;5y50DfZwj-VFi%8>`n&6z7}_LE;Ka<=1~dXp zjDi9Va+|4OxxNAD0E~?3z?%U_@Y-(Ubij6U`ZYB-Q^dPqU>_f?I8w*MzMf1mItJi* zw*SpR?w@K3Nr;bES68RF%9_vX=mfC^fkhb)$F#{tfCxL0hy084@c^TYf-RH-MUH^k zV}RWBig_@^^BJ6XaMl7NBU5`9XlZCbg>naw1w)E?J>%4Ty%Eg8)%TyHqFva;Cwd)L zD==(I`2c@T84;H>*wKC6oJUJscv(b=Cb6d!pOQjLN%?j#&!AG~AI3F})5F6FCsLt$ z0k3_A1o3@6`_uLGq$KxK(4bE4<>T|{w~uma`zZwlB!iHIQF}NI4;^q~9%2jM=jM*^ z*j^cFyJc!(V#XDPjKdw=VAW?L@Tl+V%FeIwIIAdX0W}B;A|@zdnKdxXwvBP{3g$y2 z>9f+SSROv?i;tAB+tZ*Sf0ZoVDfH%GA_v-y-^A6cS3PvrCmZ5(r`Iz>I|tE&F5{YB z2@7hYjJ6uTLP|>7bHnQH-MgE%Ku9Y~MYZ0Vxct_7`>+jML~;`p3SrpZ*_j11UN8s0 zruh5Rh%#z&-naPIY+1kJVqxx6O0)`36lgJEtTx2cWbsnD2*?KQ zXt{wph7No~fMAd$pjY;}KRjj3xIXOX&!6}3IC09`^&A;w4vv(&+h4=c($?nh(Wgvs zbiV3KMH2kVAvzeU0ivunG>S)`tJSz%?1F8IYL@y88~+RCO!b=mtpzw|2*d_ea8`g-Ij%t#B!EO9Km*_t;46KNyvo=U+3L zxBA<4z?jnS7^gmd?328x9%*s3Z%$qWlkmYcOphg>q~DJb8TvaTVTq}aj(a0PfOl#Q zQHh0rp*!KzswrzaMJ}GFfkPJ%RRjfn^B^4qbo8&HVJ35(;O>2e90NK;>p}5PBtoCyf2Y)hG&DD#ocD(rcx!U%zfrTgS#Hu2FV! zY)dXIA~GS>hd$!vXoy`!L5uTym15?{{`$H}o`$*k7NnU+eO1*29Py!o@3V>7*#gkG zNVqM02wA_Uruw8kWbg|tp>0){JrubjI=u&|N2SDynTrd?X2HJ+PgBRg|6lxyvT|Tx zU~Qr?B%tA^O(f|o$ zSE%iR^W~v4kn@hNw6M?`GzzY$Wx)HO7fB_FgNUkYXsD&vVn0qxB=vV&kWfhHVB(SQ zGeIO+N)XI1Uc7*Y1X}t`FKD57U|vYRd2_c(D4`=1Aip$~;6trUO0@J|(<@^M_IQ!}M_UyMgcU@bbdcLsdE3`-uW5v~I4hRyH;a z3OV2M@+?3lg><2n`vLi?X$!ChlZXv8s=3pa$0PPkJ++{~3fvVa@-Q$A^RPh-zlOFO zzdt2BLyNcMiJ#zh{NFUCHHN<&rk>Me7nYa#;71qmLzXc=$n7crKoW$7g+-s~ z0O){L3k?E14I`r!3{ty~C*;BD@OyxAu*3n6fBmzzUI3$(yBuvsdX4q_KsJrc>9KXJkM}f&sg}zSnRsFy#g)=zS1$ML zgjDx2KN5nu75LfL0tjLmB?ufa04MOF;L53}7}n^hjlz#7hOB3z;5T59 z2NuCWK`>AT7%+p+_t{2B`62j!H3RSa-jB5Wxi>V;%8fHNgcf_@57GGBMfC~#=sj_# UH#XJfr+f9{sW|e7fSU9F0S}>aPXGV_ literal 50638 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 - - - - - -Alerts - - - - - - - - -
-
- - -
-

Alerts

- --- - - - - - -
Author:Arvid Norberg, arvid@rasterbar.com
Version:1.0.0
-
-

Table of contents

- -
-

The pop_alerts() function on session is the main interface for retrieving -alerts (warnings, messages and errors from libtorrent). If no alerts have -been posted by libtorrent pop_alert() will return an empty list.

-

By default, only errors are reported. set_alert_mask() can be used to -specify which kinds of events should be reported. The alert mask is -comprised by bits from the category_t enum.

-

Every alert belongs to one or more category. There is a small cost involved -in posting alerts. Only alerts that belong to an enabled category are -posted. Setting the alert bitmask to 0 will disable all alerts (except those -that are non-discardable).

-

There are other alert base classes that some alerts derive from, all the -alerts that are generated for a specific torrent are derived from -torrent_alert, and tracker events derive from tracker_alert.

-
-

alert

-

Declared in "libtorrent/alert.hpp"

-

The alert class is the base class that specific messages are derived from.

-
-class alert
-{
-   ptime timestamp () const;
-   virtual int type () const = 0;
-   virtual char const* what () const = 0;
-   virtual std::string message () const = 0;
-   virtual int category () const = 0;
-   virtual bool discardable () const;
-   virtual std::auto_ptr<alert> clone () const = 0;
-
-   enum category_t
-   {
-      error_notification,
-      peer_notification,
-      port_mapping_notification,
-      storage_notification,
-      tracker_notification,
-      debug_notification,
-      status_notification,
-      progress_notification,
-      ip_block_notification,
-      performance_warning,
-      dht_notification,
-      stats_notification,
-      rss_notification,
-      all_categories,
-   };
-};
-
-
-

timestamp()

-
-ptime timestamp () const;
-
-

a timestamp is automatically created in the constructor

-
-
-

type()

-
-virtual int type () const = 0;
-
-

returns an integer that is unique to this alert type. It can be -compared against a specific alert by querying a static constant called alert_type -in the alert. It can be used to determine the run-time type of an alert* in -order to cast to that alert type and access specific members.

-

e.g:

-
-std::auto_ptr<alert> a = ses.pop_alert();
-switch (a->type())
-{
-        case read_piece_alert::alert_type:
-        {
-                read_piece_alert* p = (read_piece_alert*)a.get();
-                if (p->ec) {
-                        // read_piece failed
-                        break;
-                }
-                // use p
-                break;
-        }
-        case file_renamed_alert::alert_type:
-        {
-                // etc...
-        }
-}
-
-
-
-

what()

-
-virtual char const* what () const = 0;
-
-

returns a string literal describing the type of the alert. It does -not include any information that might be bundled with the alert.

-
-
-

message()

-
-virtual std::string message () const = 0;
-
-

generate a string describing the alert and the information bundled -with it. This is mainly intended for debug and development use. It is not suitable -to use this for applications that may be localized. Instead, handle each alert -type individually and extract and render the information from the alert depending -on the locale.

-
-
-

category()

-
-virtual int category () const = 0;
-
-

returns a bitmask specifying which categories this alert belong to.

-
-
-

discardable()

-
-virtual bool discardable () const;
-
-

determines whether or not an alert is allowed to be discarded -when the alert queue is full. There are a few alerts which may not be discared, -since they would break the user contract, such as save_resume_data_alert.

-
-
-

clone()

-
-virtual std::auto_ptr<alert> clone () const = 0;
-
-

returns a pointer to a copy of the alert.

-
-
-

enum category_t

-

Declared in "libtorrent/alert.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
error_notification1

Enables alerts that report an error. This includes:

-
    -
  • tracker errors
  • -
  • tracker warnings
  • -
  • file errors
  • -
  • resume data failures
  • -
  • web seed errors
  • -
  • .torrent files errors
  • -
  • listen socket errors
  • -
  • port mapping errors
  • -
-
peer_notification2Enables alerts when peers send invalid requests, get banned or -snubbed.
port_mapping_notification4Enables alerts for port mapping events. For NAT-PMP and UPnP.
storage_notification8Enables alerts for events related to the storage. File errors and -synchronization events for moving the storage, renaming files etc.
tracker_notification16Enables all tracker events. Includes announcing to trackers, -receiving responses, warnings and errors.
debug_notification32Low level alerts for when peers are connected and disconnected.
status_notification64Enables alerts for when a torrent or the session changes state.
progress_notification128Alerts for when blocks are requested and completed. Also when -pieces are completed.
ip_block_notification256Alerts when a peer is blocked by the ip blocker or port blocker.
performance_warning512Alerts when some limit is reached that might limit the download -or upload rate.
dht_notification1024Alerts on events in the DHT node. For incoming searches or -bootstrapping being done etc.
stats_notification2048If you enable these alerts, you will receive a stats_alert -approximately once every second, for every active torrent. -These alerts contain all statistics counters for the interval since -the lasts stats alert.
rss_notification4096Alerts on RSS related events, like feeds being updated, feed error -conditions and successful RSS feed updates. Enabling this categoty -will make you receive rss_alert alerts.
all_categories2147483647

The full bitmask, representing all available categories.

-

since the enum is signed, make sure this isn't -interpreted as -1. For instance, boost.python -does that and fails when assigning it to an -unsigned parameter.

-
-
-
-
-

torrent_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This is a base class for alerts that are associated with a -specific torrent. It contains a handle to the torrent.

-
-struct torrent_alert: alert
-{
-   torrent_handle handle;
-};
-
-
-
handle
-
The torrent_handle pointing to the torrent this -alert is associated with.
-
-
-
-

peer_alert

-

Declared in "libtorrent/alert_types.hpp"

-

The peer alert is a base class for alerts that refer to a specific peer. It includes all -the information to identify the peer. i.e. ip and peer-id.

-
-struct peer_alert: torrent_alert
-{
-   virtual int category () const;
-   virtual std::string message () const;
-
-   const static int alert_type = 2;
-   const static int static_category = alert::peer_notification;
-   tcp::endpoint ip;
-   peer_id pid;
-};
-
-
-
ip
-
The peer's IP address and port.
-
-
-
pid
-
the peer ID, if known.
-
-
-
-

tracker_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This is a base class used for alerts that are associated with a -specific tracker. It derives from torrent_alert since a tracker -is also associated with a specific torrent.

-
-struct tracker_alert: torrent_alert
-{
-   virtual int category () const;
-   virtual std::string message () const;
-
-   const static int alert_type = 3;
-   const static int static_category = alert::tracker_notification;
-   std::string url;
-};
-
-
-
url
-
The tracker URL
-
-
-
-

torrent_added_alert

-

Declared in "libtorrent/alert_types.hpp"

-

The torrent_added_alert is posted once every time a torrent is successfully -added. It doesn't contain any members of its own, but inherits the torrent handle -from its base class. -It's posted when the status_notification bit is set in the alert_mask.

-
-struct torrent_added_alert: torrent_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::status_notification;
-};
-
-
-
-

torrent_removed_alert

-

Declared in "libtorrent/alert_types.hpp"

-

The torrent_removed_alert is posted whenever a torrent is removed. Since -the torrent handle in its baseclass will always be invalid (since the torrent -is already removed) it has the info hash as a member, to identify it. -It's posted when the status_notification bit is set in the alert_mask.

-

Even though the handle member doesn't point to an existing torrent anymore, -it is still useful for comparing to other handles, which may also no -longer point to existing torrents, but to the same non-existing torrents.

-

The torrent_handle acts as a weak_ptr, even though its object no -longer exists, it can still compare equal to another weak pointer which -points to the same non-existent object.

-
-struct torrent_removed_alert: torrent_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::status_notification;
-   sha1_hash info_hash;
-};
-
-
-
-

read_piece_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is posted when the asynchronous read operation initiated by -a call to torrent_handle::read_piece() is completed. If the read failed, the torrent -is paused and an error state is set and the buffer member of the alert -is 0. If successful, buffer points to a buffer containing all the data -of the piece. piece is the piece index that was read. size is the -number of bytes that was read.

-

If the operation fails, ec will indicat what went wrong.

-
-struct read_piece_alert: torrent_alert
-{
-   virtual bool discardable () const;
-   virtual std::string message () const;
-
-   const static int static_category = alert::storage_notification;
-   error_code ec;
-   boost::shared_array<char> buffer;
-   int piece;
-   int size;
-};
-
-
-
-

file_completed_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This is posted whenever an individual file completes its download. i.e. -All pieces overlapping this file have passed their hash check.

-
-struct file_completed_alert: torrent_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::progress_notification;
-   int index;
-};
-
-
-
index
-
refers to the index of the file that completed.
-
-
-
-

file_renamed_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This is posted as a response to a torrent_handle::rename_file() call, if the rename -operation succeeds.

-
-struct file_renamed_alert: torrent_alert
-{
-   virtual bool discardable () const;
-   virtual std::string message () const;
-
-   const static int static_category = alert::storage_notification;
-   std::string name;
-   int index;
-};
-
-
-
index
-
refers to the index of the file that was renamed, -name is the new name of the file.
-
-
-
-

file_rename_failed_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This is posted as a response to a torrent_handle::rename_file() call, if the rename -operation failed.

-
-struct file_rename_failed_alert: torrent_alert
-{
-   virtual bool discardable () const;
-   virtual std::string message () const;
-
-   const static int static_category = alert::storage_notification;
-   int index;
-   error_code error;
-};
-
- -
-
index error
-
refers to the index of the file that was supposed to be renamed, -error is the error code returned from the filesystem.
-
-
-
-

performance_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when a limit is reached that might have a negative impact on -upload or download rate performance.

-
-struct performance_alert: torrent_alert
-{
-   virtual std::string message () const;
-
-   enum performance_warning_t
-   {
-      outstanding_disk_buffer_limit_reached,
-      outstanding_request_limit_reached,
-      upload_limit_too_low,
-      download_limit_too_low,
-      send_buffer_watermark_too_low,
-      too_many_optimistic_unchoke_slots,
-      too_high_disk_queue_limit,
-      bittyrant_with_no_uplimit,
-      too_few_outgoing_ports,
-      too_few_file_descriptors,
-      num_warnings,
-   };
-
-   const static int static_category = alert::performance_warning;
-   performance_warning_t warning_code;
-};
-
-
-

enum performance_warning_t

-

Declared in "libtorrent/alert_types.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
outstanding_disk_buffer_limit_reached0This warning means that the number of bytes queued to be written to disk -exceeds the max disk byte queue setting (session_settings::max_queued_disk_bytes). -This might restrict the download rate, by not queuing up enough write jobs -to the disk I/O thread. When this alert is posted, peer connections are -temporarily stopped from downloading, until the queued disk bytes have fallen -below the limit again. Unless your max_queued_disk_bytes setting is already -high, you might want to increase it to get better performance.
outstanding_request_limit_reached1This is posted when libtorrent would like to send more requests to a peer, -but it's limited by session_settings::max_out_request_queue. The queue length -libtorrent is trying to achieve is determined by the download rate and the -assumed round-trip-time (session_settings::request_queue_time). The assumed -rount-trip-time is not limited to just the network RTT, but also the remote disk -access time and message handling time. It defaults to 3 seconds. The target number -of outstanding requests is set to fill the bandwidth-delay product (assumed RTT -times download rate divided by number of bytes per request). When this alert -is posted, there is a risk that the number of outstanding requests is too low -and limits the download rate. You might want to increase the max_out_request_queue -setting.
upload_limit_too_low2This warning is posted when the amount of TCP/IP overhead is greater than the -upload rate limit. When this happens, the TCP/IP overhead is caused by a much -faster download rate, triggering TCP ACK packets. These packets eat into the -rate limit specified to libtorrent. When the overhead traffic is greater than -the rate limit, libtorrent will not be able to send any actual payload, such -as piece requests. This means the download rate will suffer, and new requests -can be sent again. There will be an equilibrium where the download rate, on -average, is about 20 times the upload rate limit. If you want to maximize the -download rate, increase the upload rate limit above 5% of your download capacity.
download_limit_too_low3This is the same warning as upload_limit_too_low but referring to the download -limit instead of upload. This suggests that your download rate limit is mcuh lower -than your upload capacity. Your upload rate will suffer. To maximize upload rate, -make sure your download rate limit is above 5% of your upload capacity.
send_buffer_watermark_too_low4

We're stalled on the disk. We want to write to the socket, and we can write -but our send buffer is empty, waiting to be refilled from the disk. -This either means the disk is slower than the network connection -or that our send buffer watermark is too small, because we can -send it all before the disk gets back to us. -The number of bytes that we keep outstanding, requested from the disk, is calculated -as follows:

-
-min(512, max(upload_rate * send_buffer_watermark_factor / 100, send_buffer_watermark))
-
-

If you receive this alert, you migth want to either increase your send_buffer_watermark -or send_buffer_watermark_factor.

-
too_many_optimistic_unchoke_slots5If the half (or more) of all upload slots are set as optimistic unchoke slots, this -warning is issued. You probably want more regular (rate based) unchoke slots.
too_high_disk_queue_limit6If the disk write queue ever grows larger than half of the cache size, this warning -is posted. The disk write queue eats into the total disk cache and leaves very little -left for the actual cache. This causes the disk cache to oscillate in evicting large -portions of the cache before allowing peers to download any more, onto the disk write -queue. Either lower max_queued_disk_bytes or increase cache_size.
bittyrant_with_no_uplimit7 
too_few_outgoing_ports8This is generated if outgoing peer connections are failing because of address in use -errors, indicating that session_settings::outgoing_ports is set and is too small of -a range. Consider not using the outgoing_ports setting at all, or widen the range to -include more ports.
too_few_file_descriptors9 
num_warnings10 
-
-
-
-

state_changed_alert

-

Declared in "libtorrent/alert_types.hpp"

-

Generated whenever a torrent changes its state.

-
-struct state_changed_alert: torrent_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::status_notification;
-   torrent_status::state_t state;
-   torrent_status::state_t prev_state;
-};
-
-
-
state
-
the new state of the torrent.
-
-
-
prev_state
-
the previous state.
-
-
-
-

tracker_error_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated on tracker time outs, premature disconnects, invalid response or -a HTTP response other than "200 OK". From the alert you can get the handle to the torrent -the tracker belongs to.

-

The times_in_row member says how many times in a row this tracker has failed. -status_code is the code returned from the HTTP server. 401 means the tracker needs -authentication, 404 means not found etc. If the tracker timed out, the code will be set -to 0.

-
-struct tracker_error_alert: tracker_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::tracker_notification | alert::error_notification;
-   int times_in_row;
-   int status_code;
-   error_code error;
-   std::string msg;
-};
-
-
-
-

tracker_warning_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is triggered if the tracker reply contains a warning field. Usually this -means that the tracker announce was successful, but the tracker has a message to -the client.

-
-struct tracker_warning_alert: tracker_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::tracker_notification | alert::error_notification;
-   std::string msg;
-};
-
-
-
msg
-
contains the warning message from the tracker.
-
-
-
-

scrape_reply_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when a scrape request succeeds.

-
-struct scrape_reply_alert: tracker_alert
-{
-   virtual std::string message () const;
-
-   int incomplete;
-   int complete;
-};
-
- -
-
incomplete complete
-
the data returned in the scrape response. These numbers -may be -1 if the reponse was malformed.
-
-
-
-

scrape_failed_alert

-

Declared in "libtorrent/alert_types.hpp"

-

If a scrape request fails, this alert is generated. This might be due -to the tracker timing out, refusing connection or returning an http response -code indicating an error.

-
-struct scrape_failed_alert: tracker_alert
-{
-   scrape_failed_alert (torrent_handle const& h
-      , std::string const& u
-      , std::string const& m);
-   virtual std::string message () const;
-
-   const static int static_category = alert::tracker_notification | alert::error_notification;
-   std::string msg;
-};
-
-
-
msg
-
contains a message describing the error.
-
-
-
-

tracker_reply_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is only for informational purpose. It is generated when a tracker announce -succeeds. It is generated regardless what kind of tracker was used, be it UDP, HTTP or -the DHT.

-
-struct tracker_reply_alert: tracker_alert
-{
-   virtual std::string message () const;
-
-   int num_peers;
-};
-
-
-
num_peers
-
tells how many peers the tracker returned in this response. This is -not expected to be more thant the num_want settings. These are not necessarily -all new peers, some of them may already be connected.
-
-
-
-

dht_reply_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated each time the DHT receives peers from a node. num_peers -is the number of peers we received in this packet. Typically these packets are -received from multiple DHT nodes, and so the alerts are typically generated -a few at a time.

-
-struct dht_reply_alert: tracker_alert
-{
-   virtual std::string message () const;
-
-   int num_peers;
-};
-
-
-
-

tracker_announce_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated each time a tracker announce is sent (or attempted to be sent). -There are no extra data members in this alert. The url can be found in the base class -however.

-
-struct tracker_announce_alert: tracker_alert
-{
-   virtual std::string message () const;
-
-   int event;
-};
-
-
-
event
-

specifies what event was sent to the tracker. It is defined as:

-
    -
  1. None
  2. -
  3. Completed
  4. -
  5. Started
  6. -
  7. Stopped
  8. -
-
-
-
-
-

hash_failed_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when a finished piece fails its hash check. You can get the handle -to the torrent which got the failed piece and the index of the piece itself from the alert.

-
-struct hash_failed_alert: torrent_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::status_notification;
-   int piece_index;
-};
-
-
-
-

peer_ban_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when a peer is banned because it has sent too many corrupt pieces -to us. ip is the endpoint to the peer that was banned.

-
-struct peer_ban_alert: peer_alert
-{
-   virtual std::string message () const;
-};
-
-
-
-

peer_unsnubbed_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when a peer is unsnubbed. Essentially when it was snubbed for stalling -sending data, and now it started sending data again.

-
-struct peer_unsnubbed_alert: peer_alert
-{
-   virtual std::string message () const;
-};
-
-
-
-

peer_snubbed_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when a peer is snubbed, when it stops sending data when we request -it.

-
-struct peer_snubbed_alert: peer_alert
-{
-   virtual std::string message () const;
-};
-
-
-
-

peer_error_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when a peer sends invalid data over the peer-peer protocol. The peer -will be disconnected, but you get its ip address from the alert, to identify it.

-
-struct peer_error_alert: peer_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::peer_notification;
-   error_code error;
-};
-
-
-
error
-
tells you what error caused this alert.
-
-
-
-

peer_connect_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is posted every time an outgoing peer connect attempts succeeds.

-
-struct peer_connect_alert: peer_alert
-{
-   virtual std::string message () const;;
-
-   const static int static_category = alert::debug_notification;
-   int socket_type;
-};
-
-
-
-

peer_disconnected_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when a peer is disconnected for any reason (other than the ones -covered by peer_error_alert ).

-
-struct peer_disconnected_alert: peer_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::debug_notification;
-   error_code error;
-};
-
-
-
error
-
tells you what error caused peer to disconnect.
-
-
-
-

invalid_request_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This is a debug alert that is generated by an incoming invalid piece request. -ip is the address of the peer and the request is the actual incoming -request from the peer. See peer_request for more info.

-
-struct invalid_request_alert: peer_alert
-{
-   virtual std::string message () const;
-
-   peer_request request;
-};
-
-
-
-

torrent_finished_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when a torrent switches from being a downloader to a seed. -It will only be generated once per torrent. It contains a torrent_handle to the -torrent in question.

-
-struct torrent_finished_alert: torrent_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::status_notification;
-};
-
-
-
-

piece_finished_alert

-

Declared in "libtorrent/alert_types.hpp"

-

this alert is posted every time a piece completes downloading -and passes the hash check. This alert derives from torrent_alert -which contains the torrent_handle to the torrent the piece belongs to.

-
-struct piece_finished_alert: torrent_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::progress_notification;
-   int piece_index;
-};
-
-
-
piece_index
-
the index of the piece that finished
-
-
-
-

request_dropped_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when a peer rejects or ignores a piece request.

-
-struct request_dropped_alert: peer_alert
-{
-   virtual std::string message () const;
-
-   | alert::peer_notification;
-   int block_index;
-   int piece_index;
-};
-
-
-
-

block_timeout_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when a block request times out.

-
-struct block_timeout_alert: peer_alert
-{
-   virtual std::string message () const;
-
-   | alert::peer_notification;
-   int block_index;
-   int piece_index;
-};
-
-
-
-

block_finished_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when a block request receives a response.

-
-struct block_finished_alert: peer_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::progress_notification;
-   int block_index;
-   int piece_index;
-};
-
-
-
-

block_downloading_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when a block request is sent to a peer.

-
-struct block_downloading_alert: peer_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::progress_notification;
-   char const* peer_speedmsg;
-   int block_index;
-   int piece_index;
-};
-
-
-
-

unwanted_block_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when a block is received that was not requested or -whose request timed out.

-
-struct unwanted_block_alert: peer_alert
-{
-   virtual std::string message () const;
-
-   int block_index;
-   int piece_index;
-};
-
-
-
-

storage_moved_alert

-

Declared in "libtorrent/alert_types.hpp"

-

The storage_moved_alert is generated when all the disk IO has completed and the -files have been moved, as an effect of a call to torrent_handle::move_storage. This -is useful to synchronize with the actual disk. The path member is the new path of -the storage.

-
-struct storage_moved_alert: torrent_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::storage_notification;
-   std::string path;
-};
-
-
-
-

storage_moved_failed_alert

-

Declared in "libtorrent/alert_types.hpp"

-

The storage_moved_failed_alert is generated when an attempt to move the storage, -via torrent_handle::move_storage(), fails.

-
-struct storage_moved_failed_alert: torrent_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::storage_notification;
-   error_code error;
-};
-
-
-
-

torrent_deleted_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when a request to delete the files of a torrent complete.

-

The info_hash is the info-hash of the torrent that was just deleted. Most of -the time the torrent_handle in the torrent_alert will be invalid by the time -this alert arrives, since the torrent is being deleted. The info_hash member -is hence the main way of identifying which torrent just completed the delete.

-

This alert is posted in the storage_notification category, and that bit -needs to be set in the alert_mask.

-
-struct torrent_deleted_alert: torrent_alert
-{
-   virtual bool discardable () const;
-   virtual std::string message () const;
-
-   const static int static_category = alert::storage_notification;
-   sha1_hash info_hash;
-};
-
-
-
-

torrent_delete_failed_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when a request to delete the files of a torrent fails. -Just removing a torrent from the session cannot fail

-
-struct torrent_delete_failed_alert: torrent_alert
-{
-   virtual bool discardable () const;
-   virtual std::string message () const;
-
-   | alert::error_notification;
-   error_code error;
-   sha1_hash info_hash;
-};
-
-
-
error
-
tells you why it failed.
-
-
-
info_hash
-
the info hash of the torrent whose files failed to be deleted
-
-
-
-

save_resume_data_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated as a response to a torrent_handle::save_resume_data request. -It is generated once the disk IO thread is done writing the state for this torrent.

-
-struct save_resume_data_alert: torrent_alert
-{
-   virtual bool discardable () const;
-   virtual std::string message () const;
-
-   const static int static_category = alert::storage_notification;
-   boost::shared_ptr<entry> resume_data;
-};
-
-
-
resume_data
-
points to the resume data.
-
-
-
-

save_resume_data_failed_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated instead of save_resume_data_alert if there was an error -generating the resume data. error describes what went wrong.

-
-struct save_resume_data_failed_alert: torrent_alert
-{
-   virtual bool discardable () const;
-   virtual std::string message () const;
-
-   | alert::error_notification;
-   error_code error;
-};
-
-
-
-

torrent_paused_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated as a response to a torrent_handle::pause request. It is -generated once all disk IO is complete and the files in the torrent have been closed. -This is useful for synchronizing with the disk.

-
-struct torrent_paused_alert: torrent_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::status_notification;
-};
-
-
-
-

torrent_resumed_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated as a response to a torrent_handle::resume() request. It is -generated when a torrent goes from a paused state to an active state.

-
-struct torrent_resumed_alert: torrent_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::status_notification;
-};
-
-
-
-

torrent_checked_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is posted when a torrent completes checking. i.e. when it transitions -out of the checking files state into a state where it is ready to start downloading

-
-struct torrent_checked_alert: torrent_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::status_notification;
-};
-
-
-
-

url_seed_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when a HTTP seed name lookup fails.

-
-struct url_seed_alert: torrent_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::peer_notification | alert::error_notification;
-   std::string url;
-   std::string msg;
-};
-
-
-
url
-
the HTTP seed that failed
-
-
-
msg
-
the error message, potentially from the server
-
-
-
-

file_error_alert

-

Declared in "libtorrent/alert_types.hpp"

-

If the storage fails to read or write files that it needs access to, this alert is -generated and the torrent is paused.

-
-struct file_error_alert: torrent_alert
-{
-   virtual std::string message () const;
-
-   | alert::storage_notification;
-   std::string file;
-   error_code error;
-};
-
-
-
file
-
the path to the file that was accessed when the error occurred.
-
-
-
error
-
the error code describing the error.
-
-
-
-

metadata_failed_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when the metadata has been completely received and the info-hash -failed to match it. i.e. the metadata that was received was corrupt. libtorrent will -automatically retry to fetch it in this case. This is only relevant when running a -torrent-less download, with the metadata extension provided by libtorrent.

-
-struct metadata_failed_alert: torrent_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::error_notification;
-   error_code error;
-};
-
-
-
error
-
the error that occurred
-
-
-
-

metadata_received_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when the metadata has been completely received and the torrent -can start downloading. It is not generated on torrents that are started with metadata, but -only those that needs to download it from peers (when utilizing the libtorrent extension).

-

There are no additional data members in this alert.

-

Typically, when receiving this alert, you would want to save the torrent file in order -to load it back up again when the session is restarted. Here's an example snippet of -code to do that:

-
-torrent_handle h = alert->handle();
-if (h.is_valid()) {
-        boost::intrusive_ptr<torrent_info const> ti = h.torrent_file();
-        create_torrent ct(*ti);
-        entry te = ct.generate();
-        std::vector<char> buffer;
-        bencode(std::back_inserter(buffer), te);
-        FILE* f = fopen((to_hex(ti->info_hash().to_string()) + ".torrent").c_str(), "wb+");
-        if (f) {
-                fwrite(&buffer[0], 1, buffer.size(), f);
-                fclose(f);
-        }
-}
-
-
-struct metadata_received_alert: torrent_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::status_notification;
-};
-
-
-
-

udp_error_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is posted when there is an error on the UDP socket. The -UDP socket is used for all uTP, DHT and UDP tracker traffic. It's -global to the session.

-
-struct udp_error_alert: alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::error_notification;
-   udp::endpoint endpoint;
-   error_code error;
-};
-
-
-
endpoint
-
the source address associated with the error (if any)
-
-
-
error
-
the error code describing the error
-
-
-
-

external_ip_alert

-

Declared in "libtorrent/alert_types.hpp"

-

Whenever libtorrent learns about the machines external IP, this alert is -generated. The external IP address can be acquired from the tracker (if it -supports that) or from peers that supports the extension protocol. -The address can be accessed through the external_address member.

-
-struct external_ip_alert: alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::status_notification;
-   address external_address;
-};
-
-
-
external_address
-
the IP address that is believed to be our external IP
-
-
-
-

listen_failed_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when none of the ports, given in the port range, to -session can be opened for listening. The endpoint member is the -interface and port that failed, error is the error code describing -the failure.

-

libtorrent may sometimes try to listen on port 0, if all other ports failed. -Port 0 asks the operating system to pick a port that's free). If that fails -you may see a listen_failed_alert with port 0 even if you didn't ask to -listen on it.

-
-struct listen_failed_alert: alert
-{
-   virtual bool discardable () const;
-   virtual std::string message () const;
-
-   enum socket_type_t
-   {
-      tcp,
-      tcp_ssl,
-      udp,
-      i2p,
-      socks5,
-   };
-
-   enum op_t
-   {
-      parse_addr,
-      open,
-      bind,
-      listen,
-      get_peer_name,
-      accept,
-   };
-
-   const static int static_category = alert::status_notification | alert::error_notification;
-   tcp::endpoint endpoint;
-   error_code error;
-   int operation;
-   socket_type_t sock_type;
-};
-
-
-

enum socket_type_t

-

Declared in "libtorrent/alert_types.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
tcp0 
tcp_ssl1 
udp2 
i2p3 
socks54 
-
-
-

enum op_t

-

Declared in "libtorrent/alert_types.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
parse_addr0 
open1 
bind2 
listen3 
get_peer_name4 
accept5 
-
-
endpoint
-
the endpoint libtorrent attempted to listen on
-
-
-
error
-
the error the system returned
-
-
-
operation
-
the specific low level operation that failed. See op_t.
-
-
-
sock_type
-
the type of listen socket this alert refers to.
-
-
-
-
-

listen_succeeded_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is posted when the listen port succeeds to be opened on a -particular interface. endpoint is the endpoint that successfully -was opened for listening.

-
-struct listen_succeeded_alert: alert
-{
-   virtual bool discardable () const;
-   virtual std::string message () const;
-
-   enum socket_type_t
-   {
-      tcp,
-      tcp_ssl,
-      udp,
-   };
-
-   const static int static_category = alert::status_notification;
-   tcp::endpoint endpoint;
-   socket_type_t sock_type;
-};
-
-
-

enum socket_type_t

-

Declared in "libtorrent/alert_types.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
tcp0 
tcp_ssl1 
udp2 
-
-
endpoint
-
the endpoint libtorrent ended up listening on. The address -refers to the local interface and the port is the listen port.
-
-
-
sock_type
-
the type of listen socket this alert refers to.
-
-
-
-
-

portmap_error_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when a NAT router was successfully found but some -part of the port mapping request failed. It contains a text message that -may help the user figure out what is wrong. This alert is not generated in -case it appears the client is not running on a NAT:ed network or if it -appears there is no NAT router that can be remote controlled to add port -mappings.

-
-struct portmap_error_alert: alert
-{
-   virtual std::string message () const;
-
-   | alert::error_notification;
-   int mapping;
-   int map_type;
-   error_code error;
-};
-
-
-
mapping
-
refers to the mapping index of the port map that failed, i.e. -the index returned from add_mapping().
-
-
-
map_type
-
is 0 for NAT-PMP and 1 for UPnP.
-
-
-
error
-
tells you what failed.
-
-
-
-

portmap_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when a NAT router was successfully found and -a port was successfully mapped on it. On a NAT:ed network with a NAT-PMP -capable router, this is typically generated once when mapping the TCP -port and, if DHT is enabled, when the UDP port is mapped.

-
-struct portmap_alert: alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::port_mapping_notification;
-   int mapping;
-   int external_port;
-   int map_type;
-};
-
-
-
mapping
-
refers to the mapping index of the port map that failed, i.e. -the index returned from add_mapping().
-
-
-
external_port
-
the external port allocated for the mapping.
-
-
-
map_type
-
0 for NAT-PMP and 1 for UPnP.
-
-
-
-

portmap_log_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated to log informational events related to either -UPnP or NAT-PMP. They contain a log line and the type (0 = NAT-PMP -and 1 = UPnP). Displaying these messages to an end user is only useful -for debugging the UPnP or NAT-PMP implementation.

-
-struct portmap_log_alert: alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::port_mapping_notification;
-   int map_type;
-   std::string msg;
-};
-
-
-
-

fastresume_rejected_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when a fastresume file has been passed to add_torrent() but the -files on disk did not match the fastresume file. The error_code explains the reason why the -resume file was rejected.

-
-struct fastresume_rejected_alert: torrent_alert
-{
-   virtual std::string message () const;
-
-   | alert::error_notification;
-   error_code error;
-};
-
-
-
-

peer_blocked_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is posted when an incoming peer connection, or a peer that's about to be added -to our peer list, is blocked for some reason. This could be any of:

-
    -
  • the IP filter
  • -
  • i2p mixed mode restrictions (a normal peer is not allowed on an i2p swarm)
  • -
  • the port filter
  • -
  • the peer has a low port and no_connect_privileged_ports is enabled
  • -
  • the protocol of the peer is blocked (uTP/TCP blocking)
  • -
-
-struct peer_blocked_alert: torrent_alert
-{
-   virtual std::string message () const;
-
-   enum reason_t
-   {
-      ip_filter,
-      port_filter,
-      i2p_mixed,
-      privileged_ports,
-      utp_disabled,
-      tcp_disabled,
-   };
-
-   const static int static_category = alert::ip_block_notification;
-   address ip;
-   int reason;
-};
-
-
-

enum reason_t

-

Declared in "libtorrent/alert_types.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
ip_filter0 
port_filter1 
i2p_mixed2 
privileged_ports3 
utp_disabled4 
tcp_disabled5 
-
-
ip
-
the address that was blocked.
-
-
-
-
-

dht_announce_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when a DHT node announces to an info-hash on our -DHT node. It belongs to the dht_notification category.

-
-struct dht_announce_alert: alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::dht_notification;
-   address ip;
-   int port;
-   sha1_hash info_hash;
-};
-
-
-
-

dht_get_peers_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when a DHT node sends a get_peers message to -our DHT node. It belongs to the dht_notification category.

-
-struct dht_get_peers_alert: alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::dht_notification;
-   sha1_hash info_hash;
-};
-
-
-
-

stats_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is posted approximately once every second, and it contains -byte counters of most statistics that's tracked for torrents. Each active -torrent posts these alerts regularly.

-
-struct stats_alert: torrent_alert
-{
-   virtual std::string message () const;
-
-   enum stats_channel
-   {
-      upload_payload,
-      upload_protocol,
-      download_payload,
-      download_protocol,
-      upload_ip_protocol,
-      upload_dht_protocol,
-      upload_tracker_protocol,
-      download_ip_protocol,
-      download_dht_protocol,
-      download_tracker_protocol,
-      num_channels,
-   };
-
-   const static int static_category = alert::stats_notification;
-   int transferred[num_channels];
-   int interval;
-};
-
-
-

enum stats_channel

-

Declared in "libtorrent/alert_types.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
upload_payload0 
upload_protocol1 
download_payload2 
download_protocol3 
upload_ip_protocol4 
upload_dht_protocol5 
upload_tracker_protocol6 
download_ip_protocol7 
download_dht_protocol8 
download_tracker_protocol9 
num_channels10 
-
-
transferred[num_channels]
-
an array of samples. The enum describes what each sample is a -measurement of. All of these are raw, and not smoothing is performed.
-
-
-
interval
-
the number of milliseconds during which these stats were collected. -This is typically just above 1000, but if CPU is limited, it may be -higher than that.
-
-
-
-
-

cache_flushed_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is posted when the disk cache has been flushed for a specific -torrent as a result of a call to torrent_handle::flush_cache(). This -alert belongs to the storage_notification category, which must be -enabled to let this alert through. The alert is also posted when removing -a torrent from the session, once the outstanding cache flush is complete -and the torrent does no longer have any files open.

-
-struct cache_flushed_alert: torrent_alert
-{
-   const static int static_category = alert::storage_notification;
-};
-
-
-
-

anonymous_mode_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is posted when a bittorrent feature is blocked because of the -anonymous mode. For instance, if the tracker proxy is not set up, no -trackers will be used, because trackers can only be used through proxies -when in anonymous mode.

-
-struct anonymous_mode_alert: torrent_alert
-{
-   virtual std::string message () const;
-
-   enum kind_t
-   {
-      tracker_not_anonymous,
-   };
-
-   const static int static_category = alert::error_notification;
-   int kind;
-   std::string str;
-};
-
-
-

enum kind_t

-

Declared in "libtorrent/alert_types.hpp"

- ----- - - - - - - - - - - - - -
namevaluedescription
tracker_not_anonymous0means that there's no proxy set up for tracker -communication and the tracker will not be contacted. -The tracker which this failed for is specified in the str member.
- -
-
kind str
-
specifies what error this is, see kind_t.
-
-
-
-
-

lsd_peer_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is generated when we receive a local service discovery message -from a peer for a torrent we're currently participating in.

-
-struct lsd_peer_alert: peer_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::peer_notification;
-};
-
-
-
-

trackerid_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is posted whenever a tracker responds with a trackerid. -The tracker ID is like a cookie. The libtorrent will store the tracker ID -for this tracker and repeat it in subsequent announces.

-
-struct trackerid_alert: tracker_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::status_notification;
-   std::string trackerid;
-};
-
-
-
trackerid
-
The tracker ID returned by the tracker
-
-
-
-

dht_bootstrap_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is posted when the initial DHT bootstrap is done.

-
-struct dht_bootstrap_alert: alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::dht_notification;
-};
-
-
-
-

rss_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is posted on RSS feed events such as start of RSS feed updates, -successful completed updates and errors during updates.

-

This alert is only posted if the rss_notifications category is enabled -in the alert_mask.

-
-struct rss_alert: alert
-{
-   virtual std::string message () const;
-
-   enum state_t
-   {
-      state_updating,
-      state_updated,
-      state_error,
-   };
-
-   const static int static_category = alert::rss_notification;
-   feed_handle handle;
-   std::string url;
-   int state;
-   error_code error;
-};
-
-
-

enum state_t

-

Declared in "libtorrent/alert_types.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
state_updating0An update of this feed was just initiated, it will either succeed -or fail soon.
state_updated1The feed just completed a successful update, there may be new items -in it. If you're adding torrents manually, you may want to request -the feed status of the feed and look through the items vector.
state_error2An error just occurred. See the error field for information on -what went wrong.
-
-
handle
-
the handle to the feed which generated this alert.
-
-
-
url
-
a short cut to access the url of the feed, without -having to call feed_handle::get_settings().
-
-
-
state
-
one of the values from rss_alert::state_t.
-
-
-
error
-
an error code used for when an error occurs on the feed.
-
-
-
-
-

torrent_error_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This is posted whenever a torrent is transitioned into the error state.

-
-struct torrent_error_alert: torrent_alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::error_notification | alert::status_notification;
-   error_code error;
-};
-
-
-
error
-
specifies which error the torrent encountered.
-
-
-
-

torrent_need_cert_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This is always posted for SSL torrents. This is a reminder to the client that -the torrent won't work unless torrent_handle::set_ssl_certificate() is called with -a valid certificate. Valid certificates MUST be signed by the SSL certificate -in the .torrent file.

-
-struct torrent_need_cert_alert: torrent_alert
-{
-   virtual bool discardable () const;
-   virtual std::string message () const;
-
-   const static int static_category = alert::status_notification;
-   error_code error;
-};
-
-
-
-

incoming_connection_alert

-

Declared in "libtorrent/alert_types.hpp"

-

The incoming connection alert is posted every time we successfully accept -an incoming connection, through any mean. The most straigh-forward ways -of accepting incoming connections are through the TCP listen socket and -the UDP listen socket for uTP sockets. However, connections may also be -accepted ofer a Socks5 or i2p listen socket, or via a torrent specific -listen socket for SSL torrents.

-
-struct incoming_connection_alert: alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::peer_notification;
-   int socket_type;
-   tcp::endpoint ip;
-};
-
-
-
socket_type
-

tells you what kind of socket the connection was accepted -as:

-
    -
  1. none (no socket instantiated)
  2. -
  3. TCP
  4. -
  5. Socks5
  6. -
  7. HTTP
  8. -
  9. uTP
  10. -
  11. i2p
  12. -
  13. SSL/TCP
  14. -
  15. SSL/Socks5
  16. -
  17. HTTPS (SSL/HTTP)
  18. -
  19. SSL/uTP
  20. -
-
-
-
-
ip
-
is the IP address and port the connection came from.
-
-
-
-

add_torrent_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is always posted when a torrent was attempted to be added -and contains the return status of the add operation. The torrent handle of the new -torrent can be found in the base class' handle member. If adding -the torrent failed, error contains the error code.

-
-struct add_torrent_alert : torrent_alert
-{
-   virtual bool discardable () const;
-   virtual std::string message () const;
-
-   const static int static_category = alert::status_notification;
-   add_torrent_params params;
-   error_code error;
-};
-
-
-
params
-
a copy of the parameters used when adding the torrent, it can be used -to identify which invocation to async_add_torrent() caused this alert.
-
-
-
error
-
set to the error, if one occurred while adding the torrent.
-
-
-
-

state_update_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is only posted when requested by the user, by calling session::post_torrent_updates() -on the session. It contains the torrent status of all torrents that changed -since last time this message was posted. Its category is status_notification, but -it's not subject to filtering, since it's only manually posted anyway.

-
-struct state_update_alert : alert
-{
-   virtual bool discardable () const;
-   virtual std::string message () const;
-
-   const static int static_category = alert::status_notification;
-   std::vector<torrent_status> status;
-};
-
-
-
status
-
contains the torrent status of all torrents that changed since last time -this message was posted. Note that you can map a torrent status to a specific torrent -via its handle member. The receiving end is suggested to have all torrents sorted -by the torrent_handle or hashed by it, for efficient updates.
-
-
-
-

torrent_update_alert

-

Declared in "libtorrent/alert_types.hpp"

-

When a torrent changes its info-hash, this alert is posted. This only happens in very -specific cases. For instance, when a torrent is downloaded from a URL, the true info -hash is not known immediately. First the .torrent file must be downloaded and parsed.

-

Once this download completes, the torrent_update_alert is posted to notify the client -of the info-hash changing.

-
-struct torrent_update_alert : torrent_alert
-{
-   virtual bool discardable () const;
-   virtual std::string message () const;
-
-   const static int static_category = alert::status_notification;
-   sha1_hash old_ih;
-   sha1_hash new_ih;
-};
-
- -
-
old_ih new_ih
-
old_ih and new_ih are the previous and new info-hash for the torrent, respectively.
-
-
-
-

rss_item_alert

-

Declared in "libtorrent/alert_types.hpp"

-

This alert is posted every time a new RSS item (i.e. torrent) is received -from an RSS feed.

-

It is only posted if the rss_notifications category is enabled in the -alert_mask.

-
-struct rss_item_alert : alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::rss_notification;
-   feed_handle handle;
-   feed_item item;
-};
-
-
-
-

dht_error_alert

-

Declared in "libtorrent/alert_types.hpp"

-

posted when something fails in the DHT. This is not necessarily a fatal -error, but it could prevent proper operation

-
-struct dht_error_alert: alert
-{
-   virtual std::string message () const;
-
-   enum op_t
-   {
-      unknown,
-      hostname_lookup,
-   };
-
-   | alert::dht_notification;
-   error_code error;
-   op_t operation;
-};
-
-
-

enum op_t

-

Declared in "libtorrent/alert_types.hpp"

- ----- - - - - - - - - - - - - - - - - -
namevaluedescription
unknown0 
hostname_lookup1 
-
-
error
-
the error code
-
-
-
operation
-
the operation that failed
-
-
-
-
-

dht_immutable_item_alert

-

Declared in "libtorrent/alert_types.hpp"

-

this alert is posted as a response to a call to session::get_item(), -specifically the overload for looking up immutable items in the DHT.

-
-struct dht_immutable_item_alert: alert
-{
-   virtual bool discardable () const;
-   dht_immutable_item_alert (sha1_hash const& t, entry const& i);
-   virtual std::string message () const;
-
-   | alert::dht_notification;
-   sha1_hash target;
-   entry item;
-};
-
-
-
target
-
the target hash of the immutable item. This must -match the sha-1 hash of the bencoded form of item.
-
-
-
item
-
the data for this item
-
-
-
-

dht_mutable_item_alert

-

Declared in "libtorrent/alert_types.hpp"

-

this alert is posted as a response to a call to session::get_item(), -specifically the overload for looking up mutable items in the DHT.

-
-struct dht_mutable_item_alert: alert
-{
-   dht_mutable_item_alert (boost::array<char, 32> k
-      , boost::array<char, 64> sig
-      , boost::uint64_t sequence
-      , std::string const& s
-      , entry const& i);
-   virtual bool discardable () const;
-   virtual std::string message () const;
-
-   | alert::dht_notification;
-   boost::array<char, 32> key;
-   boost::array<char, 64> signature;
-   boost::uint64_t seq;
-   std::string salt;
-   entry item;
-};
-
-
-
key
-
the public key that was looked up
-
-
-
signature
-
the signature of the data. This is not the signature of the -plain encoded form of the item, but it includes the sequence number -and possibly the hash as well. See the dht_store document for more -information. This is primarily useful for echoing back in a store -request.
-
-
-
seq
-
the sequence number of this item
-
-
-
salt
-
the salf, if any, used to lookup and store this item. If no -salt was used, this is an empty string
-
-
-
item
-
the data for this item
-
-
-
-

dht_put_alert

-

Declared in "libtorrent/alert_types.hpp"

-

this is posted when a DHT put operation completes. This is useful if the -client is waiting for a put to complete before shutting down for instance.

-
-struct dht_put_alert: alert
-{
-   virtual std::string message () const;
-
-   const static int static_category = alert::dht_notification;
-   sha1_hash target;
-   boost::array<char, 32> public_key;
-   boost::array<char, 64> signature;
-   std::string salt;
-   boost::uint64_t seq;
-};
-
-
-
target
-
the target hash the item was stored under if this was an immutable -item.
-
- - - -
-
public_key signature salt seq
-
if a mutable item was stored, these are the public key, signature, -salt and sequence number the item was stored under.
-
-
-
-

i2p_alert

-

Declared in "libtorrent/alert_types.hpp"

-

this alert is used to report errors in the i2p SAM connection

-
-struct i2p_alert : alert
-{
-   i2p_alert (error_code const& ec);
-   virtual std::string message () const;
-
-   const static int static_category = alert::error_notification;
-   error_code error;
-};
-
-
-
error
-
the error that occurred in the i2p SAM connection
-
-
-
- - diff --git a/docs/reference-Bencoding.html b/docs/reference-Bencoding.html deleted file mode 100644 index 93da5034d..000000000 --- a/docs/reference-Bencoding.html +++ /dev/null @@ -1,785 +0,0 @@ - - - - - - -Bencoding - - - - - - - - -
-
-
- -
- -
-

Bencoding

- --- - - - - - -
Author:Arvid Norberg, arvid@rasterbar.com
Version:1.0.0
- -

Bencoding is a common representation in bittorrent used for -for dictionary, list, int and string hierarchies. It's used -to encode .torrent files and some messages in the network -protocol. libtorrent also uses it to store settings, resume -data and other state between sessions.

-

Strings in bencoded structures are not necessarily representing -text. Strings are raw byte buffers of a certain length. If a -string is meant to be interpreted as text, it is required to -be UTF-8 encoded. See BEP 3.

-

There are two mechanims to decode bencoded buffers in libtorrent.

-

The most flexible one is bdecode(), which returns a structure -represented by entry. When a buffer is decoded with this function, -it can be discarded. The entry does not contain any references back -to it. This means that bdecode() actually copies all the data out -of the buffer and into its own hierarchy. This makes this -function potentially expensive, if you're parsing large amounts -of data.

-

Another consideration is that bdecode() is a recursive parser. -For this reason, in order to avoid DoS attacks by triggering -a stack overflow, there is a recursion limit. This limit is -a sanity check to make sure it doesn't run the risk of -busting the stack.

-

The second mechanism is lazy_bdecode(), which returns a -bencoded structure represented by lazy_entry. This function -builds a tree that points back into the original buffer. -The returned lazy_entry will not be valid once the buffer -it was parsed out of is discarded.

-

Not only is this function more efficient because of less -memory allocation and data copy, the parser is also not -recursive, which means it probably performs a little bit -better and can have a higher recursion limit on the structures -it's parsing.

-
-

invalid_encoding

-

Declared in "libtorrent/bencode.hpp"

-

thrown by bdecode() if the provided bencoded buffer does not contain -valid encoding.

-
-struct invalid_encoding: std::exception
-{
-   virtual const char* what () const throw();
-};
-
-
-
-

type_error

-

Declared in "libtorrent/entry.hpp"

-

thrown by any accessor function of entry if the accessor -function requires a type different than the actual type -of the entry object.

-
-struct type_error: std::runtime_error
-{
-   type_error (const char* error);
-};
-
-
-
-

entry

-

Declared in "libtorrent/entry.hpp"

-

The entry class represents one node in a bencoded hierarchy. It works as a -variant type, it can be either a list, a dictionary (std::map), an integer -or a string.

-
-class entry
-{
-   data_type type () const;
-   entry (list_type const&);
-   entry (integer_type const&);
-   entry (dictionary_type const&);
-   entry (string_type const&);
-   entry (data_type t);
-   void operator= (string_type const&);
-   void operator= (entry const&);
-   void operator= (integer_type const&);
-   void operator= (lazy_entry const&);
-   void operator= (dictionary_type const&);
-   void operator= (list_type const&);
-   const integer_type& integer () const;
-   const string_type& string () const;
-   const dictionary_type& dict () const;
-   string_type& string ();
-   list_type& list ();
-   dictionary_type& dict ();
-   integer_type& integer ();
-   const list_type& list () const;
-   void swap (entry& e);
-   entry& operator[] (std::string const& key);
-   const entry& operator[] (std::string const& key) const;
-   entry& operator[] (char const* key);
-   const entry& operator[] (char const* key) const;
-   entry const* find_key (char const* key) const;
-   entry* find_key (char const* key);
-   entry* find_key (std::string const& key);
-   entry const* find_key (std::string const& key) const;
-   std::string to_string () const;
-
-   enum data_type
-   {
-      int_t,
-      string_t,
-      list_t,
-      dictionary_t,
-      undefined_t,
-   };
-
-   mutable boost::uint8_t m_type_queried:1;
-};
-
-
-

type()

-
-data_type type () const;
-
-

returns the concrete type of the entry

-
-
-

entry()

-
-entry (list_type const&);
-entry (integer_type const&);
-entry (dictionary_type const&);
-entry (string_type const&);
-
-

constructors directly from a specific type. -The content of the argument is copied into the -newly constructed entry

-
-
-

entry()

-
-entry (data_type t);
-
-

construct an empty entry of the specified type. -see data_type enum.

-
-
-

operator=()

-
-void operator= (string_type const&);
-void operator= (entry const&);
-void operator= (integer_type const&);
-void operator= (lazy_entry const&);
-void operator= (dictionary_type const&);
-void operator= (list_type const&);
-
-

copies the structure of the right hand side into this -entry.

- - - -
-
-

string() dict() integer() list()

-
-const integer_type& integer () const;
-const string_type& string () const;
-const dictionary_type& dict () const;
-string_type& string ();
-list_type& list ();
-dictionary_type& dict ();
-integer_type& integer ();
-const list_type& list () const;
-
-

The integer(), string(), list() and dict() functions -are accessors that return the respective type. If the entry object -isn't of the type you request, the accessor will throw -libtorrent_exception (which derives from std::runtime_error). You -can ask an entry for its type through the type() function.

-

If you want to create an entry you give it the type you want it to -have in its constructor, and then use one of the non-const accessors -to get a reference which you then can assign the value you want it to -have.

-

The typical code to get info from a torrent file will then look like -this:

-
-entry torrent_file;
-// ...
-
-// throws if this is not a dictionary
-entry::dictionary_type const& dict = torrent_file.dict();
-entry::dictionary_type::const_iterator i;
-i = dict.find("announce");
-if (i != dict.end())
-{
-        std::string tracker_url = i->second.string();
-        std::cout << tracker_url << "\n";
-}
-
-

The following code is equivalent, but a little bit shorter:

-
-entry torrent_file;
-// ...
-
-// throws if this is not a dictionary
-if (entry* i = torrent_file.find_key("announce"))
-{
-        std::string tracker_url = i->string();
-        std::cout << tracker_url << "\n";
-}
-
-

To make it easier to extract information from a torrent file, the -class torrent_info exists.

-
-
-

swap()

-
-void swap (entry& e);
-
-

swaps the content of this with e.

-
-
-

operator[]()

-
-entry& operator[] (std::string const& key);
-const entry& operator[] (std::string const& key) const;
-entry& operator[] (char const* key);
-const entry& operator[] (char const* key) const;
-
-

All of these functions requires the entry to be a dictionary, if it -isn't they will throw libtorrent::type_error.

-

The non-const versions of the operator[] will return a reference -to either the existing element at the given key or, if there is no -element with the given key, a reference to a newly inserted element at -that key.

-

The const version of operator[] will only return a reference to an -existing element at the given key. If the key is not found, it will -throw libtorrent::type_error.

-
-
-

find_key()

-
-entry const* find_key (char const* key) const;
-entry* find_key (char const* key);
-entry* find_key (std::string const& key);
-entry const* find_key (std::string const& key) const;
-
-

These functions requires the entry to be a dictionary, if it isn't -they will throw libtorrent::type_error.

-

They will look for an element at the given key in the dictionary, if -the element cannot be found, they will return 0. If an element with -the given key is found, the return a pointer to it.

-
-
-

to_string()

-
-std::string to_string () const;
-
-

returns a pretty-printed string representation -of the bencoded structure, with JSON-style syntax

-
-
-

enum data_type

-

Declared in "libtorrent/entry.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
int_t0 
string_t1 
list_t2 
dictionary_t3 
undefined_t4 
-
-
m_type_queried
-
in debug mode this is set to false by bdecode to indicate that the -program has not yet queried the type of this entry, and sould not -assume that it has a certain type. This is asserted in the accessor -functions. This does not apply if exceptions are used.
-
-
-
-
-

pascal_string

-

Declared in "libtorrent/lazy_entry.hpp"

-

this is a string that is not NULL-terminated. Instead it -comes with a length, specified in bytes. This is particularly -useful when parsing bencoded structures, because strings are -not NULL-terminated internally, and requiring NULL termination -would require copying the string.

-

see lazy_entry::string_pstr().

-
-struct pascal_string
-{
-   pascal_string (char const* p, int l);
-   bool operator< (pascal_string const& rhs) const;
-
-   int len;
-   char const* ptr;
-};
-
-
-

pascal_string()

-
-pascal_string (char const* p, int l);
-
-

construct a string pointing to the characters at p -of length l characters. No NULL termination is required.

-
-
-

operator<()

-
-bool operator< (pascal_string const& rhs) const;
-
-

lexicographical comparison of strings. Order is consisten -with memcmp.

-
-
len
-
the number of characters in the string.
-
-
-
ptr
-
the pointer to the first character in the string. This is -not NULL terminated, but instead consult the len field -to know how many characters follow.
-
-
-
-
-

lazy_entry

-

Declared in "libtorrent/lazy_entry.hpp"

-

this object represent a node in a bencoded structure. It is a variant -type whose concrete type is one of:

-
    -
  1. dictionary (maps strings -> lazy_entry)
  2. -
  3. list (sequence of lazy_entry, i.e. heterogenous)
  4. -
  5. integer
  6. -
  7. string
  8. -
-

There is also a none type, which is used for uninitialized -lazy_entries.

-
-struct lazy_entry
-{
-   entry_type_t type () const;
-   void construct_int (char const* start, int length);
-   boost::int64_t int_value () const;
-   char const* string_ptr () const;
-   char const* string_cstr () const;
-   pascal_string string_pstr () const;
-   std::string string_value () const;
-   int string_length () const;
-   lazy_entry const* dict_find_string (char const* name) const;
-   lazy_entry* dict_find (char const* name);
-   lazy_entry const* dict_find (char const* name) const;
-   pascal_string dict_find_pstr (char const* name) const;
-   std::string dict_find_string_value (char const* name) const;
-   boost::int64_t dict_find_int_value (char const* name, boost::int64_t default_val = 0) const;
-   lazy_entry const* dict_find_int (char const* name) const;
-   lazy_entry const* dict_find_list (char const* name) const;
-   lazy_entry const* dict_find_dict (char const* name) const;
-   std::pair<std::string, lazy_entry const*> dict_at (int i) const;
-   int dict_size () const;
-   lazy_entry* list_at (int i);
-   lazy_entry const* list_at (int i) const;
-   std::string list_string_value_at (int i) const;
-   pascal_string list_pstr_at (int i) const;
-   boost::int64_t list_int_value_at (int i, boost::int64_t default_val = 0) const;
-   int list_size () const;
-   std::pair<char const*, int> data_section () const;
-   void swap (lazy_entry& e);
-
-   enum entry_type_t
-   {
-      none_t,
-      dict_t,
-      list_t,
-      string_t,
-      int_t,
-   };
-};
-
-
-

type()

-
-entry_type_t type () const;
-
-

tells you which specific type this lazy entry has. -See entry_type_t. The type determines which subset of -member functions are valid to use.

-
-
-

construct_int()

-
-void construct_int (char const* start, int length);
-
-

start points to the first decimal digit -length is the number of digits

-
-
-

int_value()

-
-boost::int64_t int_value () const;
-
-

requires the type to be an integer. return the integer value

-
-
-

string_ptr()

-
-char const* string_ptr () const;
-
-

the string is not null-terminated! -use string_length() to determine how many bytes -are part of the string.

-
-
-

string_cstr()

-
-char const* string_cstr () const;
-
-

this will return a null terminated string -it will write to the source buffer!

-
-
-

string_pstr()

-
-pascal_string string_pstr () const;
-
-

if this is a string, returns a pascal_string -representing the string value.

-
-
-

string_value()

-
-std::string string_value () const;
-
-

if this is a string, returns the string as a std::string. -(which requires a copy)

-
-
-

string_length()

-
-int string_length () const;
-
-

if the lazy_entry is a string, returns the -length of the string, in bytes.

- -
-
-

dict_find() dict_find_string()

-
-lazy_entry const* dict_find_string (char const* name) const;
-lazy_entry* dict_find (char const* name);
-lazy_entry const* dict_find (char const* name) const;
-
-

if this is a dictionary, look for a key name, and return -a pointer to its value, or NULL if there is none.

- -
-
-

dict_find_pstr() dict_find_string_value()

-
-pascal_string dict_find_pstr (char const* name) const;
-std::string dict_find_string_value (char const* name) const;
-
-

if this is a dictionary, look for a key name whose value -is a string. If such key exist, return a pointer to -its value, otherwise NULL.

- -
-
-

dict_find_int_value() dict_find_int()

-
-boost::int64_t dict_find_int_value (char const* name, boost::int64_t default_val = 0) const;
-lazy_entry const* dict_find_int (char const* name) const;
-
-

if this is a dictionary, look for a key name whose value -is an int. If such key exist, return a pointer to its value, -otherwise NULL.

- -
-
-

dict_find_list() dict_find_dict()

-
-lazy_entry const* dict_find_list (char const* name) const;
-lazy_entry const* dict_find_dict (char const* name) const;
-
-

these functions require that this is a dictionary. -(this->type() == dict_t). They look for an element with the -specified name in the dictionary. dict_find_dict only -finds dictionaries and dict_find_list only finds lists. -if no key with the corresponding value of the right type is -found, NULL is returned.

-
-
-

dict_at()

-
-std::pair<std::string, lazy_entry const*> dict_at (int i) const;
-
-

if this is a dictionary, return the key value pair at -position i from the dictionary.

-
-
-

dict_size()

-
-int dict_size () const;
-
-

requires that this is a dictionary. return the -number of items in it

-
-
-

list_at()

-
-lazy_entry* list_at (int i);
-lazy_entry const* list_at (int i) const;
-
-

requires that this is a list. return -the item at index i.

- -
-
-

list_string_value_at() list_pstr_at()

-
-std::string list_string_value_at (int i) const;
-pascal_string list_pstr_at (int i) const;
-
-

these functions require this to have the type list. -(this->type() == list_t). list_string_value_at returns -the string at index i. list_pstr_at -returns a pascal_string of the string value at index i. -if the element at i is not a string, an empty string -is returned.

-
-
-

list_int_value_at()

-
-boost::int64_t list_int_value_at (int i, boost::int64_t default_val = 0) const;
-
-

this function require this to have the type list. -(this->type() == list_t). returns the integer value at -index i. If the element at i is not an integer -default_val is returned, which defaults to 0.

-
-
-

list_size()

-
-int list_size () const;
-
-

if this is a list, return the number of items in it.

-
-
-

data_section()

-
-std::pair<char const*, int> data_section () const;
-
-

returns pointers into the source buffer where -this entry has its bencoded data

-
-
-

swap()

-
-void swap (lazy_entry& e);
-
-

swap values of this and e.

-
-
-

enum entry_type_t

-

Declared in "libtorrent/lazy_entry.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
none_t0 
dict_t1 
list_t2 
string_t3 
int_t4 
- -
-
-

bdecode() bencode()

-

Declared in "libtorrent/bencode.hpp"

-
-template<class InIt> entry bdecode (InIt start, InIt end);
-template<class OutIt> int bencode (OutIt out, const entry& e);
-template<class InIt> entry bdecode (InIt start, InIt end, int& len);
-
-

These functions will encode data to bencoded or decode bencoded data.

-

If possible, lazy_bdecode() should be preferred over bdecode().

-

The entry class is the internal representation of the bencoded data -and it can be used to retrieve information, an entry can also be build by -the program and given to bencode() to encode it into the OutIt -iterator.

-

The OutIt and InIt are iterators -(InputIterator and OutputIterator respectively). They -are templates and are usually instantiated as ostream_iterator, -back_insert_iterator or istream_iterator. These -functions will assume that the iterator refers to a character -(char). So, if you want to encode entry e into a buffer -in memory, you can do it like this:

-
-std::vector<char> buffer;
-bencode(std::back_inserter(buf), e);
-
-

If you want to decode a torrent file from a buffer in memory, you can do it like this:

-
-std::vector<char> buffer;
-// ...
-entry e = bdecode(buf.begin(), buf.end());
-
-

Or, if you have a raw char buffer:

-
-const char* buf;
-// ...
-entry e = bdecode(buf, buf + data_size);
-
-

Now we just need to know how to retrieve information from the entry.

-

If bdecode() encounters invalid encoded data in the range given to it -it will throw libtorrent_exception.

-
-
-

operator<<()

-

Declared in "libtorrent/entry.hpp"

-
-inline std::ostream& operator<< (std::ostream& os, const entry& e);
-
-

prints the bencoded structure to the ostream as a JSON-style structure.

-
-
-

lazy_bdecode()

-

Declared in "libtorrent/lazy_entry.hpp"

-
-int lazy_bdecode (char const* start, char const* end
-   , lazy_entry& ret, error_code& ec, int* error_pos = 0
-   , int depth_limit = 1000, int item_limit = 1000000);
-
-

This function decodes bencoded data.

-

Whenever possible, lazy_bdecode() should be preferred over bdecode(). -It is more efficient and more secure. It supports having constraints on the -amount of memory is consumed by the parser.

-

lazy refers to the fact that it doesn't copy any actual data out of the -bencoded buffer. It builds a tree of lazy_entry which has pointers into -the bencoded buffer. This makes it very fast and efficient. On top of that, -it is not recursive, which saves a lot of stack space when parsing deeply -nested trees. However, in order to protect against potential attacks, the -depth_limit and item_limit control how many levels deep the tree is -allowed to get. With recursive parser, a few thousand levels would be enough -to exhaust the threads stack and terminate the process. The item_limit -protects against very large structures, not necessarily deep. Each bencoded -item in the structure causes the parser to allocate some amount of memory, -this memory is constant regardless of how much data actually is stored in -the item. One potential attack is to create a bencoded list of hundreds of -thousands empty strings, which would cause the parser to allocate a significant -amount of memory, perhaps more than is available on the machine, and effectively -provide a denial of service. The default item limit is set as a reasonable -upper limit for desktop computers. Very few torrents have more items in them. -The limit corresponds to about 25 MB, which might be a bit much for embedded -systems.

-

start and end defines the bencoded buffer to be decoded. ret is -the lazy_entry which is filled in with the whole decoded tree. ec -is a reference to an error_code which is set to describe the error encountered -in case the function fails. error_pos is an optional pointer to an int, -which will be set to the byte offset into the buffer where an error occurred, -in case the function fails.

-
-
-

print_entry()

-

Declared in "libtorrent/lazy_entry.hpp"

-
-std::string print_entry (lazy_entry const& e
-   , bool single_line = false, int indent = 0);
-
-

print the bencoded structure in a human-readable format to a stting -that's returned.

-
-
-
- - diff --git a/docs/reference-Core.html b/docs/reference-Core.html deleted file mode 100644 index 033f70daf..000000000 --- a/docs/reference-Core.html +++ /dev/null @@ -1,3550 +0,0 @@ - - - - - - -Core - - - - - - - - -
-
-
- -
- -
-

Core

- --- - - - - - -
Author:Arvid Norberg, arvid@rasterbar.com
Version:1.0.0
- -
-

disk_buffer_holder

-

Declared in "libtorrent/disk_buffer_holder.hpp"

-

The disk buffer holder acts like a scoped_ptr that frees a disk buffer -when it's destructed, unless it's released. release returns the disk -buffer and transferres ownership and responsibility to free it to the caller.

-

A disk buffer is freed by passing it to session_impl::free_disk_buffer().

-

buffer() returns the pointer without transferring responsibility. If -this buffer has been released, buffer() will return 0.

-
-struct disk_buffer_holder
-{
-   disk_buffer_holder (disk_buffer_pool& disk_pool, char* buf);
-   ~disk_buffer_holder ();
-   char* release ();
-   char* get () const;
-   void reset (char* buf = 0);
-   void swap (disk_buffer_holder& h);
-};
-
-
-

disk_buffer_holder()

-
-disk_buffer_holder (disk_buffer_pool& disk_pool, char* buf);
-
-

construct a buffer holder that will free the held buffer -using a disk buffer pool directly (there's only one -disk_buffer_pool per session)

-
-
-

~disk_buffer_holder()

-
-~disk_buffer_holder ();
-
-

frees any unreleased disk buffer held by this object

-
-
-

release()

-
-char* release ();
-
-

return the held disk buffer and clear it from the -holder. The responsibility to free it is passed on -to the caller

-
-
-

get()

-
-char* get () const;
-
-

return a pointer to the held buffer

-
-
-

reset()

-
-void reset (char* buf = 0);
-
-

set the holder object to hold the specified buffer -(or NULL by default). If it's already holding a -disk buffer, it will first be freed.

-
-
-

swap()

-
-void swap (disk_buffer_holder& h);
-
-

swap pointers of two disk buffer holders.

-
-
-
-

hasher

-

Declared in "libtorrent/hasher.hpp"

-

this is a SHA-1 hash class.

-

You use it by first instantiating it, then call update() to feed it -with data. i.e. you don't have to keep the entire buffer of which you want to -create the hash in memory. You can feed the hasher parts of it at a time. When -You have fed the hasher with all the data, you call final() and it -will return the sha1-hash of the data.

-

The constructor that takes a char const* and an integer will construct the -sha1 context and feed it the data passed in.

-

If you want to reuse the hasher object once you have created a hash, you have to -call reset() to reinitialize it.

-

The sha1-algorithm used was implemented by Steve Reid and released as public domain. -For more info, see src/sha1.cpp.

-
-class hasher
-{
-   hasher ();
-   hasher (const char* data, int len);
-   hasher& operator= (hasher const& h);
-   hasher (hasher const& h);
-   hasher& update (std::string const& data);
-   hasher& update (const char* data, int len);
-   sha1_hash final ();
-   void reset ();
-   ~hasher ();
-};
-
-
-

hasher()

-
-hasher (const char* data, int len);
-
-

this is the same as default constructing followed by a call to -update(data, len).

-
-
-

update()

-
-hasher& update (std::string const& data);
-hasher& update (const char* data, int len);
-
-

append the following bytes to what is being hashed

-
-
-

final()

-
-sha1_hash final ();
-
-

returns the SHA-1 digest of the buffers previously passed to -update() and the hasher constructor.

-
-
-

reset()

-
-void reset ();
-
-

restore the hasher state to be as if the hasher has just been -default constructed.

-
-
-
-

peer_info

-

Declared in "libtorrent/peer_info.hpp"

-

holds information and statistics about one peer -that libtorrent is connected to

-
-struct peer_info
-{
-   enum peer_flags_t
-   {
-      interesting,
-      choked,
-      remote_interested,
-      remote_choked,
-      supports_extensions,
-      local_connection,
-      handshake,
-      connecting,
-      queued,
-      on_parole,
-      seed,
-      optimistic_unchoke,
-      snubbed,
-      upload_only,
-      endgame_mode,
-      holepunched,
-      i2p_socket,
-      utp_socket,
-      ssl_socket,
-      rc4_encrypted,
-      plaintext_encrypted,
-   };
-
-   enum peer_source_flags
-   {
-      tracker,
-      dht,
-      pex,
-      lsd,
-      resume_data,
-      incoming,
-   };
-
-   enum bw_state
-   {
-      bw_idle,
-      bw_limit,
-      bw_network,
-      bw_disk,
-   };
-
-   enum connection_type_t
-   {
-      standard_bittorrent,
-      web_seed,
-      http_seed,
-   };
-
-   boost::uint32_t flags;
-   int source;
-   char read_state;
-   char write_state;
-   tcp::endpoint ip;
-   int up_speed;
-   int down_speed;
-   int payload_up_speed;
-   int payload_down_speed;
-   size_type total_download;
-   size_type total_upload;
-   peer_id pid;
-   bitfield pieces;
-   int upload_limit;
-   int download_limit;
-   time_duration last_request;
-   time_duration last_active;
-   time_duration download_queue_time;
-   int queue_bytes;
-   int request_timeout;
-   int send_buffer_size;
-   int used_send_buffer;
-   int receive_buffer_size;
-   int used_receive_buffer;
-   int num_hashfails;
-   char country[2];
-   std::string inet_as_name;
-   int inet_as;
-   int download_queue_length;
-   int timed_out_requests;
-   int busy_requests;
-   int requests_in_buffer;
-   int target_dl_queue_length;
-   int upload_queue_length;
-   int failcount;
-   int downloading_piece_index;
-   int downloading_block_index;
-   int downloading_progress;
-   int downloading_total;
-   std::string client;
-   int connection_type;
-   int remote_dl_rate;
-   int pending_disk_bytes;
-   int send_quota;
-   int receive_quota;
-   int rtt;
-   int num_pieces;
-   int download_rate_peak;
-   int upload_rate_peak;
-   int progress_ppm;
-   int estimated_reciprocation_rate;
-   tcp::endpoint local_endpoint;
-};
-
-
-

enum peer_flags_t

-

Declared in "libtorrent/peer_info.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
interesting1we are interested in pieces from this peer.
choked2we have choked this peer.
remote_interested4the peer is interested in us
remote_choked8the peer has choked us.
supports_extensions16means that this peer supports the -extension protocol.
local_connection32The connection was initiated by us, the peer has a -listen port open, and that port is the same as in the -address of this peer. If this flag is not set, this -peer connection was opened by this peer connecting to -us.
handshake64The connection is opened, and waiting for the -handshake. Until the handshake is done, the peer -cannot be identified.
connecting128The connection is in a half-open state (i.e. it is -being connected).
queued256The connection is currently queued for a connection -attempt. This may happen if there is a limit set on -the number of half-open TCP connections.
on_parole512The peer has participated in a piece that failed the -hash check, and is now "on parole", which means we're -only requesting whole pieces from this peer until -it either fails that piece or proves that it doesn't -send bad data.
seed1024This peer is a seed (it has all the pieces).
optimistic_unchoke2048This peer is subject to an optimistic unchoke. It has -been unchoked for a while to see if it might unchoke -us in return an earn an upload/unchoke slot. If it -doesn't within some period of time, it will be choked -and another peer will be optimistically unchoked.
snubbed4096This peer has recently failed to send a block within -the request timeout from when the request was sent. -We're currently picking one block at a time from this -peer.
upload_only8192This peer has either explicitly (with an extension) -or implicitly (by becoming a seed) told us that it -will not downloading anything more, regardless of -which pieces we have.
endgame_mode16384This means the last time this peer picket a piece, -it could not pick as many as it wanted because there -were not enough free ones. i.e. all pieces this peer -has were already requested from other peers.
holepunched32768This flag is set if the peer was in holepunch mode -when the connection succeeded. This typically only -happens if both peers are behind a NAT and the peers -connect via the NAT holepunch mechanism.
i2p_socket65536indicates that this socket is runnin on top of the -I2P transport.
utp_socket131072indicates that this socket is a uTP socket
ssl_socket262144indicates that this socket is running on top of an SSL -(TLS) channel
rc4_encrypted1048576this connection is obfuscated with RC4
plaintext_encrypted2097152the handshake of this connection was obfuscated -with a diffie-hellman exchange
-
-
-

enum peer_source_flags

-

Declared in "libtorrent/peer_info.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
tracker1The peer was received from the tracker.
dht2The peer was received from the kademlia DHT.
pex4The peer was received from the peer exchange -extension.
lsd8The peer was received from the local service -discovery (The peer is on the local network).
resume_data16The peer was added from the fast resume data.
incoming32we received an incoming connection from this peer
-
-
-

enum bw_state

-

Declared in "libtorrent/peer_info.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
bw_idle0The peer is not waiting for any external events to -send or receive data.
bw_limit1The peer is waiting for the rate limiter.
bw_network2The peer has quota and is currently waiting for a -network read or write operation to complete. This is -the state all peers are in if there are no bandwidth -limits.
bw_disk4The peer is waiting for the disk I/O thread to catch -up writing buffers to disk before downloading more.
-
-
-

enum connection_type_t

-

Declared in "libtorrent/peer_info.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
standard_bittorrent0Regular bittorrent connection over TCP
web_seed1HTTP connection using the BEP 19 protocol
http_seed2HTTP connection using the BEP 17 protocol
-
-
flags
-
tells you in which state the peer is in. It is set to -any combination of the peer_flags_t enum.
-
-
-
source
-
a combination of flags describing from which sources this peer -was received.
-
- -
-
read_state write_state
-
bitmasks indicating what state this peer is in with regards to sending -and receiving data. The states are declared in the bw_state enum.
-
-
-
ip
-
the IP-address to this peer. The type is an asio endpoint. For -more info, see the asio documentation.
-
- -
-
up_speed down_speed
-
the current upload and download speed we have to and from this peer -(including any protocol messages). updated about once per second
-
- -
-
payload_up_speed payload_down_speed
-
The transfer rates of payload data only updated about once per second
-
- -
-
total_download total_upload
-
the total number of bytes downloaded from and uploaded to this peer. -These numbers do not include the protocol chatter, but only the -payload data.
-
-
-
pid
-
the peer's id as used in the bit torrent protocol. This id can be used -to extract 'fingerprints' from the peer. Sometimes it can tell you -which client the peer is using. See identify_client()_
-
-
-
pieces
-
a bitfield, with one bit per piece in the torrent. -Each bit tells you if the peer has that piece (if it's set to 1) -or if the peer miss that piece (set to 0).
-
- -
-
upload_limit download_limit
-
the number of bytes per second we are allowed to send to or receive -from this peer. It may be -1 if there's no local limit on the peer. -The global limit and the torrent limit may also be enforced.
-
- -
-
last_request last_active
-
the time since we last sent a request -to this peer and since any transfer occurred with this peer
-
- -
-
download_queue_time queue_bytes
-
the time until all blocks in the request -queue will be d
-
-
-
request_timeout
-
the number of seconds until the current front piece request will time -out. This timeout can be adjusted through -session_settings::request_timeout. --1 means that there is not outstanding request.
-
- -
-
send_buffer_size used_send_buffer
-
the number of bytes allocated -and used for the peer's send buffer, respectively.
-
- -
-
receive_buffer_size used_receive_buffer
-
the number of bytes -allocated and used as receive buffer, respectively.
-
-
-
num_hashfails
-
the number of pieces this peer has participated in -sending us that turned out to fail the hash check.
-
-
-
country[2]
-
the two letter ISO 3166 country code for the country the peer is -connected from. If the country hasn't been resolved yet, both chars -are set to 0. If the resolution failed for some reason, the field is -set to "--". If the resolution service returns an invalid country -code, it is set to "!!". The countries.nerd.dk service is used to -look up countries. This field will remain set to 0 unless the torrent -is set to resolve countries, see resolve_countries().
-
-
-
inet_as_name
-
the name of the AS this peer is located in. This might be -an empty string if there is no name in the geo ip database.
-
-
-
inet_as
-
the AS number the peer is located in.
-
-
-
download_queue_length
-
this is the number of requests -we have sent to this peer -that we haven't got a response -for yet
-
-
-
timed_out_requests
-
the number of block requests that have -timed out, and are still in the download -queue
-
-
-
busy_requests
-
the number of busy requests in the download -queue. A budy request is a request for a block -we've also requested from a different peer
-
-
-
requests_in_buffer
-
the number of requests messages that are currently in the -send buffer waiting to be sent.
-
-
-
target_dl_queue_length
-
the number of requests that is -tried to be maintained (this is -typically a function of download speed)
-
-
-
upload_queue_length
-
the number of piece-requests we have received from this peer -that we haven't answered with a piece yet.
-
-
-
failcount
-
the number of times this peer has "failed". i.e. failed to connect or -disconnected us. The failcount is decremented when we see this peer in -a tracker response or peer exchange message.
-
- - - -
-
downloading_piece_index downloading_block_index downloading_progress downloading_total
-
You can know which piece, and which part of that piece, that is -currently being downloaded from a specific peer by looking at these -four members. downloading_piece_index is the index of the piece -that is currently being downloaded. This may be set to -1 if there's -currently no piece downloading from this peer. If it is >= 0, the -other three members are valid. downloading_block_index is the -index of the block (or sub-piece) that is being downloaded. -downloading_progress is the number of bytes of this block we have -received from the peer, and downloading_total is the total number -of bytes in this block.
-
-
-
client
-
a string describing the software at the other end of the connection. -In some cases this information is not available, then it will contain -a string that may give away something about which software is running -in the other end. In the case of a web seed, the server type and -version will be a part of this string.
-
-
-
connection_type
-
the kind of connection this peer uses. See connection_type_t.
-
-
-
remote_dl_rate
-
an estimate of the rate this peer is downloading at, in -bytes per second.
-
-
-
pending_disk_bytes
-
the number of bytes this peer has pending in the disk-io thread. -Downloaded and waiting to be written to disk. This is what is capped -by session_settings::max_queued_disk_bytes.
-
- -
-
send_quota receive_quota
-
the number of bytes this peer has been assigned to be allowed to send -and receive until it has to request more quota from the bandwidth -manager.
-
-
-
rtt
-
an estimated round trip time to this peer, in milliseconds. It is -estimated by timing the the tcp connect(). It may be 0 for -incoming connections.
-
-
-
num_pieces
-
the number of pieces this peer has.
-
- -
-
download_rate_peak upload_rate_peak
-
the highest download and upload rates seen on this connection. They -are given in bytes per second. This number is reset to 0 on reconnect.
-
-
-
progress_ppm
-
indicates the download progress of the peer in the range [0, 1000000] -(parts per million).
-
-
-
estimated_reciprocation_rate
-
this is an estimation of the upload rate, to this peer, where it will -unchoke us. This is a coarse estimation based on the rate at which -we sent right before we were choked. This is primarily used for the -bittyrant choking algorithm.
-
-
-
local_endpoint
-
the IP and port pair the socket is bound to locally. i.e. the IP -address of the interface it's going out over. This may be useful for -multi-homed clients with multiple interfaces to the internet.
-
-
-
-
-

peer_request

-

Declared in "libtorrent/peer_request.hpp"

-

represents a byte range within a piece. Internally this is -is used for incoming piece requests.

-
-struct peer_request
-{
-   bool operator== (peer_request const& r) const;
-
-   int piece;
-   int start;
-   int length;
-};
-
-
-

operator==()

-
-bool operator== (peer_request const& r) const;
-
-

returns true if the right hand side peer_request refers to the same -range as this does.

-
-
piece
-
the index of the piece in which the range starts.
-
-
-
start
-
the offset within that piece where the range starts.
-
-
-
length
-
the size of the range, in bytes.
-
-
-
-
-

block_info

-

Declared in "libtorrent/torrent_handle.hpp"

-

holds the state of a block in a piece. Who we requested -it from and how far along we are at downloading it.

-
-struct block_info
-{
-   void set_peer (tcp::endpoint const& ep);
-   tcp::endpoint peer () const;
-
-   enum block_state_t
-   {
-      none,
-      requested,
-      writing,
-      finished,
-   };
-
-   unsigned bytes_progress:15;
-   unsigned block_size:15;
-   unsigned state:2;
-   unsigned num_peers:14;
-};
-
- -
-

set_peer() peer()

-
-void set_peer (tcp::endpoint const& ep);
-tcp::endpoint peer () const;
-
-

The peer is the ip address of the peer this block was downloaded from.

-
-
-

enum block_state_t

-

Declared in "libtorrent/torrent_handle.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
none0This block has not been downloaded or requested form any peer.
requested1The block has been requested, but not completely downloaded yet.
writing2The block has been downloaded and is currently queued for being -written to disk.
finished3The block has been written to disk.
-
-
bytes_progress
-
the number of bytes that have been received for this block
-
-
-
block_size
-
the total number of bytes in this block.
-
-
-
state
-
the state this block is in (see block_state_t)
-
-
-
num_peers
-
the number of peers that is currently requesting this block. Typically -this is 0 or 1, but at the end of the torrent blocks may be requested -by more peers in parallel to speed things up.
-
-
-
-
-

partial_piece_info

-

Declared in "libtorrent/torrent_handle.hpp"

-

This class holds information about pieces that have outstanding requests -or outstanding writes

-
-struct partial_piece_info
-{
-   enum state_t
-   {
-      none,
-      slow,
-      medium,
-      fast,
-   };
-
-   int piece_index;
-   int blocks_in_piece;
-   int finished;
-   int writing;
-   int requested;
-   block_info* blocks;
-   state_t piece_state;
-};
-
-
-

enum state_t

-

Declared in "libtorrent/torrent_handle.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
none0 
slow1 
medium2 
fast3 
-
-
piece_index
-
the index of the piece in question. blocks_in_piece is the number -of blocks in this particular piece. This number will be the same for -most pieces, but -the last piece may have fewer blocks than the standard pieces.
-
-
-
blocks_in_piece
-
the number of blocks in this piece
-
-
-
finished
-
the number of blocks that are in the finished state
-
-
-
writing
-
the number of blocks that are in the writing state
-
-
-
requested
-
the number of blocks that are in the requested state
-
-
-
blocks
-

this is an array of blocks_in_piece number of -items. One for each block in the piece.

-
-

Warning

-

This is a pointer that points to an array -that's owned by the session object. The next time -get_download_queue() is called, it will be invalidated.

-
-
-
-
-
piece_state
-

the download speed class this piece falls into. -this is used internally to cluster peers of the same -speed class together when requesting blocks.

-

set to either fast, medium, slow or none. It tells -which download rate category the peers downloading this piece falls -into. none means that no peer is currently downloading any part of -the piece. Peers prefer picking pieces from the same category as -themselves. The reason for this is to keep the number of partially -downloaded pieces down. Pieces set to none can be converted into -any of fast, medium or slow as soon as a peer want to -download from it.

-
-
-
-
-
-

torrent_handle

-

Declared in "libtorrent/torrent_handle.hpp"

-

You will usually have to store your torrent handles somewhere, since it's -the object through which you retrieve information about the torrent and -aborts the torrent.

-
-

Warning

-

Any member function that returns a value or fills in a value has to be -made synchronously. This means it has to wait for the main thread to -complete the query before it can return. This might potentially be -expensive if done from within a GUI thread that needs to stay -responsive. Try to avoid quering for information you don't need, and -try to do it in as few calls as possible. You can get most of the -interesting information about a torrent from the -torrent_handle::status() call.

-
-

The default constructor will initialize the handle to an invalid state. -Which means you cannot perform any operation on it, unless you first -assign it a valid handle. If you try to perform any operation on an -uninitialized handle, it will throw invalid_handle.

-
-

Warning

-

All operations on a torrent_handle may throw libtorrent_exception -exception, in case the handle is no longer refering to a torrent. -There is one exception is_valid() will never throw. Since the torrents -are processed by a background thread, there is no guarantee that a -handle will remain valid between two calls.

-
-
-struct torrent_handle
-{
-   torrent_handle ();
-   void add_piece (int piece, char const* data, int flags = 0) const;
-   void read_piece (int piece) const;
-   bool have_piece (int piece) const;
-   void get_peer_info (std::vector<peer_info>& v) const;
-   torrent_status status (boost::uint32_t flags = 0xffffffff) const;
-   void get_download_queue (std::vector<partial_piece_info>& queue) const;
-   void reset_piece_deadline (int index) const;
-   void clear_piece_deadlines () const;
-   void set_piece_deadline (int index, int deadline, int flags = 0) const;
-   void set_priority (int prio) const;
-   void file_progress (std::vector<size_type>& progress, int flags = 0) const;
-   void clear_error () const;
-   std::vector<announce_entry> trackers () const;
-   void replace_trackers (std::vector<announce_entry> const&) const;
-   void add_tracker (announce_entry const&) const;
-   void add_url_seed (std::string const& url) const;
-   void remove_url_seed (std::string const& url) const;
-   std::set<std::string> url_seeds () const;
-   void add_http_seed (std::string const& url) const;
-   void remove_http_seed (std::string const& url) const;
-   std::set<std::string> http_seeds () const;
-   void add_extension (
-      boost::function<boost::shared_ptr<torrent_plugin>(torrent*, void*)> const& ext
-      , void* userdata = 0);
-   bool set_metadata (char const* metadata, int size) const;
-   bool is_valid () const;
-   void pause (int flags = 0) const;
-   void resume () const;
-   void set_upload_mode (bool b) const;
-   void set_share_mode (bool b) const;
-   void flush_cache () const;
-   void apply_ip_filter (bool b) const;
-   void force_recheck () const;
-   void save_resume_data (int flags = 0) const;
-   bool need_save_resume_data () const;
-   void auto_managed (bool m) const;
-   void queue_position_down () const;
-   void queue_position_top () const;
-   int queue_position () const;
-   void queue_position_bottom () const;
-   void queue_position_up () const;
-   void resolve_countries (bool r);
-   bool resolve_countries () const;
-   void set_ssl_certificate (std::string const& certificate
-      , std::string const& private_key
-      , std::string const& dh_params
-      , std::string const& passphrase = "");
-   void set_ssl_certificate_buffer (std::string const& certificate
-      , std::string const& private_key
-      , std::string const& dh_params);
-   storage_interface* get_storage_impl () const;
-   boost::intrusive_ptr<torrent_info const> torrent_file () const;
-   void use_interface (const char* net_interface) const;
-   void piece_availability (std::vector<int>& avail) const;
-   int piece_priority (int index) const;
-   void piece_priority (int index, int priority) const;
-   void prioritize_pieces (std::vector<int> const& pieces) const;
-   std::vector<int> piece_priorities () const;
-   int file_priority (int index) const;
-   void prioritize_files (std::vector<int> const& files) const;
-   void file_priority (int index, int priority) const;
-   std::vector<int> file_priorities () const;
-   void force_reannounce (int seconds = 0, int tracker_index = -1) const;
-   void force_dht_announce () const;
-   void scrape_tracker () const;
-   int upload_limit () const;
-   int download_limit () const;
-   void set_upload_limit (int limit) const;
-   void set_download_limit (int limit) const;
-   void set_sequential_download (bool sd) const;
-   void connect_peer (tcp::endpoint const& adr, int source = 0) const;
-   int max_uploads () const;
-   void set_max_uploads (int max_uploads) const;
-   int max_connections () const;
-   void set_max_connections (int max_connections) const;
-   void set_tracker_login (std::string const& name
-      , std::string const& password) const;
-   void move_storage (std::string const& save_path, int flags = 0) const;
-   void rename_file (int index, std::string const& new_name) const;
-   void super_seeding (bool on) const;
-   sha1_hash info_hash () const;
-   bool operator!= (const torrent_handle& h) const;
-   bool operator< (const torrent_handle& h) const;
-   bool operator== (const torrent_handle& h) const;
-   boost::shared_ptr<torrent> native_handle () const;
-
-   enum flags_t
-   {
-      overwrite_existing,
-   };
-
-   enum status_flags_t
-   {
-      query_distributed_copies,
-      query_accurate_download_counters,
-      query_last_seen_complete,
-      query_pieces,
-      query_verified_pieces,
-      query_torrent_file,
-      query_name,
-      query_save_path,
-   };
-
-   enum deadline_flags
-   {
-      alert_when_available,
-   };
-
-   enum file_progress_flags_t
-   {
-      piece_granularity,
-   };
-
-   enum pause_flags_t
-   {
-      graceful_pause,
-   };
-
-   enum save_resume_flags_t
-   {
-      flush_disk_cache,
-      save_info_dict,
-   };
-};
-
-
-

torrent_handle()

-
-torrent_handle ();
-
-

constructs a torrent handle that does not refer to a torrent. -i.e. is_valid() will return false.

-
-
-

add_piece()

-
-void add_piece (int piece, char const* data, int flags = 0) const;
-
-

This function will write data to the storage as piece piece, -as if it had been downloaded from a peer. data is expected to -point to a buffer of as many bytes as the size of the specified piece. -The data in the buffer is copied and passed on to the disk IO thread -to be written at a later point.

-

By default, data that's already been downloaded is not overwritten by -this buffer. If you trust this data to be correct (and pass the piece -hash check) you may pass the overwrite_existing flag. This will -instruct libtorrent to overwrite any data that may already have been -downloaded with this data.

-

Since the data is written asynchronously, you may know that is passed -or failed the hash check by waiting for piece_finished_alert or -hash_failed_alert.

-
-
-

read_piece()

-
-void read_piece (int piece) const;
-
-

This function starts an asynchronous read operation of the specified -piece from this torrent. You must have completed the download of the -specified piece before calling this function.

-

When the read operation is completed, it is passed back through an -alert, read_piece_alert. Since this alert is a reponse to an explicit -call, it will always be posted, regardless of the alert mask.

-

Note that if you read multiple pieces, the read operations are not -guaranteed to finish in the same order as you initiated them.

-
-
-

have_piece()

-
-bool have_piece (int piece) const;
-
-

Returns true if this piece has been completely downloaded, and false -otherwise.

-
-
-

get_peer_info()

-
-void get_peer_info (std::vector<peer_info>& v) const;
-
-

takes a reference to a vector that will be cleared and filled with one -entry for each peer connected to this torrent, given the handle is -valid. If the torrent_handle is invalid, it will throw -libtorrent_exception exception. Each entry in the vector contains -information about that particular peer. See peer_info.

-
-
-

status()

-
-torrent_status status (boost::uint32_t flags = 0xffffffff) const;
-
-

status() will return a structure with information about the status -of this torrent. If the torrent_handle is invalid, it will throw -libtorrent_exception exception. See torrent_status. The flags -argument filters what information is returned in the torrent_status. -Some information in there is relatively expensive to calculate, and if -you're not interested in it (and see performance issues), you can -filter them out.

-

By default everything is included. The flags you can use to decide -what to include are defined in the status_flags_t enum.

-
-
-

get_download_queue()

-
-void get_download_queue (std::vector<partial_piece_info>& queue) const;
-
-

get_download_queue() takes a non-const reference to a vector which -it will fill with information about pieces that are partially -downloaded or not downloaded at all but partially requested. See -partial_piece_info for the fields in the returned vector.

- - -
-
-

clear_piece_deadlines() reset_piece_deadline() set_piece_deadline()

-
-void reset_piece_deadline (int index) const;
-void clear_piece_deadlines () const;
-void set_piece_deadline (int index, int deadline, int flags = 0) const;
-
-

This function sets or resets the deadline associated with a specific -piece index (index). libtorrent will attempt to download this -entire piece before the deadline expires. This is not necessarily -possible, but pieces with a more recent deadline will always be -prioritized over pieces with a deadline further ahead in time. The -deadline (and flags) of a piece can be changed by calling this -function again.

-

The flags parameter can be used to ask libtorrent to send an alert -once the piece has been downloaded, by passing alert_when_available. -When set, the read_piece_alert alert will be delivered, with the piece -data, when it's downloaded.

-

If the piece is already downloaded when this call is made, nothing -happens, unless the alert_when_available flag is set, in which case it -will do the same thing as calling read_piece() for index.

-

deadline is the number of milliseconds until this piece should be -completed.

-

reset_piece_deadline removes the deadline from the piece. If it -hasn't already been downloaded, it will no longer be considered a -priority.

-

clear_piece_deadlines() removes deadlines on all pieces in -the torrent. As if reset_piece_deadline() was called on all pieces.

-
-
-

set_priority()

-
-void set_priority (int prio) const;
-
-

This sets the bandwidth priority of this torrent. The priority of a -torrent determines how much bandwidth its peers are assigned when -distributing upload and download rate quotas. A high number gives more -bandwidth. The priority must be within the range [0, 255].

-

The default priority is 0, which is the lowest priority.

-

To query the priority of a torrent, use the -torrent_handle::status() call.

-

Torrents with higher priority will not nececcarily get as much -bandwidth as they can consume, even if there's is more quota. Other -peers will still be weighed in when bandwidth is being distributed. -With other words, bandwidth is not distributed strictly in order of -priority, but the priority is used as a weight.

-

Peers whose Torrent has a higher priority will take precedence when -distributing unchoke slots. This is a strict prioritization where -every interested peer on a high priority torrent will be unchoked -before any other, lower priority, torrents have any peers unchoked.

-
-
-

file_progress()

-
-void file_progress (std::vector<size_type>& progress, int flags = 0) const;
-
-

This function fills in the supplied vector with the the number of -bytes downloaded of each file in this torrent. The progress values are -ordered the same as the files in the torrent_info. This operation is -not very cheap. Its complexity is O(n + mj). Where n is the number -of files, m is the number of downloading pieces and j is the -number of blocks in a piece.

-

The flags parameter can be used to specify the granularity of the -file progress. If left at the default value of 0, the progress will be -as accurate as possible, but also more expensive to calculate. If -torrent_handle::piece_granularity is specified, the progress will -be specified in piece granularity. i.e. only pieces that have been -fully downloaded and passed the hash check count. When specifying -piece granularity, the operation is a lot cheaper, since libtorrent -already keeps track of this internally and no calculation is required.

-
-
-

clear_error()

-
-void clear_error () const;
-
-

If the torrent is in an error state (i.e. torrent_status::error is -non-empty), this will clear the error and start the torrent again.

- - -
-
-

add_tracker() replace_trackers() trackers()

-
-std::vector<announce_entry> trackers () const;
-void replace_trackers (std::vector<announce_entry> const&) const;
-void add_tracker (announce_entry const&) const;
-
-

trackers() will return the list of trackers for this torrent. The -announce entry contains both a string url which specify the -announce url for the tracker as well as an int tier, which is -specifies the order in which this tracker is tried. If you want -libtorrent to use another list of trackers for this torrent, you can -use replace_trackers() which takes a list of the same form as the -one returned from trackers() and will replace it. If you want an -immediate effect, you have to call force_reannounce(). See -announce_entry.

-

add_tracker() will look if the specified tracker is already in the -set. If it is, it doesn't do anything. If it's not in the current set -of trackers, it will insert it in the tier specified in the -announce_entry.

-

The updated set of trackers will be saved in the resume data, and when -a torrent is started with resume data, the trackers from the resume -data will replace the original ones.

- - -
-
-

url_seeds() add_url_seed() remove_url_seed()

-
-void add_url_seed (std::string const& url) const;
-void remove_url_seed (std::string const& url) const;
-std::set<std::string> url_seeds () const;
-
-

add_url_seed() adds another url to the torrent's list of url -seeds. If the given url already exists in that list, the call has no -effect. The torrent will connect to the server and try to download -pieces from it, unless it's paused, queued, checking or seeding. -remove_url_seed() removes the given url if it exists already. -url_seeds() return a set of the url seeds currently in this -torrent. Note that urls that fails may be removed automatically from -the list.

-

See http seeding for more information.

- - -
-
-

http_seeds() remove_http_seed() add_http_seed()

-
-void add_http_seed (std::string const& url) const;
-void remove_http_seed (std::string const& url) const;
-std::set<std::string> http_seeds () const;
-
-

These functions are identical as the *_url_seed() variants, but -they operate on BEP 17 web seeds instead of BEP 19.

-

See http seeding for more information.

-
-
-

add_extension()

-
-void add_extension (
-      boost::function<boost::shared_ptr<torrent_plugin>(torrent*, void*)> const& ext
-      , void* userdata = 0);
-
-

add the specified extension to this torrent. The ext argument is -a function that will be called from within libtorrent's context -passing in the internal torrent object and the specified userdata -pointer. The function is expected to return a shared pointer to -a torrent_plugin instance.

-
-
-

set_metadata()

-
-bool set_metadata (char const* metadata, int size) const;
-
-

set_metadata expects the info section of metadata. i.e. The -buffer passed in will be hashed and verified against the info-hash. If -it fails, a metadata_failed_alert will be generated. If it passes, -a metadata_received_alert is generated. The function returns true -if the metadata is successfully set on the torrent, and false -otherwise. If the torrent already has metadata, this function will not -affect the torrent, and false will be returned.

-
-
-

is_valid()

-
-bool is_valid () const;
-
-

Returns true if this handle refers to a valid torrent and false if it -hasn't been initialized or if the torrent it refers to has been -aborted. Note that a handle may become invalid after it has been added -to the session. Usually this is because the storage for the torrent is -somehow invalid or if the filenames are not allowed (and hence cannot -be opened/created) on your filesystem. If such an error occurs, a -file_error_alert is generated and all handles that refers to that -torrent will become invalid.

- -
-
-

pause() resume()

-
-void pause (int flags = 0) const;
-void resume () const;
-
-

pause(), and resume() will disconnect all peers and reconnect -all peers respectively. When a torrent is paused, it will however -remember all share ratios to all peers and remember all potential (not -connected) peers. Torrents may be paused automatically if there is a -file error (e.g. disk full) or something similar. See -file_error_alert.

-

To know if a torrent is paused or not, call -torrent_handle::status() and inspect torrent_status::paused.

-

The flags argument to pause can be set to -torrent_handle::graceful_pause which will delay the disconnect of -peers that we're still downloading outstanding requests from. The -torrent will not accept any more requests and will disconnect all idle -peers. As soon as a peer is done transferring the blocks that were -requested from it, it is disconnected. This is a graceful shut down of -the torrent in the sense that no downloaded bytes are wasted.

-

torrents that are auto-managed may be automatically resumed again. It -does not make sense to pause an auto-managed torrent without making it -not automanaged first. Torrents are auto-managed by default when added -to the session. For more information, see queuing.

-
-
-

set_upload_mode()

-
-void set_upload_mode (bool b) const;
-
-

Explicitly sets the upload mode of the torrent. In upload mode, the -torrent will not request any pieces. If the torrent is auto managed, -it will automatically be taken out of upload mode periodically (see -session_settings::optimistic_disk_retry). Torrents are -automatically put in upload mode whenever they encounter a disk write -error.

-

m should be true to enter upload mode, and false to leave it.

-

To test if a torrent is in upload mode, call -torrent_handle::status() and inspect -torrent_status::upload_mode.

-
-
-

set_share_mode()

-
-void set_share_mode (bool b) const;
-
-

Enable or disable share mode for this torrent. When in share mode, the -torrent will not necessarily be downloaded, especially not the whole -of it. Only parts that are likely to be distributed to more than 2 -other peers are downloaded, and only if the previous prediction was -correct.

-
-
-

flush_cache()

-
-void flush_cache () const;
-
-

Instructs libtorrent to flush all the disk caches for this torrent and -close all file handles. This is done asynchronously and you will be -notified that it's complete through cache_flushed_alert.

-

Note that by the time you get the alert, libtorrent may have cached -more data for the torrent, but you are guaranteed that whatever cached -data libtorrent had by the time you called -torrent_handle::flush_cache() has been written to disk.

-
-
-

apply_ip_filter()

-
-void apply_ip_filter (bool b) const;
-
-

Set to true to apply the session global IP filter to this torrent -(which is the default). Set to false to make this torrent ignore the -IP filter.

-
-
-

force_recheck()

-
-void force_recheck () const;
-
-

force_recheck puts the torrent back in a state where it assumes to -have no resume data. All peers will be disconnected and the torrent -will stop announcing to the tracker. The torrent will be added to the -checking queue, and will be checked (all the files will be read and -compared to the piece hashes). Once the check is complete, the torrent -will start connecting to peers again, as normal.

-
-
-

save_resume_data()

-
-void save_resume_data (int flags = 0) const;
-
-

save_resume_data() generates fast-resume data and returns it as an -entry. This entry is suitable for being bencoded. For more information -about how fast-resume works, see fast resume.

-

The flags argument is a bitmask of flags ORed together. see -save_resume_flags_t

-

This operation is asynchronous, save_resume_data will return -immediately. The resume data is delivered when it's done through an -save_resume_data_alert.

-

The fast resume data will be empty in the following cases:

-
-
    -
  1. The torrent handle is invalid.
  2. -
  3. The torrent is checking (or is queued for checking) its storage, it -will obviously not be ready to write resume data.
  4. -
  5. The torrent hasn't received valid metadata and was started without -metadata (see libtorrent's metadata from peers extension)
  6. -
-
-

Note that by the time you receive the fast resume data, it may already -be invalid if the torrent is still downloading! The recommended -practice is to first pause the session, then generate the fast resume -data, and then close it down. Make sure to not remove_torrent() before -you receive the save_resume_data_alert though. There's no need to -pause when saving intermittent resume data.

-
-

Warning

-

If you pause every torrent individually instead of pausing the -session, every torrent will have its paused state saved in the -resume data!

-
-
-

Warning

-

The resume data contains the modification timestamps for all files. -If one file has been modified when the torrent is added again, the -will be rechecked. When shutting down, make sure to flush the disk -cache before saving the resume data. This will make sure that the -file timestamps are up to date and won't be modified after saving -the resume data. The recommended way to do this is to pause the -torrent, which will flush the cache and disconnect all peers.

-
-
-

Note

-

It is typically a good idea to save resume data whenever a torrent -is completed or paused. In those cases you don't need to pause the -torrent or the session, since the torrent will do no more writing to -its files. If you save resume data for torrents when they are -paused, you can accelerate the shutdown process by not saving resume -data again for paused torrents. Completed torrents should have their -resume data saved when they complete and on exit, since their -statistics might be updated.

-
-In full allocation mode the reume data is never invalidated by -subsequent writes to the files, since pieces won't move around. This -means that you don't need to pause before writing resume data in full -or sparse mode. If you don't, however, any data written to disk after -you saved resume data and before the session closed is lost.
-
-

It also means that if the resume data is out dated, libtorrent will -not re-check the files, but assume that it is fairly recent. The -assumption is that it's better to loose a little bit than to re-check -the entire file.

-

It is still a good idea to save resume data periodically during -download as well as when closing down.

-

Example code to pause and save resume data for all torrents and wait -for the alerts:

-
-extern int outstanding_resume_data; // global counter of outstanding resume data
-std::vector<torrent_handle> handles = ses.get_torrents();
-ses.pause();
-for (std::vector<torrent_handle>::iterator i = handles.begin();
-        i != handles.end(); ++i)
-{
-        torrent_handle& h = *i;
-        if (!h.is_valid()) continue;
-        torrent_status s = h.status();
-        if (!s.has_metadata) continue;
-        if (!s.need_save_resume_data()) continue;
-
-        h.save_resume_data();
-        ++outstanding_resume_data;
-}
-
-while (outstanding_resume_data > 0)
-{
-        alert const* a = ses.wait_for_alert(seconds(10));
-
-        // if we don't get an alert within 10 seconds, abort
-        if (a == 0) break;
-
-        std::auto_ptr<alert> holder = ses.pop_alert();
-
-        if (alert_cast<save_resume_data_failed_alert>(a))
-        {
-                process_alert(a);
-                --outstanding_resume_data;
-                continue;
-        }
-
-        save_resume_data_alert const* rd = alert_cast<save_resume_data_alert>(a);
-        if (rd == 0)
-        {
-                process_alert(a);
-                continue;
-        }
-
-        torrent_handle h = rd->handle;
-        torrent_status st = h.status(torrent_handle::query_save_path | torrent_handle::query_name);
-        std::ofstream out((st.save_path
-                + "/" + st.name + ".fastresume").c_str()
-                , std::ios_base::binary);
-        out.unsetf(std::ios_base::skipws);
-        bencode(std::ostream_iterator<char>(out), *rd->resume_data);
-        --outstanding_resume_data;
-}
-
-
-

Note

-

Note how outstanding_resume_data is a global counter in this -example. This is deliberate, otherwise there is a race condition for -torrents that was just asked to save their resume data, they posted -the alert, but it has not been received yet. Those torrents would -report that they don't need to save resume data again, and skipped by -the initial loop, and thwart the counter otherwise.

-
-
-
-

need_save_resume_data()

-
-bool need_save_resume_data () const;
-
-

This function returns true if any whole chunk has been downloaded -since the torrent was first loaded or since the last time the resume -data was saved. When saving resume data periodically, it makes sense -to skip any torrent which hasn't downloaded anything since the last -time.

-
-

Note

-

A torrent's resume data is considered saved as soon as the alert is -posted. It is important to make sure this alert is received and -handled in order for this function to be meaningful.

-
-
-
-

auto_managed()

-
-void auto_managed (bool m) const;
-
-

changes whether the torrent is auto managed or not. For more info, -see queuing.

- - - - -
-
-

queue_position() queue_position_up() queue_position_bottom() queue_position_down() queue_position_top()

-
-void queue_position_down () const;
-void queue_position_top () const;
-int queue_position () const;
-void queue_position_bottom () const;
-void queue_position_up () const;
-
-

Every torrent that is added is assigned a queue position exactly one -greater than the greatest queue position of all existing torrents. -Torrents that are being seeded have -1 as their queue position, since -they're no longer in line to be downloaded.

-

When a torrent is removed or turns into a seed, all torrents with -greater queue positions have their positions decreased to fill in the -space in the sequence.

-

queue_position() returns the torrent's position in the download -queue. The torrents with the smallest numbers are the ones that are -being downloaded. The smaller number, the closer the torrent is to the -front of the line to be started.

-

The queue position is also available in the torrent_status.

-

The queue_position_*() functions adjust the torrents position in -the queue. Up means closer to the front and down means closer to the -back of the queue. Top and bottom refers to the front and the back of -the queue respectively.

-
-
-

resolve_countries()

-
-void resolve_countries (bool r);
-bool resolve_countries () const;
-
-

Sets or gets the flag that derermines if countries should be resolved -for the peers of this torrent. It defaults to false. If it is set to -true, the peer_info structure for the peers in this torrent will have -their country member set. See peer_info for more information on -how to interpret this field.

- -
-
-

set_ssl_certificate_buffer() set_ssl_certificate()

-
-void set_ssl_certificate (std::string const& certificate
-      , std::string const& private_key
-      , std::string const& dh_params
-      , std::string const& passphrase = "");
-void set_ssl_certificate_buffer (std::string const& certificate
-      , std::string const& private_key
-      , std::string const& dh_params);
-
-

For SSL torrents, use this to specify a path to a .pem file to use as -this client's certificate. The certificate must be signed by the -certificate in the .torrent file to be valid.

-

The set_ssl_certificate_buffer() overload takes the actual certificate, -private key and DH params as strings, rather than paths to files. This -overload is only available when libtorrent is built against boost -1.54 or later.

-

cert is a path to the (signed) certificate in .pem format -corresponding to this torrent.

-

private_key is a path to the private key for the specified -certificate. This must be in .pem format.

-

dh_params is a path to the Diffie-Hellman parameter file, which -needs to be in .pem format. You can generate this file using the -openssl command like this: openssl dhparam -outform PEM -out -dhparams.pem 512.

-

passphrase may be specified if the private key is encrypted and -requires a passphrase to be decrypted.

-

Note that when a torrent first starts up, and it needs a certificate, -it will suspend connecting to any peers until it has one. It's -typically desirable to resume the torrent after setting the ssl -certificate.

-

If you receive a torrent_need_cert_alert, you need to call this to -provide a valid cert. If you don't have a cert you won't be allowed to -connect to any peers.

-
-
-

get_storage_impl()

-
-storage_interface* get_storage_impl () const;
-
-

Returns the storage implementation for this torrent. This depends on the -storage contructor function that was passed to add_torrent.

-
-
-

torrent_file()

-
-boost::intrusive_ptr<torrent_info const> torrent_file () const;
-
-

Returns a pointer to the torrent_info object associated with this -torrent. The torrent_info object may be a copy of the internal object. -If the torrent doesn't have metadata, the pointer will not be -initialized (i.e. a NULL pointer). The torrent may be in a state -without metadata only if it was started without a .torrent file, e.g. -by using the libtorrent extension of just supplying a tracker and -info-hash.

-
-
-

use_interface()

-
-void use_interface (const char* net_interface) const;
-
-

use_interface() sets the network interface this torrent will use -when it opens outgoing connections. By default, it uses the same -interface as the session uses to listen on. The parameter must be a -string containing one or more, comma separated, ip-address (either an -IPv4 or IPv6 address). When specifying multiple interfaces, the -torrent will round-robin which interface to use for each outgoing -conneciton. This is useful for clients that are multi-homed.

-
-
-

piece_availability()

-
-void piece_availability (std::vector<int>& avail) const;
-
-

Fills the specified std::vector<int> with the availability for -each piece in this torrent. libtorrent does not keep track of -availability for seeds, so if the torrent is seeding the availability -for all pieces is reported as 0.

-

The piece availability is the number of peers that we are connected -that has advertized having a particular piece. This is the information -that libtorrent uses in order to prefer picking rare pieces.

- - -
-
-

piece_priority() prioritize_pieces() piece_priorities()

-
-int piece_priority (int index) const;
-void piece_priority (int index, int priority) const;
-void prioritize_pieces (std::vector<int> const& pieces) const;
-std::vector<int> piece_priorities () const;
-
-

These functions are used to set and get the prioritiy of individual -pieces. By default all pieces have priority 1. That means that the -random rarest first algorithm is effectively active for all pieces. -You may however change the priority of individual pieces. There are 8 -different priority levels:

-
-
    -
  1. piece is not downloaded at all
  2. -
  3. normal priority. Download order is dependent on availability
  4. -
  5. higher than normal priority. Pieces are preferred over pieces with -the same availability, but not over pieces with lower availability
  6. -
  7. pieces are as likely to be picked as partial pieces.
  8. -
  9. pieces are preferred over partial pieces, but not over pieces with -lower availability
  10. -
  11. currently the same as 4
  12. -
  13. piece is as likely to be picked as any piece with availability 1
  14. -
  15. maximum priority, availability is disregarded, the piece is -preferred over any other piece with lower priority
  16. -
-
-

The exact definitions of these priorities are implementation details, -and subject to change. The interface guarantees that higher number -means higher priority, and that 0 means do not download.

-

piece_priority sets or gets the priority for an individual piece, -specified by index.

-

prioritize_pieces takes a vector of integers, one integer per -piece in the torrent. All the piece priorities will be updated with -the priorities in the vector.

-

piece_priorities returns a vector with one element for each piece -in the torrent. Each element is the current priority of that piece.

- - -
-
-

file_priorities() prioritize_files() file_priority()

-
-int file_priority (int index) const;
-void prioritize_files (std::vector<int> const& files) const;
-void file_priority (int index, int priority) const;
-std::vector<int> file_priorities () const;
-
-

index must be in the range [0, number_of_files).

-

file_priority() queries or sets the priority of file index.

-

prioritize_files() takes a vector that has at as many elements as -there are files in the torrent. Each entry is the priority of that -file. The function sets the priorities of all the pieces in the -torrent based on the vector.

-

file_priorities() returns a vector with the priorities of all -files.

-

The priority values are the same as for piece_priority().

-

Whenever a file priority is changed, all other piece priorities are -reset to match the file priorities. In order to maintain sepcial -priorities for particular pieces, piece_priority() has to be called -again for those pieces.

-

You cannot set the file priorities on a torrent that does not yet have -metadata or a torrent that is a seed. file_priority(int, int) and -prioritize_files() are both no-ops for such torrents.

- -
-
-

force_reannounce() force_dht_announce()

-
-void force_reannounce (int seconds = 0, int tracker_index = -1) const;
-void force_dht_announce () const;
-
-

force_reannounce() will force this torrent to do another tracker -request, to receive new peers. The seconds argument specifies how -many seconds from now to issue the tracker announces.

-

If the tracker's min_interval has not passed since the last -announce, the forced announce will be scheduled to happen immediately -as the min_interval expires. This is to honor trackers minimum -re-announce interval settings.

-

The tracker_index argument specifies which tracker to re-announce. -If set to -1 (which is the default), all trackers are re-announce.

-

force_dht_announce will announce the torrent to the DHT -immediately.

-
-
-

scrape_tracker()

-
-void scrape_tracker () const;
-
-

scrape_tracker() will send a scrape request to the tracker. A -scrape request queries the tracker for statistics such as total number -of incomplete peers, complete peers, number of downloads etc.

-

This request will specifically update the num_complete and -num_incomplete fields in the torrent_status struct once it -completes. When it completes, it will generate a scrape_reply_alert. -If it fails, it will generate a scrape_failed_alert.

- - - -
-
-

set_upload_limit() upload_limit() download_limit() set_download_limit()

-
-int upload_limit () const;
-int download_limit () const;
-void set_upload_limit (int limit) const;
-void set_download_limit (int limit) const;
-
-

set_upload_limit will limit the upload bandwidth used by this -particular torrent to the limit you set. It is given as the number of -bytes per second the torrent is allowed to upload. -set_download_limit works the same way but for download bandwidth -instead of upload bandwidth. Note that setting a higher limit on a -torrent then the global limit -(session_settings::upload_rate_limit) will not override the global -rate limit. The torrent can never upload more than the global rate -limit.

-

upload_limit and download_limit will return the current limit -setting, for upload and download, respectively.

-
-
-

set_sequential_download()

-
-void set_sequential_download (bool sd) const;
-
-

set_sequential_download() enables or disables sequential -download. When enabled, the piece picker will pick pieces in sequence -instead of rarest first. In this mode, piece priorities are ignored, -with the exception of priority 7, which are still preferred over the -sequential piece order.

-

Enabling sequential download will affect the piece distribution -negatively in the swarm. It should be used sparingly.

-
-
-

connect_peer()

-
-void connect_peer (tcp::endpoint const& adr, int source = 0) const;
-
-

connect_peer() is a way to manually connect to peers that one -believe is a part of the torrent. If the peer does not respond, or is -not a member of this torrent, it will simply be disconnected. No harm -can be done by using this other than an unnecessary connection attempt -is made. If the torrent is uninitialized or in queued or checking -mode, this will throw libtorrent_exception. The second (optional) -argument will be bitwised ORed into the source mask of this peer. -Typically this is one of the source flags in peer_info. i.e. -tracker, pex, dht etc.

- -
-
-

max_uploads() set_max_uploads()

-
-int max_uploads () const;
-void set_max_uploads (int max_uploads) const;
-
-

set_max_uploads() sets the maximum number of peers that's unchoked -at the same time on this torrent. If you set this to -1, there will be -no limit. This defaults to infinite. The primary setting controlling -this is the global unchoke slots limit, set by unchoke_slots_limit in -session_settings.

-

max_uploads() returns the current settings.

- -
-
-

max_connections() set_max_connections()

-
-int max_connections () const;
-void set_max_connections (int max_connections) const;
-
-

set_max_connections() sets the maximum number of connection this -torrent will open. If all connections are used up, incoming -connections may be refused or poor connections may be closed. This -must be at least 2. The default is unlimited number of connections. If --1 is given to the function, it means unlimited. There is also a -global limit of the number of connections, set by -connections_limit in session_settings.

-

max_connections() returns the current settings.

-
-
-

set_tracker_login()

-
-void set_tracker_login (std::string const& name
-      , std::string const& password) const;
-
-

sets a username and password that will be sent along in the HTTP-request -of the tracker announce. Set this if the tracker requires authorization.

-
-
-

move_storage()

-
-void move_storage (std::string const& save_path, int flags = 0) const;
-
-

Moves the file(s) that this torrent are currently seeding from or -downloading to. If the given save_path is not located on the same -drive as the original save path, the files will be copied to the new -drive and removed from their original location. This will block all -other disk IO, and other torrents download and upload rates may drop -while copying the file.

-

Since disk IO is performed in a separate thread, this operation is -also asynchronous. Once the operation completes, the -storage_moved_alert is generated, with the new path as the -message. If the move fails for some reason, -storage_moved_failed_alert is generated instead, containing the -error message.

-

The flags argument determines the behavior of the copying/moving -of the files in the torrent. see move_flags_t.

-
-
    -
  • always_replace_files = 0
  • -
  • fail_if_exist = 1
  • -
  • dont_replace = 2
  • -
-
-

always_replace_files is the default and replaces any file that -exist in both the source directory and the target directory.

-

fail_if_exist first check to see that none of the copy operations -would cause an overwrite. If it would, it will fail. Otherwise it will -proceed as if it was in always_replace_files mode. Note that there -is an inherent race condition here. If the files in the target -directory appear after the check but before the copy or move -completes, they will be overwritten. When failing because of files -already existing in the target path, the error of -move_storage_failed_alert is set to -boost::system::errc::file_exists.

-

The intention is that a client may use this as a probe, and if it -fails, ask the user which mode to use. The client may then re-issue -the move_storage call with one of the other modes.

-

dont_replace always takes the existing file in the target -directory, if there is one. The source files will still be removed in -that case.

-

Files that have been renamed to have absolute pahts are not moved by -this function. Keep in mind that files that don't belong to the -torrent but are stored in the torrent's directory may be moved as -well. This goes for files that have been renamed to absolute paths -that still end up inside the save path.

-
-
-

rename_file()

-
-void rename_file (int index, std::string const& new_name) const;
-
-

Renames the file with the given index asynchronously. The rename -operation is complete when either a file_renamed_alert or -file_rename_failed_alert is posted.

-
-
-

super_seeding()

-
-void super_seeding (bool on) const;
-
-

Enables or disabled super seeding/initial seeding for this torrent. The torrent -needs to be a seed for this to take effect.

-
-
-

info_hash()

-
-sha1_hash info_hash () const;
-
-

info_hash() returns the info-hash for the torrent.

- - -
-
-

operator!=() operator<() operator==()

-
-bool operator!= (const torrent_handle& h) const;
-bool operator< (const torrent_handle& h) const;
-bool operator== (const torrent_handle& h) const;
-
-

comparison operators. The order of the torrents is unspecified -but stable.

-
-
-

native_handle()

-
-boost::shared_ptr<torrent> native_handle () const;
-
-

This function is intended only for use by plugins and the alert -dispatch function. Any code that runs in libtorrent's network thread -may not use the public API of torrent_handle. Doing so results in a -dead-lock. For such routines, the native_handle gives access to -the underlying type representing the torrent. This type does not have -a stable API and should be relied on as little as possible.

-
-
-

enum flags_t

-

Declared in "libtorrent/torrent_handle.hpp"

- ----- - - - - - - - - - - - - -
namevaluedescription
overwrite_existing1 
-
-
-

enum status_flags_t

-

Declared in "libtorrent/torrent_handle.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
query_distributed_copies1calculates distributed_copies, distributed_full_copies and -distributed_fraction.
query_accurate_download_counters2includes partial downloaded blocks in total_done and -total_wanted_done.
query_last_seen_complete4includes last_seen_complete.
query_pieces8includes pieces.
query_verified_pieces16includes verified_pieces (only applies to torrents in seed -mode).
query_torrent_file32includes torrent_file, which is all the static information from -the .torrent file.
query_name64includes name, the name of the torrent. This is either derived -from the .torrent file, or from the &dn= magnet link argument -or possibly some other source. If the name of the torrent is not -known, this is an empty string.
query_save_path128includes save_path, the path to the directory the files of the -torrent are saved to.
-
-
-

enum deadline_flags

-

Declared in "libtorrent/torrent_handle.hpp"

- ----- - - - - - - - - - - - - -
namevaluedescription
alert_when_available1 
-
-
-

enum file_progress_flags_t

-

Declared in "libtorrent/torrent_handle.hpp"

- ----- - - - - - - - - - - - - -
namevaluedescription
piece_granularity1only calculate file progress at piece granularity. This makes -the file_progress() call cheaper and also only takes bytes that -have passed the hash check into account, so progress cannot -regress in this mode.
-
-
-

enum pause_flags_t

-

Declared in "libtorrent/torrent_handle.hpp"

- ----- - - - - - - - - - - - - -
namevaluedescription
graceful_pause1 
-
-
-

enum save_resume_flags_t

-

Declared in "libtorrent/torrent_handle.hpp"

- ----- - - - - - - - - - - - - - - - - -
namevaluedescription
flush_disk_cache1the disk cache will be flushed before creating the resume data. -This avoids a problem with file timestamps in the resume data in -case the cache hasn't been flushed yet.
save_info_dict2the resume data will contain the metadata from the torrent file as -well. This is default for any torrent that's added without a -torrent file (such as a magnet link or a URL).
-
-
-
-

torrent_status

-

Declared in "libtorrent/torrent_handle.hpp"

-

holds a snapshot of the status of a torrent, as queried by -torrent_handle::status().

-
-struct torrent_status
-{
-   bool operator== (torrent_status const& st) const;
-
-   enum state_t
-   {
-      queued_for_checking,
-      checking_files,
-      downloading_metadata,
-      downloading,
-      finished,
-      seeding,
-      allocating,
-      checking_resume_data,
-   };
-
-   torrent_handle handle;
-   std::string error;
-   std::string save_path;
-   std::string name;
-   boost::intrusive_ptr<const torrent_info> torrent_file;
-   boost::posix_time::time_duration next_announce;
-   boost::posix_time::time_duration announce_interval;
-   std::string current_tracker;
-   size_type total_download;
-   size_type total_upload;
-   size_type total_payload_download;
-   size_type total_payload_upload;
-   size_type total_failed_bytes;
-   size_type total_redundant_bytes;
-   bitfield pieces;
-   bitfield verified_pieces;
-   size_type total_done;
-   size_type total_wanted_done;
-   size_type total_wanted;
-   size_type all_time_upload;
-   size_type all_time_download;
-   time_t added_time;
-   time_t completed_time;
-   time_t last_seen_complete;
-   storage_mode_t storage_mode;
-   float progress;
-   int progress_ppm;
-   int queue_position;
-   int download_rate;
-   int upload_rate;
-   int download_payload_rate;
-   int upload_payload_rate;
-   int num_seeds;
-   int num_peers;
-   int num_complete;
-   int num_incomplete;
-   int list_seeds;
-   int list_peers;
-   int connect_candidates;
-   int num_pieces;
-   int distributed_full_copies;
-   int distributed_fraction;
-   float distributed_copies;
-   int block_size;
-   int num_uploads;
-   int num_connections;
-   int uploads_limit;
-   int connections_limit;
-   int up_bandwidth_queue;
-   int down_bandwidth_queue;
-   int time_since_upload;
-   int time_since_download;
-   int active_time;
-   int finished_time;
-   int seeding_time;
-   int seed_rank;
-   int last_scrape;
-   int sparse_regions;
-   int priority;
-   state_t state;
-   bool need_save_resume;
-   bool ip_filter_applies;
-   bool upload_mode;
-   bool share_mode;
-   bool super_seeding;
-   bool paused;
-   bool auto_managed;
-   bool sequential_download;
-   bool is_seeding;
-   bool is_finished;
-   bool has_metadata;
-   bool has_incoming;
-   bool seed_mode;
-   bool moving_storage;
-   sha1_hash info_hash;
-};
-
-
-

operator==()

-
-bool operator== (torrent_status const& st) const;
-
-

compres if the torrent status objects come from the same torrent. i.e. -only the torrent_handle field is compared.

-
-
-

enum state_t

-

Declared in "libtorrent/torrent_handle.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
queued_for_checking0The torrent is in the queue for being checked. But there -currently is another torrent that are being checked. -This torrent will wait for its turn.
checking_files1The torrent has not started its download yet, and is -currently checking existing files.
downloading_metadata2The torrent is trying to download metadata from peers. -This assumes the metadata_transfer extension is in use.
downloading3The torrent is being downloaded. This is the state -most torrents will be in most of the time. The progress -meter will tell how much of the files that has been -downloaded.
finished4In this state the torrent has finished downloading but -still doesn't have the entire torrent. i.e. some pieces -are filtered and won't get downloaded.
seeding5In this state the torrent has finished downloading and -is a pure seeder.
allocating6If the torrent was started in full allocation mode, this -indicates that the (disk) storage for the torrent is -allocated.
checking_resume_data7The torrent is currently checking the fastresume data and -comparing it to the files on disk. This is typically -completed in a fraction of a second, but if you add a -large number of torrents at once, they will queue up.
-
-
handle
-
a handle to the torrent whose status the object represents.
-
-
-
error
-
may be set to an error message describing why the torrent -was paused, in case it was paused by an error. If the torrent -is not paused or if it's paused but not because of an error, -this string is empty.
-
-
-
save_path
-
the path to the directory where this torrent's files are stored. -It's typically the path as was given to async_add_torrent() or -add_torrent() when this torrent was started. This field is only -included if the torrent status is queried with -torrent_handle::query_save_path.
-
-
-
name
-
the name of the torrent. Typically this is derived from the -.torrent file. In case the torrent was started without metadata, -and hasn't completely received it yet, it returns the name given -to it when added to the session. See session::add_torrent. -This field is only included if the torrent status is queried -with torrent_handle::query_name.
-
-
-
torrent_file
-
set to point to the torrent_info object for this torrent. It's -only included if the torrent status is queried with -torrent_handle::query_torrent_file.
-
-
-
next_announce
-
the time until the torrent will announce itself to the tracker.
-
-
-
announce_interval
-
the time the tracker want us to wait until we announce ourself -again the next time.
-
-
-
current_tracker
-
the URL of the last working tracker. If no tracker request has -been successful yet, it's set to an empty string.
-
- -
-
total_download total_upload
-
the number of bytes downloaded and uploaded to all peers, accumulated, -this session only. The session is considered to restart when a -torrent is paused and restarted again. When a torrent is paused, these -counters are reset to 0. If you want complete, persistent, stats, see -all_time_upload and all_time_download.
-
- -
-
total_payload_download total_payload_upload
-
counts the amount of bytes send and received this session, but only -the actual payload data (i.e the interesting data), these counters -ignore any protocol overhead.
-
-
-
total_failed_bytes
-
the number of bytes that has been downloaded and that has failed the -piece hash test. In other words, this is just how much crap that has -been downloaded.
-
-
-
total_redundant_bytes
-
the number of bytes that has been downloaded even though that data -already was downloaded. The reason for this is that in some situations -the same data can be downloaded by mistake. When libtorrent sends -requests to a peer, and the peer doesn't send a response within a -certain timeout, libtorrent will re-request that block. Another -situation when libtorrent may re-request blocks is when the requests -it sends out are not replied in FIFO-order (it will re-request blocks -that are skipped by an out of order block). This is supposed to be as -low as possible.
-
-
-
pieces
-
a bitmask that represents which pieces we have (set to true) and the -pieces we don't have. It's a pointer and may be set to 0 if the -torrent isn't downloading or seeding.
-
-
-
verified_pieces
-
a bitmask representing which pieces has had their hash checked. This -only applies to torrents in seed mode. If the torrent is not in seed -mode, this bitmask may be empty.
-
-
-
total_done
-
the total number of bytes of the file(s) that we have. All this does -not necessarily has to be downloaded during this session (that's -total_payload_download).
-
-
-
total_wanted_done
-
the number of bytes we have downloaded, only counting the pieces that -we actually want to download. i.e. excluding any pieces that we have -but have priority 0 (i.e. not wanted).
-
-
-
total_wanted
-
The total number of bytes we want to download. This may be smaller -than the total torrent size in case any pieces are prioritized to 0, -i.e. not wanted
-
- -
-
all_time_upload all_time_download
-
are accumulated upload and download payload byte counters. They are -saved in and restored from resume data to keep totals across sessions.
-
-
-
added_time
-
the posix-time when this torrent was added. i.e. what time(NULL) -returned at the time.
-
-
-
completed_time
-
the posix-time when this torrent was finished. If the torrent is not -yet finished, this is 0.
-
-
-
last_seen_complete
-
the time when we, or one of our peers, last saw a complete copy of -this torrent.
-
-
-
storage_mode
-
The allocation mode for the torrent. See storage_mode_t for the -options. For more information, see storage allocation.
-
-
-
progress
-
a value in the range [0, 1], that represents the progress of the -torrent's current task. It may be checking files or downloading.
-
-
-
progress_ppm
-
reflects the same value as progress, but instead in a range [0, -1000000] (ppm = parts per million). When floating point operations are -disabled, this is the only alternative to the floating point value in -progress.
-
-
-
queue_position
-
the position this torrent has in the download -queue. If the torrent is a seed or finished, this is -1.
-
- -
-
download_rate upload_rate
-
the total rates for all peers for this torrent. These will usually -have better precision than summing the rates from all peers. The rates -are given as the number of bytes per second.
-
- -
-
download_payload_rate upload_payload_rate
-
the total transfer rate of payload only, not counting protocol -chatter. This might be slightly smaller than the other rates, but if -projected over a long time (e.g. when calculating ETA:s) the -difference may be noticeable.
-
-
-
num_seeds
-
the number of peers that are seeding that this client is -currently connected to.
-
-
-
num_peers
-
the number of peers this torrent currently is connected to. Peer -connections that are in the half-open state (is attempting to connect) -or are queued for later connection attempt do not count. Although they -are visible in the peer list when you call get_peer_info().
-
- -
-
num_complete num_incomplete
-
if the tracker sends scrape info in its announce reply, these fields -will be set to the total number of peers that have the whole file and -the total number of peers that are still downloading. set to -1 if the -tracker did not send any scrape data in its announce reply.
-
- -
-
list_seeds list_peers
-
the number of seeds in our peer list and the total number of peers -(including seeds). We are not necessarily connected to all the peers -in our peer list. This is the number of peers we know of in total, -including banned peers and peers that we have failed to connect to.
-
-
-
connect_candidates
-
the number of peers in this torrent's peer list that is a candidate to -be connected to. i.e. It has fewer connect attempts than the max fail -count, it is not a seed if we are a seed, it is not banned etc. If -this is 0, it means we don't know of any more peers that we can try.
-
-
-
num_pieces
-
the number of pieces that has been downloaded. It is equivalent to: -std::accumulate(pieces->begin(), pieces->end()). So you don't have -to count yourself. This can be used to see if anything has updated -since last time if you want to keep a graph of the pieces up to date.
-
-
-
distributed_full_copies
-
the number of distributed copies of the torrent. Note that one copy -may be spread out among many peers. It tells how many copies there are -currently of the rarest piece(s) among the peers this client is -connected to.
-
-
-
distributed_fraction
-

tells the share of pieces that have more copies than the rarest -piece(s). Divide this number by 1000 to get the fraction.

-

For example, if distributed_full_copies is 2 and -distrbuted_fraction is 500, it means that the rarest pieces have -only 2 copies among the peers this torrent is connected to, and that -50% of all the pieces have more than two copies.

-

If we are a seed, the piece picker is deallocated as an optimization, -and piece availability is no longer tracked. In this case the -distributed copies members are set to -1.

-
-
-
-
distributed_copies
-

the number of distributed copies of the file. note that one copy may -be spread out among many peers. This is a floating point -representation of the distributed copies.

-
-
the integer part tells how many copies
-
there are of the rarest piece(s)
-
the fractional part tells the fraction of pieces that
-
have more copies than the rarest piece(s).
-
-
-
-
-
block_size
-
the size of a block, in bytes. A block is a sub piece, it is the -number of bytes that each piece request asks for and the number of -bytes that each bit in the partial_piece_info's bitset represents, -see get_download_queue(). This is typically 16 kB, but it may be -larger if the pieces are larger.
-
-
-
num_uploads
-
the number of unchoked peers in this torrent.
-
-
-
num_connections
-
the number of peer connections this torrent has, including half-open -connections that hasn't completed the bittorrent handshake yet. This -is always >= num_peers.
-
-
-
uploads_limit
-
the set limit of upload slots (unchoked peers) for this torrent.
-
-
-
connections_limit
-
the set limit of number of connections for this torrent.
-
- -
-
up_bandwidth_queue down_bandwidth_queue
-
the number of peers in this torrent that are waiting for more -bandwidth quota from the torrent rate limiter. This can determine if -the rate you get from this torrent is bound by the torrents limit or -not. If there is no limit set on this torrent, the peers might still -be waiting for bandwidth quota from the global limiter, but then they -are counted in the session_status object.
-
- -
-
time_since_upload time_since_download
-
the number of seconds since any peer last uploaded from this torrent -and the last time a downloaded piece passed the hash check, -respectively.
-
- - -
-
active_time finished_time seeding_time
-
These keep track of the number of seconds this torrent has been active -(not paused) and the number of seconds it has been active while being -finished and active while being a seed. seeding_time should be <= -finished_time which should be <= active_time. They are all -saved in and restored from resume data, to keep totals across -sessions.
-
-
-
seed_rank
-
A rank of how important it is to seed the torrent, it is used to -determine which torrents to seed and which to queue. It is based on -the peer to seed ratio from the tracker scrape. For more information, -see queuing. Higher value means more important to seed
-
-
-
last_scrape
-
the number of seconds since this torrent acquired scrape data. -If it has never done that, this value is -1.
-
-
-
sparse_regions
-
the number of regions of non-downloaded pieces in the torrent. This is -an interesting metric on windows vista, since there is a limit on the -number of sparse regions in a single file there.
-
-
-
priority
-
the priority of this torrent
-
-
-
state
-
the main state the torrent is in. See torrent_status::state_t.
-
-
-
need_save_resume
-
true if this torrent has unsaved changes -to its download state and statistics since the last resume data -was saved.
-
-
-
ip_filter_applies
-
true if the session global IP filter applies -to this torrent. This defaults to true.
-
-
-
upload_mode
-
true if the torrent is blocked from downloading. This typically -happens when a disk write operation fails. If the torrent is -auto-managed, it will periodically be taken out of this state, in the -hope that the disk condition (be it disk full or permission errors) -has been resolved. If the torrent is not auto-managed, you have to -explicitly take it out of the upload mode by calling set_upload_mode() -on the torrent_handle.
-
-
-
share_mode
-
true if the torrent is currently in share-mode, i.e. not downloading -the torrent, but just helping the swarm out.
-
-
-
super_seeding
-
true if the torrent is in super seeding mode
-
-
-
paused
-
set to true if the torrent is paused and false otherwise. It's only -true if the torrent itself is paused. If the torrent is not running -because the session is paused, this is still false. To know if a -torrent is active or not, you need to inspect both -torrent_status::paused and session::is_paused().
-
-
-
auto_managed
-
set to true if the torrent is auto managed, i.e. libtorrent is -responsible for determining whether it should be started or queued. -For more info see queuing
-
-
-
sequential_download
-
true when the torrent is in sequential download mode. In this mode -pieces are downloaded in order rather than rarest first.
-
-
-
is_seeding
-
true if all pieces have been downloaded.
-
-
-
is_finished
-
true if all pieces that have a priority > 0 are downloaded. There is -only a distinction between finished and seeding if some pieces or -files have been set to priority 0, i.e. are not downloaded.
-
-
-
has_metadata
-
true if this torrent has metadata (either it was started from a -.torrent file or the metadata has been downloaded). The only scenario -where this can be false is when the torrent was started torrent-less -(i.e. with just an info-hash and tracker ip, a magnet link for -instance).
-
-
-
has_incoming
-
true if there has ever been an incoming connection attempt to this -torrent.
-
-
-
seed_mode
-
true if the torrent is in seed_mode. If the torrent was started in -seed mode, it will leave seed mode once all pieces have been checked -or as soon as one piece fails the hash check.
-
-
-
moving_storage
-
this is true if this torrent's storage is currently being moved from -one location to another. This may potentially be a long operation -if a large file ends up being copied from one drive to another.
-
-
-
info_hash
-
the info-hash for this torrent
-
-
-
-
-

announce_entry

-

Declared in "libtorrent/torrent_info.hpp"

-

this class holds information about one bittorrent tracker, as it -relates to a specific torrent.

-
-struct announce_entry
-{
-   announce_entry (std::string const& u);
-   ~announce_entry ();
-   announce_entry ();
-   int next_announce_in () const;
-   int min_announce_in () const;
-   void reset ();
-   void failed (session_settings const& sett, int retry_interval = 0);
-   bool can_announce (ptime now, bool is_seed) const;
-   bool is_working () const;
-   void trim ();
-
-   enum tracker_source
-   {
-      source_torrent,
-      source_client,
-      source_magnet_link,
-      source_tex,
-   };
-
-   std::string url;
-   std::string trackerid;
-   std::string message;
-   error_code last_error;
-   ptime next_announce;
-   ptime min_announce;
-   int scrape_incomplete;
-   int scrape_complete;
-   int scrape_downloaded;
-   boost::uint8_t tier;
-   boost::uint8_t fail_limit;
-   boost::uint8_t fails:7;
-   bool updating:1;
-   boost::uint8_t source:4;
-   bool verified:1;
-   bool start_sent:1;
-   bool complete_sent:1;
-   bool send_stats:1;
-};
-
- -
-

announce_entry() ~announce_entry()

-
-announce_entry (std::string const& u);
-~announce_entry ();
-announce_entry ();
-
-

constructs a tracker announce entry with u as the URL.

- -
-
-

min_announce_in() next_announce_in()

-
-int next_announce_in () const;
-int min_announce_in () const;
-
-

returns the number of seconds to the next announce on -this tracker. min_announce_in() returns the number of seconds until we are -allowed to force another tracker update with this tracker.

-

If the last time this tracker was contacted failed, last_error is the error -code describing what error occurred.

-
-
-

reset()

-
-void reset ();
-
-

reset announce counters and clears the started sent flag. -The announce_entry will look like we've never talked to -the tracker.

-
-
-

failed()

-
-void failed (session_settings const& sett, int retry_interval = 0);
-
-

updates the failure counter and time-outs for re-trying. -This is called when the tracker announce fails.

-
-
-

can_announce()

-
-bool can_announce (ptime now, bool is_seed) const;
-
-

returns true if we can announec to this tracker now. -The current time is passed in as now. The is_seed -argument is necessary because once we become a seed, we -need to announce right away, even if the re-announce timer -hasn't expired yet.

-
-
-

is_working()

-
-bool is_working () const;
-
-

returns true if the last time we tried to announce to this -tracker succeeded, or if we haven't tried yet.

-
-
-

trim()

-
-void trim ();
-
-

trims whitespace characters from the beginning of the URL.

-
-
-

enum tracker_source

-

Declared in "libtorrent/torrent_info.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
source_torrent1the tracker was part of the .torrent file
source_client2the tracker was added programatically via the add_troacker()_ function
source_magnet_link4the tracker was part of a magnet link
source_tex8the tracker was received from the swarm via tracker exchange
-
-
url
-
tracker URL as it appeared in the torrent file
-
-
-
trackerid
-
the current &trackerid= argument passed to the tracker. -this is optional and is normally empty (in which case no -trackerid is sent).
-
-
-
message
-
if this tracker has returned an error or warning message -that message is stored here
-
-
-
last_error
-
if this tracker failed the last time it was contacted -this error code specifies what error occurred
-
-
-
next_announce
-
the time of next tracker announce
-
-
-
min_announce
-
no announces before this time
-
- - -
-
scrape_incomplete scrape_complete scrape_downloaded
-
if this tracker has returned scrape data, these fields are filled -in with valid numbers. Otherwise they are set to -1. -the number of current downloaders
-
-
-
tier
-
the tier this tracker belongs to
-
-
-
fail_limit
-
the max number of failures to announce to this tracker in -a row, before this tracker is not used anymore. 0 means unlimited
-
-
-
fails
-
the number of times in a row we have failed to announce to this -tracker.
-
-
-
updating
-
true while we're waiting for a response from the tracker.
-
-
-
source
-
a bitmask specifying which sources we got this tracker from.
-
-
-
verified
-
set to true the first time we receive a valid response -from this tracker.
-
-
-
start_sent
-
set to true when we get a valid response from an announce -with event=started. If it is set, we won't send start in the subsequent -announces.
-
-
-
complete_sent
-
set to true when we send a event=completed.
-
-
-
send_stats
-
this is false the stats sent to this tracker will be 0
-
-
-
-
-

torrent_info

-

Declared in "libtorrent/torrent_info.hpp"

-

This class represents the information stored in a .torrent file

-
-class torrent_info : public intrusive_ptr_base<torrent_info>
-{
-   torrent_info (std::string const& filename, int flags = 0);
-   torrent_info (char const* buffer, int size, error_code& ec, int flags = 0);
-   torrent_info (sha1_hash const& info_hash, int flags = 0);
-   torrent_info (lazy_entry const& torrent_file, int flags = 0);
-   torrent_info (char const* buffer, int size, int flags = 0);
-   torrent_info (lazy_entry const& torrent_file, error_code& ec, int flags = 0);
-   torrent_info (torrent_info const& t, int flags = 0);
-   torrent_info (std::string const& filename, error_code& ec, int flags = 0);
-   ~torrent_info ();
-   file_storage const& files () const;
-   file_storage const& orig_files () const;
-   void rename_file (int index, std::string const& new_filename);
-   void remap_files (file_storage const& f);
-   std::vector<announce_entry> const& trackers () const;
-   void add_tracker (std::string const& url, int tier = 0);
-   void add_url_seed (std::string const& url
-      , std::string const& extern_auth = std::string()
-      , web_seed_entry::headers_t const& extra_headers = web_seed_entry::headers_t());
-   std::vector<web_seed_entry> const& web_seeds () const;
-   void add_http_seed (std::string const& url
-      , std::string const& extern_auth = std::string()
-      , web_seed_entry::headers_t const& extra_headers = web_seed_entry::headers_t());
-   int num_pieces () const;
-   size_type total_size () const;
-   int piece_length () const;
-   const sha1_hash& info_hash () const;
-   int num_files () const;
-   file_entry file_at (int index) const;
-   std::vector<file_slice> map_block (int piece, size_type offset, int size) const;
-   peer_request map_file (int file, size_type offset, int size) const;
-   std::string ssl_cert () const;
-   bool is_valid () const;
-   bool priv () const;
-   bool is_i2p () const;
-   sha1_hash hash_for_piece (int index) const;
-   char const* hash_for_piece_ptr (int index) const;
-   int piece_size (int index) const;
-   std::vector<sha1_hash> const& merkle_tree () const;
-   void set_merkle_tree (std::vector<sha1_hash>& h);
-   boost::optional<time_t> creation_date () const;
-   const std::string& name () const;
-   const std::string& comment () const;
-   const std::string& creator () const;
-   nodes_t const& nodes () const;
-   void add_node (std::pair<std::string, int> const& node);
-   bool parse_info_section (lazy_entry const& e, error_code& ec, int flags);
-   lazy_entry const* info (char const* key) const;
-   void swap (torrent_info& ti);
-   int metadata_size () const;
-   boost::shared_array<char> metadata () const;
-   bool is_merkle_torrent () const;
-};
-
-
-

torrent_info()

-
-torrent_info (std::string const& filename, int flags = 0);
-torrent_info (char const* buffer, int size, error_code& ec, int flags = 0);
-torrent_info (sha1_hash const& info_hash, int flags = 0);
-torrent_info (lazy_entry const& torrent_file, int flags = 0);
-torrent_info (char const* buffer, int size, int flags = 0);
-torrent_info (lazy_entry const& torrent_file, error_code& ec, int flags = 0);
-torrent_info (torrent_info const& t, int flags = 0);
-torrent_info (std::string const& filename, error_code& ec, int flags = 0);
-
-

The constructor that takes an info-hash will initialize the info-hash to the given value, -but leave all other fields empty. This is used internally when downloading torrents without -the metadata. The metadata will be created by libtorrent as soon as it has been downloaded -from the swarm.

-

The constructor that takes a lazy_entry will create a torrent_info object from the -information found in the given torrent_file. The lazy_entry represents a tree node in -an bencoded file. To load an ordinary .torrent file -into a lazy_entry, use lazy_bdecode().

-

The version that takes a buffer pointer and a size will decode it as a .torrent file and -initialize the torrent_info object for you.

-

The version that takes a filename will simply load the torrent file and decode it inside -the constructor, for convenience. This might not be the most suitable for applications that -want to be able to report detailed errors on what might go wrong.

-

The overloads that takes an error_code const& never throws if an error occur, they -will simply set the error code to describe what went wrong and not fully initialize the -torrent_info object. The overloads that do not take the extra error_code parameter will -always throw if an error occurs. These overloads are not available when building without -exception support.

-

The flags argument is currently unused.

-
-
-

~torrent_info()

-
-~torrent_info ();
-
-

frees all storage associated with this torrent_info object

- -
-
-

orig_files() files()

-
-file_storage const& files () const;
-file_storage const& orig_files () const;
-
-

The file_storage object contains the information on how to map the pieces to -files. It is separated from the torrent_info object because when creating torrents -a storage object needs to be created without having a torrent file. When renaming files -in a storage, the storage needs to make its own copy of the file_storage in order -to make its mapping differ from the one in the torrent file.

-

orig_files() returns the original (unmodified) file storage for this torrent. This -is used by the web server connection, which needs to request files with the original -names. Filename may be chaged using torrent_info::rename_file().

-

For more information on the file_storage object, see the separate document on how -to create torrents.

-
-
-

rename_file()

-
-void rename_file (int index, std::string const& new_filename);
-
-

Renames a the file with the specified index to the new name. The new filename is -reflected by the file_storage returned by files() but not by the one -returned by orig_files().

-

If you want to rename the base name of the torrent (for a multifile torrent), you -can copy the file_storage (see files() and orig_files() ), change the name, and -then use remap_files().

-

The new_filename can both be a relative path, in which case the file name -is relative to the save_path of the torrent. If the new_filename is -an absolute path (i.e. is_complete(new_filename) == true), then the file -is detached from the save_path of the torrent. In this case the file is -not moved when move_storage() is invoked.

-
-
-

remap_files()

-
-void remap_files (file_storage const& f);
-
-

Remaps the file storage to a new file layout. This can be used to, for instance, -download all data in a torrent to a single file, or to a number of fixed size -sector aligned files, regardless of the number and sizes of the files in the torrent.

-

The new specified file_storage must have the exact same size as the current one.

- -
-
-

trackers() add_tracker()

-
-std::vector<announce_entry> const& trackers () const;
-void add_tracker (std::string const& url, int tier = 0);
-
-

add_tracker() adds a tracker to the announce-list. The tier determines the order in -which the trackers are to be tried.

-

The trackers() function will return a sorted vector of announce_entry. -Each announce entry contains a string, which is the tracker url, and a tier index. The -tier index is the high-level priority. No matter which trackers that works or not, the -ones with lower tier will always be tried before the one with higher tier number. -For more information, see announce_entry.

- - -
-
-

add_url_seed() add_http_seed() web_seeds()

-
-void add_url_seed (std::string const& url
-      , std::string const& extern_auth = std::string()
-      , web_seed_entry::headers_t const& extra_headers = web_seed_entry::headers_t());
-std::vector<web_seed_entry> const& web_seeds () const;
-void add_http_seed (std::string const& url
-      , std::string const& extern_auth = std::string()
-      , web_seed_entry::headers_t const& extra_headers = web_seed_entry::headers_t());
-
-

web_seeds() returns all url seeds and http seeds in the torrent. Each entry -is a web_seed_entry and may refer to either a url seed or http seed.

-

add_url_seed() and add_http_seed() adds one url to the list of -url/http seeds. Currently, the only transport protocol supported for the url -is http.

-

The extern_auth argument can be used for other athorization schemese than -basic HTTP authorization. If set, it will override any username and password -found in the URL itself. The string will be sent as the HTTP authorization header's -value (without specifying "Basic").

-

The extra_headers argument defaults to an empty list, but can be used to -insert custom HTTP headers in the requests to a specific web seed.

-

See http seeding for more information.

- - -
-
-

piece_length() num_pieces() total_size()

-
-int num_pieces () const;
-size_type total_size () const;
-int piece_length () const;
-
-

total_size(), piece_length() and num_pieces() returns the total -number of bytes the torrent-file represents (all the files in it), the number of byte for -each piece and the total number of pieces, respectively. The difference between -piece_size() and piece_length() is that piece_size() takes -the piece index as argument and gives you the exact size of that piece. It will always -be the same as piece_length() except in the case of the last piece, which may -be smaller.

-
-
-

info_hash()

-
-const sha1_hash& info_hash () const;
-
-

returns the info-hash of the torrent

- -
-
-

num_files() file_at()

-
-int num_files () const;
-file_entry file_at (int index) const;
-
-

If you need index-access to files you can use the num_files() and file_at() -to access files using indices.

-
-
-

map_block()

-
-std::vector<file_slice> map_block (int piece, size_type offset, int size) const;
-
-

This function will map a piece index, a byte offset within that piece and -a size (in bytes) into the corresponding files with offsets where that data -for that piece is supposed to be stored. See file_slice.

-
-
-

map_file()

-
-peer_request map_file (int file, size_type offset, int size) const;
-
-

This function will map a range in a specific file into a range in the torrent. -The file_offset parameter is the offset in the file, given in bytes, where -0 is the start of the file. See peer_request.

-

The input range is assumed to be valid within the torrent. file_offset -+ size is not allowed to be greater than the file size. file_index -must refer to a valid file, i.e. it cannot be >= num_files().

-
-
-

ssl_cert()

-
-std::string ssl_cert () const;
-
-

Returns the SSL root certificate for the torrent, if it is an SSL -torrent. Otherwise returns an empty string. The certificate is -the the public certificate in x509 format.

-
-
-

is_valid()

-
-bool is_valid () const;
-
-

returns true if this torrent_info object has a torrent loaded. -This is primarily used to determine if a magnet link has had its -metadata resolved yet or not.

-
-
-

priv()

-
-bool priv () const;
-
-

returns true if this torrent is private. i.e., it should not be -distributed on the trackerless network (the kademlia DHT).

-
-
-

is_i2p()

-
-bool is_i2p () const;
-
-

returns true if this is an i2p torrent. This is determined by whether -or not it has a tracker whose URL domain name ends with ".i2p". i2p -torrents disable the DHT and local peer discovery as well as talking -to peers over anything other than the i2p network.

- - -
-
-

hash_for_piece_ptr() hash_for_piece() piece_size()

-
-sha1_hash hash_for_piece (int index) const;
-char const* hash_for_piece_ptr (int index) const;
-int piece_size (int index) const;
-
-

hash_for_piece() takes a piece-index and returns the 20-bytes sha1-hash for that -piece and info_hash() returns the 20-bytes sha1-hash for the info-section of the -torrent file. -hash_for_piece_ptr() returns a pointer to the 20 byte sha1 digest for the piece. -Note that the string is not null-terminated.

- -
-
-

set_merkle_tree() merkle_tree()

-
-std::vector<sha1_hash> const& merkle_tree () const;
-void set_merkle_tree (std::vector<sha1_hash>& h);
-
-

merkle_tree() returns a reference to the merkle tree for this torrent, if any.

-

set_merkle_tree() moves the passed in merkle tree into the torrent_info object. -i.e. h will not be identical after the call. You need to set the merkle tree for -a torrent that you've just created (as a merkle torrent). The merkle tree is retrieved -from the create_torrent::merkle_tree() function, and need to be saved separately -from the torrent file itself. Once it's added to libtorrent, the merkle tree will be -persisted in the resume data.

- - - -
-
-

creator() creation_date() name() comment()

-
-boost::optional<time_t> creation_date () const;
-const std::string& name () const;
-const std::string& comment () const;
-const std::string& creator () const;
-
-

name() returns the name of the torrent.

-

comment() returns the comment associated with the torrent. If there's no comment, -it will return an empty string. creation_date() returns the creation date of -the torrent as time_t (posix time). If there's no time stamp in the torrent file, -the optional object will be uninitialized.

-

Both the name and the comment is UTF-8 encoded strings.

-

creator() returns the creator string in the torrent. If there is no creator string -it will return an empty string.

-
-
-

nodes()

-
-nodes_t const& nodes () const;
-
-

If this torrent contains any DHT nodes, they are put in this vector in their original -form (host name and port number).

-
-
-

add_node()

-
-void add_node (std::pair<std::string, int> const& node);
-
-

This is used when creating torrent. Use this to add a known DHT node. It may -be used, by the client, to bootstrap into the DHT network.

-
-
-

parse_info_section()

-
-bool parse_info_section (lazy_entry const& e, error_code& ec, int flags);
-
-

populates the torrent_info by providing just the info-dict buffer. This is used when -loading a torrent from a magnet link for instance, where we only have the info-dict. -The lazy_entry e points to a parsed info-dictionary. ec returns an error code -if something fails (typically if the info dictionary is malformed). flags are currently -unused.

-
-
-

info()

-
-lazy_entry const* info (char const* key) const;
-
-

This function looks up keys from the info-dictionary of the loaded torrent file. -It can be used to access extension values put in the .torrent file. If the specified -key cannot be found, it returns NULL.

-
-
-

swap()

-
-void swap (torrent_info& ti);
-
-

swap the content of this and ti`.

- -
-
-

metadata_size() metadata()

-
-int metadata_size () const;
-boost::shared_array<char> metadata () const;
-
-

metadata() returns a the raw info section of the torrent file. The size -of the metadata is returned by metadata_size().

-
-
-

is_merkle_torrent()

-
-bool is_merkle_torrent () const;
-
-

returns whether or not this is a merkle torrent. -see BEP30.

-
-
-

make_magnet_uri()

-

Declared in "libtorrent/magnet_uri.hpp"

-
-std::string make_magnet_uri (torrent_handle const& handle);
-std::string make_magnet_uri (torrent_info const& info);
-
-

Generates a magnet URI from the specified torrent. If the torrent -handle is invalid, an empty string is returned.

-

For more information about magnet links, see magnet links.

-
-
-

parse_magnet_uri()

-

Declared in "libtorrent/magnet_uri.hpp"

-
-void parse_magnet_uri (std::string const& uri, add_torrent_params& p, error_code& ec);
-
-

This function parses out information from the magnet link and populates the -add_torrent_params object.

-
-
-

hash_value()

-

Declared in "libtorrent/torrent_handle.hpp"

-
-std::size_t hash_value (torrent_status const& ts);
-
-

allows torrent_handle to be used in unordered_map and unordered_set.

-
-
-

sign_mutable_item()

-

Declared in "libtorrent/kademlia/item.hpp"

-
-void sign_mutable_item (
-   std::pair<char const*, int> v
-   , std::pair<char const*, int> salt
-   , boost::uint64_t seq
-   , char const* pk
-   , char const* sk
-   , char* sig);
-
-

given a byte range v and an optional byte range salt, a -sequence number, public key pk (must be 32 bytes) and a secret key -sk (must be 64 bytes), this function produces a signature which -is written into a 64 byte buffer pointed to by sig. The caller -is responsible for allocating the destination buffer that's passed in -as the sig argument. Typically it would be allocated on the stack.

-
-
-
- - diff --git a/docs/reference-Create_Torrents.html b/docs/reference-Create_Torrents.html deleted file mode 100644 index a8256b52f..000000000 --- a/docs/reference-Create_Torrents.html +++ /dev/null @@ -1,426 +0,0 @@ - - - - - - -Create Torrents - - - - - - - - -
-
-
- -
- -
-

Create Torrents

- --- - - - - - -
Author:Arvid Norberg, arvid@rasterbar.com
Version:1.0.0
-
-

Table of contents

- -
-

This section describes the functions and classes that are used -to create torrent files. It is a layered API with low level classes -and higher level convenience functions. A torrent is created in 4 -steps:

-
    -
  1. first the files that will be part of the torrent are determined.
  2. -
  3. the torrent properties are set, such as tracker url, web seeds, -DHT nodes etc.
  4. -
  5. Read through all the files in the torrent, SHA-1 all the data -and set the piece hashes.
  6. -
  7. The torrent is bencoded into a file or buffer.
  8. -
-

If there are a lot of files and or deep directoy hierarchies to -traverse, step one can be time consuming.

-

Typically step 3 is by far the most time consuming step, since it -requires to read all the bytes from all the files in the torrent.

-

All of these classes and functions are declared by including -libtorrent/create_torrent.hpp.

-

example:

-
-file_storage fs;
-
-// recursively adds files in directories
-add_files(fs, "./my_torrent");
-
-create_torrent t(fs);
-t.add_tracker("http://my.tracker.com/announce");
-t.set_creator("libtorrent example");
-
-// reads the files and calculates the hashes
-set_piece_hashes(t, ".");
-
-ofstream out("my_torrent.torrent", std::ios_base::binary);
-bencode(std::ostream_iterator<char>(out), t.generate());
-
-
-

create_torrent

-

Declared in "libtorrent/create_torrent.hpp"

-

This class holds state for creating a torrent. After having added -all information to it, call create_torrent::generate() to generate -the torrent. The entry that's returned can then be bencoded into a -.torrent file using bencode().

-
-struct create_torrent
-{
-   create_torrent (file_storage& fs, int piece_size = 0
-      , int pad_file_limit = -1, int flags = optimize, int alignment = -1);
-   create_torrent (torrent_info const& ti);
-   entry generate () const;
-   file_storage const& files () const;
-   void set_comment (char const* str);
-   void set_creator (char const* str);
-   void set_hash (int index, sha1_hash const& h);
-   void set_file_hash (int index, sha1_hash const& h);
-   void add_url_seed (std::string const& url);
-   void add_http_seed (std::string const& url);
-   void add_node (std::pair<std::string, int> const& node);
-   void add_tracker (std::string const& url, int tier = 0);
-   void set_root_cert (std::string const& pem);
-   bool priv () const;
-   void set_priv (bool p);
-   int num_pieces () const;
-   int piece_length () const;
-   int piece_size (int i) const;
-   std::vector<sha1_hash> const& merkle_tree () const;
-
-   enum flags_t
-   {
-      optimize,
-      merkle,
-      modification_time,
-      symlinks,
-      calculate_file_hashes,
-   };
-};
-
-
-

create_torrent()

-
-create_torrent (file_storage& fs, int piece_size = 0
-      , int pad_file_limit = -1, int flags = optimize, int alignment = -1);
-create_torrent (torrent_info const& ti);
-
-

The piece_size is the size of each piece in bytes. It must -be a multiple of 16 kiB. If a piece size of 0 is specified, a -piece_size will be calculated such that the torrent file is roughly 40 kB.

-

If a pad_size_limit is specified (other than -1), any file larger than -the specified number of bytes will be preceeded by a pad file to align it -with the start of a piece. The pad_file_limit is ignored unless the -optimize flag is passed. Typically it doesn't make sense to set this -any lower than 4kiB.

-

The overload that takes a torrent_info object will make a verbatim -copy of its info dictionary (to preserve the info-hash). The copy of -the info dictionary will be used by create_torrent::generate(). This means -that none of the member functions of create_torrent that affects -the content of the info dictionary (such as set_hash()), will -have any affect.

-

The flags arguments specifies options for the torrent creation. It can -be any combination of the flags defined by create_torrent::flags_t.

-

alignment is used when pad files are enabled. This is the size -eligible files are aligned to. The default is -1, which means the -piece size of the torrent.

-
-
-

generate()

-
-entry generate () const;
-
-

This function will generate the .torrent file as a bencode tree. In order to -generate the flat file, use the bencode() function.

-

It may be useful to add custom entries to the torrent file before bencoding it -and saving it to disk.

-

If anything goes wrong during torrent generation, this function will return -an empty entry structure. You can test for this condition by querying the -type of the entry:

-
-file_storage fs;
-// add file ...
-create_torrent t(fs);
-// add trackers and piece hashes ...
-e = t.generate();
-
-if (e.type() == entry::undefined_t)
-{
-        // something went wrong
-}
-
-

For instance, you cannot generate a torrent with 0 files in it. If you don't add -any files to the file_storage, torrent generation will fail.

-
-
-

files()

-
-file_storage const& files () const;
-
-

returns an immutable reference to the file_storage used to create -the torrent from.

-
-
-

set_comment()

-
-void set_comment (char const* str);
-
-

Sets the comment for the torrent. The string str should be utf-8 encoded. -The comment in a torrent file is optional.

-
-
-

set_creator()

-
-void set_creator (char const* str);
-
-

Sets the creator of the torrent. The string str should be utf-8 encoded. -This is optional.

-
-
-

set_hash()

-
-void set_hash (int index, sha1_hash const& h);
-
-

This sets the SHA-1 hash for the specified piece (index). You are required -to set the hash for every piece in the torrent before generating it. If you have -the files on disk, you can use the high level convenience function to do this. -See set_piece_hashes().

-
-
-

set_file_hash()

-
-void set_file_hash (int index, sha1_hash const& h);
-
-

This sets the sha1 hash for this file. This hash will end up under the key sha1 -associated with this file (for multi-file torrents) or in the root info dictionary -for single-file torrents.

- -
-
-

add_url_seed() add_http_seed()

-
-void add_url_seed (std::string const& url);
-void add_http_seed (std::string const& url);
-
-

This adds a url seed to the torrent. You can have any number of url seeds. For a -single file torrent, this should be an HTTP url, pointing to a file with identical -content as the file of the torrent. For a multi-file torrent, it should point to -a directory containing a directory with the same name as this torrent, and all the -files of the torrent in it.

-

The second function, add_http_seed() adds an HTTP seed instead.

-
-
-

add_node()

-
-void add_node (std::pair<std::string, int> const& node);
-
-

This adds a DHT node to the torrent. This especially useful if you're creating a -tracker less torrent. It can be used by clients to bootstrap their DHT node from. -The node is a hostname and a port number where there is a DHT node running. -You can have any number of DHT nodes in a torrent.

-
-
-

add_tracker()

-
-void add_tracker (std::string const& url, int tier = 0);
-
-

Adds a tracker to the torrent. This is not strictly required, but most torrents -use a tracker as their main source of peers. The url should be an http:// or udp:// -url to a machine running a bittorrent tracker that accepts announces for this torrent's -info-hash. The tier is the fallback priority of the tracker. All trackers with tier 0 are -tried first (in any order). If all fail, trackers with tier 1 are tried. If all of those -fail, trackers with tier 2 are tried, and so on.

-
-
-

set_root_cert()

-
-void set_root_cert (std::string const& pem);
-
-

This function sets an X.509 certificate in PEM format to the torrent. This makes the -torrent an SSL torrent. An SSL torrent requires that each peer has a valid certificate -signed by this root certificate. For SSL torrents, all peers are connecting over SSL -connections. For more information, see the section on ssl torrents.

-

The string is not the path to the cert, it's the actual content of the certificate, -loaded into a std::string.

- -
-
-

priv() set_priv()

-
-bool priv () const;
-void set_priv (bool p);
-
-

Sets and queries the private flag of the torrent. -Torrents with the private flag set ask clients to not use any other -sources than the tracker for peers, and to not advertize itself publicly, -apart from the tracker.

-
-
-

num_pieces()

-
-int num_pieces () const;
-
-

returns the number of pieces in the associated file_storage object.

- -
-
-

piece_length() piece_size()

-
-int piece_length () const;
-int piece_size (int i) const;
-
-

piece_length() returns the piece size of all pieces but the -last one. piece_size() returns the size of the specified piece. -these functions are just forwarding to the associated file_storage.

-
-
-

merkle_tree()

-
-std::vector<sha1_hash> const& merkle_tree () const;
-
-

This function returns the merkle hash tree, if the torrent was created as a merkle -torrent. The tree is created by generate() and won't be valid until that function -has been called. When creating a merkle tree torrent, the actual tree itself has to -be saved off separately and fed into libtorrent the first time you start seeding it, -through the torrent_info::set_merkle_tree() function. From that point onwards, the -tree will be saved in the resume data.

-
-
-

enum flags_t

-

Declared in "libtorrent/create_torrent.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
optimize1This will insert pad files to align the files to piece boundaries, for -optimized disk-I/O.
merkle2This will create a merkle hash tree torrent. A merkle torrent cannot -be opened in clients that don't specifically support merkle torrents. -The benefit is that the resulting torrent file will be much smaller and -not grow with more pieces. When this option is specified, it is -recommended to have a fairly small piece size, say 64 kiB. -When creating merkle torrents, the full hash tree is also generated -and should be saved off separately. It is accessed through the -create_torrent::merkle_tree() function.
modification_time4This will include the file modification time as part of the torrent. -This is not enabled by default, as it might cause problems when you -create a torrent from separate files with the same content, hoping to -yield the same info-hash. If the files have different modification times, -with this option enabled, you would get different info-hashes for the -files.
symlinks8If this flag is set, files that are symlinks get a symlink attribute -set on them and their data will not be included in the torrent. This -is useful if you need to reconstruct a file hierarchy which contains -symlinks.
calculate_file_hashes16If this is set, the set_piece_hashes() function will, as it calculates -the piece hashes, also calculate the file hashes and add those associated -with each file. Note that unless you use the set_piece_hashes() function, -this flag will have no effect.
-
-
-

add_files()

-

Declared in "libtorrent/create_torrent.hpp"

-
-template <class Pred> void add_files (file_storage& fs, std::string const& file, Pred p, boost::uint32_t flags = 0);
-inline void add_files (file_storage& fs, std::string const& file, boost::uint32_t flags = 0);
-
-

Adds the file specified by path to the file_storage object. In case path -refers to a diretory, files will be added recursively from the directory.

-

If specified, the predicate p is called once for every file and directory that -is encountered. files for which p returns true are added, and directories for -which p returns true are traversed. p must have the following signature:

-
-bool Pred(std::string const& p);
-
-

The path that is passed in to the predicate is the full path of the file or -directory. If no predicate is specified, all files are added, and all directories -are traveresed.

-

The ".." directory is never traversed.

-

The flags argument should be the same as the flags passed to the create_torrent -constructor.

-
-
-

set_piece_hashes()

-

Declared in "libtorrent/create_torrent.hpp"

-
-void set_piece_hashes (create_torrent& t, std::string const& p
-   , boost::function<void(int)> f, error_code& ec);
-inline void set_piece_hashes (create_torrent& t, std::string const& p);
-inline void set_piece_hashes (create_torrent& t, std::string const& p, error_code& ec);
-
-

This function will assume that the files added to the torrent file exists at path -p, read those files and hash the content and set the hashes in the create_torrent -object. The optional function f is called in between every hash that is set. f -must have the following signature:

-
-void Fun(int);
-
-

The overloads that don't take an error_code& may throw an exception in case of a -file error, the other overloads sets the error code to reflect the error, if any.

-
-
-
- - diff --git a/docs/reference-Custom_Storage.html b/docs/reference-Custom_Storage.html deleted file mode 100644 index 7b4bdb956..000000000 --- a/docs/reference-Custom_Storage.html +++ /dev/null @@ -1,621 +0,0 @@ - - - - - - -Custom Storage - - - - - - - - -
-
-
- -
- -
-

Custom Storage

- --- - - - - - -
Author:Arvid Norberg, arvid@rasterbar.com
Version:1.0.0
-
-

Table of contents

- -
-

libtorrent provides a customization point for storage of data. By default, -(default_storage) downloaded files are saved to disk according with the -general conventions of bittorrent clients, mimicing the original file layout -when the torrent was created. The libtorrent user may define a custom -storage to store piece data in a different way.

-

A custom storage implementation must derive from and implement the -storage_interface. You must also provide a function that constructs the -custom storage object and provide this function to the add_torrent() call -via add_torrent_params. Either passed in to the constructor or by setting -the add_torrent_params::storage field.

-

This is an example storage implementation that stores all pieces in a -std::map, i.e. in RAM. It's not necessarily very useful in practice, but -illustrates the basics of implementing a custom storage.

-
-struct temp_storage : storage_interface
-{
-        temp_storage(file_storage const& fs) : m_files(fs) {}
-        void set_file_priority(std::vector<boost::uint8_t> const& prio) {}
-        virtual bool initialize(bool allocate_files) { return false; }
-        virtual bool has_any_file() { return false; }
-        virtual int read(char* buf, int slot, int offset, int size)
-        {
-                std::map<int, std::vector<char> >::const_iterator i = m_file_data.find(slot);
-                if (i == m_file_data.end()) return 0;
-                int available = i->second.size() - offset;
-                if (available <= 0) return 0;
-                if (available > size) available = size;
-                memcpy(buf, &i->second[offset], available);
-                return available;
-        }
-        virtual int write(const char* buf, int slot, int offset, int size)
-        {
-                std::vector<char>& data = m_file_data[slot];
-                if (data.size() < offset + size) data.resize(offset + size);
-                std::memcpy(&data[offset], buf, size);
-                return size;
-        }
-        virtual bool rename_file(int file, std::string const& new_name)
-        { assert(false); return false; }
-        virtual bool move_storage(std::string const& save_path) { return false; }
-        virtual bool verify_resume_data(lazy_entry const& rd, error_code& error) { return false; }
-        virtual bool write_resume_data(entry& rd) const { return false; }
-        virtual bool move_slot(int src_slot, int dst_slot) { assert(false); return false; }
-        virtual bool swap_slots(int slot1, int slot2) { assert(false); return false; }
-        virtual bool swap_slots3(int slot1, int slot2, int slot3) { assert(false); return false; }
-        virtual size_type physical_offset(int slot, int offset)
-        { return slot * m_files.piece_length() + offset; };
-        virtual sha1_hash hash_for_slot(int slot, partial_hash& ph, int piece_size)
-        {
-                int left = piece_size - ph.offset;
-                assert(left >= 0);
-                if (left > 0)
-                {
-                        std::vector<char>& data = m_file_data[slot];
-                        // if there are padding files, those blocks will be considered
-                        // completed even though they haven't been written to the storage.
-                        // in this case, just extend the piece buffer to its full size
-                        // and fill it with zeroes.
-                        if (data.size() < piece_size) data.resize(piece_size, 0);
-                        ph.h.update(&data[ph.offset], left);
-                }
-                return ph.h.final();
-        }
-        virtual bool release_files() { return false; }
-        virtual bool delete_files() { return false; }
-
-        std::map<int, std::vector<char> > m_file_data;
-        file_storage m_files;
-};
-
-storage_interface* temp_storage_constructor(
-        file_storage const& fs, file_storage const* mapped
-        , std::string const& path, file_pool& fp
-        , std::vector<boost::uint8_t> const& prio)
-{
-        return new temp_storage(fs);
-}
-
-
-

file_pool

-

Declared in "libtorrent/file_pool.hpp"

-

this is an internal cache of open file handles. It's primarily used by -storage_interface implementations. It provides semi weak guarantees of -not opening more file handles than specified. Given multiple threads, -each with the ability to lock a file handle (via smart pointer), there -may be windows where more file handles are open.

-
-struct file_pool : boost::noncopyable
-{
-   ~file_pool ();
-   file_pool (int size = 40);
-   boost::intrusive_ptr<file> open_file (void* st, std::string const& p
-      , int file_index, file_storage const& fs, int m, error_code& ec);
-   void release (void* st);
-   void release (void* st, int file_index);
-   void resize (int size);
-   int size_limit () const;
-};
-
- -
-

~file_pool() file_pool()

-
-~file_pool ();
-file_pool (int size = 40);
-
-

size specifies the number of allowed files handles -to hold open at any given time.

-
-
-

open_file()

-
-boost::intrusive_ptr<file> open_file (void* st, std::string const& p
-      , int file_index, file_storage const& fs, int m, error_code& ec);
-
-

return an open file handle to file at file_index in the -file_storage fs opened at save path p. m is the -file open mode (see file::open_mode_t).

-
-
-

release()

-
-void release (void* st);
-void release (void* st, int file_index);
-
-

release all files belonging to the specified storage_interface (st) -the overload that takes file_index releases only the file with -that index in storage st.

-
-
-

resize()

-
-void resize (int size);
-
-

update the allowed number of open file handles to size.

-
-
-

size_limit()

-
-int size_limit () const;
-
-

returns the current limit of number of allowed open file handles held -by the file_pool.

-
-
-
-

storage_interface

-

Declared in "libtorrent/storage.hpp"

-

The storage interface is a pure virtual class that can be implemented to -customize how and where data for a torrent is stored. The default storage -implementation uses regular files in the filesystem, mapping the files in -the torrent in the way one would assume a torrent is saved to disk. -Implementing your own storage interface makes it possible to store all -data in RAM, or in some optimized order on disk (the order the pieces are -received for instance), or saving multifile torrents in a single file in -order to be able to take advantage of optimized disk-I/O.

-

It is also possible to write a thin class that uses the default storage -but modifies some particular behavior, for instance encrypting the data -before it's written to disk, and decrypting it when it's read again.

-

The storage interface is based on slots, each slot is 'piece_size' number -of bytes. All access is done by writing and reading whole or partial -slots. One slot is one piece in the torrent, but the data in the slot -does not necessarily correspond to the piece with the same index (in -compact allocation mode it won't).

-

libtorrent comes with two built-in storage implementations; -default_storage and disabled_storage. Their constructor functions -are called default_storage_constructor() and -disabled_storage_constructor respectively. The disabled storage does -just what it sounds like. It throws away data that's written, and it -reads garbage. It's useful mostly for benchmarking and profiling purpose.

-
-struct storage_interface
-{
-   virtual bool initialize (bool allocate_files) = 0;
-   virtual bool has_any_file () = 0;
-   virtual void set_file_priority (std::vector<boost::uint8_t> const& prio) = 0;
-   virtual int writev (file::iovec_t const* bufs, int slot, int offset, int num_bufs, int flags = file::random_access);
-   virtual int readv (file::iovec_t const* bufs, int slot, int offset, int num_bufs, int flags = file::random_access);
-   virtual void hint_read (int, int, int);
-   virtual int read (char* buf, int slot, int offset, int size) = 0;
-   virtual int write (const char* buf, int slot, int offset, int size) = 0;
-   virtual size_type physical_offset (int slot, int offset) = 0;
-   virtual int sparse_end (int start) const;
-   virtual int move_storage (std::string const& save_path, int flags) = 0;
-   virtual bool verify_resume_data (lazy_entry const& rd, error_code& error) = 0;
-   virtual bool write_resume_data (entry& rd) const = 0;
-   virtual bool move_slot (int src_slot, int dst_slot) = 0;
-   virtual bool swap_slots (int slot1, int slot2) = 0;
-   virtual bool swap_slots3 (int slot1, int slot2, int slot3) = 0;
-   virtual bool release_files () = 0;
-   virtual bool rename_file (int index, std::string const& new_filename) = 0;
-   virtual bool delete_files () = 0;
-   disk_buffer_pool* disk_pool ();
-   session_settings const& settings () const;
-   void set_error (std::string const& file, error_code const& ec) const;
-   error_code const& error () const;
-   std::string const& error_file () const;
-   virtual void clear_error ();
-};
-
-
-

initialize()

-
-virtual bool initialize (bool allocate_files) = 0;
-
-

This function is called when the storage is to be initialized. The -default storage will create directories and empty files at this point. -If allocate_files is true, it will also ftruncate all files to -their target size.

-

Returning true indicates an error occurred.

-
-
-

has_any_file()

-
-virtual bool has_any_file () = 0;
-
-

This function is called when first checking (or re-checking) the -storage for a torrent. It should return true if any of the files that -is used in this storage exists on disk. If so, the storage will be -checked for existing pieces before starting the download.

-
-
-

set_file_priority()

-
-virtual void set_file_priority (std::vector<boost::uint8_t> const& prio) = 0;
-
-

change the priorities of files.

- -
-
-

writev() readv()

-
-virtual int writev (file::iovec_t const* bufs, int slot, int offset, int num_bufs, int flags = file::random_access);
-virtual int readv (file::iovec_t const* bufs, int slot, int offset, int num_bufs, int flags = file::random_access);
-
-

These functions should read or write the data in or to the given -slot at the given offset. It should read or write num_bufs -buffers sequentially, where the size of each buffer is specified in -the buffer array bufs. The file::iovec_t type has the following -members:

-
-struct iovec_t { void* iov_base; size_t iov_len; };
-
-

The return value is the number of bytes actually read or written, or --1 on failure. If it returns -1, the error code is expected to be set -to

-

Every buffer in bufs can be assumed to be page aligned and be of a -page aligned size, except for the last buffer of the torrent. The -allocated buffer can be assumed to fit a fully page aligned number of -bytes though. This is useful when reading and writing the last piece -of a file in unbuffered mode.

-

The offset is aligned to 16 kiB boundries most of the time, but -there are rare exceptions when it's not. Specifically if the read -cache is disabled/or full and a client requests unaligned data, or the -file itself is not aligned in the torrent. Most clients request -aligned data.

-
-
-

hint_read()

-
-virtual void hint_read (int, int, int);
-
-

This function is called when a read job is queued. It gives the -storage wrapper an opportunity to hint the operating system about this -coming read. For instance, the storage may call -posix_fadvise(POSIX_FADV_WILLNEED) or fcntl(F_RDADVISE).

-
-
-

read()

-
-virtual int read (char* buf, int slot, int offset, int size) = 0;
-
-

negative return value indicates an error

-
-
-

write()

-
-virtual int write (const char* buf, int slot, int offset, int size) = 0;
-
-

negative return value indicates an error

-
-
-

physical_offset()

-
-virtual size_type physical_offset (int slot, int offset) = 0;
-
-

returns the offset on the physical storage medium for the -byte at offset offset in slot slot.

-
-
-

sparse_end()

-
-virtual int sparse_end (int start) const;
-
-

This function is optional. It is supposed to return the first piece, -starting at start that is fully contained within a data-region on -disk (i.e. non-sparse region). The purpose of this is to skip parts of -files that can be known to contain zeros when checking files.

-
-
-

move_storage()

-
-virtual int move_storage (std::string const& save_path, int flags) = 0;
-
-

This function should move all the files belonging to the storage to -the new save_path. The default storage moves the single file or the -directory of the torrent.

-

Before moving the files, any open file handles may have to be closed, -like release_files().

-

returns one of: -| no_error = 0 -| need_full_check = -1 -| fatal_disk_error = -2 -| file_exist = -4

-
-
-

verify_resume_data()

-
-virtual bool verify_resume_data (lazy_entry const& rd, error_code& error) = 0;
-
-

This function should verify the resume data rd with the files -on disk. If the resume data seems to be up-to-date, return true. If -not, set error to a description of what mismatched and return false.

-

The default storage may compare file sizes and time stamps of the files.

-

Returning false indicates an error occurred.

-
-
-

write_resume_data()

-
-virtual bool write_resume_data (entry& rd) const = 0;
-
-

This function should fill in resume data, the current state of the -storage, in rd. The default storage adds file timestamps and -sizes.

-

Returning true indicates an error occurred.

-
-
-

move_slot()

-
-virtual bool move_slot (int src_slot, int dst_slot) = 0;
-
-

This function should copy or move the data in slot src_slot to -the slot dst_slot. This is only used in compact mode.

-

If the storage caches slots, this could be implemented more -efficient than reading and writing the data.

-

Returning true indicates an error occurred.

-
-
-

swap_slots()

-
-virtual bool swap_slots (int slot1, int slot2) = 0;
-
-

This function should swap the data in slot1 and slot2. The -default storage uses a scratch buffer to read the data into, then -moving the other slot and finally writing back the temporary slot's -data

-

This is only used in compact mode.

-

Returning true indicates an error occurred.

-
-
-

swap_slots3()

-
-virtual bool swap_slots3 (int slot1, int slot2, int slot3) = 0;
-
-

This function should do a 3-way swap, or shift of the slots. slot1 -should move to slot2, which should be moved to slot3 which in -turn should be moved to slot1.

-

This is only used in compact mode.

-

Returning true indicates an error occurred.

-
-
-

release_files()

-
-virtual bool release_files () = 0;
-
-

This function should release all the file handles that it keeps open to files -belonging to this storage. The default implementation just calls -file_pool::release_files(this).

-

Returning true indicates an error occurred.

-
-
-

rename_file()

-
-virtual bool rename_file (int index, std::string const& new_filename) = 0;
-
-

Rename file with index file to the thame new_name. If there is an error, -true should be returned.

-
-
-

delete_files()

-
-virtual bool delete_files () = 0;
-
-

This function should delete all files and directories belonging to -this storage.

-

Returning true indicates an error occurred.

-

The disk_buffer_pool is used to allocate and free disk buffers. It -has the following members:

-
-struct disk_buffer_pool : boost::noncopyable
-{
-        char* allocate_buffer(char const* category);
-        void free_buffer(char* buf);
-
-        char* allocate_buffers(int blocks, char const* category);
-        void free_buffers(char* buf, int blocks);
-
-        int block_size() const { return m_block_size; }
-
-        void release_memory();
-};
-
-
-
-

disk_pool()

-
-disk_buffer_pool* disk_pool ();
-
-

access global disk_buffer_pool, for allocating and freeing disk buffers

-
-
-

settings()

-
-session_settings const& settings () const;
-
-

access global session_settings

-
-
-

set_error()

-
-void set_error (std::string const& file, error_code const& ec) const;
-
-

called by the storage implementation to set it into an -error state. Typically whenever a critical file operation -fails.

- -
-
-

error_file() error()

-
-error_code const& error () const;
-std::string const& error_file () const;
-
-

returns the currently set error code and file path associated with it, -if set.

-
-
-

clear_error()

-
-virtual void clear_error ();
-
-

reset the error state to allow continuing reading and writing -to the storage

-
-
-
-

default_storage

-

Declared in "libtorrent/storage.hpp"

-

The default implementation of storage_interface. Behaves as a normal -bittorrent client. It is possible to derive from this class in order to -override some of its behavior, when implementing a custom storage.

-
-class default_storage : public storage_interface, boost::noncopyable
-{
-   default_storage (file_storage const& fs, file_storage const* mapped
-      , std::string const& path, file_pool& fp
-      , std::vector<boost::uint8_t> const& file_prio);
-   bool move_slot (int src_slot, int dst_slot);
-   void hint_read (int slot, int offset, int len);
-   bool rename_file (int index, std::string const& new_filename);
-   void set_file_priority (std::vector<boost::uint8_t> const& prio);
-   bool has_any_file ();
-   int move_storage (std::string const& save_path, int flags);
-   bool write_resume_data (entry& rd) const;
-   int write (char const* buf, int slot, int offset, int size);
-   int writev (file::iovec_t const* buf, int slot, int offset, int num_bufs, int flags = file::random_access);
-   size_type physical_offset (int slot, int offset);
-   bool release_files ();
-   bool delete_files ();
-   bool verify_resume_data (lazy_entry const& rd, error_code& error);
-   int readv (file::iovec_t const* bufs, int slot, int offset, int num_bufs, int flags = file::random_access);
-   bool swap_slots3 (int slot1, int slot2, int slot3);
-   bool initialize (bool allocate_files);
-   int read (char* buf, int slot, int offset, int size);
-   bool swap_slots (int slot1, int slot2);
-   int sparse_end (int start) const;
-   file_storage const& files () const;
-};
-
-
-

default_storage()

-
-default_storage (file_storage const& fs, file_storage const* mapped
-      , std::string const& path, file_pool& fp
-      , std::vector<boost::uint8_t> const& file_prio);
-
-

constructs the default_storage based on the give file_storage (fs). -mapped is an optional argument (it may be NULL). If non-NULL it -represents the file mappsing that have been made to the torrent before -adding it. That's where files are supposed to be saved and looked for -on disk. save_path is the root save folder for this torrent. -file_pool is the cache of file handles that the storage will use. -All files it opens will ask the file_pool to open them. file_prio -is a vector indicating the priority of files on startup. It may be -an empty vector. Any file whose index is not represented by the vector -(because the vector is too short) are assumed to have priority 1. -this is used to treat files with priority 0 slightly differently.

-
-
-

files()

-
-file_storage const& files () const;
-
-

if the files in this storage are mapped, returns the mapped -file_storage, otherwise returns the original file_storage object.

-
-
-

enum move_flags_t

-

Declared in "libtorrent/storage.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
always_replace_files0replace any files in the destination when copying -or moving the storage
fail_if_exist1if any files that we want to copy exist in the destination -exist, fail the whole operation and don't perform -any copy or move. There is an inherent race condition -in this mode. The files are checked for existence before -the operation starts. In between the check and performing -the copy, the destination files may be created, in which -case they are replaced.
dont_replace2if any file exist in the target, take those files instead -of the ones we may have in the source.
-
-
-
- - diff --git a/docs/reference-Error_Codes.html b/docs/reference-Error_Codes.html deleted file mode 100644 index 9b621ad67..000000000 --- a/docs/reference-Error_Codes.html +++ /dev/null @@ -1,1111 +0,0 @@ - - - - - - -Error Codes - - - - - - - - -
-
-
- -
- -
-

Error Codes

- --- - - - - - -
Author:Arvid Norberg, arvid@rasterbar.com
Version:1.0.0
-
-

Table of contents

- -
-
-

libtorrent_exception

-

Declared in "libtorrent/error_code.hpp"

-
-struct libtorrent_exception: std::exception
-{
-   error_code error () const;
-   virtual ~libtorrent_exception () throw();
-   libtorrent_exception (error_code const& s);
-   virtual const char* what () const throw();
-};
-
-
-

get_libtorrent_category()

-

Declared in "libtorrent/error_code.hpp"

-
-boost::system::error_category& get_libtorrent_category ();
-
-

return the instance of the libtorrent_error_category which -maps libtorrent error codes to human readable error messages.

-
-
-

get_http_category()

-

Declared in "libtorrent/error_code.hpp"

-
-boost::system::error_category& get_http_category ();
-
-

returns the error_category for HTTP errors

-
-
-

get_i2p_category()

-

Declared in "libtorrent/i2p_stream.hpp"

-
-boost::system::error_category& get_i2p_category ();
-
-

returns the error category for I2P errors

-
-
-

get_bdecode_category()

-

Declared in "libtorrent/lazy_entry.hpp"

-
-boost::system::error_category& get_bdecode_category ();
-
-

get the error_category for bdecode errors

-
-
-

get_socks_category()

-

Declared in "libtorrent/socks5_stream.hpp"

-
-boost::system::error_category& get_socks_category ();
-
-

returns the error_category for SOCKS5 errors

-
-
-

get_upnp_category()

-

Declared in "libtorrent/upnp.hpp"

-
-boost::system::error_category& get_upnp_category ();
-
-
-
-

enum error_code_enum

-

Declared in "libtorrent/error_code.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
no_error0Not an error
file_collision1Two torrents has files which end up overwriting each other
failed_hash_check2A piece did not match its piece hash
torrent_is_no_dict3The .torrent file does not contain a bencoded dictionary at -its top level
torrent_missing_info4The .torrent file does not have an info dictionary
torrent_info_no_dict5The .torrent file's info entry is not a dictionary
torrent_missing_piece_length6The .torrent file does not have a piece length entry
torrent_missing_name7The .torrent file does not have a name entry
torrent_invalid_name8The .torrent file's name entry is invalid
torrent_invalid_length9The length of a file, or of the whole .torrent file is invalid. -Either negative or not an integer
torrent_file_parse_failed10Failed to parse a file entry in the .torrent
torrent_missing_pieces11The pieces field is missing or invalid in the .torrent file
torrent_invalid_hashes12The pieces string has incorrect length
too_many_pieces_in_torrent13The .torrent file has more pieces than is supported by libtorrent
invalid_swarm_metadata14The metadata (.torrent file) that was received from the swarm -matched the info-hash, but failed to be parsed
invalid_bencoding15The file or buffer is not correctly bencoded
no_files_in_torrent16The .torrent file does not contain any files
invalid_escaped_string17The string was not properly url-encoded as expected
session_is_closing18Operation is not permitted since the session is shutting down
duplicate_torrent19There's already a torrent with that info-hash added to the -session
invalid_torrent_handle20The supplied torrent_handle is not referring to a valid torrent
invalid_entry_type21The type requested from the entry did not match its type
missing_info_hash_in_uri22The specified URI does not contain a valid info-hash
file_too_short23One of the files in the torrent was unexpectadly small. This -might be caused by files being changed by an external process
unsupported_url_protocol24The URL used an unknown protocol. Currently http and -https (if built with openssl support) are recognized. For -trackers udp is recognized as well.
url_parse_error25The URL did not conform to URL syntax and failed to be parsed
peer_sent_empty_piece26The peer sent a 'piece' message of length 0
parse_failed27A bencoded structure was currupt and failed to be parsed
invalid_file_tag28The fast resume file was missing or had an invalid file version -tag
missing_info_hash29The fast resume file was missing or had an invalid info-hash
mismatching_info_hash30The info-hash did not match the torrent
invalid_hostname31The URL contained an invalid hostname
invalid_port32The URL had an invalid port
port_blocked33The port is blocked by the port-filter, and prevented the -connection
expected_close_bracket_in_address34The IPv6 address was expected to end with ']'
destructing_torrent35The torrent is being destructed, preventing the operation to -succeed
timed_out36The connection timed out
upload_upload_connection37The peer is upload only, and we are upload only. There's no point -in keeping the connection
uninteresting_upload_peer38The peer is upload only, and we're not interested in it. There's -no point in keeping the connection
invalid_info_hash39The peer sent an unknown info-hash
torrent_paused40The torrent is paused, preventing the operation from succeeding
invalid_have41The peer sent an invalid have message, either wrong size or -referring to a piece that doesn't exist in the torrent
invalid_bitfield_size42The bitfield message had the incorrect size
too_many_requests_when_choked43The peer kept requesting pieces after it was choked, possible -abuse attempt.
invalid_piece44The peer sent a piece message that does not correspond to a -piece request sent by the client
no_memory45memory allocation failed
torrent_aborted46The torrent is aborted, preventing the operation to succeed
self_connection47The peer is a connection to ourself, no point in keeping it
invalid_piece_size48The peer sent a piece message with invalid size, either negative -or greater than one block
timed_out_no_interest49The peer has not been interesting or interested in us for too -long, no point in keeping it around
timed_out_inactivity50The peer has not said anything in a long time, possibly dead
timed_out_no_handshake51The peer did not send a handshake within a reasonable amount of -time, it might not be a bittorrent peer
timed_out_no_request52The peer has been unchoked for too long without requesting any -data. It might be lying about its interest in us
invalid_choke53The peer sent an invalid choke message
invalid_unchoke54The peer send an invalid unchoke message
invalid_interested55The peer sent an invalid interested message
invalid_not_interested56The peer sent an invalid not-interested message
invalid_request57The peer sent an invalid piece request message
invalid_hash_list58The peer sent an invalid hash-list message (this is part of the -merkle-torrent extension)
invalid_hash_piece59The peer sent an invalid hash-piece message (this is part of the -merkle-torrent extension)
invalid_cancel60The peer sent an invalid cancel message
invalid_dht_port61The peer sent an invalid DHT port-message
invalid_suggest62The peer sent an invalid suggest piece-message
invalid_have_all63The peer sent an invalid have all-message
invalid_have_none64The peer sent an invalid have none-message
invalid_reject65The peer sent an invalid reject message
invalid_allow_fast66The peer sent an invalid allow fast-message
invalid_extended67The peer sent an invalid extesion message ID
invalid_message68The peer sent an invalid message ID
sync_hash_not_found69The synchronization hash was not found in the encrypted handshake
invalid_encryption_constant70The encryption constant in the handshake is invalid
no_plaintext_mode71The peer does not support plaintext, which is the selected mode
no_rc4_mode72The peer does not support rc4, which is the selected mode
unsupported_encryption_mode73The peer does not support any of the encryption modes that the -client supports
unsupported_encryption_mode_selected74The peer selected an encryption mode that the client did not -advertise and does not support
invalid_pad_size75The pad size used in the encryption handshake is of invalid size
invalid_encrypt_handshake76The encryption handshake is invalid
no_incoming_encrypted77The client is set to not support incoming encrypted connections -and this is an encrypted connection
no_incoming_regular78The client is set to not support incoming regular bittorrent -connections, and this is a regular connection
duplicate_peer_id79The client is already connected to this peer-ID
torrent_removed80Torrent was removed
packet_too_large81The packet size exceeded the upper sanity check-limit
reserved82 
http_error83The web server responded with an error
missing_location84The web server response is missing a location header
invalid_redirection85The web seed redirected to a path that no longer matches the -.torrent directory structure
redirecting86The connection was closed becaused it redirected to a different -URL
invalid_range87The HTTP range header is invalid
no_content_length88The HTTP response did not have a content length
banned_by_ip_filter89The IP is blocked by the IP filter
too_many_connections90At the connection limit
peer_banned91The peer is marked as banned
stopping_torrent92The torrent is stopping, causing the operation to fail
too_many_corrupt_pieces93The peer has sent too many corrupt pieces and is banned
torrent_not_ready94The torrent is not ready to receive peers
peer_not_constructed95The peer is not completely constructed yet
session_closing96The session is closing, causing the operation to fail
optimistic_disconnect97The peer was disconnected in order to leave room for a -potentially better peer
torrent_finished98The torrent is finished
no_router99No UPnP router found
metadata_too_large100The metadata message says the metadata exceeds the limit
invalid_metadata_request101The peer sent an invalid metadata request message
invalid_metadata_size102The peer advertised an invalid metadata size
invalid_metadata_offset103The peer sent a message with an invalid metadata offset
invalid_metadata_message104The peer sent an invalid metadata message
pex_message_too_large105The peer sent a peer exchange message that was too large
invalid_pex_message106The peer sent an invalid peer exchange message
invalid_lt_tracker_message107The peer sent an invalid tracker exchange message
too_frequent_pex108The peer sent an pex messages too often. This is a possible -attempt of and attack
no_metadata109The operation failed because it requires the torrent to have -the metadata (.torrent file) and it doesn't have it yet. -This happens for magnet links before they have downloaded the -metadata, and also torrents added by URL.
invalid_dont_have110The peer sent an invalid dont_have message. The dont have -message is an extension to allow peers to advertise that the -no longer has a piece they previously had.
requires_ssl_connection111The peer tried to connect to an SSL torrent without connecting -over SSL.
invalid_ssl_cert112The peer tried to connect to a torrent with a certificate -for a different torrent.
not_an_ssl_torrent113the torrent is not an SSL torrent, and the operation requires -an SSL torrent
unsupported_protocol_version120The NAT-PMP router responded with an unsupported protocol version
natpmp_not_authorized121You are not authorized to map ports on this NAT-PMP router
network_failure122The NAT-PMP router failed because of a network failure
no_resources123The NAT-PMP router failed because of lack of resources
unsupported_opcode124The NAT-PMP router failed because an unsupported opcode was sent
missing_file_sizes130The resume data file is missing the 'file sizes' entry
no_files_in_resume_data131The resume data file 'file sizes' entry is empty
missing_pieces132The resume data file is missing the 'pieces' and 'slots' entry
mismatching_number_of_files133The number of files in the resume data does not match the number -of files in the torrent
mismatching_file_size134One of the files on disk has a different size than in the fast -resume file
mismatching_file_timestamp135One of the files on disk has a different timestamp than in the -fast resume file
not_a_dictionary136The resume data file is not a dictionary
invalid_blocks_per_piece137The 'blocks per piece' entry is invalid in the resume data file
missing_slots138The resume file is missing the 'slots' entry, which is required -for torrents with compact allocation
too_many_slots139The resume file contains more slots than the torrent
invalid_slot_list140The 'slot' entry is invalid in the resume data
invalid_piece_index141One index in the 'slot' list is invalid
pieces_need_reorder142The pieces on disk needs to be re-ordered for the specified -allocation mode. This happens if you specify sparse allocation -and the files on disk are using compact storage. The pieces needs -to be moved to their right position
http_parse_error150The HTTP header was not correctly formatted
http_missing_location151The HTTP response was in the 300-399 range but lacked a location -header
http_failed_decompress152The HTTP response was encoded with gzip or deflate but -decompressing it failed
no_i2p_router160The URL specified an i2p address, but no i2p router is configured
scrape_not_available170The tracker URL doesn't support transforming it into a scrape -URL. i.e. it doesn't contain "announce.
invalid_tracker_response171invalid tracker response
invalid_peer_dict172invalid peer dictionary entry. Not a dictionary
tracker_failure173tracker sent a failure message
invalid_files_entry174missing or invalid 'files' entry
invalid_hash_entry175missing or invalid 'hash' entry
invalid_peers_entry176missing or invalid 'peers' and 'peers6' entry
invalid_tracker_response_length177udp tracker response packet has invalid size
invalid_tracker_transaction_id178invalid transaction id in udp tracker response
invalid_tracker_action179invalid action field in udp tracker response
error_code_max180the number of error codes
-
-
-

enum http_errors

-

Declared in "libtorrent/error_code.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
cont100 
ok200 
created201 
accepted202 
no_content204 
multiple_choices300 
moved_permanently301 
moved_temporarily302 
not_modified304 
bad_request400 
unauthorized401 
forbidden403 
not_found404 
internal_server_error500 
not_implemented501 
bad_gateway502 
service_unavailable503 
-
-
-

enum i2p_error_code

-

Declared in "libtorrent/i2p_stream.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
no_error0 
parse_failed1 
cant_reach_peer2 
i2p_error3 
invalid_key4 
invalid_id5 
timeout6 
key_not_found7 
duplicated_id8 
num_errors9 
-
-
-

enum error_code_enum

-

Declared in "libtorrent/lazy_entry.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
no_error0Not an error
expected_string1expected string in bencoded string
expected_colon2expected colon in bencoded string
unexpected_eof3unexpected end of file in bencoded string
expected_value4expected value (list, dict, int or string) in bencoded string
depth_exceeded5bencoded recursion depth limit exceeded
limit_exceeded6bencoded item count limit exceeded
overflow7integer overflow
error_code_max8the number of error codes
-
-
-

enum socks_error_code

-

Declared in "libtorrent/socks5_stream.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
no_error0 
unsupported_version1 
unsupported_authentication_method2 
unsupported_authentication_version3 
authentication_error4 
username_required5 
general_failure6 
command_not_supported7 
no_identd8 
identd_error9 
num_errors10 
-
-
-

enum error_code_enum

-

Declared in "libtorrent/upnp.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
no_error0No error
invalid_argument402One of the arguments in the request is invalid
action_failed501The request failed
value_not_in_array714The specified value does not exist in the array
source_ip_cannot_be_wildcarded715The source IP address cannot be wild-carded, but -must be fully specified
external_port_cannot_be_wildcarded716The external port cannot be wildcarded, but must -be specified
port_mapping_conflict718The port mapping entry specified conflicts with a -mapping assigned previously to another client
internal_port_must_match_external724Internal and external port value must be the same
only_permanent_leases_supported725The NAT implementation only supports permanent -lease times on port mappings
remote_host_must_be_wildcard726RemoteHost must be a wildcard and cannot be a -specific IP addres or DNS name
external_port_must_be_wildcard727ExternalPort must be a wildcard and cannot be a -specific port
-
-
-
- - diff --git a/docs/reference-Filter.html b/docs/reference-Filter.html deleted file mode 100644 index 5c5e956ae..000000000 --- a/docs/reference-Filter.html +++ /dev/null @@ -1,211 +0,0 @@ - - - - - - -Filter - - - - - - - - -
-
-
- -
- -
-

Filter

- --- - - - - - -
Author:Arvid Norberg, arvid@rasterbar.com
Version:1.0.0
-
-

Table of contents

- -
-
-

ip_filter

-

Declared in "libtorrent/ip_filter.hpp"

-

The ip_filter class is a set of rules that uniquely categorizes all -ip addresses as allowed or disallowed. The default constructor creates -a single rule that allows all addresses (0.0.0.0 - 255.255.255.255 for -the IPv4 range, and the equivalent range covering all addresses for the -IPv6 range).

-

A default constructed ip_filter does not filter any address.

-
-struct ip_filter
-{
-   void add_rule (address first, address last, int flags);
-   int access (address const& addr) const;
-   filter_tuple_t export_filter () const;
-
-   enum access_flags
-   {
-      blocked,
-   };
-};
-
-
-

add_rule()

-
-void add_rule (address first, address last, int flags);
-
-

Adds a rule to the filter. first and last defines a range of -ip addresses that will be marked with the given flags. The flags -can currently be 0, which means allowed, or ip_filter::blocked, which -means disallowed.

-

precondition: -first.is_v4() == last.is_v4() && first.is_v6() == last.is_v6()

-

postcondition: -access(x) == flags for every x in the range [first, last]

-

This means that in a case of overlapping ranges, the last one applied takes -precedence.

-
-
-

access()

-
-int access (address const& addr) const;
-
-

Returns the access permissions for the given address (addr). The permission -can currently be 0 or ip_filter::blocked. The complexity of this operation -is O(log n), where n is the minimum number of non-overlapping ranges to describe -the current filter.

-
-
-

export_filter()

-
-filter_tuple_t export_filter () const;
-
-

This function will return the current state of the filter in the minimum number of -ranges possible. They are sorted from ranges in low addresses to high addresses. Each -entry in the returned vector is a range with the access control specified in its -flags field.

-

The return value is a tuple containing two range-lists. One for IPv4 addresses -and one for IPv6 addresses.

-
-
-

enum access_flags

-

Declared in "libtorrent/ip_filter.hpp"

- ----- - - - - - - - - - - - - -
namevaluedescription
blocked1indicates that IPs in this range should not be connected -to nor accepted as incoming connections
-
-
-
-

port_filter

-

Declared in "libtorrent/ip_filter.hpp"

-

the port filter maps non-overlapping port ranges to flags. This -is primarily used to indicate whether a range of ports should -be connected to or not. The default is to have the full port -range (0-65535) set to flag 0.

-
-class port_filter
-{
-   void add_rule (boost::uint16_t first, boost::uint16_t last, int flags);
-   int access (boost::uint16_t port) const;
-
-   enum access_flags
-   {
-      blocked,
-   };
-};
-
-
-

add_rule()

-
-void add_rule (boost::uint16_t first, boost::uint16_t last, int flags);
-
-

set the flags for the specified port range (first, last) to -flags overwriting any existing rule for those ports. The range -is inclusive, i.e. the port last also has the flag set on it.

-
-
-

access()

-
-int access (boost::uint16_t port) const;
-
-

test the specified port (port) for whether it is blocked -or not. The returned value is the flags set for this port. -see acces_flags.

-
-
-

enum access_flags

-

Declared in "libtorrent/ip_filter.hpp"

- ----- - - - - - - - - - - - - -
namevaluedescription
blocked1this flag indicates that destination ports in the -range should not be connected to
-
-
-
- - diff --git a/docs/reference-Plugins.html b/docs/reference-Plugins.html deleted file mode 100644 index fad193594..000000000 --- a/docs/reference-Plugins.html +++ /dev/null @@ -1,613 +0,0 @@ - - - - - - - - - - - - - - -
-
-
- -
- -
- - -
-

Plugins

- --- - - - - - -
Author:Arvid Norberg, arvid@rasterbar.com
Version:1.0.0
-
-

Table of contents

- -
-

libtorrent has a plugin interface for implementing extensions to the protocol. -These can be general extensions for transferring metadata or peer exchange -extensions, or it could be used to provide a way to customize the protocol -to fit a particular (closed) network.

-

In short, the plugin interface makes it possible to:

-
    -
  • register extension messages (sent in the extension handshake), see -extensions.
  • -
  • add data and parse data from the extension handshake.
  • -
  • send extension messages and standard bittorrent messages.
  • -
  • override or block the handling of standard bittorrent messages.
  • -
  • save and restore state via the session state
  • -
  • see all alerts that are posted
  • -
-
-

a word of caution

-

Writing your own plugin is a very easy way to introduce serious bugs such as -dead locks and race conditions. Since a plugin has access to internal -structures it is also quite easy to sabotage libtorrent's operation.

-

All the callbacks in this interface are called with the main libtorrent thread -mutex locked. And they are always called from the libtorrent network thread. In -case portions of your plugin are called from other threads, typically the main -thread, you cannot use any of the member functions on the internal structures -in libtorrent, since those require the mutex to be locked. Futhermore, you would -also need to have a mutex on your own shared data within the plugin, to make -sure it is not accessed at the same time from the libtorrent thread (through a -callback). See boost thread's mutex. If you need to send out a message from -another thread, it is advised to use an internal queue, and do the actual -sending in tick().

-

Since the plugin interface gives you easy access to internal structures, it -is not supported as a stable API. Plugins should be considered spcific to a -specific version of libtorrent. Although, in practice the internals mostly -don't change that dramatically.

-
-
-
-

plugin-interface

-

The plugin interface consists of three base classes that the plugin may -implement. These are called plugin, torrent_plugin and peer_plugin. -They are found in the <libtorrent/extensions.hpp> header.

-

These plugins are instantiated for each session, torrent and possibly each peer, -respectively.

-

For plugins that only need per torrent state, it is enough to only implement -torrent_plugin and pass a constructor function or function object to -session::add_extension() or torrent_handle::add_extension() (if the -torrent has already been started and you want to hook in the extension at -run-time).

-

The signature of the function is:

-
-boost::shared_ptr<torrent_plugin> (*)(torrent*, void*);
-
-

The first argument is the internal torrent object, the second argument -is the userdata passed to session::add_torrent() or -torrent_handle::add_extension().

-

The function should return a boost::shared_ptr<torrent_plugin> which -may or may not be 0. If it is a null pointer, the extension is simply ignored -for this torrent. If it is a valid pointer (to a class inheriting -torrent_plugin), it will be associated with this torrent and callbacks -will be made on torrent events.

-

For more elaborate plugins which require session wide state, you would -implement plugin, construct an object (in a boost::shared_ptr) and pass -it in to session::add_extension().

-
-
-

custom alerts

-

Since plugins are running within internal libtorrent threads, one convenient -way to communicate with the client is to post custom alerts.

-

The expected interface of any alert, apart from deriving from the alert -base class, looks like this:

-
-const static int alert_type = <unique alert ID>;
-virtual int type() const { return alert_type; }
-
-virtual std::string message() const;
-
-virtual std::auto_ptr<alert> clone() const
-{ return std::auto_ptr<alert>(new name(*this)); }
-
-const static int static_category = <bitmask of alert::category_t flags>;
-virtual int category() const { return static_category; }
-
-virtual char const* what() const { return <string literal of the name of this alert>; }
-
-

The alert_type is used for the type-checking in alert_cast. It must -not collide with any other alert. The built-in alerts in libtorrent will -not use alert type IDs greater than user_alert_id. When defining your -own alert, make sure it's greater than this constant.

-

type() is the run-time equivalence of the alert_type.

-

The message() virtual function is expected to construct a useful -string representation of the alert and the event or data it represents. -Something convenient to put in a log file for instance.

-

clone() is used internally to copy alerts. The suggested implementation -of simply allocating a new instance as a copy of *this is all that's -expected.

-

The static category is required for checking wether or not the category -for a specific alert is enabled or not, without instantiating the alert. -The category virtual function is the run-time equivalence.

-

The what() virtual function may simply be a string literal of the class -name of your alert.

-

For more information, see the alert section.

-
-

plugin

-

Declared in "libtorrent/extensions.hpp"

-

this is the base class for a session plugin. One primary feature -is that it is notified of all torrents that are added to the session, -and can add its own torrent_plugins.

-
-struct plugin
-{
-   virtual boost::shared_ptr<torrent_plugin> new_torrent (torrent*, void*);
-   virtual void added (aux::session_impl*);
-   virtual void on_alert (alert const*);
-   virtual void on_tick ();
-   virtual bool on_optimistic_unchoke (std::vector<policy::peer*>& /* peers */);
-   virtual void save_state (entry&) const;
-   virtual void load_state (lazy_entry const&);
-};
-
-
-

new_torrent()

-
-virtual boost::shared_ptr<torrent_plugin> new_torrent (torrent*, void*);
-
-

this is called by the session every time a new torrent is added. -The torrent* points to the internal torrent object created -for the new torrent. The void* is the userdata pointer as -passed in via add_torrent_params.

-

If the plugin returns a torrent_plugin instance, it will be added -to the new torrent. Otherwise, return an empty shared_ptr to a -torrent_plugin (the default).

-
-
-

added()

-
-virtual void added (aux::session_impl*);
-
-

called when plugin is added to a session

-
-
-

on_alert()

-
-virtual void on_alert (alert const*);
-
-

called when an alert is posted -alerts that are filtered are not -posted

-
-
-

on_tick()

-
-virtual void on_tick ();
-
-

called once per second

-
-
-

on_optimistic_unchoke()

-
-virtual bool on_optimistic_unchoke (std::vector<policy::peer*>& /* peers */);
-
-

called when choosing peers to optimisticly unchoke -peer's will be unchoked in the order they appear in the given -vector which is initiallity sorted by when they were last -optimistically unchoked. -if the plugin returns true then the ordering provided will be -used and no other plugin will be allowed to change it.

-
-
-

save_state()

-
-virtual void save_state (entry&) const;
-
-

called when saving settings state

-
-
-

load_state()

-
-virtual void load_state (lazy_entry const&);
-
-

called when loading settings state

-
-
-
-

torrent_plugin

-

Declared in "libtorrent/extensions.hpp"

-

Torrent plugins are associated with a single torrent and have a number -of functions called at certain events. Many of its functions have the -ability to change or override the default libtorrent behavior.

-
-struct torrent_plugin
-{
-   virtual boost::shared_ptr<peer_plugin> new_connection (peer_connection*);
-   virtual void on_piece_pass (int /*index*/);
-   virtual void on_piece_failed (int /*index*/);
-   virtual void tick ();
-   virtual bool on_resume ();
-   virtual bool on_pause ();
-   virtual void on_files_checked ();
-   virtual void on_state (int /*s*/);
-   virtual void on_add_peer (tcp::endpoint const&,
-      int /*src*/, int /*flags*/);
-};
-
-
-

new_connection()

-
-virtual boost::shared_ptr<peer_plugin> new_connection (peer_connection*);
-
-

This function is called each time a new peer is connected to the torrent. You -may choose to ignore this by just returning a default constructed -shared_ptr (in which case you don't need to override this member -function).

-

If you need an extension to the peer connection (which most plugins do) you -are supposed to return an instance of your peer_plugin class. Which in -turn will have its hook functions called on event specific to that peer.

-

The peer_connection will be valid as long as the shared_ptr is being -held by the torrent object. So, it is generally a good idea to not keep a -shared_ptr to your own peer_plugin. If you want to keep references to it, -use weak_ptr.

-

If this function throws an exception, the connection will be closed.

- -
-
-

on_piece_failed() on_piece_pass()

-
-virtual void on_piece_pass (int /*index*/);
-virtual void on_piece_failed (int /*index*/);
-
-

These hooks are called when a piece passes the hash check or fails the hash -check, respectively. The index is the piece index that was downloaded. -It is possible to access the list of peers that participated in sending the -piece through the torrent and the piece_picker.

-
-
-

tick()

-
-virtual void tick ();
-
-

This hook is called approximately once per second. It is a way of making it -easy for plugins to do timed events, for sending messages or whatever.

- -
-
-

on_resume() on_pause()

-
-virtual bool on_resume ();
-virtual bool on_pause ();
-
-

These hooks are called when the torrent is paused and unpaused respectively. -The return value indicates if the event was handled. A return value of -true indicates that it was handled, and no other plugin after this one -will have this hook function called, and the standard handler will also not be -invoked. So, returning true effectively overrides the standard behavior of -pause or unpause.

-

Note that if you call pause() or resume() on the torrent from your -handler it will recurse back into your handler, so in order to invoke the -standard handler, you have to keep your own state on whether you want standard -behavior or overridden behavior.

-
-
-

on_files_checked()

-
-virtual void on_files_checked ();
-
-

This function is called when the initial files of the torrent have been -checked. If there are no files to check, this function is called immediately.

-

i.e. This function is always called when the torrent is in a state where it -can start downloading.

-
-
-

on_state()

-
-virtual void on_state (int /*s*/);
-
-

called when the torrent changes state -the state is one of torrent_status::state_t -enum members

-
-
-

on_add_peer()

-
-virtual void on_add_peer (tcp::endpoint const&,
-      int /*src*/, int /*flags*/);
-
-

called every time a new peer is added to the peer list. -This is before the peer is connected to. For flags, see -torrent_plugin::flags_t. The source argument refers to -the source where we learned about this peer from. It's a -bitmask, because many sources may have told us about the same -peer. For peer source flags, see peer_info::peer_source_flags.

-
-
-
-

peer_plugin

-

Declared in "libtorrent/extensions.hpp"

-

peer plugins are associated with a specific peer. A peer could be -both a regular bittorrent peer (bt_peer_connection) or one of the -web seed connections (web_peer_connection or http_seed_connection). -In order to only attach to certain peers, make your -torrent_plugin::new_connection only return a plugin for certain peer -connection types

-
-struct peer_plugin
-{
-   virtual char const* type () const;
-   virtual void add_handshake (entry&);
-   virtual void on_disconnect (error_code const& /*ec*/);
-   virtual void on_connected ();
-   virtual bool on_handshake (char const* /*reserved_bits*/);
-   virtual bool on_extension_handshake (lazy_entry const&);
-   virtual bool on_have (int /*index*/);
-   virtual bool on_bitfield (bitfield const& /*bitfield*/);
-   virtual bool on_have_all ();
-   virtual bool on_reject (peer_request const&);
-   virtual bool on_request (peer_request const&);
-   virtual bool on_unchoke ();
-   virtual bool on_interested ();
-   virtual bool on_allowed_fast (int /*index*/);
-   virtual bool on_have_none ();
-   virtual bool on_choke ();
-   virtual bool on_not_interested ();
-   virtual bool on_piece (peer_request const& /*piece*/
-      , disk_buffer_holder& /*data*/);
-   virtual bool on_suggest (int /*index*/);
-   virtual bool on_cancel (peer_request const&);
-   virtual bool on_dont_have (int /*index*/);
-   virtual void sent_unchoke ();
-   virtual bool can_disconnect (error_code const& /*ec*/);
-   virtual bool on_extended (int /*length*/, int /*msg*/,
-      buffer::const_interval /*body*/);
-   virtual bool on_unknown_message (int /*length*/, int /*msg*/,
-      buffer::const_interval /*body*/);
-   virtual void on_piece_pass (int /*index*/);
-   virtual void on_piece_failed (int /*index*/);
-   virtual void tick ();
-   virtual bool write_request (peer_request const&);
-};
-
-
-

type()

-
-virtual char const* type () const;
-
-

This function is expected to return the name of -the plugin.

-
-
-

add_handshake()

-
-virtual void add_handshake (entry&);
-
-

can add entries to the extension handshake -this is not called for web seeds

-
-
-

on_disconnect()

-
-virtual void on_disconnect (error_code const& /*ec*/);
-
-

called when the peer is being disconnected.

-
-
-

on_connected()

-
-virtual void on_connected ();
-
-

called when the peer is successfully connected. Note that -incoming connections will have been connected by the time -the peer plugin is attached to it, and won't have this hook -called.

-
-
-

on_handshake()

-
-virtual bool on_handshake (char const* /*reserved_bits*/);
-
-

this is called when the initial BT handshake is received. Returning false -means that the other end doesn't support this extension and will remove -it from the list of plugins. -this is not called for web seeds

-
-
-

on_extension_handshake()

-
-virtual bool on_extension_handshake (lazy_entry const&);
-
-

called when the extension handshake from the other end is received -if this returns false, it means that this extension isn't -supported by this peer. It will result in this peer_plugin -being removed from the peer_connection and destructed. -this is not called for web seeds

- - - - - - - - - - - - - - -
-
-

on_bitfield() on_have_none() on_suggest() on_unchoke() on_cancel() on_have() on_choke() on_piece() on_request() on_reject() on_not_interested() on_interested() on_allowed_fast() on_have_all() on_dont_have()

-
-virtual bool on_have (int /*index*/);
-virtual bool on_bitfield (bitfield const& /*bitfield*/);
-virtual bool on_have_all ();
-virtual bool on_reject (peer_request const&);
-virtual bool on_request (peer_request const&);
-virtual bool on_unchoke ();
-virtual bool on_interested ();
-virtual bool on_allowed_fast (int /*index*/);
-virtual bool on_have_none ();
-virtual bool on_choke ();
-virtual bool on_not_interested ();
-virtual bool on_piece (peer_request const& /*piece*/
-      , disk_buffer_holder& /*data*/);
-virtual bool on_suggest (int /*index*/);
-virtual bool on_cancel (peer_request const&);
-virtual bool on_dont_have (int /*index*/);
-
-

returning true from any of the message handlers -indicates that the plugin has handeled the message. -it will break the plugin chain traversing and not let -anyone else handle the message, including the default -handler.

-
-
-

sent_unchoke()

-
-virtual void sent_unchoke ();
-
-

called after a choke message has been sent to the peer

-
-
-

can_disconnect()

-
-virtual bool can_disconnect (error_code const& /*ec*/);
-
-

called when libtorrent think this peer should be disconnected. -if the plugin returns false, the peer will not be disconnected.

-
-
-

on_extended()

-
-virtual bool on_extended (int /*length*/, int /*msg*/,
-      buffer::const_interval /*body*/);
-
-

called when an extended message is received. If returning true, -the message is not processed by any other plugin and if false -is returned the next plugin in the chain will receive it to -be able to handle it -this is not called for web seeds

-
-
-

on_unknown_message()

-
-virtual bool on_unknown_message (int /*length*/, int /*msg*/,
-      buffer::const_interval /*body*/);
-
-

this is not called for web seeds

- -
-
-

on_piece_failed() on_piece_pass()

-
-virtual void on_piece_pass (int /*index*/);
-virtual void on_piece_failed (int /*index*/);
-
-

called when a piece that this peer participated in either -fails or passes the hash_check

-
-
-

tick()

-
-virtual void tick ();
-
-

called aproximately once every second

-
-
-

write_request()

-
-virtual bool write_request (peer_request const&);
-
-

called each time a request message is to be sent. If true -is returned, the original request message won't be sent and -no other plugin will have this function called.

-
-
-

create_lt_trackers_plugin()

-

Declared in "libtorrent/extensions/lt_trackers.hpp"

-
-boost::shared_ptr<torrent_plugin> create_lt_trackers_plugin (torrent*, void*);
-
-

constructor function for the trackers exchange extension. This can -either be passed in the add_torrent_params::extensions field, or -via torrent_handle::add_extension().

-
-
-

create_smart_ban_plugin()

-

Declared in "libtorrent/extensions/smart_ban.hpp"

-
-boost::shared_ptr<torrent_plugin> create_smart_ban_plugin (torrent*, void*);
-
-

constructor function for the smart ban extension. The extension keeps -track of the data peers have sent us for failing pieces and once the -piece completes and passes the hash check bans the peers that turned -out to have sent corrupt data. -This function can either be passed in the add_torrent_params::extensions -field, or via torrent_handle::add_extension().

-
-
-

create_ut_metadata_plugin()

-

Declared in "libtorrent/extensions/ut_metadata.hpp"

-
-boost::shared_ptr<torrent_plugin> create_ut_metadata_plugin (torrent*, void*);
-
-

constructor function for the ut_metadata extension. The ut_metadata -extension allows peers to request the .torrent file (or more -specifically the 'info'-dictionary of the .torrent file) from each -other. This is the main building block in making magnet links work. -This extension is enabled by default unless explicitly disabled in -the session constructor.

-

This can either be passed in the add_torrent_params::extensions field, or -via torrent_handle::add_extension().

-
-
-

create_ut_pex_plugin()

-

Declared in "libtorrent/extensions/ut_pex.hpp"

-
-boost::shared_ptr<torrent_plugin> create_ut_pex_plugin (torrent*, void*);
-
-

constructor function for the ut_pex extension. The ut_pex -extension allows peers to gossip about their connections, allowing -the swarm stay well connected and peers aware of more peers in the -swarm. This extension is enabled by default unless explicitly disabled in -the session constructor.

-

This can either be passed in the add_torrent_params::extensions field, or -via torrent_handle::add_extension().

-
-
-
-
- - diff --git a/docs/reference-RSS.html b/docs/reference-RSS.html deleted file mode 100644 index 69a7f7d9a..000000000 --- a/docs/reference-RSS.html +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - -RSS - - - - - - - - -
-
-
- -
- -
-

RSS

- --- - - - - - -
Author:Arvid Norberg, arvid@rasterbar.com
Version:1.0.0
-
-

Table of contents

- -
-
-

feed_item

-

Declared in "libtorrent/rss.hpp"

-

represents one item from an RSS feed. Specifically -a feed of torrents.

-
-struct feed_item
-{
-   feed_item ();
-   ~feed_item ();
-
-   std::string url;
-   std::string uuid;
-   std::string title;
-   std::string description;
-   std::string comment;
-   std::string category;
-   size_type size;
-   torrent_handle handle;
-   sha1_hash info_hash;
-};
-
- - - - - -
-
url uuid title description comment category
-
these are self explanatory and may be empty if the feed does not specify -those fields.
-
-
-
size
-
the total size of the content the torrent refers to, or -1 -if no size was specified by the feed.
-
-
-
handle
-
the handle to the torrent, if the session is already downloading -this torrent.
-
-
-
info_hash
-
the info-hash of the torrent, or cleared (i.e. all zeroes) if -the feed does not specify the info-hash.
-
-
-
-

feed_settings

-

Declared in "libtorrent/rss.hpp"

-

the feed_settings object is all the information -and configuration for a specific feed. All of -these settings can be changed by the user -after adding the feed

-
-struct feed_settings
-{
-   feed_settings ();
-
-   std::string url;
-   bool auto_download;
-   bool auto_map_handles;
-   int default_ttl;
-   add_torrent_params add_args;
-};
-
-
-
auto_download
-
By default auto_download is true, which means all torrents in -the feed will be downloaded. Set this to false in order to manually -add torrents to the session. You may react to the rss_alert when -a feed has been updated to poll it for the new items in the feed -when adding torrents manually. When torrents are added automatically, -an add_torrent_alert is posted which includes the torrent handle -as well as the error code if it failed to be added. You may also call -session::get_torrents() to get the handles to the new torrents.
-
-
-
auto_map_handles
-
auto_map_handles defaults to true and determines whether or -not to set the handle field in the feed_item, returned -as the feed status. If auto-download is enabled, this setting -is ignored. If auto-download is not set, setting this to false -will save one pass through all the feed items trying to find -corresponding torrents in the session.
-
-
-
default_ttl
-
The default_ttl is the default interval for refreshing a feed. -This may be overridden by the feed itself (by specifying the <ttl> -tag) and defaults to 30 minutes. The field specifies the number of -minutes between refreshes.
-
-
-
add_args
-
If torrents are added automatically, you may want to set the -add_args to appropriate values for download directory etc. -This object is used as a template for adding torrents from feeds, -but some torrent specific fields will be overridden by the -individual torrent being added. For more information on the -add_torrent_params, see async_add_torrent() and add_torrent().
-
-
-
-

feed_status

-

Declared in "libtorrent/rss.hpp"

-

holds information about the status of an RSS feed. Retrieved by -calling get_feed_status() on feed_handle.

-
-struct feed_status
-{
-   feed_status ();
-
-   std::string url;
-   std::string title;
-   std::string description;
-   time_t last_update;
-   int next_update;
-   bool updating;
-   std::vector<feed_item> items;
-   error_code error;
-   int ttl;
-};
-
-
-
url
-
the URL of the feed.
-
-
-
title
-
the name of the feed (as specified by the feed itself). This -may be empty if we have not recevied a response from the RSS server yet, -or if the feed does not specify a title.
-
-
-
description
-
the feed description (as specified by the feed itself). -This may be empty if we have not received a response from the RSS server -yet, or if the feed does not specify a description.
-
-
-
last_update
-
the posix time of the last successful response from the feed.
-
-
-
next_update
-
the number of seconds, from now, when the feed will be -updated again.
-
-
-
updating
-
true if the feed is currently being updated (i.e. waiting for -DNS resolution, connecting to the server or waiting for the response to the -HTTP request, or receiving the response).
-
-
-
items
-
a vector of all items that we have received from the feed. See -feed_item for more information.
-
-
-
error
-
set to the appropriate error code if the feed encountered an -error. See error_code for more info.
-
-
-
ttl
-
the current refresh time (in minutes). It's either the configured -default ttl, or the ttl specified by the feed.
-
-
-
-

feed_handle

-

Declared in "libtorrent/rss.hpp"

-

The feed_handle refers to a specific RSS feed that is watched by the session.

-
-struct feed_handle
-{
-   feed_handle ();
-   void update_feed ();
-   feed_status get_feed_status () const;
-   void set_settings (feed_settings const& s);
-   feed_settings settings () const;
-};
-
-
-

update_feed()

-
-void update_feed ();
-
-

Forces an update/refresh of the feed. Regular updates of the feed is managed -by libtorrent, be careful to not call this too frequently since it may -overload the RSS server.

-
-
-

get_feed_status()

-
-feed_status get_feed_status () const;
-
-

Queries the RSS feed for information, including all the items in the feed. -see feed_status.

- -
-
-

settings() set_settings()

-
-void set_settings (feed_settings const& s);
-feed_settings settings () const;
-
-

Sets and gets settings for this feed. For more information on the -available settings, see add_feed().

-
-
-

add_feed_item()

-

Declared in "libtorrent/rss.hpp"

-
-torrent_handle add_feed_item (session& s, feed_item const& fi
-   , add_torrent_params const& p);
-torrent_handle add_feed_item (session& s, feed_item const& fi
-   , add_torrent_params const& p, error_code& ec);
-
-

given a feed_item f, add the torrent it refers to to session s.

-
-
-

new_feed()

-

Declared in "libtorrent/rss.hpp"

-
-boost::shared_ptr<feed> new_feed (aux::session_impl& ses, feed_settings const& sett);
-
-
-
-
- - diff --git a/docs/reference-Session.html b/docs/reference-Session.html index e645fd686..5c10e3aec 100644 --- a/docs/reference-Session.html +++ b/docs/reference-Session.html @@ -55,14 +55,15 @@
@@ -107,10 +108,12 @@ struct add_torrent_params flag_super_seeding, flag_sequential_download, flag_use_resume_save_path, + flag_pinned, + | flag_auto_managed | flag_paused | flag_apply_ip_filter, }; int version; - boost::intrusive_ptr<torrent_info> ti; + boost::shared_ptr<torrent_info> ti; std::vector<std::string> trackers; std::vector<std::string> url_seeds; std::vector<std::pair<std::string, int> > dht_nodes; @@ -147,9 +150,9 @@ data for the torrent. For more information, see the Declared in "libtorrent/add_torrent_params.hpp"

---+++ @@ -289,12 +292,25 @@ the torrent handle immediately after adding it. present, is honored. This defaults to not being set, in which case the save_path specified in add_torrent_params is always used. + + + + + + + +
name
flag_pinned8192indicates that this torrent should never be unloaded from RAM, even +if unloading torrents are allowed in general. Setting this makes +the torrent exempt from loading/unloading management.
+
flag_auto_managed | flag_paused | flag_apply_ip_filter
+
+
8194 
version
-
filled in by the constructor and should be left untouched. It -is used for forward binary compatibility.
+
filled in by the constructor and should be left untouched. It is used +for forward binary compatibility.
ti
@@ -388,8 +404,8 @@ items which has UUIDs specified.
source_feed_url
-
should point to the URL of the RSS feed this torrent comes from, -if it comes from an RSS feed.
+
should point to the URL of the RSS feed this torrent comes from, if it +comes from an RSS feed.
flags
@@ -429,29 +445,41 @@ struct cache_status { cache_status (); - size_type blocks_written; - size_type writes; - size_type blocks_read; + std::vector<cached_piece_info> pieces; + atomic_count blocks_written; + atomic_count writes; + atomic_count blocks_read; size_type blocks_read_hit; - size_type reads; - mutable size_type queued_bytes; - int cache_size; + atomic_count reads; + int write_cache_size; int read_cache_size; + int pinned_blocks; mutable int total_used_buffers; - int average_queue_time; int average_read_time; int average_write_time; int average_hash_time; int average_job_time; - int average_sort_time; - int job_queue_length; - boost::uint32_t cumulative_job_time; - boost::uint32_t cumulative_read_time; - boost::uint32_t cumulative_write_time; - boost::uint32_t cumulative_hash_time; - boost::uint32_t cumulative_sort_time; + atomic_count cumulative_job_time; + atomic_count cumulative_read_time; + atomic_count cumulative_write_time; + atomic_count cumulative_hash_time; int total_read_back; int read_queue_size; + int blocked_jobs; + int queued_jobs; + int peak_queued; + int pending_jobs; + int num_jobs; + int num_read_jobs; + int num_write_jobs; + int arc_mru_size; + int arc_mru_ghost_size; + int arc_mfu_size; + int arc_mfu_ghost_size; + int arc_write_size; + int arc_volatile_size; + int num_writing_threads; + int num_fence_jobs[disk_io_job::num_job_ids]; };
@@ -490,31 +518,25 @@ for the read cache.

reads
the number of read operations used
-
-
queued_bytes
-
the number of bytes waiting, in the disk job queue, to be written -or inserted into the disk cache
-
-
-
cache_size
-
the number of 16 KiB blocks currently in the disk cache (both read and write). -This includes both read and write cache.
+
+
write_cache_size
+
the number of blocks in the cache used for write cache
read_cache_size
the number of 16KiB blocks in the read cache.
+
+
pinned_blocks
+
the number of blocks with a refcount > 0, i.e. +they may not be evicted
+
total_used_buffers
the total number of buffers currently in use. This includes the read/write disk cache as well as send and receive buffers used in peer connections.
-
-
average_queue_time
-
the number of microseconds an average disk I/O job -has to wait in the job queue before it get processed.
-
average_read_time
the time read jobs takes on average to complete @@ -528,24 +550,18 @@ in microseconds. This does not include the time the job sits in the disk job queue or in the write cache, only blocks that are flushed to disk.
- -
-
average_hash_time average_job_time average_sort_time
+
+
average_hash_time average_job_time
the time hash jobs takes to complete on average, in microseconds. Hash jobs include running SHA-1 on the data (which for the most part is done incrementally) and sometimes reading back parts of the piece. It also includes checking files without valid resume data.
-
-
job_queue_length
-
the number of jobs in the job queue.
-
- -
-
cumulative_job_time cumulative_read_time cumulative_write_time cumulative_hash_time cumulative_sort_time
+
+
cumulative_job_time cumulative_read_time cumulative_write_time cumulative_hash_time
the number of milliseconds spent in all disk jobs, and specific ones since the start of the session. Times are specified in milliseconds
@@ -559,8 +575,73 @@ is large, a larger cache could significantly improve performance
read_queue_size
number of read jobs in the disk job queue
-
+
+
blocked_jobs
+
number of jobs blocked because of a fence
+
+
+
queued_jobs
+
number of jobs waiting to be issued (m_to_issue) +average over 30 seconds
+
+
+
peak_queued
+
largest ever seen number of queued jobs
+
+
+
pending_jobs
+
number of jobs waiting to complete (m_pending) +average over 30 seconds
+
+
+
num_jobs
+
total number of disk job objects allocated right now
+
+
+
num_read_jobs
+
total number of disk read job objects allocated right now
+
+
+
num_write_jobs
+
total number of disk write job objects allocated right now
+
+ + + + + +
+
arc_mru_size arc_mru_ghost_size arc_mfu_size arc_mfu_ghost_size arc_write_size arc_volatile_size
+
ARC cache stats. All of these counters are in number of pieces +not blocks. A piece does not necessarily correspond to a certain +number of blocks. The pieces in the ghost list never have any +blocks in them
+
+
+
num_writing_threads
+
the number of threads currently writing to disk
+
+
+
num_fence_jobs[disk_io_job
+
counts only fence jobs that are currently blocking jobs +not fences that are themself blocked
+
+
+
+

stats_metric

+

Declared in "libtorrent/session.hpp"

+

describes one statistics metric from the session. For more information, +see the session statistics section.

+
+struct stats_metric
+{
+   char const* name;
+   int value_index;
+   int type;
+};
+
+

session_proxy

Declared in "libtorrent/session.hpp"

@@ -576,7 +657,7 @@ class session_proxy session_proxy (); }; -
+

session_proxy()

 session_proxy ();
@@ -585,7 +666,7 @@ class session_proxy
 implementation object.

-
+

session

Declared in "libtorrent/session.hpp"

The session holds all state that spans multiple torrents. Among other @@ -593,6 +674,11 @@ things it runs the network loop and manages all torrents. Once it's created, the session object will spawn the main thread that will do all the work. The main thread will be idle as long it doesn't have any torrents to participate in.

+

You have some control over session configuration through the +session::apply_settings() member function. To change one or more +configuration options, create a settings_pack. object and fill it with +the settings to be set and pass it in to session::apply_settings().

+

see apply_settings().

 class session: public boost::noncopyable
 {
@@ -601,6 +687,10 @@ class session: public boost::noncopyable
       , int flags = start_default_features | add_default_plugins
       , boost::uint32_t alert_mask = alert::error_notification
       TORRENT_LOGPATH_ARG_DEFAULT);
+   session (settings_pack const& pack
+      , fingerprint const& print = fingerprint("LT"
+      , LIBTORRENT_VERSION_MAJOR, LIBTORRENT_VERSION_MINOR, 0, 0)
+      , int flags = start_default_features | add_default_plugins);
    session (fingerprint const& print
       , std::pair<int, int> listen_port_range
       , char const* listen_interface = "0.0.0.0"
@@ -616,6 +706,7 @@ class session: public boost::noncopyable
       , boost::function<bool(torrent_status const&)> const& pred
       , boost::uint32_t flags = 0) const;
    void post_torrent_updates ();
+   void post_session_stats ();
    torrent_handle find_torrent (sha1_hash const& info_hash) const;
    std::vector<torrent_handle> get_torrents () const;
    void async_add_torrent (add_torrent_params const& params);
@@ -625,16 +716,13 @@ class session: public boost::noncopyable
    void resume ();
    void pause ();
    bool is_paused () const;
+   void set_load_function (user_load_function_t fun);
    session_status status () const;
-   cache_status get_cache_status () const;
-   void get_cache_info (sha1_hash const& ih
-      , std::vector<cached_piece_info>& ret) const;
+   void get_cache_info (cache_status* ret, torrent_handle h = torrent_handle(), int flags = 0) const;
    feed_handle add_feed (feed_settings const& feed);
    void remove_feed (feed_handle h);
    void get_feeds (std::vector<feed_handle>& f) const;
    bool is_dht_running () const;
-   void start_dht ();
-   void stop_dht ();
    void set_dht_settings (dht_settings const& settings);
    void add_dht_router (std::pair<std::string, int> const& node);
    void add_dht_node (std::pair<std::string, int> const& node);
@@ -658,53 +746,34 @@ class session: public boost::noncopyable
    peer_id id () const;
    void set_peer_id (peer_id const& pid);
    void set_key (int key);
-   void listen_on (
-      std::pair<int, int> const& port_range
-      , error_code& ec
-      , const char* net_interface = 0
-      , int flags = 0);
    bool is_listening () const;
    unsigned short listen_port () const;
    unsigned short ssl_listen_port () const;
+   void set_peer_class_filter (ip_filter const& f);
+   void set_peer_class_type_filter (peer_class_type_filter const& f);
+   int create_peer_class (char const* name);
+   void delete_peer_class (int cid);
+   peer_class_info get_peer_class (int cid);
+   void set_peer_class (int cid, peer_class_info const& pci);
    void remove_torrent (const torrent_handle& h, int options = 0);
-   session_settings settings () const;
-   void set_settings (session_settings const& s);
-   pe_settings get_pe_settings () const;
-   void set_pe_settings (pe_settings const& settings);
-   void set_proxy (proxy_settings const& s);
-   proxy_settings proxy () const;
-   proxy_settings i2p_proxy () const;
-   void set_i2p_proxy (proxy_settings const& s);
+   aux::session_settings get_settings () const;
+   void apply_settings (settings_pack const& s);
    void pop_alerts (std::deque<alert*>* alerts);
    std::auto_ptr<alert> pop_alert ();
    alert const* wait_for_alert (time_duration max_wait);
-   void set_alert_mask (boost::uint32_t m);
    void set_alert_dispatch (boost::function<void(std::auto_ptr<alert>)> const& fun);
-   void stop_lsd ();
-   void start_lsd ();
-   void stop_upnp ();
-   void start_upnp ();
    void delete_port_mapping (int handle);
    int add_port_mapping (protocol_type t, int external_port, int local_port);
-   void stop_natpmp ();
-   void start_natpmp ();
 
    enum save_state_flags_t
    {
       save_settings,
       save_dht_settings,
       save_dht_state,
-      save_proxy,
-      save_i2p_proxy,
       save_encryption_settings,
       save_feeds,
    };
 
-   enum listen_on_flags_t
-   {
-      listen_no_system_port,
-   };
-
    enum options_t
    {
       delete_files,
@@ -723,7 +792,7 @@ class session: public boost::noncopyable
    };
 };
 
-
+

session()

 session (fingerprint const& print = fingerprint("LT"
@@ -731,6 +800,10 @@ class session: public boost::noncopyable
       , int flags = start_default_features | add_default_plugins
       , boost::uint32_t alert_mask = alert::error_notification
       TORRENT_LOGPATH_ARG_DEFAULT);
+session (settings_pack const& pack
+      , fingerprint const& print = fingerprint("LT"
+      , LIBTORRENT_VERSION_MAJOR, LIBTORRENT_VERSION_MINOR, 0, 0)
+      , int flags = start_default_features | add_default_plugins);
 session (fingerprint const& print
       , std::pair<int, int> listen_port_range
       , char const* listen_interface = "0.0.0.0"
@@ -738,24 +811,10 @@ class session: public boost::noncopyable
       , int alert_mask = alert::error_notification
       TORRENT_LOGPATH_ARG_DEFAULT);
 
-

If the fingerprint in the first overload is omited, the client will -get a default fingerprint stating the version of libtorrent. The -fingerprint is a short string that will be used in the peer-id to -identify the client and the client's version. For more details see the -fingerprint class. The constructor that only takes a fingerprint will -not open a listen port for the session, to get it running you'll have -to call session::listen_on(). The other constructor, that takes a -port range and an interface as well as the fingerprint will -automatically try to listen on a port on the given interface. For more -information about the parameters, see listen_on() function.

-

The flags paramater can be used to start default features (upnp & -nat-pmp) and default plugins (ut_metadata, ut_pex and smart_ban). The -default is to start those things. If you do not want them to start, -pass 0 as the flags parameter.

-

The alert_mask is the same mask that you would send to -set_alert_mask().

+

TODO: 3 could the fingerprint be a setting as well? And should the +settings_pack be optional?

-
+

~session()

 ~session ();
@@ -765,7 +824,7 @@ have been shut down. If some trackers are down, they will time out.
 All this before the destructor of session returns. So, it's advised
 that any kind of interface (such as windows) are closed before
 destructing the session object. Because it can take a few second for
-it to finish. The timeout can be set with set_settings().

+it to finish. The timeout can be set with apply_settings().

@@ -829,6 +888,16 @@ containing the status of all torrents whose state changed since the last time this function was called.

Only torrents who has the state subscription flag set will be included. This flag is on by default. See add_torrent_params.

+
+
+

post_session_stats()

+
+void post_session_stats ();
+
+

This function will post a session_stats_alert object, containing a snapshot of +the performance counters from the internals of libtorrent. To interpret these counters, +query the session via session_stats_metrics().

+

For more information, see the session statistics section.

@@ -913,6 +982,30 @@ mechanism. Resuming will restore the torrents to their previous paused state. i.e. the session pause state is separate from the torrent pause state. A torrent is inactive if it is paused or if the session is paused.

+
+
+

set_load_function()

+
+void set_load_function (user_load_function_t fun);
+
+

This function enables dynamic loading of torrent files. When a +torrent is unloaded but needs to be availabe in memory, this function +is called from within the libtorrent network thread. From within +this thread, you can not use any of the public APIs of libtorrent +itself. The the info-hash of the torrent is passed in to the function +and it is expected to fill in the passed in vector<char> with the +.torrent file corresponding to it.

+

If there is an error loading the torrent file, the error_code +(ec) should be set to reflect the error. In such case, the torrent +itself is stopped and set to an error state with the corresponding +error code.

+

Given that the function is called from the internal network thread of +libtorrent, it's important to not stall. libtorrent will not be able +to send nor receive any data until the function call returns.

+

The signature of the function to pass in is:

+
+void fun(sha1_hash const& info_hash, std::vector<char>& buf, error_code& ec);
+

status()

@@ -921,24 +1014,15 @@ session_status status () const;

returns session wide-statistics and status. For more information, see the session_status struct.

-
-
-

get_cache_status()

-
-cache_status get_cache_status () const;
-
-

Returns status of the disk cache for this session. For more -information, see the cache_status type.

get_cache_info()

-void get_cache_info (sha1_hash const& ih
-      , std::vector<cached_piece_info>& ret) const;
+void get_cache_info (cache_status* ret, torrent_handle h = torrent_handle(), int flags = 0) const;
 
-

fills out the supplied vector with information for -each piece that is currently in the disk cache for the torrent with the -specified info-hash (ih).

+

Fills in the cache_status struct with information about the given torrent. +If flags is session::disk_cache_no_pieces the cache_status::pieces field +will not be set. This may significantly reduce the cost of this call.

add_feed()

@@ -970,44 +1054,13 @@ void get_feeds (std::vector<feed_handle>& f) const;

Returns a list of all RSS feeds that are being watched by the session.

- - -
-
-

is_dht_running() start_dht() set_dht_settings() stop_dht()

+
+
+

is_dht_running() set_dht_settings()

 bool is_dht_running () const;
-void start_dht ();
-void stop_dht ();
 void set_dht_settings (dht_settings const& settings);
 
-

starts/stops UPnP, NATPMP or LSD port mappers they are stopped by -default These functions are not available in case -TORRENT_DISABLE_DHT is defined. start_dht starts the dht node -and makes the trackerless service available to torrents. The startup -state is optional and can contain nodes and the node id from the -previous session. The dht node state is a bencoded dictionary with the -following entries:

-
-
nodes
-
A list of strings, where each string is a node endpoint encoded in -binary. If the string is 6 bytes long, it is an IPv4 address of 4 -bytes, encoded in network byte order (big endian), followed by a 2 -byte port number (also network byte order). If the string is 18 -bytes long, it is 16 bytes of IPv6 address followed by a 2 bytes -port number (also network byte order).
-
node-id
-
The node id written as a readable string as a hexadecimal number.
-
-

dht_state will return the current state of the dht node, this can -be used to start up the node again, passing this entry to -start_dht. It is a good idea to save this to disk when the session -is closed, and read it up again when starting.

-

If the port the DHT is supposed to listen on is already in use, and -exception is thrown, asio::error.

-

stop_dht stops the dht node.

-

add_dht_node adds a node to the routing table. This can be used if -your client has its own source of bootstrapping nodes.

set_dht_settings sets some parameters availavle to the dht node. See dht_settings for more information.

is_dht_running() returns true if the DHT support has been started @@ -1024,11 +1077,12 @@ void add_dht_node (std::pair<std::string, int> const&

add_dht_node takes a host name and port pair. That endpoint will be pinged, and if a valid DHT reply is received, the node will be added to the routing table.

-

add_dht_router adds the given endpoint to a list of DHT router nodes. -If a search is ever made while the routing table is empty, those nodes will -be used as backups. Nodes in the router node list will also never be added -to the regular routing table, which effectively means they are only used -for bootstrapping, to keep the load off them.

+

add_dht_router adds the given endpoint to a list of DHT router +nodes. If a search is ever made while the routing table is empty, +those nodes will be used as backups. Nodes in the router node list +will also never be added to the regular routing table, which +effectively means they are only used for bootstrapping, to keep the +load off them.

An example routing node that you could typically add is router.bittorrent.com.

@@ -1040,7 +1094,7 @@ void dht_get_item (sha1_hash const& target);

query the DHT for an immutable item at the target hash. the result is posted as a dht_immutable_item_alert.

-
+

dht_get_item()

 void dht_get_item (boost::array<char, 32> key
@@ -1062,7 +1116,7 @@ the returned hash is the key that is to be used to look the item
 up agan. It's just the sha-1 hash of the bencoded form of the
 structure.

-
+

dht_put_item()

 void dht_put_item (boost::array<char, 32> key
@@ -1218,41 +1272,26 @@ void set_key (int key);
 by libtorrent. The key may be used by the tracker to identify the
 peer potentially across you changing your IP.

-
-
-

listen_port() listen_on() ssl_listen_port() is_listening()

+
+

listen_port() ssl_listen_port() is_listening()

-void listen_on (
-      std::pair<int, int> const& port_range
-      , error_code& ec
-      , const char* net_interface = 0
-      , int flags = 0);
 bool is_listening () const;
 unsigned short listen_port () const;
 unsigned short ssl_listen_port () const;
 

is_listening() will tell you whether or not the session has successfully opened a listening port. If it hasn't, this function will -return false, and then you can use listen_on() to make another -attempt.

-

listen_port() returns the port we ended up listening on. Since you -just pass a port-range to the constructor and to listen_on(), to -know which port it ended up using, you have to ask the session using -this function.

-

listen_on() will change the listen port and/or the listen -interface. If the session is already listening on a port, this socket -will be closed and a new socket will be opened with these new -settings. The port range is the ports it will try to listen on, if the -first port fails, it will continue trying the next port within the -range and so on. The interface parameter can be left as 0, in that -case the os will decide which interface to listen on, otherwise it -should be the ip-address of the interface you want the listener socket -bound to. listen_on() returns the error code of the operation in -ec. If this indicates success, the session is listening on a port -within the specified range. If it fails, it will also generate an -appropriate alert (listen_failed_alert).

+return false, and then you can set a new +settings_pack::listen_interfaces to try another interface and port to +bind to.

+

listen_port() returns the port we ended up listening on. If the +port specified in settings_pack::listen_interfaces failed, libtorrent +will try to bind to the next port, and so on. If it fails +settings_pack::max_retry_port_bind times, it will bind to port 0 +(meaning the OS picks the port). The only way to know which port it +ended up binding to is to ask for it by calling listen_port().

If all ports in the specified range fails to be opened for listening, libtorrent will try to use port 0 (which tells the operating system to pick a port that's free). If that still fails you may see a @@ -1271,25 +1310,109 @@ succeeded.

socket option on the listen socket(s). By default, the listen socket does not use reuse address. If you're running a service that needs to run on a specific port no matter if it's in use, set this flag.

-

If you're also starting the DHT, it is a good idea to do that after -you've called listen_on(), since the default listen port for the -DHT is the same as the tcp listen socket. If you start the DHT first, -it will assume the tcp port is free and open the udp socket on that -port, then later, when listen_on() is called, it may turn out that -the tcp port is in use. That results in the DHT and the bittorrent -socket listening on different ports. If the DHT is active when -listen_on is called, the udp port will be rebound to the new port, -if it was configured to use the same port as the tcp socket, and if -the listen_on call failed to bind to the same port that the udp uses.

-

If you want the OS to pick a port for you, pass in 0 as both first and -second.

-

The reason why it's a good idea to run the DHT and the bittorrent -socket on the same port is because that is an assumption that may be -used to increase performance. One way to accelerate the connecting of -peers on windows may be to first ping all peers with a DHT ping -packet, and connect to those that responds first. On windows one can -only connect to a few peers at a time because of a built in limitation -(in XP Service pack 2).

+
+
+

set_peer_class_filter()

+
+void set_peer_class_filter (ip_filter const& f);
+
+

Sets the peer class filter for this session. All new peer connections +will take this into account and be added to the peer classes specified +by this filter, based on the peer's IP address.

+

The ip-filter essentially maps an IP -> uint32. Each bit in that 32 +bit integer represents a peer class. The least significant bit +represents class 0, the next bit class 1 and so on.

+

For more info, see ip_filter.

+

For example, to make all peers in the range 200.1.1.0 - 200.1.255.255 +belong to their own peer class, apply the following filter:

+
+ip_filter f;
+int my_class = ses.create_peer_class("200.1.x.x IP range");
+f.add_rule(address_v4::from_string("200.1.1.0")
+        , address_v4::from_string("200.1.255.255")
+        , 1 << my_class);
+ses.set_peer_class_filter(f);
+
+

This setting only applies to new connections, it won't affect existing +peer connections.

+

This function is limited to only peer class 0-31, since there are only +32 bits in the IP range mapping. Only the set bits matter; no peer +class will be removed from a peer as a result of this call, peer +classes are only added.

+

The peer_class argument cannot be greater than 31. The bitmasks +representing peer classes in the peer_class_filter are 32 bits.

+

For more information, see peer classes.

+
+
+

set_peer_class_type_filter()

+
+void set_peer_class_type_filter (peer_class_type_filter const& f);
+
+

Sets and gets the peer class type filter. This is controls automatic +peer class assignments to peers based on what kind of socket it is.

+

It does not only support assigning peer classes, it also supports +removing peer classes based on socket type.

+

The order of these rules being applied are:

+
    +
  1. peer-class IP filter
  2. +
  3. peer-class type filter, removing classes
  4. +
  5. peer-class type filter, adding classes
  6. +
+

For more information, see peer classes. +TODO: add get_peer_class_type_filter() as well

+
+
+

create_peer_class()

+
+int create_peer_class (char const* name);
+
+

Creates a new peer class (see peer classes) with the given name. The +returned integer is the new peer class' identifier. Peer classes may +have the same name, so each invocation of this function creates a new +class and returns a unique identifier.

+

Identifiers are assigned from low numbers to higher. So if you plan on +using certain peer classes in a call to set_peer_class_filter(), +make sure to create those early on, to get low identifiers.

+

For more information on peer classes, see peer classes.

+
+
+

delete_peer_class()

+
+void delete_peer_class (int cid);
+
+

This call dereferences the reference count of the specified peer +class. When creating a peer class it's automatically referenced by 1. +If you want to recycle a peer class, you may call this function. You +may only call this function once per peer class you create. +Calling it more than once for the same class will lead to memory +corruption.

+

Since peer classes are reference counted, this function will not +remove the peer class if it's still assigned to torrents or peers. It +will however remove it once the last peer and torrent drops their +references to it.

+

There is no need to call this function for custom peer classes. All +peer classes will be properly destructed when the session object +destructs.

+

For more information on peer classes, see peer classes.

+ +
+
+

get_peer_class() set_peer_class()

+
+peer_class_info get_peer_class (int cid);
+void set_peer_class (int cid, peer_class_info const& pci);
+
+

These functions queries information from a peer class and updates the +configuration of a peer class, respectively.

+

cid must refer to an existing peer class. If it does not, the +return value of get_peer_class() is undefined.

+

set_peer_class() sets all the information in the +peer_class_info object in the specified peer class. There is no +option to only update a single property.

+

A peer or torrent balonging to more than one class, the highest +priority among any of its classes is the one that is taken into +account.

+

For more information, see peer classes.

remove_torrent()

@@ -1306,49 +1429,17 @@ files downloaded by this torrent. To do so, pass in the value there is no guarantee that adding the same torrent immediately after it was removed will not throw a libtorrent_exception exception. Once the torrent is deleted, a torrent_deleted_alert is posted.

- - - -
-
-

set_pe_settings() settings() get_pe_settings() set_settings()

+ +
+
+

get_settings() apply_settings()

-session_settings settings () const;
-void set_settings (session_settings const& s);
-pe_settings get_pe_settings () const;
-void set_pe_settings (pe_settings const& settings);
+aux::session_settings get_settings () const;
+void apply_settings (settings_pack const& s);
 
-

Sets the session settings and the packet encryption settings -respectively. See session_settings and pe_settings for more -information on available options.

- -
-
-

set_proxy() proxy()

-
-void set_proxy (proxy_settings const& s);
-proxy_settings proxy () const;
-
-

These functions sets and queries the proxy settings to be used for the -session.

-

For more information on what settings are available for proxies, see -proxy_settings. If the session is not in anonymous mode, proxies that -aren't working or fail, will automatically be disabled and packets -will flow without using any proxy. If you want to enforce using a -proxy, even when the proxy doesn't work, enable anonymous_mode in -session_settings.

- -
-
-

i2p_proxy() set_i2p_proxy()

-
-proxy_settings i2p_proxy () const;
-void set_i2p_proxy (proxy_settings const& s);
-
-

set_i2p_proxy sets the i2p proxy, and tries to open a persistant -connection to it. The only used fields in the proxy settings structs -are hostname and port.

-

i2p_proxy returns the current i2p proxy in use.

+

Applies the settings specified by the settings_pack s. This is an +asynchronous operation that will return immediately and actually apply +the settings to the main thread of libtorrent some time later.

@@ -1360,9 +1451,9 @@ std::auto_ptr<alert> pop_alert (); alert const* wait_for_alert (time_duration max_wait);

pop_alert() is used to ask the session if any errors or events has -occurred. With set_alert_mask() you can filter which alerts to receive -through pop_alert(). For information about the alert categories, -see alerts.

+occurred. With settings_pack::alert_mask you can filter which alerts +to receive through pop_alert(). For information about the alert +categories, see alerts.

pop_alerts() pops all pending alerts in a single call. In high performance environments with a very high alert churn rate, this can save significant amount of time compared to popping alerts one at a @@ -1404,16 +1495,6 @@ milliseconds to wait as an integer.

session_settings::alert_queue_size.

save_resume_data_alert and save_resume_data_failed_alert are always posted, regardelss of the alert mask.

-
-
-

set_alert_mask()

-
-void set_alert_mask (boost::uint32_t m);
-
-

Changes the mask of which alerts to receive. By default only errors -are reported. m is a bitmask where each bit represents a category -of alerts.

-

See category_t enum for options.

set_alert_dispatch()

@@ -1428,34 +1509,6 @@ libtorrent's external API functions. Doing so results in a dead lock.

platform-dependent message queues or signalling systems. For instance, on windows, one could post a message to an HNWD or on linux, write to a pipe or an eventfd.

- -
-
-

start_lsd() stop_lsd()

-
-void stop_lsd ();
-void start_lsd ();
-
-

Starts and stops Local Service Discovery. This service will broadcast -the infohashes of all the non-private torrents on the local network to -look for peers on the same swarm within multicast reach.

-

It is turned off by default.

- -
-
-

stop_upnp() start_upnp()

-
-void stop_upnp ();
-void start_upnp ();
-
-

Starts and stops the UPnP service. When started, the listen port and -the DHT port are attempted to be forwarded on local UPnP router -devices.

-

The upnp object returned by start_upnp() can be used to add and -remove arbitrary port mappings. Mapping status is returned through the -portmap_alert and the portmap_error_alert. The object will be valid -until stop_upnp() is called. See upnp and nat pmp.

-

It is off by default.

@@ -1468,22 +1521,6 @@ int add_port_mapping (protocol_type t, int external_port, int l whichever is enabled. The return value is a handle referring to the port mapping that was just created. Pass it to delete_port_mapping() to remove it.

- -
-
-

start_natpmp() stop_natpmp()

-
-void stop_natpmp ();
-void start_natpmp ();
-
-

Starts and stops the NAT-PMP service. When started, the listen port -and the DHT port are attempted to be forwarded on the router through -NAT-PMP.

-

The natpmp object returned by start_natpmp() can be used to add -and remove arbitrary port mappings. Mapping status is returned through -the portmap_alert and the portmap_error_alert. The object will be -valid until stop_natpmp() is called. See upnp and nat pmp.

-

It is off by default.

enum save_state_flags_t

@@ -1503,7 +1540,7 @@ valid until stop_natpmp() is called. See save_settings 1 -saves settings (i.e. the session_settings) +saves settings (i.e. the session_settings) save_dht_settings 2 @@ -1514,17 +1551,9 @@ valid until stop_natpmp() is called. See saves dht state such as nodes and node-id, possibly accelerating joining the DHT if provided at next session startup. -save_proxy -8 -save proxy_settings - -save_i2p_proxy -16 -save i2p_proxy settings - save_encryption_settings 32 -save pe_settings +save pe_settings save_feeds 128 @@ -1532,29 +1561,6 @@ joining the DHT if provided at next
-
-

enum listen_on_flags_t

-

Declared in "libtorrent/session.hpp"

- ----- - - - - - - - - - - - - -
namevaluedescription
listen_no_system_port2 

enum options_t

@@ -1738,18 +1744,6 @@ struct utp_status int num_connected; int num_fin_sent; int num_close_wait; - boost::uint64_t packet_loss; - boost::uint64_t timeout; - boost::uint64_t packets_in; - boost::uint64_t packets_out; - boost::uint64_t fast_retransmit; - boost::uint64_t packet_resend; - boost::uint64_t samples_above_target; - boost::uint64_t samples_below_target; - boost::uint64_t payload_pkts_in; - boost::uint64_t payload_pkts_out; - boost::uint64_t invalid_pkts_in; - boost::uint64_t redundant_pkts_in; }; @@ -1761,22 +1755,6 @@ struct utp_status
gauges. These are snapshots of the number of uTP sockets in each respective state
- - - - - - - - - - - -
-
packet_loss timeout packets_in packets_out fast_retransmit packet_resend samples_above_target samples_below_target payload_pkts_in payload_pkts_out invalid_pkts_in redundant_pkts_in
-
counters. These are monotonically increasing -and cumulative counters for their respective event.
-

session_status

@@ -1809,6 +1787,7 @@ struct session_status size_type total_redundant_bytes; size_type total_failed_bytes; int num_peers; + int num_dead_peers; int num_unchoked; int allowed_upload_slots; int up_bandwidth_queue; @@ -1828,6 +1807,8 @@ struct session_status int dht_total_allocations; utp_status utp_stats; int peerlist_size; + int num_torrents; + int num_paused_torrents; };
@@ -1984,6 +1965,36 @@ by the DHT.
the number of known peers across all torrents. These are not necessarily connected peers, just peers we know of.
+ +
+
num_torrents num_paused_torrents
+
the number of torrents in the +session and the number of them that are currently paused, respectively.
+
+
+

find_metric_idx()

+

Declared in "libtorrent/session.hpp"

+
+int find_metric_idx (std::vector<stats_metric> const& metrics
+   , char const* name);
+
+

given a vector if stats_metric objects (as returned by +session_stats_metrics()) and a name of a metric, this function returns +the counter index of it, or -1 if it could not be found. The counter +index is the index into the values array returned by session_stats_alert.

+
+
+

session_stats_metrics()

+

Declared in "libtorrent/session.hpp"

+
+std::vector<stats_metric> session_stats_metrics ();
+
+

This free function returns the list of available metrics exposed by +libtorrent's statistics API. Each metric has a name and a value index. +The value index is the index into the array in session_stats_alert where +this metric's value can be found when the session stats is sampled (by +calling post_session_stats()).

+
diff --git a/docs/reference-Settings.html b/docs/reference-Settings.html deleted file mode 100644 index 73c9f0d14..000000000 --- a/docs/reference-Settings.html +++ /dev/null @@ -1,2198 +0,0 @@ - - - - - - -Settings - - - - - - - - -
-
-
- -
- -
-

Settings

- --- - - - - - -
Author:Arvid Norberg, arvid@rasterbar.com
Version:1.0.0
- -
-

proxy_settings

-

Declared in "libtorrent/session_settings.hpp"

-

The proxy_settings structs contains the information needed to -direct certain traffic to a proxy.

-
-struct proxy_settings
-{
-   proxy_settings ();
-
-   enum proxy_type
-   {
-      none,
-      socks4,
-      socks5,
-      socks5_pw,
-      http,
-      http_pw,
-      i2p_proxy,
-   };
-
-   std::string hostname;
-   std::string username;
-   std::string password;
-   boost::uint8_t type;
-   boost::uint16_t port;
-   bool proxy_hostnames;
-   bool proxy_peer_connections;
-};
-
-
-

proxy_settings()

-
-proxy_settings ();
-
-

defaults constructs proxy settings, initializing it to the default -settings.

-
-
-

enum proxy_type

-

Declared in "libtorrent/session_settings.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
none0This is the default, no proxy server is used, all other fields are -ignored.
socks41The server is assumed to be a SOCKS4 server that requires a -username.
socks52The server is assumed to be a SOCKS5 server (RFC 1928) that does -not require any authentication. The username and password are -ignored.
socks5_pw3The server is assumed to be a SOCKS5 server that supports plain -text username and password authentication (RFC 1929). The -username and password specified may be sent to the proxy if it -requires.
http4The server is assumed to be an HTTP proxy. If the transport used -for the connection is non-HTTP, the server is assumed to support -the CONNECT method. i.e. for web seeds and HTTP trackers, a plain -proxy will suffice. The proxy is assumed to not require -authorization. The username and password will not be used.
http_pw5The server is assumed to be an HTTP proxy that requires user -authorization. The username and password will be sent to the proxy.
i2p_proxy6route through a i2p SAM proxy
-
-
hostname
-
the name or IP of the proxy server. port is the port number the -proxy listens to. If required, username and password can be -set to authenticate with the proxy.
-
- -
-
username password
-
when using a proy type that requires authentication, the username -and password fields must be set to the credentials for the proxy.
-
-
-
type
-
tells libtorrent what kind of proxy server it is. See proxy_type -enum for options
-
-
-
port
-
the port the proxy server is running on
-
-
-
proxy_hostnames
-
defaults to true. It means that hostnames should be attempted to be -resolved through the proxy instead of using the local DNS service. -This is only supported by SOCKS5 and HTTP.
-
-
-
proxy_peer_connections
-
determines whether or not to excempt peer and web seed connections -from using the proxy. This defaults to true, i.e. peer connections are -proxied by default.
-
-
-
-
-

session_settings

-

Declared in "libtorrent/session_settings.hpp"

-

This holds most of the session-wide settings in libtorrent. Pass this -to session::set_settings() to change the settings, initialize it from -session::get_settings() to get the current settings.

-
-struct session_settings
-{
-   session_settings (std::string const& user_agent = "libtorrent/"
-      LIBTORRENT_VERSION);
-   ~session_settings ();
-
-   enum suggest_mode_t
-   {
-      no_piece_suggestions,
-      suggest_read_cache,
-   };
-
-   enum choking_algorithm_t
-   {
-      fixed_slots_choker,
-      auto_expand_choker,
-      rate_based_choker,
-      bittyrant_choker,
-   };
-
-   enum seed_choking_algorithm_t
-   {
-      round_robin,
-      fastest_upload,
-      anti_leech,
-   };
-
-   enum io_buffer_mode_t
-   {
-      enable_os_cache,
-      disable_os_cache_for_aligned_files,
-      disable_os_cache,
-   };
-
-   enum disk_cache_algo_t
-   {
-      lru,
-      largest_contiguous,
-      avoid_readback,
-   };
-
-   enum bandwidth_mixed_algo_t
-   {
-      prefer_tcp,
-      peer_proportional,
-   };
-
-   int version;
-   std::string user_agent;
-   int tracker_completion_timeout;
-   int tracker_receive_timeout;
-   int stop_tracker_timeout;
-   int tracker_maximum_response_length;
-   int piece_timeout;
-   int request_timeout;
-   int request_queue_time;
-   int max_allowed_in_request_queue;
-   int max_out_request_queue;
-   int whole_pieces_threshold;
-   int peer_timeout;
-   int urlseed_timeout;
-   int urlseed_pipeline_size;
-   int urlseed_wait_retry;
-   int file_pool_size;
-   bool allow_multiple_connections_per_ip;
-   int max_failcount;
-   int min_reconnect_time;
-   int peer_connect_timeout;
-   bool ignore_limits_on_local_network;
-   int connection_speed;
-   bool send_redundant_have;
-   bool lazy_bitfields;
-   int inactivity_timeout;
-   int unchoke_interval;
-   int optimistic_unchoke_interval;
-   std::string announce_ip;
-   int num_want;
-   int initial_picker_threshold;
-   int allowed_fast_set_size;
-   int suggest_mode;
-   int max_queued_disk_bytes;
-   int max_queued_disk_bytes_low_watermark;
-   int handshake_timeout;
-   bool use_dht_as_fallback;
-   bool free_torrent_hashes;
-   bool upnp_ignore_nonrouters;
-   int send_buffer_low_watermark;
-   int send_buffer_watermark;
-   int send_buffer_watermark_factor;
-   int choking_algorithm;
-   int seed_choking_algorithm;
-   bool use_parole_mode;
-   int cache_size;
-   int cache_buffer_chunk_size;
-   int cache_expiry;
-   bool use_read_cache;
-   bool explicit_read_cache;
-   int explicit_cache_interval;
-   int disk_io_write_mode;
-   int disk_io_read_mode;
-   bool coalesce_reads;
-   bool coalesce_writes;
-   std::pair<int, int> outgoing_ports;
-   char peer_tos;
-   int active_downloads;
-   int active_seeds;
-   int active_dht_limit;
-   int active_tracker_limit;
-   int active_lsd_limit;
-   int active_limit;
-   bool auto_manage_prefer_seeds;
-   bool dont_count_slow_torrents;
-   int auto_manage_interval;
-   float share_ratio_limit;
-   float seed_time_ratio_limit;
-   int seed_time_limit;
-   int peer_turnover_interval;
-   float peer_turnover;
-   float peer_turnover_cutoff;
-   bool close_redundant_connections;
-   int auto_scrape_interval;
-   int auto_scrape_min_interval;
-   int max_peerlist_size;
-   int max_paused_peerlist_size;
-   int min_announce_interval;
-   bool prioritize_partial_pieces;
-   int auto_manage_startup;
-   bool rate_limit_ip_overhead;
-   bool announce_to_all_trackers;
-   bool announce_to_all_tiers;
-   bool prefer_udp_trackers;
-   bool strict_super_seeding;
-   int seeding_piece_quota;
-   int max_sparse_regions;
-   bool lock_disk_cache;
-   int max_rejects;
-   int recv_socket_buffer_size;
-   int send_socket_buffer_size;
-   bool optimize_hashing_for_speed;
-   int file_checks_delay_per_block;
-   disk_cache_algo_t disk_cache_algorithm;
-   int read_cache_line_size;
-   int write_cache_line_size;
-   int optimistic_disk_retry;
-   bool disable_hash_checks;
-   bool allow_reordered_disk_operations;
-   bool allow_i2p_mixed;
-   int max_suggest_pieces;
-   bool drop_skipped_requests;
-   bool low_prio_disk;
-   int local_service_announce_interval;
-   int dht_announce_interval;
-   int udp_tracker_token_expiry;
-   bool volatile_read_cache;
-   bool guided_read_cache;
-   int default_cache_min_age;
-   int num_optimistic_unchoke_slots;
-   bool no_atime_storage;
-   int default_est_reciprocation_rate;
-   int increase_est_reciprocation_rate;
-   int decrease_est_reciprocation_rate;
-   bool incoming_starts_queued_torrents;
-   bool report_true_downloaded;
-   bool strict_end_game_mode;
-   bool broadcast_lsd;
-   bool enable_outgoing_utp;
-   bool enable_incoming_utp;
-   bool enable_outgoing_tcp;
-   bool enable_incoming_tcp;
-   int max_pex_peers;
-   bool ignore_resume_timestamps;
-   bool no_recheck_incomplete_resume;
-   bool anonymous_mode;
-   bool force_proxy;
-   int tick_interval;
-   bool report_web_seed_downloads;
-   int share_mode_target;
-   int upload_rate_limit;
-   int download_rate_limit;
-   int local_upload_rate_limit;
-   int local_download_rate_limit;
-   int dht_upload_rate_limit;
-   int unchoke_slots_limit;
-   int half_open_limit;
-   int connections_limit;
-   int connections_slack;
-   int utp_target_delay;
-   int utp_gain_factor;
-   int utp_min_timeout;
-   int utp_syn_resends;
-   int utp_fin_resends;
-   int utp_num_resends;
-   int utp_connect_timeout;
-   bool utp_dynamic_sock_buf;
-   int utp_loss_multiplier;
-   int mixed_mode_algorithm;
-   bool rate_limit_utp;
-   int listen_queue_size;
-   bool announce_double_nat;
-   int torrent_connect_boost;
-   bool seeding_outgoing_connections;
-   bool no_connect_privileged_ports;
-   int alert_queue_size;
-   int max_metadata_size;
-   bool smooth_connects;
-   bool always_send_user_agent;
-   bool apply_ip_filter_to_trackers;
-   int read_job_every;
-   bool use_disk_read_ahead;
-   bool lock_files;
-   int ssl_listen;
-   int tracker_backoff;
-   bool ban_web_seeds;
-   int max_http_recv_buffer_size;
-   bool support_share_mode;
-   bool support_merkle_torrents;
-   bool report_redundant_bytes;
-   std::string handshake_client_version;
-   bool use_disk_cache_pool;
-   int inactive_down_rate;
-   int inactive_up_rate;
-};
-
- -
-

~session_settings() session_settings()

-
-session_settings (std::string const& user_agent = "libtorrent/"
-      LIBTORRENT_VERSION);
-~session_settings ();
-
-

initializes the session_settings to the default settings.

-
-
-

enum suggest_mode_t

-

Declared in "libtorrent/session_settings.hpp"

- ----- - - - - - - - - - - - - - - - - -
namevaluedescription
no_piece_suggestions0the default. will not send out suggest messages.
suggest_read_cache1send out suggest messages for the most recent pieces that are in -the read cache.
-
-
-

enum choking_algorithm_t

-

Declared in "libtorrent/session_settings.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
fixed_slots_choker0the traditional choker with a fixed number of unchoke slots, as -specified by session::set_max_uploads()..
auto_expand_choker1opens at least the number of slots as specified by -session::set_max_uploads() but opens up more slots if the upload -capacity is not saturated. This unchoker will work just like the -fixed_slot_choker if there's no global upload rate limit set.
rate_based_choker2opens up unchoke slots based on the upload rate achieved to peers. -The more slots that are opened, the marginal upload rate required -to open up another slot increases.
bittyrant_choker3attempts to optimize download rate by finding the reciprocation -rate of each peer individually and prefers peers that gives the -highest return on investment. It still allocates all upload -capacity, but shuffles it around to the best peers first. For this -choker to be efficient, you need to set a global upload rate limit -session_settings::upload_rate_limit. For more information about -this choker, see the paper.
-
-
-

enum seed_choking_algorithm_t

-

Declared in "libtorrent/session_settings.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
round_robin0round-robins the peers that are unchoked when seeding. This -distributes the upload bandwidht uniformly and fairly. It minimizes -the ability for a peer to download everything without -redistributing it.
fastest_upload1unchokes the peers we can send to the fastest. This might be a bit -more reliable in utilizing all available capacity.
anti_leech2prioritizes peers who have just started or are just about to finish -the download. The intention is to force peers in the middle of the -download to trade with each other.
-
-
-

enum io_buffer_mode_t

-

Declared in "libtorrent/session_settings.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
enable_os_cache0This is the default and files are opened normally, with the OS -caching reads and writes.
disable_os_cache_for_aligned_files1This will open files in unbuffered mode for files where every read -and write would be sector aligned. Using aligned disk offsets is a -requirement on some operating systems.
disable_os_cache2This opens all files in unbuffered mode (if allowed by the -operating system). Linux and Windows, for instance, require disk -offsets to be sector aligned, and in those cases, this option is -the same as disable_os_caches_for_aligned_files.
-
-
-

enum disk_cache_algo_t

-

Declared in "libtorrent/session_settings.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
lru0This flushes the entire piece, in the write cache, that was least -recently written to.
largest_contiguous1will flush the largest sequences of contiguous blocks from the -write cache, regarless of the piece's last use time.
avoid_readback2will prioritize flushing blocks that will avoid having to read them -back in to verify the hash of the piece once it's done. This is -especially useful for high throughput setups, where reading from -the disk is especially expensive.
-
-
-

enum bandwidth_mixed_algo_t

-

Declared in "libtorrent/session_settings.hpp"

- ----- - - - - - - - - - - - - - - - - -
namevaluedescription
prefer_tcp0disables the mixed mode bandwidth balancing
peer_proportional1does not throttle uTP, throttles TCP to the same proportion -of throughput as there are TCP connections
-
-
version
-
automatically set to the libtorrent version you're using in order to -be forward binary compatible. This field should not be changed.
-
-
-
user_agent
-
the client identification to the tracker. The recommended format of -this string is: "ClientName/ClientVersion -libtorrent/libtorrentVersion". This name will not only be used when -making HTTP requests, but also when sending extended headers to peers -that support that extension.
-
-
-
tracker_completion_timeout
-
the number of seconds the tracker connection will wait from when it -sent the request until it considers the tracker to have timed-out. -Default value is 60 seconds.
-
-
-
tracker_receive_timeout
-
the number of seconds to wait to receive any data from the tracker. If -no data is received for this number of seconds, the tracker will be -considered as having timed out. If a tracker is down, this is the kind -of timeout that will occur. The default value is 20 seconds.
-
-
-
stop_tracker_timeout
-

the time to wait when sending a stopped message before considering a -tracker to have timed out. this is usually shorter, to make the client -quit faster

-

This is given in seconds. Default is 10 seconds.

-
-
-
-
tracker_maximum_response_length
-
the maximum number of bytes in a tracker response. If a response size -passes this number it will be rejected and the connection will be -closed. On gzipped responses this size is measured on the uncompressed -data. So, if you get 20 bytes of gzip response that'll expand to 2 -megs, it will be interrupted before the entire response has been -uncompressed (given your limit is lower than 2 megs). Default limit is -1 megabyte.
-
-
-
piece_timeout
-
controls the number of seconds from a request is sent until it times -out if no piece response is returned.
-
-
-
request_timeout
-
the number of seconds one block (16kB) is expected to be received -within. If it's not, the block is requested from a different peer
-
-
-
request_queue_time
-
the length of the request queue given in the number of seconds it -should take for the other end to send all the pieces. i.e. the actual -number of requests depends on the download rate and this number.
-
-
-
max_allowed_in_request_queue
-
the number of outstanding block requests a peer is allowed to queue up -in the client. If a peer sends more requests than this (before the -first one has been sent) the last request will be dropped. the higher -this is, the faster upload speeds the client can get to a single peer.
-
-
-
max_out_request_queue
-
the maximum number of outstanding requests to send to a peer. This -limit takes precedence over request_queue_time. i.e. no matter the -download speed, the number of outstanding requests will never exceed -this limit.
-
-
-
whole_pieces_threshold
-
if a whole piece can be downloaded in this number of seconds, or less, -the peer_connection will prefer to request whole pieces at a time from -this peer. The benefit of this is to better utilize disk caches by -doing localized accesses and also to make it easier to identify bad -peers if a piece fails the hash check.
-
-
-
peer_timeout
-
the number of seconds to wait for any activity on the peer wire before -closing the connectiong due to time out. This defaults to 120 seconds, -since that's what's specified in the protocol specification. After -half the time out, a keep alive message is sent.
-
-
-
urlseed_timeout
-
same as peer_timeout, but only applies to url-seeds. this is usually -set lower, because web servers are expected to be more reliable. This -value defaults to 20 seconds.
-
-
-
urlseed_pipeline_size
-
controls the pipelining with the web server. When using persistent -connections to HTTP 1.1 servers, the client is allowed to send more -requests before the first response is received. This number controls -the number of outstanding requests to use with url-seeds. Default is -5.
-
-
-
urlseed_wait_retry
-
time to wait until a new retry takes place
-
-
-
file_pool_size
-
sets the upper limit on the total number of files this session will -keep open. The reason why files are left open at all is that some anti -virus software hooks on every file close, and scans the file for -viruses. deferring the closing of the files will be the difference -between a usable system and a completely hogged down system. Most -operating systems also has a limit on the total number of file -descriptors a process may have open. It is usually a good idea to find -this limit and set the number of connections and the number of files -limits so their sum is slightly below it.
-
-
-
allow_multiple_connections_per_ip
-
determines if connections from the same IP address as existing -connections should be rejected or not. Multiple connections from the -same IP address is not allowed by default, to prevent abusive behavior -by peers. It may be useful to allow such connections in cases where -simulations are run on the same machie, and all peers in a swarm has -the same IP address.
-
-
-
max_failcount
-
the maximum times we try to connect to a peer before stop connecting -again. If a peer succeeds, its failcounter is reset. If a peer is -retrieved from a peer source (other than DHT) the failcount is -decremented by one, allowing another try.
-
-
-
min_reconnect_time
-
the number of seconds to wait to reconnect to a peer. this time is -multiplied with the failcount.
-
-
-
peer_connect_timeout
-
the number of seconds to wait after a connection attempt is initiated -to a peer until it is considered as having timed out. The default is -10 seconds. This setting is especially important in case the number of -half-open connections are limited, since stale half-open connection -may delay the connection of other peers considerably.
-
-
-
ignore_limits_on_local_network
-
if set to true, upload, download and unchoke limits are ignored for -peers on the local network.
-
-
-
connection_speed
-
the number of connection attempts that are made per second. If a -number < 0 is specified, it will default to 200 connections per -second. If 0 is specified, it means don't make outgoing connections at -all.
-
-
-
send_redundant_have
-
if this is set to true, have messages will be sent to peers that -already have the piece. This is typically not necessary, but it might -be necessary for collecting statistics in some cases. Default is -false.
-
-
-
lazy_bitfields
-
prevents outgoing bitfields from being full. If the client is seed, a -few bits will be set to 0, and later filled in with have-messages. -This is an old attempt to prevent certain ISPs from stopping people -from seeding.
-
-
-
inactivity_timeout
-
if a peer is uninteresting and uninterested for longer than this -number of seconds, it will be disconnected. default is 10 minutes
-
-
-
unchoke_interval
-
the number of seconds between chokes/unchokes. On this interval, peers -are re-evaluated for being choked/unchoked. This is defined as 30 -seconds in the protocol, and it should be significantly longer than -what it takes for TCP to ramp up to it's max rate.
-
-
-
optimistic_unchoke_interval
-
the number of seconds between each optimistic unchoke. On this -timer, the currently optimistically unchoked peer will change.
-
-
-
announce_ip
-
the ip address passed along to trackers as the &ip= parameter. If -left as the default (an empty string), that parameter is omitted. Most -trackers ignore this argument. This is here for completeness for -edge-cases where it may be useful.
-
-
-
num_want
-
the number of peers we want from each tracker request. It defines what -is sent as the &num_want= parameter to the tracker. Stopped -messages always send num_want=0. This setting control what to say in -the case where we actually want peers.
-
-
-
initial_picker_threshold
-
specifies the number of pieces we need before we switch to rarest -first picking. This defaults to 4, which means the 4 first pieces in -any torrent are picked at random, the following pieces are picked in -rarest first order.
-
-
-
allowed_fast_set_size
-
the number of allowed pieces to send to choked peers that supports the -fast extensions
-
-
-
suggest_mode
-

this determines which pieces will be suggested to peers suggest read -cache will make libtorrent suggest pieces that are fresh in the disk -read cache, to potentially lower disk access and increase the cache -hit ratio

-

for options, see suggest_mode_t.

-
-
-
-
max_queued_disk_bytes
-

the maximum number of bytes a connection may have pending in the disk -write queue before its download rate is being throttled. This prevents -fast downloads to slow medias to allocate more memory indefinitely. -This should be set to at least 16 kB to not completely disrupt normal -downloads. If it's set to 0, you will be starving the disk thread and -nothing will be written to disk. this is a per session setting.

-

When this limit is reached, the peer connections will stop reading -data from their sockets, until the disk thread catches up. Setting -this too low will severly limit your download rate.

-
-
-
-
max_queued_disk_bytes_low_watermark
-
this is the low watermark for the disk buffer queue. whenever the -number of queued bytes exceed the max_queued_disk_bytes, libtorrent -will wait for it to drop below this value before issuing more reads -from the sockets. If set to 0, the low watermark will be half of the -max queued disk bytes
-
-
-
handshake_timeout
-
the number of seconds to wait for a handshake response from a peer. If -no response is received within this time, the peer is disconnected.
-
-
-
use_dht_as_fallback
-
determines how the DHT is used. If this is true, the DHT will only be -used for torrents where all trackers in its tracker list has failed. -Either by an explicit error message or a time out. This is false by -default, which means the DHT is used by default regardless of if the -trackers fail or not.
-
-
-
free_torrent_hashes
-
determines whether or not the torrent's piece hashes are kept in -memory after the torrent becomes a seed or not. If it is set to -true the hashes are freed once the torrent is a seed (they're not -needed anymore since the torrent won't download anything more). If -it's set to false they are not freed. If they are freed, the -torrent_info returned by get_torrent_info() will return an object that -may be incomplete, that cannot be passed back to async_add_torrent() -and add_torrent() for instance.
-
-
-
upnp_ignore_nonrouters
-
indicates whether or not the UPnP implementation should ignore any -broadcast response from a device whose address is not the configured -router for this machine. i.e. it's a way to not talk to other people's -routers by mistake.
-
-
-
send_buffer_low_watermark
-
This is the minimum send buffer target size (send buffer includes -bytes pending being read from disk). For good and snappy seeding -performance, set this fairly high, to at least fit a few blocks. This -is essentially the initial window size which will determine how fast -we can ramp up the send rate
-
-
-
send_buffer_watermark
-

the upper limit of the send buffer low-watermark.

-

if the send buffer has fewer bytes than this, we'll read another 16kB -block onto it. If set too small, upload rate capacity will suffer. If -set too high, memory will be wasted. The actual watermark may be lower -than this in case the upload rate is low, this is the upper limit.

-
-
-
-
send_buffer_watermark_factor
-

the current upload rate to a peer is multiplied by this factor to get -the send buffer watermark. The factor is specified as a percentage. -i.e. 50 indicates a factor of 0.5.

-

This product is clamped to the send_buffer_watermark setting to not -exceed the max. For high speed upload, this should be set to a greater -value than 100. The default is 50.

-

For high capacity connections, setting this higher can improve upload -performance and disk throughput. Setting it too high may waste RAM and -create a bias towards read jobs over write jobs.

-
-
-
-
choking_algorithm
-
specifies which algorithm to use to determine which peers to unchoke. -This setting replaces the deprecated settings auto_upload_slots -and auto_upload_slots_rate_based. For options, see -choking_algorithm_t.
-
-
-
seed_choking_algorithm
-
controls the seeding unchoke behavior. For options, see -seed_choking_algorithm_t.
-
-
-
use_parole_mode
-
specifies if parole mode should be used. Parole mode means that peers -that participate in pieces that fail the hash check are put in a mode -where they are only allowed to download whole pieces. If the whole -piece a peer in parole mode fails the hash check, it is banned. If a -peer participates in a piece that passes the hash check, it is taken -out of parole mode.
-
-
-
cache_size
-

the disk write and read cache. It is specified in units of 16 KiB -blocks. Buffers that are part of a peer's send or receive buffer also -count against this limit. Send and receive buffers will never be -denied to be allocated, but they will cause the actual cached blocks -to be flushed or evicted. If this is set to -1, the cache size is -automatically set to the amount of physical RAM available in the -machine divided by 8. If the amount of physical RAM cannot be -determined, it's set to 1024 (= 16 MiB).

-

Disk buffers are allocated using a pool allocator, the number of -blocks that are allocated at a time when the pool needs to grow can be -specified in cache_buffer_chunk_size. This defaults to 16 blocks. -Lower numbers saves memory at the expense of more heap allocations. It -must be at least 1.

-
-
-
-
cache_buffer_chunk_size
-
this is the number of disk buffer blocks (16 kiB) that should be -allocated at a time. It must be at least 1. Lower number saves memory -at the expense of more heap allocations
-
-
-
cache_expiry
-
the number of seconds a write cache entry sits idle in the cache -before it's forcefully flushed to disk.
-
-
-
use_read_cache
-
when set to true (default), the disk cache is also used to cache -pieces read from disk. Blocks for writing pieces takes presedence.
-
-
-
explicit_read_cache
-
defaults to 0. If set to something greater than 0, the disk read cache -will not be evicted by cache misses and will explicitly be controlled -based on the rarity of pieces. Rare pieces are more likely to be -cached. This would typically be used together with suggest_mode -set to suggest_read_cache. The value is the number of pieces to -keep in the read cache. If the actual read cache can't fit as many, it -will essentially be clamped.
-
-
-
explicit_cache_interval
-
the number of seconds in between each refresh of a part of the -explicit read cache. Torrents take turns in refreshing and this is the -time in between each torrent refresh. Refreshing a torrent's explicit -read cache means scanning all pieces and picking a random set of the -rarest ones. There is an affinity to pick pieces that are already in -the cache, so that subsequent refreshes only swaps in pieces that are -rarer than whatever is in the cache at the time.
-
- -
-
disk_io_write_mode disk_io_read_mode
-

determines how files are opened when they're in read only mode versus -read and write mode. For options, see io_buffer_mode_t.

-

One reason to disable caching is that it may help the operating system -from growing its file cache indefinitely. Since some OSes only allow -aligned files to be opened in unbuffered mode, It is recommended to -make the largest file in a torrent the first file (with offset 0) or -use pad files to align all files to piece boundries.

-
-
- -
-
coalesce_reads coalesce_writes
-
when set to true, instead of issuing multiple adjacent reads or writes -to the disk, allocate a larger buffer, copy all writes into it and -issue a single write. For reads, read into a larger buffer and copy -the buffer into the smaller individual read buffers afterwards. This -may save system calls, but will cost in additional memory allocation -and copying.
-
-
-
outgoing_ports
-

if set to something other than (0, 0) is a range of ports used to bind -outgoing sockets to. This may be useful for users whose router allows -them to assign QoS classes to traffic based on its local port. It is a -range instead of a single port because of the problems with failing to -reconnect to peers if a previous socket to that peer and port is in -TIME_WAIT state.

-
-

Warning

-

setting outgoing ports will limit the ability to keep multiple -connections to the same client, even for different torrents. It is not -recommended to change this setting. Its main purpose is to use as an -escape hatch for cheap routers with QoS capability but can only -classify flows based on port numbers.

-
-
-
-
-
peer_tos
-
determines the TOS byte set in the IP header of every packet sent to -peers (including web seeds). The default value for this is 0x0 (no -marking). One potentially useful TOS mark is 0x20, this represents -the QBone scavenger service. For more details, see QBSS.
-
- - - - - -
-
active_downloads active_seeds active_dht_limit active_tracker_limit active_lsd_limit active_limit
-

for auto managed torrents, these are the limits they are subject to. -If there are too many torrents some of the auto managed ones will be -paused until some slots free up.

-

active_dht_limit and active_tracker_limit limits the number of -torrents that will be active on the DHT and their tracker. If the -active limit is set higher than these numbers, some torrents will be -"active" in the sense that they will accept incoming connections, but -not announce on the DHT or their trackers.

-

active_lsd_limit is the max number of torrents to announce to the -local network over the local service discovery protocol. By default -this is 80, which is no more than one announce every 5 seconds -(assuming the default announce interval of 5 minutes).

-

active_limit is a hard limit on the number of active torrents. -This applies even to slow torrents.

-

You can have more torrents active, even though they are not -announced to the DHT, lsd or their tracker. If some peer knows about -you for any reason and tries to connect, it will still be accepted, -unless the torrent is paused, which means it won't accept any -connections.

-

active_downloads and active_seeds controls how many active -seeding and downloading torrents the queuing mechanism allows. The -target number of active torrents is min(active_downloads + -active_seeds, active_limit). active_downloads and -active_seeds are upper limits on the number of downloading -torrents and seeding torrents respectively. Setting the value to -1 -means unlimited.

-

For example if there are 10 seeding torrents and 10 downloading -torrents, and active_downloads is 4 and active_seeds is 4, -there will be 4 seeds active and 4 downloading torrents. If the -settings are active_downloads = 2 and active_seeds = 4, then -there will be 2 downloading torrents and 4 seeding torrents active. -Torrents that are not auto managed are also counted against these -limits. If there are non-auto managed torrents that use up all the -slots, no auto managed torrent will be activated.

-
-
-
-
auto_manage_prefer_seeds
-
prefer seeding torrents when determining which torrents to give active -slots to, the default is false which gives preference to downloading -torrents
-
-
-
dont_count_slow_torrents
-
if true, torrents without any payload transfers are not subject to the -active_seeds and active_downloads limits. This is intended to -make it more likely to utilize all available bandwidth, and avoid -having torrents that don't transfer anything block the active slots.
-
-
-
auto_manage_interval
-
the number of seconds in between recalculating which torrents to -activate and which ones to queue
-
-
-
share_ratio_limit
-

when a seeding torrent reaches either the share ratio (bytes up / -bytes down) or the seed time ratio (seconds as seed / seconds as -downloader) or the seed time limit (seconds as seed) it is considered -done, and it will leave room for other torrents the default value for -share ratio is 2 the default seed time ratio is 7, because that's a -common asymmetry ratio on connections

-
-

Note

-

This is an out-dated option that doesn't make much sense. It will be -removed in future versions of libtorrent

-
-
-
-
-
seed_time_ratio_limit
-
the seeding time / downloading time ratio limit for considering a -seeding torrent to have met the seed limit criteria. See queuing.
-
-
-
seed_time_limit
-
the limit on the time a torrent has been an active seed (specified in -seconds) before it is considered having met the seed limit criteria. -See queuing.
-
- - -
-
peer_turnover_interval peer_turnover peer_turnover_cutoff
-

controls a feature where libtorrent periodically can disconnect the -least useful peers in the hope of connecting to better ones. -peer_turnover_interval controls the interval of this optimistic -disconnect. It defaults to every 5 minutes, and is specified in -seconds.

-

peer_turnover Is the fraction of the peers that are disconnected. -This is a float where 1.f represents all peers an 0 represents no -peers. It defaults to 4% (i.e. 0.04f)

-

peer_turnover_cutoff is the cut off trigger for optimistic -unchokes. If a torrent has more than this fraction of its connection -limit, the optimistic unchoke is triggered. This defaults to 90% (i.e. -0.9f).

-
-
-
-
close_redundant_connections
-
specifies whether libtorrent should close connections where both ends -have no utility in keeping the connection open. For instance if both -ends have completed their downloads, there's no point in keeping it -open. This defaults to true.
-
-
-
auto_scrape_interval
-
the number of seconds between scrapes of queued torrents (auto managed -and paused torrents). Auto managed torrents that are paused, are -scraped regularly in order to keep track of their downloader/seed -ratio. This ratio is used to determine which torrents to seed and -which to pause.
-
-
-
auto_scrape_min_interval
-
the minimum number of seconds between any automatic scrape (regardless -of torrent). In case there are a large number of paused auto managed -torrents, this puts a limit on how often a scrape request is sent.
-
-
-
max_peerlist_size
-
the maximum number of peers in the list of known peers. These peers -are not necessarily connected, so this number should be much greater -than the maximum number of connected peers. Peers are evicted from the -cache when the list grows passed 90% of this limit, and once the size -hits the limit, peers are no longer added to the list. If this limit -is set to 0, there is no limit on how many peers we'll keep in the -peer list.
-
-
-
max_paused_peerlist_size
-
the max peer list size used for torrents that are paused. This default -to the same as max_peerlist_size, but can be used to save memory -for paused torrents, since it's not as important for them to keep a -large peer list.
-
-
-
min_announce_interval
-
the minimum allowed announce interval for a tracker. This is specified -in seconds, defaults to 5 minutes and is used as a sanity check on -what is returned from a tracker. It mitigates hammering misconfigured -trackers.
-
-
-
prioritize_partial_pieces
-
If true, partial pieces are picked before pieces that are more rare. -If false, rare pieces are always prioritized, unless the number of -partial pieces is growing out of proportion.
-
-
-
auto_manage_startup
-
the number of seconds a torrent is considered active after it was -started, regardless of upload and download speed. This is so that -newly started torrents are not considered inactive until they have a -fair chance to start downloading.
-
-
-
rate_limit_ip_overhead
-
if set to true, the estimated TCP/IP overhead is drained from the rate -limiters, to avoid exceeding the limits with the total traffic
-
-
-
announce_to_all_trackers
-
controls how multi tracker torrents are treated. If this is set to -true, all trackers in the same tier are announced to in parallel. If -all trackers in tier 0 fails, all trackers in tier 1 are announced as -well. If it's set to false, the behavior is as defined by the multi -tracker specification. It defaults to false, which is the same -behavior previous versions of libtorrent has had as well.
-
-
-
announce_to_all_tiers
-
controls how multi tracker torrents are treated. When this is set to -true, one tracker from each tier is announced to. This is the uTorrent -behavior. This is false by default in order to comply with the -multi-tracker specification.
-
-
-
prefer_udp_trackers
-
true by default. It means that trackers may be rearranged in a way -that udp trackers are always tried before http trackers for the same -hostname. Setting this to fails means that the trackers' tier is -respected and there's no preference of one protocol over another.
-
-
-
strict_super_seeding
-
when this is set to true, a piece has to have been forwarded to a -third peer before another one is handed out. This is the traditional -definition of super seeding.
-
-
-
seeding_piece_quota
-
the number of pieces to send to a peer, when seeding, before rotating -in another peer to the unchoke set. It defaults to 3 pieces, which -means that when seeding, any peer we've sent more than this number of -pieces to will be unchoked in favour of a choked peer.
-
-
-
max_sparse_regions
-
is a limit of the number of sparse regions in a torrent. A sparse -region is defined as a hole of pieces we have not yet downloaded, in -between pieces that have been downloaded. This is used as a hack for -windows vista which has a bug where you cannot write files with more -than a certain number of sparse regions. This limit is not hard, it -will be exceeded. Once it's exceeded, pieces that will maintain or -decrease the number of sparse regions are prioritized. To disable this -functionality, set this to 0. It defaults to 0 on all platforms except -windows.
-
-
-
lock_disk_cache
-
if lock disk cache is set to true the disk cache that's in use, will -be locked in physical memory, preventing it from being swapped out.
-
-
-
max_rejects
-
the number of piece requests we will reject in a row while a peer is -choked before the peer is considered abusive and is disconnected.
-
- -
-
recv_socket_buffer_size send_socket_buffer_size
-
specifies the buffer sizes set on peer sockets. 0 (which is the -default) means the OS default (i.e. don't change the buffer sizes). -The socket buffer sizes are changed using setsockopt() with -SOL_SOCKET/SO_RCVBUF and SO_SNDBUFFER.
-
-
-
optimize_hashing_for_speed
-
chooses between two ways of reading back piece data from disk when its -complete and needs to be verified against the piece hash. This happens -if some blocks were flushed to the disk out of order. Everything that -is flushed in order is hashed as it goes along. Optimizing for speed -will allocate space to fit all the the remaingin, unhashed, part of -the piece, reads the data into it in a single call and hashes it. This -is the default. If optimizing_hashing_for_speed is false, a single -block will be allocated (16 kB), and the unhashed parts of the piece -are read, one at a time, and hashed in this single block. This is -appropriate on systems that are memory constrained.
-
-
-
file_checks_delay_per_block
-
the number of milliseconds to sleep -in between disk read operations when checking torrents. This defaults -to 0, but can be set to higher numbers to slow down the rate at which -data is read from the disk while checking. This may be useful for -background tasks that doesn't matter if they take a bit longer, as long -as they leave disk I/O time for other processes.
-
-
-
disk_cache_algorithm
-
tells the disk I/O thread which cache flush algorithm to use. -This is specified by the disk_cache_algo_t enum.
-
-
-
read_cache_line_size
-

the number of blocks to read into the read cache when a read cache -miss occurs. Setting this to 0 is essentially the same thing as -disabling read cache. The number of blocks read into the read cache is -always capped by the piece boundry.

-

When a piece in the write cache has write_cache_line_size -contiguous blocks in it, they will be flushed. Setting this to 1 -effectively disables the write cache.

-
-
-
-
write_cache_line_size
-
whenever a contiguous range of this many blocks is found in the write -cache, it is flushed immediately
-
-
-
optimistic_disk_retry
-

the number of seconds from a disk write errors occur on a torrent -until libtorrent will take it out of the upload mode, to test if the -error condition has been fixed.

-

libtorrent will only do this automatically for auto managed torrents.

-

You can explicitly take a torrent out of upload only mode using -set_upload_mode().

-
-
-
-
disable_hash_checks
-
controls if downloaded pieces are verified against the piece hashes in -the torrent file or not. The default is false, i.e. to verify all -downloaded data. It may be useful to turn this off for performance -profiling and simulation scenarios. Do not disable the hash check for -regular bittorrent clients.
-
-
-
allow_reordered_disk_operations
-
if this is true, disk read operations may be re-ordered based on their -physical disk read offset. This greatly improves throughput when -uploading to many peers. This assumes a traditional hard drive with a -read head and spinning platters. If your storage medium is a solid -state drive, this optimization doesn't give you an benefits
-
-
-
allow_i2p_mixed
-
if this is true, i2p torrents are allowed to also get peers from other -sources than the tracker, and connect to regular IPs, not providing -any anonymization. This may be useful if the user is not interested in -the anonymization of i2p, but still wants to be able to connect to i2p -peers.
-
-
-
max_suggest_pieces
-
the max number of suggested piece indices received from a peer that's -remembered. If a peer floods suggest messages, this limit prevents -libtorrent from using too much RAM. It defaults to 10.
-
-
-
drop_skipped_requests
-
If set to true (it defaults to false), piece requests that have been -skipped enough times when piece messages are received, will be -considered lost. Requests are considered skipped when the returned -piece messages are re-ordered compared to the order of the requests. -This was an attempt to get out of dead-locks caused by BitComet peers -silently ignoring some requests. It may cause problems at high rates, -and high level of reordering in the uploading peer, that's why it's -disabled by default.
-
-
-
low_prio_disk
-
determines if the disk I/O should use a normal -or low priority policy. This defaults to true, which means that -it's low priority by default. Other processes doing disk I/O will -normally take priority in this mode. This is meant to improve the -overall responsiveness of the system while downloading in the -background. For high-performance server setups, this might not -be desirable.
-
-
-
local_service_announce_interval
-
the time between local -network announces for a torrent. By default, when local service -discovery is enabled a torrent announces itself every 5 minutes. -This interval is specified in seconds.
-
-
-
dht_announce_interval
-
the number of seconds between announcing -torrents to the distributed hash table (DHT). This is specified to -be 15 minutes which is its default.
-
-
-
udp_tracker_token_expiry
-
the number of seconds libtorrent -will keep UDP tracker connection tokens around for. This is specified -to be 60 seconds, and defaults to that. The higher this value is, the -fewer packets have to be sent to the UDP tracker. In order for higher -values to work, the tracker needs to be configured to match the -expiration time for tokens.
-
-
-
volatile_read_cache
-
if this is set to true, read cache blocks -that are hit by peer read requests are removed from the disk cache -to free up more space. This is useful if you don't expect the disk -cache to create any cache hits from other peers than the one who -triggered the cache line to be read into the cache in the first place.
-
-
-
guided_read_cache
-
enables the disk cache to adjust the size -of a cache line generated by peers to depend on the upload rate -you are sending to that peer. The intention is to optimize the RAM -usage of the cache, to read ahead further for peers that you're -sending faster to.
-
-
-
default_cache_min_age
-
the minimum number of seconds any read cache line is kept in the -cache. This defaults to one second but may be greater if -guided_read_cache is enabled. Having a lower bound on the time a -cache line stays in the cache is an attempt to avoid swapping the same -pieces in and out of the cache in case there is a shortage of spare -cache space.
-
-
-
num_optimistic_unchoke_slots
-
the number of optimistic unchoke slots to use. It defaults to 0, which -means automatic. Having a higher number of optimistic unchoke slots -mean you will find the good peers faster but with the trade-off to use -up more bandwidth. When this is set to 0, libtorrent opens up 20% of -your allowed upload slots as optimistic unchoke slots.
-
-
-
no_atime_storage
-
this is a linux-only option and passes in the O_NOATIME to -open() when opening files. This may lead to some disk performance -improvements.
-
-
-
default_est_reciprocation_rate
-
the assumed reciprocation rate from peers when using the BitTyrant -choker. This defaults to 14 kiB/s. If set too high, you will -over-estimate your peers and be more altruistic while finding the true -reciprocation rate, if it's set too low, you'll be too stingy and -waste finding the true reciprocation rate.
-
-
-
increase_est_reciprocation_rate
-
specifies how many percent the extimated reciprocation rate should be -increased by each unchoke interval a peer is still choking us back. -This defaults to 20%. This only applies to the BitTyrant choker.
-
-
-
decrease_est_reciprocation_rate
-
specifies how many percent the estimated reciprocation rate should be -decreased by each unchoke interval a peer unchokes us. This default to -3%. This only applies to the BitTyrant choker.
-
-
-
incoming_starts_queued_torrents
-
defaults to false. If a torrent has been paused by the auto managed -feature in libtorrent, i.e. the torrent is paused and auto managed, -this feature affects whether or not it is automatically started on an -incoming connection. The main reason to queue torrents, is not to make -them unavailable, but to save on the overhead of announcing to the -trackers, the DHT and to avoid spreading one's unchoke slots too thin. -If a peer managed to find us, even though we're no in the torrent -anymore, this setting can make us start the torrent and serve it.
-
-
-
report_true_downloaded
-
when set to true, the downloaded counter sent to trackers will include -the actual number of payload bytes donwnloaded including redundant -bytes. If set to false, it will not include any redundany bytes
-
-
-
strict_end_game_mode
-
defaults to true, and controls when a block may be requested twice. If -this is true, a block may only be requested twice when there's ay -least one request to every piece that's left to download in the -torrent. This may slow down progress on some pieces sometimes, but it -may also avoid downloading a lot of redundant bytes. If this is -false, libtorrent attempts to use each peer connection to its max, -by always requesting something, even if it means requesting something -that has been requested from another peer already.
-
-
-
broadcast_lsd
-
if set to true, the local peer discovery (or Local Service Discovery) -will not only use IP multicast, but also broadcast its messages. This -can be useful when running on networks that don't support multicast. -Since broadcast messages might be expensive and disruptive on -networks, only every 8th announce uses broadcast.
-
- - - -
-
enable_outgoing_utp enable_incoming_utp enable_outgoing_tcp enable_incoming_tcp
-
these all determines if libtorrent should attempt to make outgoing -connections of the specific type, or allow incoming connection. By -default all of them are enabled.
-
-
-
max_pex_peers
-
the max number of peers we accept from pex messages from a single peer. -this limits the number of concurrent peers any of our peers claims to -be connected to. If they clain to be connected to more than this, we'll -ignore any peer that exceeds this limit
-
-
-
ignore_resume_timestamps
-
determines if the storage, when loading resume data files, should -verify that the file modification time with the timestamps in the -resume data. This defaults to false, which means timestamps are taken -into account, and resume data is less likely to accepted (torrents are -more likely to be fully checked when loaded). It might be useful to -set this to true if your network is faster than your disk, and it -would be faster to redownload potentially missed pieces than to go -through the whole storage to look for them.
-
-
-
no_recheck_incomplete_resume
-
determines if the storage should check the whole files when resume -data is incomplete or missing or whether it should simply assume we -don't have any of the data. By default, this is determined by the -existance of any of the files. By setting this setting to true, the -files won't be checked, but will go straight to download mode.
-
-
-
anonymous_mode
-

defaults to false. When set to true, the client tries to hide its -identity to a certain degree. The peer-ID will no longer include the -client's fingerprint. The user-agent will be reset to an empty string. -It will also try to not leak other identifying information, such as -your local listen port, your IP etc.

-

If you're using I2P, a VPN or a proxy, it might make sense to enable -anonymous mode.

-
-
-
-
force_proxy
-
disables any communication that's not going over a proxy. Enabling -this requires a proxy to be configured as well, see -set_proxy_settings. The listen sockets are closed, and incoming -connections will only be accepted through a SOCKS5 or I2P proxy (if a -peer proxy is set up and is run on the same machine as the tracker -proxy). This setting also disabled peer country lookups, since those -are done via DNS lookups that aren't supported by proxies.
-
-
-
tick_interval
-
specifies the number of milliseconds between internal ticks. This is -the frequency with which bandwidth quota is distributed to peers. It -should not be more than one second (i.e. 1000 ms). Setting this to a -low value (around 100) means higher resolution bandwidth quota -distribution, setting it to a higher value saves CPU cycles.
-
-
-
report_web_seed_downloads
-
specifies whether downloads from web seeds is reported to the -tracker or not. Defaults to on
-
-
-
share_mode_target
-
specifies the target share ratio for share mode torrents. This -defaults to 3, meaning we'll try to upload 3 times as much as we -download. Setting this very high, will make it very conservative and -you might end up not downloading anything ever (and not affecting your -share ratio). It does not make any sense to set this any lower than 2. -For instance, if only 3 peers need to download the rarest piece, it's -impossible to download a single piece and upload it more than 3 times. -If the share_mode_target is set to more than 3, nothing is downloaded.
-
- - - -
-
upload_rate_limit download_rate_limit local_upload_rate_limit local_download_rate_limit
-

sets the session-global limits of upload and download rate limits, in -bytes per second. The local rates refer to peers on the local network. -By default peers on the local network are not rate limited.

-

These rate limits are only used for local peers (peers within the same -subnet as the client itself) and it is only used when -session_settings::ignore_limits_on_local_network is set to true -(which it is by default). These rate limits default to unthrottled, -but can be useful in case you want to treat local peers -preferentially, but not quite unthrottled.

-

A value of 0 means unlimited.

-
-
-
-
dht_upload_rate_limit
-
sets the rate limit on the DHT. This is specified in bytes per second -and defaults to 4000. For busy boxes with lots of torrents that -requires more DHT traffic, this should be raised.
-
-
-
unchoke_slots_limit
-
the max number of unchoked peers in the session. The number of unchoke -slots may be ignored depending on what choking_algorithm is set -to. A value of -1 means infinite.
-
-
-
half_open_limit
-
sets the maximum number of half-open connections libtorrent will have -when connecting to peers. A half-open connection is one where -connect() has been called, but the connection still hasn't been -established (nor failed). Windows XP Service Pack 2 sets a default, -system wide, limit of the number of half-open connections to 10. So, -this limit can be used to work nicer together with other network -applications on that system. The default is to have no limit, and -passing -1 as the limit, means to have no limit. When limiting the -number of simultaneous connection attempts, peers will be put in a -queue waiting for their turn to get connected.
-
-
-
connections_limit
-
sets a global limit on the number of connections opened. The number of -connections is set to a hard minimum of at least two per torrent, so -if you set a too low connections limit, and open too many torrents, -the limit will not be met.
-
-
-
connections_slack
-
the number of extra incoming connections allowed temporarily, in order -to support replacing peers
-
-
-
utp_target_delay
-
the target delay for uTP sockets in milliseconds. A high value will -make uTP connections more aggressive and cause longer queues in the -upload bottleneck. It cannot be too low, since the noise in the -measurements would cause it to send too slow. The default is 50 -milliseconds.
-
-
-
utp_gain_factor
-
the number of bytes the uTP congestion window can increase at the most -in one RTT. This defaults to 300 bytes. If this is set too high, the -congestion controller reacts too hard to noise and will not be stable, -if it's set too low, it will react slow to congestion and not back off -as fast.
-
-
-
utp_min_timeout
-

the shortest allowed uTP socket timeout, specified in milliseconds. -This defaults to 500 milliseconds. The timeout depends on the RTT of -the connection, but is never smaller than this value. A connection -times out when every packet in a window is lost, or when a packet is -lost twice in a row (i.e. the resent packet is lost as well).

-

The shorter the timeout is, the faster the connection will recover -from this situation, assuming the RTT is low enough.

-
-
-
-
utp_syn_resends
-
the number of SYN packets that are sent (and timed out) before -giving up and closing the socket.
-
-
-
utp_fin_resends
-
the number of resent packets sent on a closed socket before giving up
-
-
-
utp_num_resends
-
the number of times a packet is sent (and lossed or timed out) -before giving up and closing the connection.
-
-
-
utp_connect_timeout
-
the number of milliseconds of timeout for the initial SYN packet for -uTP connections. For each timed out packet (in a row), the timeout is -doubled.
-
-
-
utp_dynamic_sock_buf
-
controls if the uTP socket manager is allowed to increase the socket -buffer if a network interface with a large MTU is used (such as -loopback or ethernet jumbo frames). This defaults to true and might -improve uTP throughput. For RAM constrained systems, disabling this -typically saves around 30kB in user space and probably around 400kB in -kernel socket buffers (it adjusts the send and receive buffer size on -the kernel socket, both for IPv4 and IPv6).
-
-
-
utp_loss_multiplier
-
controls how the congestion window is changed when a packet loss is -experienced. It's specified as a percentage multiplier for cwnd. -By default it's set to 50 (i.e. cut in half). Do not change this value -unless you know what you're doing. Never set it higher than 100.
-
-
-
mixed_mode_algorithm
-

determines how to treat TCP connections when there are uTP -connections. Since uTP is designed to yield to TCP, there's an -inherent problem when using swarms that have both TCP and uTP -connections. If nothing is done, uTP connections would often be -starved out for bandwidth by the TCP connections. This mode is -prefer_tcp. The peer_proportional mode simply looks at the -current throughput and rate limits all TCP connections to their -proportional share based on how many of the connections are TCP. This -works best if uTP connections are not rate limited by the global rate -limiter, see rate_limit_utp.

-

see bandwidth_mixed_algo_t for options.

-
-
-
-
rate_limit_utp
-
determines if uTP connections should be throttled by the global rate -limiter or not. By default they are.
-
-
-
listen_queue_size
-
the value passed in to listen() for the listen socket. It is the -number of outstanding incoming connections to queue up while we're not -actively waiting for a connection to be accepted. The default is 5 -which should be sufficient for any normal client. If this is a high -performance server which expects to receive a lot of connections, or -used in a simulator or test, it might make sense to raise this number. -It will not take affect until listen_on() is called again (or for the -first time).
-
-
-
announce_double_nat
-
if true, the &ip= argument in tracker requests (unless otherwise -specified) will be set to the intermediate IP address, if the user is -double NATed. If ther user is not double NATed, this option has no -affect.
-
-
-
torrent_connect_boost
-
the number of peers to try to connect to immediately when the first -tracker response is received for a torrent. This is a boost to given -to new torrents to accelerate them starting up. The normal connect -scheduler is run once every second, this allows peers to be connected -immediately instead of waiting for the session tick to trigger -connections.
-
-
-
seeding_outgoing_connections
-
determines if seeding (and finished) torrents should attempt to make -outgoing connections or not. By default this is true. It may be set to -false in very specific applications where the cost of making outgoing -connections is high, and there are no or small benefits of doing so. -For instance, if no nodes are behind a firewall or a NAT, seeds don't -need to make outgoing connections.
-
-
-
no_connect_privileged_ports
-
if true (which is the default), libtorrent will not connect to any -peers on priviliged ports (<= 1023). This can mitigate using -bittorrent swarms for certain DDoS attacks.
-
-
-
alert_queue_size
-
the maximum number of alerts queued up internally. If alerts are not -popped, the queue will eventually fill up to this level. This defaults -to 1000.
-
-
-
max_metadata_size
-
the maximum allowed size (in bytes) to be received -by the metadata extension, i.e. magnet links. It defaults to 1 MiB.
-
-
-
smooth_connects
-
true by default, which means the number of connection attempts per -second may be limited to below the connection_speed, in case we're -close to bump up against the limit of number of connections. The -intention of this setting is to more evenly distribute our connection -attempts over time, instead of attempting to connectin in batches, and -timing them out in batches.
-
-
-
always_send_user_agent
-
defaults to false. When set to true, web connections will include a -user-agent with every request, as opposed to just the first request in -a connection.
-
-
-
apply_ip_filter_to_trackers
-
defaults to true. It determines whether the IP filter applies to -trackers as well as peers. If this is set to false, trackers are -exempt from the IP filter (if there is one). If no IP filter is set, -this setting is irrelevant.
-
-
-
read_job_every
-
used to avoid starvation of read jobs in the disk I/O thread. By -default, read jobs are deferred, sorted by physical disk location and -serviced once all write jobs have been issued. In scenarios where the -download rate is enough to saturate the disk, there's a risk the read -jobs will never be serviced. With this setting, every x write job, -issued in a row, will instead pick one read job off of the sorted -queue, where x is read_job_every.
-
-
-
use_disk_read_ahead
-
defaults to true and will attempt to optimize disk reads by giving the -operating system heads up of disk read requests as they are queued in -the disk job queue. This gives a significant performance boost for -seeding.
-
-
-
lock_files
-
determines whether or not to lock files which libtorrent is -downloading to or seeding from. This is implemented using -fcntl(F_SETLK) on unix systems and by not passing in -SHARE_READ and SHARE_WRITE on windows. This might prevent 3rd -party processes from corrupting the files under libtorrent's feet.
-
-
-
ssl_listen
-

sets the listen port for SSL connections. If this is set to 0, no SSL -listen port is opened. Otherwise a socket is opened on this port. This -setting is only taken into account when opening the regular listen -port, and won't re-open the listen socket simply by changing this -setting.

-

if this is 0, outgoing SSL connections are disabled

-

It defaults to port 4433.

-
-
-
-
tracker_backoff
-

tracker_backoff determines how aggressively to back off from -retrying failing trackers. This value determines x in the following -formula, determining the number of seconds to wait until the next -retry:

-
-delay = 5 + 5 * x / 100 * fails^2
-

It defaults to 250.

-

This setting may be useful to make libtorrent more or less aggressive -in hitting trackers.

-
-
-
-
ban_web_seeds
-
enables banning web seeds. By default, web seeds that send corrupt -data are banned.
-
-
-
max_http_recv_buffer_size
-
specifies the max number of bytes to receive into RAM buffers when -downloading stuff over HTTP. Specifically when specifying a URL to a -.torrent file when adding a torrent or when announcing to an HTTP -tracker. The default is 2 MiB.
-
-
-
support_share_mode
-
enables or disables the share mode extension. This is enabled by -default.
-
-
-
support_merkle_torrents
-
enables or disables the merkle tree torrent support. This is enabled -by default.
-
-
-
report_redundant_bytes
-
enables or disables reporting redundant bytes to the tracker. This is -enabled by default.
-
-
-
handshake_client_version
-
the version string to advertise for this client in the peer protocol -handshake. If this is empty the user_agent is used
-
-
-
use_disk_cache_pool
-
if this is true, the disk cache uses a pool allocator for disk cache -blocks. Enabling this improves performance of the disk cache with the -side effect that the disk cache is less likely and slower at returning -memory to the kernel when cache pressure is low.
-
- -
-
inactive_down_rate inactive_up_rate
-
the download and upload rate limits for a torrent to be considered -active by the queuing mechanism. A torrent whose download rate is less -than inactive_down_rate and whose upload rate is less than -inactive_up_rate for auto_manage_startup seconds, is -considered inactive, and another queued torrent may be startert. -This logic is disabled if dont_count_slow_torrents is false.
-
-
-
-
-

dht_settings

-

Declared in "libtorrent/session_settings.hpp"

-

structure used to hold configuration options for the DHT

-

The dht_settings struct used to contain a service_port member to -control which port the DHT would listen on and send messages from. This -field is deprecated and ignored. libtorrent always tries to open the UDP -socket on the same port as the TCP socket.

-
-struct dht_settings
-{
-   dht_settings ();
-
-   int max_peers_reply;
-   int search_branching;
-   int max_fail_count;
-   int max_torrents;
-   int max_dht_items;
-   int max_torrent_search_reply;
-   bool restrict_routing_ips;
-   bool restrict_search_ips;
-   bool extended_routing_table;
-   bool aggressive_lookups;
-   bool privacy_lookups;
-   bool enforce_node_id;
-   bool ignore_dark_internet;
-};
-
-
-

dht_settings()

-
-dht_settings ();
-
-

initialized dht_settings to the default values

-
-
max_peers_reply
-
the maximum number of peers to send in a reply to get_peers
-
-
-
search_branching
-
the number of concurrent search request the node will send when -announcing and refreshing the routing table. This parameter is called -alpha in the kademlia paper
-
-
-
max_fail_count
-
the maximum number of failed tries to contact a node before it is -removed from the routing table. If there are known working nodes that -are ready to replace a failing node, it will be replaced immediately, -this limit is only used to clear out nodes that don't have any node -that can replace them.
-
-
-
max_torrents
-
the total number of torrents to track from the DHT. This is simply an -upper limit to make sure malicious DHT nodes cannot make us allocate -an unbounded amount of memory.
-
-
-
max_dht_items
-
max number of items the DHT will store
-
-
-
max_torrent_search_reply
-
the max number of torrents to return in a torrent search query to the -DHT
-
-
-
restrict_routing_ips
-

determines if the routing table entries should restrict entries to one -per IP. This defaults to true, which helps mitigate some attacks on -the DHT. It prevents adding multiple nodes with IPs with a very close -CIDR distance.

-

when set, nodes whose IP address that's in the same /24 (or /64 for -IPv6) range in the same routing table bucket. This is an attempt to -mitigate node ID spoofing attacks also restrict any IP to only have a -single entry in the whole routing table

-
-
-
-
restrict_search_ips
-
determines if DHT searches should prevent adding nodes with IPs with -very close CIDR distance. This also defaults to true and helps -mitigate certain attacks on the DHT.
-
-
-
extended_routing_table
-
makes the first buckets in the DHT routing table fit 128, 64, 32 and -16 nodes respectively, as opposed to the standard size of 8. All other -buckets have size 8 still.
-
-
-
aggressive_lookups
-
slightly changes the lookup behavior in terms of how many outstanding -requests we keep. Instead of having branch factor be a hard limit, we -always keep branch factor outstanding requests to the closest nodes. -i.e. every time we get results back with closer nodes, we query them -right away. It lowers the lookup times at the cost of more outstanding -queries.
-
-
-
privacy_lookups
-
when set, perform lookups in a way that is slightly more expensive, -but which minimizes the amount of information leaked about you.
-
-
-
enforce_node_id
-
when set, node's whose IDs that are not correctly generated based on -its external IP are ignored. When a query arrives from such node, an -error message is returned with a message saying "invalid node ID".
-
-
-
ignore_dark_internet
-
ignore DHT messages from parts of the internet we wouldn't expect to -see any traffic from
-
-
-
-
-

pe_settings

-

Declared in "libtorrent/session_settings.hpp"

-

The pe_settings structure is used to control the settings related -to peer protocol encryption.

-
-struct pe_settings
-{
-   pe_settings ();
-
-   enum enc_policy
-   {
-      forced,
-      enabled,
-      disabled,
-   };
-
-   enum enc_level
-   {
-      plaintext,
-      rc4,
-      both,
-   };
-
-   boost::uint8_t out_enc_policy;
-   boost::uint8_t in_enc_policy;
-   boost::uint8_t allowed_enc_level;
-   bool prefer_rc4;
-};
-
-
-

pe_settings()

-
-pe_settings ();
-
-

initializes the encryption settings with the default vaues

-
-
-

enum enc_policy

-

Declared in "libtorrent/session_settings.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
forced0Only encrypted connections are allowed. Incoming connections that -are not encrypted are closed and if the encrypted outgoing -connection fails, a non-encrypted retry will not be made.
enabled1encrypted connections are enabled, but non-encrypted connections -are allowed. An incoming non-encrypted connection will be accepted, -and if an outgoing encrypted connection fails, a non- encrypted -connection will be tried.
disabled2only non-encrypted connections are allowed.
-
-
-

enum enc_level

-

Declared in "libtorrent/session_settings.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
plaintext1use only plaintext encryption
rc42use only rc4 encryption
both3allow both
- -
-
out_enc_policy in_enc_policy
-
control the settings for incoming -and outgoing connections respectively. -see enc_policy enum for the available options.
-
-
-
allowed_enc_level
-
determines the encryption level of the -connections. This setting will adjust which encryption scheme is -offered to the other peer, as well as which encryption scheme is -selected by the client. See enc_level enum for options.
-
-
-
prefer_rc4
-
if the allowed encryption level is both, setting this to -true will prefer rc4 if both methods are offered, plaintext -otherwise
-
- -
-
-

min_memory_usage() high_performance_seed()

-

Declared in "libtorrent/session.hpp"

-
-session_settings min_memory_usage ();
-session_settings high_performance_seed ();
-
-

The default values of the session settings are set for a regular -bittorrent client running on a desktop system. There are functions that -can set the session settings to pre set settings for other environments. -These can be used for the basis, and should be tweaked to fit your needs -better.

-

min_memory_usage returns settings that will use the minimal amount of -RAM, at the potential expense of upload and download performance. It -adjusts the socket buffer sizes, disables the disk cache, lowers the send -buffer watermarks so that each connection only has at most one block in -use at any one time. It lowers the outstanding blocks send to the disk -I/O thread so that connections only have one block waiting to be flushed -to disk at any given time. It lowers the max number of peers in the peer -list for torrents. It performs multiple smaller reads when it hashes -pieces, instead of reading it all into memory before hashing.

-

This configuration is inteded to be the starting point for embedded -devices. It will significantly reduce memory usage.

-

high_performance_seed returns settings optimized for a seed box, -serving many peers and that doesn't do any downloading. It has a 128 MB -disk cache and has a limit of 400 files in its file pool. It support fast -upload rates by allowing large send buffers.

-
-
-
- - diff --git a/docs/reference-Storage.html b/docs/reference-Storage.html deleted file mode 100644 index 1edf8605d..000000000 --- a/docs/reference-Storage.html +++ /dev/null @@ -1,604 +0,0 @@ - - - - - - -Storage - - - - - - - - -
-
-
- -
- -
-

Storage

- --- - - - - - -
Author:Arvid Norberg, arvid@rasterbar.com
Version:1.0.0
-
-

Table of contents

- -
-
-

file_entry

-

Declared in "libtorrent/file_storage.hpp"

-

information about a file in a file_storage

-
-struct file_entry
-{
-   std::string path;
-   std::string symlink_path;
-   size_type offset;
-   size_type size;
-   size_type file_base;
-   std::time_t mtime;
-   sha1_hash filehash;
-   bool pad_file:1;
-   bool hidden_attribute:1;
-   bool executable_attribute:1;
-   bool symlink_attribute:1;
-};
-
-
-
path
-
the full path of this file. The paths are unicode strings -encoded in UTF-8.
-
-
-
symlink_path
-
the path which this is a symlink to, or empty if this is -not a symlink. This field is only used if the symlink_attribute is set.
-
-
-
offset
-
the offset of this file inside the torrent
-
-
-
size
-
the size of the file (in bytes) and offset is the byte offset -of the file within the torrent. i.e. the sum of all the sizes of the files -before it in the list.
-
-
-
file_base
-
the offset in the file where the storage should start. The normal -case is to have this set to 0, so that the storage starts saving data at the start -if the file. In cases where multiple files are mapped into the same file though, -the file_base should be set to an offset so that the different regions do -not overlap. This is used when mapping "unselected" files into a so-called part -file.
-
-
-
mtime
-
the modification time of this file specified in posix time.
-
-
-
filehash
-
a sha-1 hash of the content of the file, or zeroes, if no -file hash was present in the torrent file. It can be used to potentially -find alternative sources for the file.
-
-
-
pad_file
-
set to true for files that are not part of the data of the torrent. -They are just there to make sure the next file is aligned to a particular byte offset -or piece boundry. These files should typically be hidden from an end user. They are -not written to disk.
-
-
-
hidden_attribute
-
true if the file was marked as hidden (on windows).
-
-
-
executable_attribute
-
true if the file was marked as executable (posix)
-
-
-
symlink_attribute
-
true if the file was a symlink. If this is the case -the symlink_index refers to a string which specifies the original location -where the data for this file was found.
-
-
-
-

file_slice

-

Declared in "libtorrent/file_storage.hpp"

-

represents a window of a file in a torrent.

-

The file_index refers to the index of the file (in the torrent_info). -To get the path and filename, use file_at() and give the file_index -as argument. The offset is the byte offset in the file where the range -starts, and size is the number of bytes this range is. The size + offset -will never be greater than the file size.

-
-struct file_slice
-{
-   int file_index;
-   size_type offset;
-   size_type size;
-};
-
-
-
file_index
-
the index of the file
-
-
-
offset
-
the offset from the start of the file, in bytes
-
-
-
size
-
the size of the window, in bytes
-
-
-
-

file_storage

-

Declared in "libtorrent/file_storage.hpp"

-

The file_storage class represents a file list and the piece -size. Everything necessary to interpret a regular bittorrent storage -file structure.

-
-class file_storage
-{
-   bool is_valid () const;
-   void reserve (int num_files);
-   void add_file (std::string const& p, size_type size, int flags = 0
-      , std::time_t mtime = 0, std::string const& s_p = "");
-   void add_file (file_entry const& e, char const* filehash = 0);
-   void rename_file (int index, std::string const& new_filename);
-   void rename_file_borrow (int index, char const* new_filename, int len);
-   std::vector<file_slice> map_block (int piece, size_type offset
-      , int size) const;
-   peer_request map_file (int file, size_type offset, int size) const;
-   int num_files () const;
-   file_entry at (int index) const;
-   size_type total_size () const;
-   void set_num_pieces (int n);
-   int num_pieces () const;
-   void set_piece_length (int l);
-   int piece_length () const;
-   int piece_size (int index) const;
-   void set_name (std::string const& n);
-   const std::string& name () const;
-   void swap (file_storage& ti);
-   void optimize (int pad_file_limit = -1, int alignment = -1);
-   size_type file_size (int index) const;
-   sha1_hash hash (int index) const;
-   std::string file_name (int index) const;
-   size_type file_offset (int index) const;
-   time_t mtime (int index) const;
-   bool pad_file_at (int index) const;
-   std::string const& symlink (int index) const;
-   std::string file_path (int index, std::string const& save_path = "") const;
-   int file_flags (int index) const;
-   void set_file_base (int index, size_type off);
-   size_type file_base (int index) const;
-   int file_index_at_offset (size_type offset) const;
-   char const* file_name_ptr (int index) const;
-   int file_name_len (int index) const;
-
-   enum flags_t
-   {
-      pad_file,
-      attribute_hidden,
-      attribute_executable,
-      attribute_symlink,
-   };
-
-   enum file_flags_t
-   {
-      flag_pad_file,
-      flag_hidden,
-      flag_executable,
-      flag_symlink,
-   };
-};
-
-
-

is_valid()

-
-bool is_valid () const;
-
-

returns true if the piece length has been initialized -on the file_storage. This is typically taken as a proxy -of whether the file_storage as a whole is initialized or -not.

-
-
-

reserve()

-
-void reserve (int num_files);
-
-

allocates space for num_files in the internal file list. This can -be used to avoid reallocating the internal file list when the number -of files to be added is known up-front.

-
-
-

add_file()

-
-void add_file (std::string const& p, size_type size, int flags = 0
-      , std::time_t mtime = 0, std::string const& s_p = "");
-void add_file (file_entry const& e, char const* filehash = 0);
-
-

Adds a file to the file storage. The flags argument sets attributes on the file. -The file attributes is an extension and may not work in all bittorrent clients.

-

For possible file attributes, see file_storage::flags_t.

-

If more files than one are added, certain restrictions to their paths apply. -In a multi-file file storage (torrent), all files must share the same root directory.

-

That is, the first path element of all files must be the same. -This shared path element is also set to the name of the torrent. It -can be changed by calling set_name.

-

The built in functions to traverse a directory to add files will -make sure this requirement is fulfilled.

-
-
-

rename_file()

-
-void rename_file (int index, std::string const& new_filename);
-
-

renames the file at index to new_filename. Keep in mind -that filenames are expected to be UTF-8 encoded.

-
-
-

rename_file_borrow()

-
-void rename_file_borrow (int index, char const* new_filename, int len);
-
-

this is a low-level function that sets the name of a file -by making it reference a buffer that is not owned by the file_storage. -it's an optimization used when loading .torrent files, to not -duplicate names in memory.

-
-
-

map_block()

-
-std::vector<file_slice> map_block (int piece, size_type offset
-      , int size) const;
-
-

returns a list of file_slice objects representing the portions of -files the specified piece index, byte offset and size range overlaps. -this is the inverse mapping of map_file().

-
-
-

map_file()

-
-peer_request map_file (int file, size_type offset, int size) const;
-
-

returns a peer_request representing the piece index, byte offset -and size the specified file range overlaps. This is the inverse -mapping ove map_block().

-
-
-

num_files()

-
-int num_files () const;
-
-

returns the number of files in the file_storage

-
-
-

at()

-
-file_entry at (int index) const;
-
-

returns a file_entry with information about the file -at index. Index must be in the range [0, num_files() ).

-
-
-

total_size()

-
-size_type total_size () const;
-
-

returns the total number of bytes all the files in this torrent spans

- -
-
-

num_pieces() set_num_pieces()

-
-void set_num_pieces (int n);
-int num_pieces () const;
-
-

set and get the number of pieces in the torrent

- -
-
-

piece_length() set_piece_length()

-
-void set_piece_length (int l);
-int piece_length () const;
-
-

set and get the size of each piece in this torrent. This size is typically an even power -of 2. It doesn't have to be though. It should be divisible by 16kiB however.

-
-
-

piece_size()

-
-int piece_size (int index) const;
-
-

returns the piece size of index. This will be the same as piece_length(), except -for the last piece, which may be shorter.

- -
-
-

set_name() name()

-
-void set_name (std::string const& n);
-const std::string& name () const;
-
-

set and get the name of this torrent. For multi-file torrents, this is also -the name of the root directory all the files are stored in.

-
-
-

swap()

-
-void swap (file_storage& ti);
-
-

swap all content of this with ti.

-
-
-

optimize()

-
-void optimize (int pad_file_limit = -1, int alignment = -1);
-
-

if pad_file_limit >= 0, files larger than that limit will be padded, -default is to not add any padding (-1). The alignment specifies the -alignment files should be padded to. This defaults to the piece size -(-1) but it may also make sense to set it to 16 kiB, or something -divisible by 16 kiB. -If pad_file_limit is 0, every file will be padded (except empty ones).

- - - - - - - -
- -
-

file_flags()

-
-int file_flags (int index) const;
-
-

returns a bitmask of flags from file_flags_t that apply -to file at index.

- -
-
-

file_base() set_file_base()

-
-void set_file_base (int index, size_type off);
-size_type file_base (int index) const;
-
-

The file base of a file is the offset within the file on the filsystem -where it starts to write. For the most part, this is always 0. It's -possible to map several files (in the torrent) into a single file on -the filesystem by making them all point to the same filename, but with -different file bases, so that they don't overlap. -torrent_info::remap_files() can be used to use a new file layout.

-
-
-

file_index_at_offset()

-
-int file_index_at_offset (size_type offset) const;
-
-

returns the index of the file at the given offset in the torrent

- -
-
-

file_name_len() file_name_ptr()

-
-char const* file_name_ptr (int index) const;
-int file_name_len (int index) const;
-
-

low-level function. returns a pointer to the internal storage for -the filename. This string may not be null terinated! -the file_name_len() function returns the length of the filename.

-
-
-

enum flags_t

-

Declared in "libtorrent/file_storage.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
pad_file1the file is a pad file. It's required to contain zeroes -at it will not be saved to disk. Its purpose is to make -the following file start on a piece boundary.
attribute_hidden2this file has the hidden attribute set. This is primarily -a windows attribute
attribute_executable4this file has the executable attribute set.
attribute_symlink8this file is a symbilic link. It should have a link -target string associated with it.
-
-
-

enum file_flags_t

-

Declared in "libtorrent/file_storage.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
flag_pad_file1this file is a pad file. The creator of the -torrent promises the file is entirely filled with -zeroes and does not need to be downloaded. The -purpose is just to align the next file to either -a block or piece boundary.
flag_hidden2this file is hiddent (sets the hidden attribute -on windows)
flag_executable4this file is executable (sets the executable bit -on posix like systems)
flag_symlink8this file is a symlink. The symlink target is -specified in a separate field
-
-
-

default_storage_constructor()

-

Declared in "libtorrent/storage_defs.hpp"

-
-storage_interface* default_storage_constructor (
-   file_storage const&, file_storage const* mapped, std::string const&, file_pool&
-   , std::vector<boost::uint8_t> const&);
-
-

the constructor function for the regular file storage. This is the -default value for add_torrent_params::storage.

-
-
-

disabled_storage_constructor()

-

Declared in "libtorrent/storage_defs.hpp"

-
-storage_interface* disabled_storage_constructor (
-   file_storage const&, file_storage const* mapped, std::string const&, file_pool&
-   , std::vector<boost::uint8_t> const&);
-
-

the constructor function for the disabled storage. This can be used for -testing and benchmarking. It will throw away any data written to -it and return garbage for anything read from it.

-
-
-

enum storage_mode_t

-

Declared in "libtorrent/storage_defs.hpp"

- ----- - - - - - - - - - - - - - - - - -
namevaluedescription
storage_mode_allocate0All pieces will be written to their final position, all files will be -allocated in full when the torrent is first started. This is done with -fallocate() and similar calls. This mode minimizes fragmentation.
storage_mode_sparse1All pieces will be written to the place where they belong and sparse files -will be used. This is the recommended, and default mode.
-
-
-
- - diff --git a/docs/reference-String.html b/docs/reference-String.html deleted file mode 100644 index e5b7f4347..000000000 --- a/docs/reference-String.html +++ /dev/null @@ -1,158 +0,0 @@ - - - - - - -String - - - - - - - - -
-
-
- -
- -
-

String

- --- - - - - - -
Author:Arvid Norberg, arvid@rasterbar.com
Version:1.0.0
- -
-

to_hex()

-

Declared in "libtorrent/escape_string.hpp"

-
-std::string to_hex (std::string const& s);
-
-

converts (binary) the string s to hexadecimal representation and -returns it.

-
-
-

to_hex()

-

Declared in "libtorrent/escape_string.hpp"

-
-void to_hex (char const *in, int len, char* out);
-
-

converts the binary buffer [in, in + len) to hexadecimal -and prints it to the buffer out. The caller is responsible for -making sure the buffer pointed to by out is large enough, -i.e. has at least len * 2 bytes of space.

-
-
-

from_hex()

-

Declared in "libtorrent/escape_string.hpp"

-
-bool from_hex (char const *in, int len, char* out);
-
-

converts the buffer [in, in + len) from hexadecimal to -binary. The binary output is written to the buffer pointed to -by out. The caller is responsible for making sure the buffer -at out has enough space for the result to be written to, i.e. -(len + 1) / 2 bytes.

-
-
-

is_digit()

-

Declared in "libtorrent/string_util.hpp"

-
-bool is_digit (char c);
-
-

this is used by bdecode_recursive's header file

- -
-
-

utf8_wchar() wchar_utf8()

-

Declared in "libtorrent/utf8.hpp"

-
-utf8_conv_result_t wchar_utf8 (
-   const std::wstring &wide, std::string &utf8);
-utf8_conv_result_t utf8_wchar (
-   const std::string &utf8, std::wstring &wide);
-
-

utf8_wchar converts a UTF-8 string (utf8) to a wide character -string (wide). wchar_utf8 converts a wide character string -(wide) to a UTF-8 string (utf8). The return value is one of -the enumeration values from utf8_conv_result_t.

-
-
-

enum utf8_conv_result_t

-

Declared in "libtorrent/utf8.hpp"

- ----- - - - - - - - - - - - - - - - - - - - - - - - - -
namevaluedescription
conversion_ok0conversion successful
source_exhausted1partial character in source, but hit end
target_exhausted2insuff. room in target for conversion
source_illegal3source sequence is illegal/malformed
-
-
- - diff --git a/docs/reference-Time.html b/docs/reference-Time.html deleted file mode 100644 index f17bbfc15..000000000 --- a/docs/reference-Time.html +++ /dev/null @@ -1,236 +0,0 @@ - - - - - - -Time - - - - - - - - -
-
-
- -
- -
-

Time

- --- - - - - - -
Author:Arvid Norberg, arvid@rasterbar.com
Version:1.0.0
-
-

Table of contents

- -
-

This section contains fundamental time types used internally by -libtorrent and exposed through various places in the API. The two -basic types are ptime and time_duration. The first represents -a point in time and the second the difference between two points -in time.

-

The internal representation of these types is implementation defined -and they can only be constructed via one of the construction functions -that take a well defined time unit (seconds, minutes, etc.). They can -only be turned into well defined time units by the accessor functions -(total_microseconds(), etc.).

-
-

Note

-

In a future version of libtorrent, these types will be replaced -by the standard timer types from std::chrono.

-
-
-

time_duration

-

Declared in "libtorrent/ptime.hpp"

-

libtorrent time_duration type

-
-struct time_duration
-{
-   time_duration& operator-= (time_duration const& c);
-   time_duration operator+ (time_duration const& c);
-   time_duration& operator+= (time_duration const& c);
-   time_duration operator/ (int rhs) const;
-   explicit time_duration (boost::int64_t d);
-   time_duration operator- (time_duration const& c);
-   time_duration& operator*= (int v);
-};
-
- - - - - - -
-

operator+=() time_duration() operator*=() operator-=() operator+() operator/() operator-()

-
-time_duration& operator-= (time_duration const& c);
-time_duration operator+ (time_duration const& c);
-time_duration& operator+= (time_duration const& c);
-time_duration operator/ (int rhs) const;
-explicit time_duration (boost::int64_t d);
-time_duration operator- (time_duration const& c);
-time_duration& operator*= (int v);
-
-

all operators have the same semantics as a 64 bit signed integer

-
-
-
-

ptime

-

Declared in "libtorrent/ptime.hpp"

-

This type represents a point in time.

-
-struct ptime
-{
-   ptime& operator-= (time_duration rhs);
-   ptime& operator+= (time_duration rhs);
-};
-
- -
-

operator+=() operator-=()

-
-ptime& operator-= (time_duration rhs);
-ptime& operator+= (time_duration rhs);
-
-

these operators have the same semantics as signed 64 bit integers

-
-
-

is_negative()

-

Declared in "libtorrent/ptime.hpp"

-
-inline bool is_negative (time_duration dt);
-
-

returns true of the time duration is less than 0

- - - - - - - - -
-
-

operator*() operator>=() operator!=() operator>() operator<() operator==() operator+() operator<=() operator-()

-

Declared in "libtorrent/ptime.hpp"

-
-inline time_duration operator* (int lhs, time_duration rhs);
-inline bool operator== (time_duration lhs, time_duration rhs);
-inline bool operator<= (time_duration lhs, time_duration rhs);
-inline bool operator== (ptime lhs, ptime rhs);
-inline bool operator>= (time_duration lhs, time_duration rhs);
-inline bool operator< (time_duration lhs, time_duration rhs);
-inline bool operator> (time_duration lhs, time_duration rhs);
-inline bool operator> (ptime lhs, ptime rhs);
-inline ptime operator+ (time_duration lhs, ptime rhs);
-inline ptime operator+ (ptime lhs, time_duration rhs);
-inline time_duration operator* (time_duration lhs, int rhs);
-inline time_duration operator- (ptime lhs, ptime rhs);
-inline bool operator<= (ptime lhs, ptime rhs);
-inline bool operator!= (ptime lhs, ptime rhs);
-inline bool operator< (ptime lhs, ptime rhs);
-inline bool operator>= (ptime lhs, ptime rhs);
-inline ptime operator- (ptime lhs, time_duration rhs);
-
-

all operators have the same semantics as signed 64 bit integers

-
-
-

time_now()

-

Declared in "libtorrent/time.hpp"

-
-ptime const& time_now ();
-
-

returns the current time, as represented by ptime. The -resolution of this timer is about 100 ms.

-
-
-

time_now_hires()

-

Declared in "libtorrent/time.hpp"

-
-ptime time_now_hires ();
-
-

returns the current time as represented by ptime. This is -more expensive than time_now(), but provides as high resolution -as the operating system can provide.

- -
-
-

min_time() max_time()

-

Declared in "libtorrent/time.hpp"

-
-ptime max_time ();
-ptime min_time ();
-
-

the earliest and latest possible time points -representable by ptime.

- - - - -
-
-

microsec() minutes() hours() milliseconds() seconds()

-

Declared in "libtorrent/time.hpp"

-
-time_duration microsec (boost::int64_t s);
-time_duration seconds (boost::int64_t s);
-time_duration milliseconds (boost::int64_t s);
-time_duration minutes (boost::int64_t s);
-time_duration hours (boost::int64_t s);
-
-

returns a time_duration representing the specified number of seconds, milliseconds -microseconds, minutes and hours.

- - -
-
-

total_seconds() total_microseconds() total_milliseconds()

-

Declared in "libtorrent/time.hpp"

-
-boost::int64_t total_seconds (time_duration td);
-boost::int64_t total_milliseconds (time_duration td);
-boost::int64_t total_microseconds (time_duration td);
-
-

returns the number of seconds, milliseconds and microseconds -a time_duration represents.

-
-
-
- - diff --git a/docs/reference-Utility.html b/docs/reference-Utility.html deleted file mode 100644 index dc26d86de..000000000 --- a/docs/reference-Utility.html +++ /dev/null @@ -1,430 +0,0 @@ - - - - - - -Utility - - - - - - - - -
-
-
- -
- -
-

Utility

- --- - - - - - -
Author:Arvid Norberg, arvid@rasterbar.com
Version:1.0.0
-
-

Table of contents

- -
-
-

bitfield

-

Declared in "libtorrent/bitfield.hpp"

-

The bitfiled type stores any number of bits as a bitfield -in a heap allocated or borrowed array.

-
-struct bitfield
-{
-   bitfield (int bits);
-   bitfield (bitfield const& rhs);
-   bitfield (bitfield&& rhs);
-   bitfield (int bits, bool val);
-   bitfield ();
-   bitfield (char const* b, int bits);
-   void borrow_bytes (char* b, int bits);
-   void assign (char const* b, int bits);
-   bool get_bit (int index) const;
-   bool operator[] (int index) const;
-   void clear_bit (int index);
-   void set_bit (int index);
-   bool all_set () const;
-   std::size_t size () const;
-   bool empty () const;
-   char const* bytes () const;
-   bitfield& operator= (bitfield const& rhs);
-   int count () const;
-};
-
-
-

bitfield()

-
-bitfield (int bits);
-bitfield (bitfield const& rhs);
-bitfield (bitfield&& rhs);
-bitfield (int bits, bool val);
-bitfield ();
-bitfield (char const* b, int bits);
-
-

constructs a new bitfield. The default constructor creates an empty -bitfield. bits is the size of the bitfield (specified in bits). -val is the value to initialize the bits to. If not specified -all bits are initialized to 0.

-

The constructor taking a pointer b and bits copies a bitfield -from the specified buffer, and bits number of bits (rounded up to -the nearest byte boundry).

-
-
-

borrow_bytes()

-
-void borrow_bytes (char* b, int bits);
-
-

assigns a bitfield pointed to b of bits number of bits, without -taking ownership of the buffer. This is a way to avoid copying data and -yet provide a raw buffer to functions that may operate on the bitfield -type. It is the user's responsibility to make sure the passed-in buffer's -life time exceeds all uses of the bitfield.

-
-
-

assign()

-
-void assign (char const* b, int bits);
-
-

copy bitfield from buffer b of bits number of bits, rounded up to -the nearest byte boundary.

- -
-
-

operator[]() get_bit()

-
-bool get_bit (int index) const;
-bool operator[] (int index) const;
-
-

query bit at index. Returns true if bit is 1, otherwise false.

- -
-
-

set_bit() clear_bit()

-
-void clear_bit (int index);
-void set_bit (int index);
-
-

set bit at index to 0 (clear_bit) or 1 (set_bit).

-
-
-

all_set()

-
-bool all_set () const;
-
-

returns true if all bits in the bitfield are set

-
-
-

size()

-
-std::size_t size () const;
-
-

returns the size of the bitfield in bits.

-
-
-

empty()

-
-bool empty () const;
-
-

returns true if the bitfield has zero size.

-
-
-

bytes()

-
-char const* bytes () const;
-
-

returns a pointer to the internal buffer of the bitfield.

-
-
-

operator=()

-
-bitfield& operator= (bitfield const& rhs);
-
-

copy operator

-
-
-

count()

-
-int count () const;
-
-

count the number of bits in the bitfield that are set to 1.

-
-
-
-

sha1_hash

-

Declared in "libtorrent/sha1_hash.hpp"

-

This type holds a SHA-1 digest or any other kind of 20 byte -sequence. It implements a number of convenience functions, such -as bit operations, comparison operators etc.

-

In libtorrent it is primarily used to hold info-hashes, piece-hashes, -peer IDs, node IDs etc.

-
-class sha1_hash
-{
-   sha1_hash ();
-   static sha1_hash max ();
-   static sha1_hash min ();
-   explicit sha1_hash (char const* s);
-   void assign (char const* str);
-   explicit sha1_hash (std::string const& s);
-   void assign (std::string const& s);
-   void clear ();
-   bool is_all_zeros () const;
-   sha1_hash& operator<<= (int n);
-   sha1_hash& operator>>= (int n);
-   bool operator!= (sha1_hash const& n) const;
-   bool operator< (sha1_hash const& n) const;
-   bool operator== (sha1_hash const& n) const;
-   sha1_hash operator~ ();
-   sha1_hash operator^ (sha1_hash const& n) const;
-   sha1_hash& operator^= (sha1_hash const& n);
-   sha1_hash operator& (sha1_hash const& n) const;
-   sha1_hash& operator&= (sha1_hash const& n);
-   sha1_hash& operator|= (sha1_hash const& n);
-   unsigned char& operator[] (int i);
-   unsigned char const& operator[] (int i) const;
-   const_iterator begin () const;
-   iterator begin ();
-   iterator end ();
-   const_iterator end () const;
-   std::string to_string () const;
-
-   static const int size = number_size;
-};
-
-
-

sha1_hash()

-
-sha1_hash ();
-
-

constructs an all-sero sha1-hash

-
-
-

max()

-
-static sha1_hash max ();
-
-

returns an all-F sha1-hash. i.e. the maximum value -representable by a 160 bit number (20 bytes). This is -a static member function.

-
-
-

min()

-
-static sha1_hash min ();
-
-

returns an all-zero sha1-hash. i.e. the minimum value -representable by a 160 bit number (20 bytes). This is -a static member function.

- -
-
-

assign() sha1_hash()

-
-explicit sha1_hash (char const* s);
-void assign (char const* str);
-explicit sha1_hash (std::string const& s);
-void assign (std::string const& s);
-
-

copies 20 bytes from the pointer provided, into the sha1-hash. -The passed in string MUST be at least 20 bytes. NULL terminators -are ignored, s is treated like a raw memory buffer.

-
-
-

clear()

-
-void clear ();
-
-

set the sha1-hash to all zeroes.

-
-
-

is_all_zeros()

-
-bool is_all_zeros () const;
-
-

return true if the sha1-hash is all zero.

-
-
-

operator<<=()

-
-sha1_hash& operator<<= (int n);
-
-

shift left n bits.

-
-
-

operator>>=()

-
-sha1_hash& operator>>= (int n);
-
-

shift r n bits.

- - -
-
-

operator!=() operator<() operator==()

-
-bool operator!= (sha1_hash const& n) const;
-bool operator< (sha1_hash const& n) const;
-bool operator== (sha1_hash const& n) const;
-
-

standard comparison operators

-
-
-

operator~()

-
-sha1_hash operator~ ();
-
-

returns a bit-wise negated copy of the sha1-hash

-
-
-

operator^()

-
-sha1_hash operator^ (sha1_hash const& n) const;
-
-

returns the bit-wise XOR of the two sha1-hashes.

-
-
-

operator^=()

-
-sha1_hash& operator^= (sha1_hash const& n);
-
-

in-place bit-wise XOR with the passed in sha1_hash.

-
-
-

operator&()

-
-sha1_hash operator& (sha1_hash const& n) const;
-
-

returns the bit-wise AND of the two sha1-hashes.

-
-
-

operator&=()

-
-sha1_hash& operator&= (sha1_hash const& n);
-
-

in-place bit-wise AND of the passed in sha1_hash

-
-
-

operator|=()

-
-sha1_hash& operator|= (sha1_hash const& n);
-
-

in-place bit-wise OR of the two sha1-hash.

-
-
-

operator[]()

-
-unsigned char& operator[] (int i);
-unsigned char const& operator[] (int i) const;
-
-

accessors for specific bytes

- -
-
-

begin() end()

-
-const_iterator begin () const;
-iterator begin ();
-iterator end ();
-const_iterator end () const;
-
-

start and end iterators for the hash. The value type -of these iterators is unsigned char.

-
-
-

to_string()

-
-std::string to_string () const;
-
-

return a copy of the 20 bytes representing the sha1-hash as a std::string. -It's still a binary string with 20 binary characters.

-
-
number_size
-
the number of bytes of the number
-
-
-
-

identify_client()

-

Declared in "libtorrent/identify_client.hpp"

-
-std::string identify_client (const peer_id& p);
-
-

This function can can be used to extract a string describing a client -version from its peer-id. It will recognize most clients that have this -kind of identification in the peer-id.

-
-
-

client_fingerprint()

-

Declared in "libtorrent/identify_client.hpp"

-
-boost::optional<fingerprint> client_fingerprint (peer_id const& p);
-
-

Returns an optional fingerprint if any can be identified from the peer -id. This can be used to automate the identification of clients. It will -not be able to identify peers with non- standard encodings. Only Azureus -style, Shadow's style and Mainline style.

-
-
-

operator<<()

-

Declared in "libtorrent/sha1_hash.hpp"

-
-inline std::ostream& operator<< (std::ostream& os, sha1_hash const& peer);
-
-

print a sha1_hash object to an ostream as 40 hexadecimal digits

-
-
-

operator>>()

-

Declared in "libtorrent/sha1_hash.hpp"

-
-inline std::istream& operator>> (std::istream& is, sha1_hash& peer);
-
-

read 40 hexadecimal digits from an istream into a sha1_hash

-
-
-

sleep()

-

Declared in "libtorrent/thread.hpp"

-
-void sleep (int milliseconds);
-
-

pauses the calling thread at least for the specified -number of milliseconds

-
-
-
- - diff --git a/docs/reference.html b/docs/reference.html deleted file mode 100644 index e08a16f4a..000000000 --- a/docs/reference.html +++ /dev/null @@ -1,322 +0,0 @@ - - - - - - -libtorrent reference documentation - - - - - - - -
-
-
- -
- -
-

libtorrent reference documentation

- -
- - - - - - -
-

Alerts

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
-

Filter

- -
- - - - -
-
- - diff --git a/docs/settings.rst b/docs/settings.rst new file mode 100644 index 000000000..9e564c763 --- /dev/null +++ b/docs/settings.rst @@ -0,0 +1,3217 @@ +.. _user_agent: + +.. raw:: html + + + ++------------+--------+----------------------------------+ +| name | type | default | ++============+========+==================================+ +| user_agent | string | "libtorrent/" LIBTORRENT_VERSION | ++------------+--------+----------------------------------+ + +this is the client identification to the tracker. +The recommended format of this string is: +"ClientName/ClientVersion libtorrent/libtorrentVersion". +This name will not only be used when making HTTP requests, but also when +sending extended headers to peers that support that extension. +It may not contain \r or \n + +.. _announce_ip: + +.. raw:: html + + + ++-------------+--------+---------+ +| name | type | default | ++=============+========+=========+ +| announce_ip | string | 0 | ++-------------+--------+---------+ + +``announce_ip`` is the ip address passed along to trackers as the ``&ip=`` parameter. +If left as the default, that parameter is omitted. + +.. _mmap_cache: + +.. raw:: html + + + ++------------+--------+---------+ +| name | type | default | ++============+========+=========+ +| mmap_cache | string | 0 | ++------------+--------+---------+ + +``mmap_cache`` may be set to a filename where the disk cache will be mmapped +to. This could be useful, for instance, to map the disk cache from regular +rotating hard drives onto an SSD drive. Doing that effectively introduces +a second layer of caching, allowing the disk cache to be as big as can +fit on an SSD drive (probably about one order of magnitude more than the +available RAM). The intention of this setting is to set it up once at the +start up and not change it while running. The setting may not be changed +as long as there are any disk buffers in use. This default to the empty +string, which means use regular RAM allocations for the disk cache. The file +specified will be created and truncated to the disk cache size (``cache_size``). +Any existing file with the same name will be replaced. + +Since this setting sets a hard upper limit on cache usage, it cannot be combined +with ``session_settings::contiguous_recv_buffer``, since that feature treats the +``cache_size`` setting as a soft (but still pretty hard) limit. The result of combining +the two is peers being disconnected after failing to allocate more disk buffers. + +This feature requires the ``mmap`` system call, on systems that don't have ``mmap`` +this setting is ignored. + +.. _handshake_client_version: + +.. raw:: html + + + ++--------------------------+--------+---------+ +| name | type | default | ++==========================+========+=========+ +| handshake_client_version | string | 0 | ++--------------------------+--------+---------+ + +this is the client name and version identifier sent to peers in the handshake +message. If this is an empty string, the user_agent is used instead + +.. _outgoing_interfaces: + +.. raw:: html + + + ++---------------------+--------+---------+ +| name | type | default | ++=====================+========+=========+ +| outgoing_interfaces | string | "" | ++---------------------+--------+---------+ + +sets the network interface this session will use when it opens +outgoing connections. By default, it binds outgoing connections to +INADDR_ANY and port 0 (i.e. let the OS decide). Ths parameter must +be a string containing one or more, comma separated, adapter names. +Adapter names on unix systems are of the form "eth0", "eth1", "tun0", +etc. When specifying multiple +interfaces, they will be assigned in round-robin order. This may be +useful for clients that are multi-homed. Binding an outgoing +connection to a local IP does not necessarily make the connection +via the associated NIC/Adapter. Setting this to an empty string +will disable binding of outgoing connections. + +.. _listen_interfaces: + +.. raw:: html + + + ++-------------------+--------+----------------+ +| name | type | default | ++===================+========+================+ +| listen_interfaces | string | "0.0.0.0:6881" | ++-------------------+--------+----------------+ + +a comma-separated list of (IP or device name, port) pairs. These +are the listen ports that will be opened for accepting incoming uTP +and TCP connections. It is possible to listen on multiple +interfaces and multiple ports. Binding to port 0 will make the +operating system pick the port. The default is "0.0.0.0:0", which +binds to all interfaces on a port the OS picks. +if binding fails, the listen_failed_alert is posted, otherwise the +listen_succeeded_alert. +If the DHT is running, it will also have its socket rebound to the +same port as the main listen port. + +The reason why it's a good idea to run the DHT and the bittorrent +socket on the same port is because that is an assumption that may +be used to increase performance. One way to accelerate the +connecting of peers on windows may be to first ping all peers with +a DHT ping packet, and connect to those that responds first. On +windows one can only connect to a few peers at a time because of a +built in limitation (in XP Service pack 2). + +.. _proxy_hostname: + +.. raw:: html + + + ++----------------+--------+---------+ +| name | type | default | ++================+========+=========+ +| proxy_hostname | string | "" | ++----------------+--------+---------+ + +when using a poxy, this is the hostname where the proxy is running +see proxy_type. + +.. _proxy_username: + +.. _proxy_password: + +.. raw:: html + + + + ++----------------+--------+---------+ +| name | type | default | ++================+========+=========+ +| proxy_username | string | "" | ++----------------+--------+---------+ +| proxy_password | string | "" | ++----------------+--------+---------+ + +when using a proxy, these are the credentials (if any) to use +whne connecting to it. see proxy_type + +.. _i2p_hostname: + +.. raw:: html + + + ++--------------+--------+---------+ +| name | type | default | ++==============+========+=========+ +| i2p_hostname | string | "" | ++--------------+--------+---------+ + +sets the i2p_ SAM bridge to connect to. set the port with the +``i2p_port`` setting. + +.. _i2p: http://www.i2p2.de + +.. _allow_multiple_connections_per_ip: + +.. raw:: html + + + ++-----------------------------------+------+---------+ +| name | type | default | ++===================================+======+=========+ +| allow_multiple_connections_per_ip | bool | false | ++-----------------------------------+------+---------+ + +determines if connections from the same IP address as +existing connections should be rejected or not. Multiple +connections from the same IP address is not allowed by +default, to prevent abusive behavior by peers. It may +be useful to allow such connections in cases where +simulations are run on the same machie, and all peers +in a swarm has the same IP address. + +.. _send_redundant_have: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| send_redundant_have | bool | true | ++---------------------+------+---------+ + +if set to true, upload, download and unchoke limits +are ignored for peers on the local network. +This option is *DEPRECATED*, please use set_peer_class_filter() instead. +``send_redundant_have`` controls if have messages will be sent +to peers that already have the piece. This is typically not necessary, +but it might be necessary for collecting statistics in some cases. +Default is false. + +.. _lazy_bitfields: + +.. raw:: html + + + ++----------------+------+---------+ +| name | type | default | ++================+======+=========+ +| lazy_bitfields | bool | true | ++----------------+------+---------+ + +if this is true, outgoing bitfields will never be fuil. If the +client is seed, a few bits will be set to 0, and later filled +in with have messages. This is to prevent certain ISPs +from stopping people from seeding. + +.. _use_dht_as_fallback: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| use_dht_as_fallback | bool | false | ++---------------------+------+---------+ + +``use_dht_as_fallback`` determines how the DHT is used. If this is true, +the DHT will only be used for torrents where all trackers in its tracker +list has failed. Either by an explicit error message or a time out. This +is false by default, which means the DHT is used by default regardless of + +.. _upnp_ignore_nonrouters: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| upnp_ignore_nonrouters | bool | false | ++------------------------+------+---------+ + +``upnp_ignore_nonrouters`` indicates whether or not the UPnP implementation +should ignore any broadcast response from a device whose address is not the +configured router for this machine. i.e. it's a way to not talk to other +people's routers by mistake. + +.. _use_parole_mode: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| use_parole_mode | bool | true | ++-----------------+------+---------+ + +``use_parole_mode`` specifies if parole mode should be used. Parole mode means +that peers that participate in pieces that fail the hash check are put in a mode +where they are only allowed to download whole pieces. If the whole piece a peer +in parole mode fails the hash check, it is banned. If a peer participates in a +piece that passes the hash check, it is taken out of parole mode. + +.. _use_read_cache: + +.. _use_write_cache: + +.. raw:: html + + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| use_read_cache | bool | true | ++-----------------+------+---------+ +| use_write_cache | bool | true | ++-----------------+------+---------+ + +enable and disable caching of read blocks and +blocks to be written to disk respsectively. +the purpose of the read cache is partly read-ahead of requests +but also to avoid reading blocks back from the disk multiple +times for popular pieces. +the write cache purpose is to hold off writing blocks to disk until +they have been hashed, to avoid having to read them back in again. + +.. _dont_flush_write_cache: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| dont_flush_write_cache | bool | false | ++------------------------+------+---------+ + +this will make the disk cache never flush a write +piece if it would cause is to have to re-read it +once we want to calculate the piece hash + +.. _explicit_read_cache: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| explicit_read_cache | bool | false | ++---------------------+------+---------+ + +``explicit_read_cache`` defaults to 0. If set to something greater than 0, the +disk read cache will not be evicted by cache misses and will explicitly be +controlled based on the rarity of pieces. Rare pieces are more likely to be +cached. This would typically be used together with ``suggest_mode`` set to +``suggest_read_cache``. The value is the number of pieces to keep in the read +cache. If the actual read cache can't fit as many, it will essentially be clamped. + +.. _coalesce_reads: + +.. _coalesce_writes: + +.. raw:: html + + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| coalesce_reads | bool | false | ++-----------------+------+---------+ +| coalesce_writes | bool | false | ++-----------------+------+---------+ + +allocate separate, contiguous, buffers for read and +write calls. Only used where writev/readv cannot be used +will use more RAM but may improve performance + +.. _auto_manage_prefer_seeds: + +.. raw:: html + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| auto_manage_prefer_seeds | bool | false | ++--------------------------+------+---------+ + +prefer seeding torrents when determining which torrents to give +active slots to, the default is false which gives preference to +downloading torrents + +.. _dont_count_slow_torrents: + +.. raw:: html + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| dont_count_slow_torrents | bool | true | ++--------------------------+------+---------+ + +if ``dont_count_slow_torrents`` is true, torrents without any payload transfers are +not subject to the ``active_seeds`` and ``active_downloads`` limits. This is intended +to make it more likely to utilize all available bandwidth, and avoid having torrents +that don't transfer anything block the active slots. + +.. _close_redundant_connections: + +.. raw:: html + + + ++-----------------------------+------+---------+ +| name | type | default | ++=============================+======+=========+ +| close_redundant_connections | bool | true | ++-----------------------------+------+---------+ + +``close_redundant_connections`` specifies whether libtorrent should close +connections where both ends have no utility in keeping the connection open. +For instance if both ends have completed their downloads, there's no point +in keeping it open. + +.. _prioritize_partial_pieces: + +.. raw:: html + + + ++---------------------------+------+---------+ +| name | type | default | ++===========================+======+=========+ +| prioritize_partial_pieces | bool | false | ++---------------------------+------+---------+ + +If ``prioritize_partial_pieces`` is true, partial pieces are picked +before pieces that are more rare. If false, rare pieces are always +prioritized, unless the number of partial pieces is growing out of +proportion. + +.. _rate_limit_ip_overhead: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| rate_limit_ip_overhead | bool | true | ++------------------------+------+---------+ + +if set to true, the estimated TCP/IP overhead is +drained from the rate limiters, to avoid exceeding +the limits with the total traffic + +.. _announce_to_all_tiers: + +.. _announce_to_all_trackers: + +.. raw:: html + + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| announce_to_all_tiers | bool | false | ++--------------------------+------+---------+ +| announce_to_all_trackers | bool | false | ++--------------------------+------+---------+ + +``announce_to_all_trackers`` controls how multi tracker torrents are +treated. If this is set to true, all trackers in the same tier are +announced to in parallel. If all trackers in tier 0 fails, all trackers +in tier 1 are announced as well. If it's set to false, the behavior is as +defined by the multi tracker specification. It defaults to false, which +is the same behavior previous versions of libtorrent has had as well. + +``announce_to_all_tiers`` also controls how multi tracker torrents are +treated. When this is set to true, one tracker from each tier is announced +to. This is the uTorrent behavior. This is false by default in order +to comply with the multi-tracker specification. + +.. _prefer_udp_trackers: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| prefer_udp_trackers | bool | true | ++---------------------+------+---------+ + +``prefer_udp_trackers`` is true by default. It means that trackers may +be rearranged in a way that udp trackers are always tried before http +trackers for the same hostname. Setting this to false means that the +trackers' tier is respected and there's no preference of one protocol +over another. + +.. _strict_super_seeding: + +.. raw:: html + + + ++----------------------+------+---------+ +| name | type | default | ++======================+======+=========+ +| strict_super_seeding | bool | false | ++----------------------+------+---------+ + +``strict_super_seeding`` when this is set to true, a piece has to +have been forwarded to a third peer before another one is handed out. +This is the traditional definition of super seeding. + +.. _lock_disk_cache: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| lock_disk_cache | bool | false | ++-----------------+------+---------+ + +if this is set to true, the memory allocated for the +disk cache will be locked in physical RAM, never to +be swapped out. Every time a disk buffer is allocated +and freed, there will be the extra overhead of a system call. + +.. _disable_hash_checks: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| disable_hash_checks | bool | false | ++---------------------+------+---------+ + +when set to true, all data downloaded from +peers will be assumed to be correct, and not +tested to match the hashes in the torrent +this is only useful for simulation and +testing purposes (typically combined with +disabled_storage) + +.. _allow_i2p_mixed: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| allow_i2p_mixed | bool | false | ++-----------------+------+---------+ + +if this is true, i2p torrents are allowed +to also get peers from other sources than +the tracker, and connect to regular IPs, +not providing any anonymization. This may +be useful if the user is not interested in +the anonymization of i2p, but still wants to +be able to connect to i2p peers. + +.. _low_prio_disk: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| low_prio_disk | bool | true | ++---------------+------+---------+ + +``low_prio_disk`` determines if the disk I/O should use a normal +or low priority policy. This defaults to true, which means that +it's low priority by default. Other processes doing disk I/O will +normally take priority in this mode. This is meant to improve the +overall responsiveness of the system while downloading in the +background. For high-performance server setups, this might not +be desirable. + +.. _volatile_read_cache: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| volatile_read_cache | bool | false | ++---------------------+------+---------+ + +``volatile_read_cache``, if this is set to true, read cache blocks +that are hit by peer read requests are removed from the disk cache +to free up more space. This is useful if you don't expect the disk +cache to create any cache hits from other peers than the one who +triggered the cache line to be read into the cache in the first place. + +.. _guided_read_cache: + +.. raw:: html + + + ++-------------------+------+---------+ +| name | type | default | ++===================+======+=========+ +| guided_read_cache | bool | false | ++-------------------+------+---------+ + +``guided_read_cache`` enables the disk cache to adjust the size +of a cache line generated by peers to depend on the upload rate +you are sending to that peer. The intention is to optimize the RAM +usage of the cache, to read ahead further for peers that you're +sending faster to. + +.. _no_atime_storage: + +.. raw:: html + + + ++------------------+------+---------+ +| name | type | default | ++==================+======+=========+ +| no_atime_storage | bool | true | ++------------------+------+---------+ + +``no_atime_storage`` this is a linux-only option and passes in the +``O_NOATIME`` to ``open()`` when opening files. This may lead to +some disk performance improvements. + +.. _incoming_starts_queued_torrents: + +.. raw:: html + + + ++---------------------------------+------+---------+ +| name | type | default | ++=================================+======+=========+ +| incoming_starts_queued_torrents | bool | false | ++---------------------------------+------+---------+ + +``incoming_starts_queued_torrents`` defaults to false. If a torrent +has been paused by the auto managed feature in libtorrent, i.e. +the torrent is paused and auto managed, this feature affects whether +or not it is automatically started on an incoming connection. The +main reason to queue torrents, is not to make them unavailable, but +to save on the overhead of announcing to the trackers, the DHT and to +avoid spreading one's unchoke slots too thin. If a peer managed to +find us, even though we're no in the torrent anymore, this setting +can make us start the torrent and serve it. + +.. _report_true_downloaded: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| report_true_downloaded | bool | false | ++------------------------+------+---------+ + +when set to true, the downloaded counter sent to trackers +will include the actual number of payload bytes donwnloaded +including redundant bytes. If set to false, it will not include +any redundany bytes + +.. _strict_end_game_mode: + +.. raw:: html + + + ++----------------------+------+---------+ +| name | type | default | ++======================+======+=========+ +| strict_end_game_mode | bool | true | ++----------------------+------+---------+ + +``strict_end_game_mode`` defaults to true, and controls when a block +may be requested twice. If this is ``true``, a block may only be requested +twice when there's ay least one request to every piece that's left to +download in the torrent. This may slow down progress on some pieces +sometimes, but it may also avoid downloading a lot of redundant bytes. +If this is ``false``, libtorrent attempts to use each peer connection +to its max, by always requesting something, even if it means requesting +something that has been requested from another peer already. + +.. _broadcast_lsd: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| broadcast_lsd | bool | true | ++---------------+------+---------+ + +if ``broadcast_lsd`` is set to true, the local peer discovery +(or Local Service Discovery) will not only use IP multicast, but also +broadcast its messages. This can be useful when running on networks +that don't support multicast. Since broadcast messages might be +expensive and disruptive on networks, only every 8th announce uses +broadcast. + +.. _enable_outgoing_utp: + +.. _enable_incoming_utp: + +.. _enable_outgoing_tcp: + +.. _enable_incoming_tcp: + +.. raw:: html + + + + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| enable_outgoing_utp | bool | true | ++---------------------+------+---------+ +| enable_incoming_utp | bool | true | ++---------------------+------+---------+ +| enable_outgoing_tcp | bool | true | ++---------------------+------+---------+ +| enable_incoming_tcp | bool | true | ++---------------------+------+---------+ + +when set to true, libtorrent will try to make outgoing utp connections +controls whether libtorrent will accept incoming connections or make +outgoing connections of specific type. + +.. _ignore_resume_timestamps: + +.. raw:: html + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| ignore_resume_timestamps | bool | false | ++--------------------------+------+---------+ + +``ignore_resume_timestamps`` determines if the storage, when loading +resume data files, should verify that the file modification time +with the timestamps in the resume data. This defaults to false, which +means timestamps are taken into account, and resume data is less likely +to accepted (torrents are more likely to be fully checked when loaded). +It might be useful to set this to true if your network is faster than your +disk, and it would be faster to redownload potentially missed pieces than +to go through the whole storage to look for them. + +.. _no_recheck_incomplete_resume: + +.. raw:: html + + + ++------------------------------+------+---------+ +| name | type | default | ++==============================+======+=========+ +| no_recheck_incomplete_resume | bool | false | ++------------------------------+------+---------+ + +``no_recheck_incomplete_resume`` determines if the storage should check +the whole files when resume data is incomplete or missing or whether +it should simply assume we don't have any of the data. By default, this +is determined by the existance of any of the files. By setting this setting +to true, the files won't be checked, but will go straight to download +mode. + +.. _anonymous_mode: + +.. raw:: html + + + ++----------------+------+---------+ +| name | type | default | ++================+======+=========+ +| anonymous_mode | bool | true | ++----------------+------+---------+ + +``anonymous_mode`` defaults to false. When set to true, the client tries +to hide its identity to a certain degree. The peer-ID will no longer +include the client's fingerprint. The user-agent will be reset to an +empty string. Trackers will only be used if they are using a proxy +server. The listen sockets are closed, and incoming connections will +only be accepted through a SOCKS5 or I2P proxy (if a peer proxy is set up and +is run on the same machine as the tracker proxy). Since no incoming connections +are accepted, NAT-PMP, UPnP, DHT and local peer discovery are all turned off +when this setting is enabled. + +If you're using I2P, it might make sense to enable anonymous mode as well. + +.. _report_web_seed_downloads: + +.. raw:: html + + + ++---------------------------+------+---------+ +| name | type | default | ++===========================+======+=========+ +| report_web_seed_downloads | bool | true | ++---------------------------+------+---------+ + +specifies whether downloads from web seeds is reported to the +tracker or not. Defaults to on + +.. _utp_dynamic_sock_buf: + +.. raw:: html + + + ++----------------------+------+---------+ +| name | type | default | ++======================+======+=========+ +| utp_dynamic_sock_buf | bool | true | ++----------------------+------+---------+ + +controls if the uTP socket manager is allowed to increase +the socket buffer if a network interface with a large MTU is used (such as loopback +or ethernet jumbo frames). This defaults to true and might improve uTP throughput. +For RAM constrained systems, disabling this typically saves around 30kB in user space +and probably around 400kB in kernel socket buffers (it adjusts the send and receive +buffer size on the kernel socket, both for IPv4 and IPv6). + +.. _announce_double_nat: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| announce_double_nat | bool | false | ++---------------------+------+---------+ + +set to true if uTP connections should be rate limited +This option is *DEPRECATED*, please use set_peer_class_filter() instead. +if this is true, the ``&ip=`` argument in tracker requests +(unless otherwise specified) will be set to the intermediate +IP address if the user is double NATed. If ther user is not +double NATed, this option does not have an affect + +.. _seeding_outgoing_connections: + +.. raw:: html + + + ++------------------------------+------+---------+ +| name | type | default | ++==============================+======+=========+ +| seeding_outgoing_connections | bool | true | ++------------------------------+------+---------+ + +``seeding_outgoing_connections`` determines if seeding (and finished) torrents +should attempt to make outgoing connections or not. By default this is true. It +may be set to false in very specific applications where the cost of making +outgoing connections is high, and there are no or small benefits of doing so. +For instance, if no nodes are behind a firewall or a NAT, seeds don't need to +make outgoing connections. + +.. _no_connect_privileged_ports: + +.. raw:: html + + + ++-----------------------------+------+---------+ +| name | type | default | ++=============================+======+=========+ +| no_connect_privileged_ports | bool | false | ++-----------------------------+------+---------+ + +when this is true, libtorrent will not attempt to make outgoing +connections to peers whose port is < 1024. This is a safety +precaution to avoid being part of a DDoS attack + +.. _smooth_connects: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| smooth_connects | bool | true | ++-----------------+------+---------+ + +``smooth_connects`` is true by default, which means the number of connection +attempts per second may be limited to below the ``connection_speed``, in case +we're close to bump up against the limit of number of connections. The intention +of this setting is to more evenly distribute our connection attempts over time, +instead of attempting to connectin in batches, and timing them out in batches. + +.. _always_send_user_agent: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| always_send_user_agent | bool | false | ++------------------------+------+---------+ + +always send user-agent in every web seed request. If false, only +the first request per http connection will include the user agent + +.. _apply_ip_filter_to_trackers: + +.. raw:: html + + + ++-----------------------------+------+---------+ +| name | type | default | ++=============================+======+=========+ +| apply_ip_filter_to_trackers | bool | true | ++-----------------------------+------+---------+ + +``apply_ip_filter_to_trackers`` defaults to true. It determines whether the +IP filter applies to trackers as well as peers. If this is set to false, +trackers are exempt from the IP filter (if there is one). If no IP filter +is set, this setting is irrelevant. + +.. _use_disk_read_ahead: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| use_disk_read_ahead | bool | true | ++---------------------+------+---------+ + +``use_disk_read_ahead`` defaults to true and will attempt to optimize disk reads +by giving the operating system heads up of disk read requests as they are queued +in the disk job queue. + +.. _lock_files: + +.. raw:: html + + + ++------------+------+---------+ +| name | type | default | ++============+======+=========+ +| lock_files | bool | false | ++------------+------+---------+ + +``lock_files`` determines whether or not to lock files which libtorrent is downloading +to or seeding from. This is implemented using ``fcntl(F_SETLK)`` on unix systems and +by not passing in ``SHARE_READ`` and ``SHARE_WRITE`` on windows. This might prevent +3rd party processes from corrupting the files under libtorrent's feet. + +.. _contiguous_recv_buffer: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| contiguous_recv_buffer | bool | true | ++------------------------+------+---------+ + +``contiguous_recv_buffer`` determines whether or not libtorrent should receive +data from peers into a contiguous intermediate buffer, to then copy blocks into +disk buffers from, or to make many smaller calls to ``read()``, each time passing +in the specific buffer the data belongs in. When downloading at high rates, the latter +may save some time copying data. When seeding at high rates, all incoming traffic +consists of a very large number of tiny packets, and enabling ``contiguous_recv_buffer`` +will provide higher performance. When this is enabled, it will only be used when +seeding to peers, since that's when it provides performance improvements. + +.. _ban_web_seeds: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| ban_web_seeds | bool | true | ++---------------+------+---------+ + +when true, web seeds sending bad data will be banned + +.. _allow_partial_disk_writes: + +.. raw:: html + + + ++---------------------------+------+---------+ +| name | type | default | ++===========================+======+=========+ +| allow_partial_disk_writes | bool | true | ++---------------------------+------+---------+ + +when set to false, the ``write_cache_line_size`` will apply across piece boundaries. +this is a bad idea unless the piece picker also is configured to have an affinity +to pick pieces belonging to the same write cache line as is configured in the +disk cache. + +.. _force_proxy: + +.. raw:: html + + + ++-------------+------+---------+ +| name | type | default | ++=============+======+=========+ +| force_proxy | bool | false | ++-------------+------+---------+ + +If true, disables any communication that's not going over a proxy. +Enabling this requires a proxy to be configured as well, see ``set_proxy_settings``. +The listen sockets are closed, and incoming connections will +only be accepted through a SOCKS5 or I2P proxy (if a peer proxy is set up and +is run on the same machine as the tracker proxy). This setting also +disabled peer country lookups, since those are done via DNS lookups that +aren't supported by proxies. + +.. _support_share_mode: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| support_share_mode | bool | true | ++--------------------+------+---------+ + +if false, prevents libtorrent to advertise share-mode support + +.. _support_merkle_torrents: + +.. raw:: html + + + ++-------------------------+------+---------+ +| name | type | default | ++=========================+======+=========+ +| support_merkle_torrents | bool | true | ++-------------------------+------+---------+ + +if this is false, don't advertise support for +the Tribler merkle tree piece message + +.. _report_redundant_bytes: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| report_redundant_bytes | bool | true | ++------------------------+------+---------+ + +if this is true, the number of redundant bytes +is sent to the tracker + +.. _listen_system_port_fallback: + +.. raw:: html + + + ++-----------------------------+------+---------+ +| name | type | default | ++=============================+======+=========+ +| listen_system_port_fallback | bool | true | ++-----------------------------+------+---------+ + +if this is true, libtorrent will fall back to listening on a port chosen +by the operating system (i.e. binding to port 0). If a failure is preferred, +set this to false. + +.. _use_disk_cache_pool: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| use_disk_cache_pool | bool | false | ++---------------------+------+---------+ + +``use_disk_cache_pool`` enables using a pool allocator for disk cache blocks. +Enabling it makes the cache perform better at high throughput. +It also makes the cache less likely and slower at returning memory back to the system, +once allocated. + +.. _announce_crypto_support: + +.. raw:: html + + + ++-------------------------+------+---------+ +| name | type | default | ++=========================+======+=========+ +| announce_crypto_support | bool | true | ++-------------------------+------+---------+ + +when this is true, and incoming encrypted connections are enabled, &supportcrypt=1 +is included in http tracker announces + +.. _enable_upnp: + +.. raw:: html + + + ++-------------+------+---------+ +| name | type | default | ++=============+======+=========+ +| enable_upnp | bool | true | ++-------------+------+---------+ + +Starts and stops the UPnP service. When started, the listen port and the DHT +port are attempted to be forwarded on local UPnP router devices. + +The upnp object returned by ``start_upnp()`` can be used to add and remove +arbitrary port mappings. Mapping status is returned through the +portmap_alert and the portmap_error_alert. The object will be valid until +``stop_upnp()`` is called. See upnp-and-nat-pmp_. + +.. _enable_natpmp: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| enable_natpmp | bool | true | ++---------------+------+---------+ + +Starts and stops the NAT-PMP service. When started, the listen port and the DHT +port are attempted to be forwarded on the router through NAT-PMP. + +The natpmp object returned by ``start_natpmp()`` can be used to add and remove +arbitrary port mappings. Mapping status is returned through the +portmap_alert and the portmap_error_alert. The object will be valid until +``stop_natpmp()`` is called. See upnp-and-nat-pmp_. + +.. _enable_lsd: + +.. raw:: html + + + ++------------+------+---------+ +| name | type | default | ++============+======+=========+ +| enable_lsd | bool | true | ++------------+------+---------+ + +Starts and stops Local Service Discovery. This service will broadcast +the infohashes of all the non-private torrents on the local network to +look for peers on the same swarm within multicast reach. + +.. _enable_dht: + +.. raw:: html + + + ++------------+------+---------+ +| name | type | default | ++============+======+=========+ +| enable_dht | bool | true | ++------------+------+---------+ + +starts the dht node and makes the trackerless service +available to torrents. + +.. _prefer_rc4: + +.. raw:: html + + + ++------------+------+---------+ +| name | type | default | ++============+======+=========+ +| prefer_rc4 | bool | false | ++------------+------+---------+ + +if the allowed encryption level is both, setting this to +true will prefer rc4 if both methods are offered, plaintext +otherwise + +.. _proxy_hostnames: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| proxy_hostnames | bool | true | ++-----------------+------+---------+ + +if true, hostname lookups are done via the configured proxy (if +any). This is only supported by SOCKS5 and HTTP. + +.. _proxy_peer_connections: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| proxy_peer_connections | bool | true | ++------------------------+------+---------+ + +if true, peer connections are made (and accepted) over the +configured proxy, if any. + +.. _tracker_completion_timeout: + +.. raw:: html + + + ++----------------------------+------+---------+ +| name | type | default | ++============================+======+=========+ +| tracker_completion_timeout | int | 60 | ++----------------------------+------+---------+ + +``tracker_completion_timeout`` is the number of seconds the tracker +connection will wait from when it sent the request until it considers the +tracker to have timed-out. Default value is 60 seconds. + +.. _tracker_receive_timeout: + +.. raw:: html + + + ++-------------------------+------+---------+ +| name | type | default | ++=========================+======+=========+ +| tracker_receive_timeout | int | 40 | ++-------------------------+------+---------+ + +``tracker_receive_timeout`` is the number of seconds to wait to receive +any data from the tracker. If no data is received for this number of +seconds, the tracker will be considered as having timed out. If a tracker +is down, this is the kind of timeout that will occur. + +.. _stop_tracker_timeout: + +.. raw:: html + + + ++----------------------+------+---------+ +| name | type | default | ++======================+======+=========+ +| stop_tracker_timeout | int | 5 | ++----------------------+------+---------+ + +the time to wait when sending a stopped message +before considering a tracker to have timed out. +this is usually shorter, to make the client quit +faster + +.. _tracker_maximum_response_length: + +.. raw:: html + + + ++---------------------------------+------+-----------+ +| name | type | default | ++=================================+======+===========+ +| tracker_maximum_response_length | int | 1024*1024 | ++---------------------------------+------+-----------+ + +this is the maximum number of bytes in a tracker +response. If a response size passes this number +of bytes it will be rejected and the connection +will be closed. On gzipped responses this size is +measured on the uncompressed data. So, if you get +20 bytes of gzip response that'll expand to 2 megabytes, +it will be interrupted before the entire response +has been uncompressed (assuming the limit is lower +than 2 megs). + +.. _piece_timeout: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| piece_timeout | int | 20 | ++---------------+------+---------+ + +the number of seconds from a request is sent until +it times out if no piece response is returned. + +.. _request_timeout: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| request_timeout | int | 50 | ++-----------------+------+---------+ + +the number of seconds one block (16kB) is expected +to be received within. If it's not, the block is +requested from a different peer + +.. _request_queue_time: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| request_queue_time | int | 3 | ++--------------------+------+---------+ + +the length of the request queue given in the number +of seconds it should take for the other end to send +all the pieces. i.e. the actual number of requests +depends on the download rate and this number. + +.. _max_allowed_in_request_queue: + +.. raw:: html + + + ++------------------------------+------+---------+ +| name | type | default | ++==============================+======+=========+ +| max_allowed_in_request_queue | int | 500 | ++------------------------------+------+---------+ + +the number of outstanding block requests a peer is +allowed to queue up in the client. If a peer sends +more requests than this (before the first one has +been sent) the last request will be dropped. +the higher this is, the faster upload speeds the +client can get to a single peer. + +.. _max_out_request_queue: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| max_out_request_queue | int | 500 | ++-----------------------+------+---------+ + +``max_out_request_queue`` is the maximum number of outstanding requests to +send to a peer. This limit takes precedence over ``request_queue_time``. i.e. +no matter the download speed, the number of outstanding requests will never +exceed this limit. + +.. _whole_pieces_threshold: + +.. raw:: html + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| whole_pieces_threshold | int | 20 | ++------------------------+------+---------+ + +if a whole piece can be downloaded in this number +of seconds, or less, the peer_connection will prefer +to request whole pieces at a time from this peer. +The benefit of this is to better utilize disk caches by +doing localized accesses and also to make it easier +to identify bad peers if a piece fails the hash check. + +.. _peer_timeout: + +.. raw:: html + + + ++--------------+------+---------+ +| name | type | default | ++==============+======+=========+ +| peer_timeout | int | 120 | ++--------------+------+---------+ + +``peer_timeout`` is the number of seconds the peer connection should +wait (for any activity on the peer connection) before closing it due +to time out. This defaults to 120 seconds, since that's what's specified +in the protocol specification. After half the time out, a keep alive message +is sent. + +.. _urlseed_timeout: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| urlseed_timeout | int | 20 | ++-----------------+------+---------+ + +same as peer_timeout, but only applies to url-seeds. +this is usually set lower, because web servers are +expected to be more reliable. + +.. _urlseed_pipeline_size: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| urlseed_pipeline_size | int | 5 | ++-----------------------+------+---------+ + +controls the pipelining size of url-seeds. i.e. the number +of HTTP request to keep outstanding before waiting for +the first one to complete. It's common for web servers +to limit this to a relatively low number, like 5 + +.. _urlseed_wait_retry: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| urlseed_wait_retry | int | 30 | ++--------------------+------+---------+ + +time to wait until a new retry of a web seed takes place + +.. _file_pool_size: + +.. raw:: html + + + ++----------------+------+---------+ +| name | type | default | ++================+======+=========+ +| file_pool_size | int | 40 | ++----------------+------+---------+ + +sets the upper limit on the total number of files this +session will keep open. The reason why files are +left open at all is that some anti virus software +hooks on every file close, and scans the file for +viruses. deferring the closing of the files will +be the difference between a usable system and +a completely hogged down system. Most operating +systems also has a limit on the total number of +file descriptors a process may have open. It is +usually a good idea to find this limit and set the +number of connections and the number of files +limits so their sum is slightly below it. + +.. _max_failcount: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| max_failcount | int | 3 | ++---------------+------+---------+ + +``max_failcount`` is the maximum times we try to connect to a peer before +stop connecting again. If a peer succeeds, the failcounter is reset. If +a peer is retrieved from a peer source (other than DHT) the failcount is +decremented by one, allowing another try. + +.. _min_reconnect_time: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| min_reconnect_time | int | 60 | ++--------------------+------+---------+ + +the number of seconds to wait to reconnect to a peer. +this time is multiplied with the failcount. + +.. _peer_connect_timeout: + +.. raw:: html + + + ++----------------------+------+---------+ +| name | type | default | ++======================+======+=========+ +| peer_connect_timeout | int | 15 | ++----------------------+------+---------+ + +``peer_connect_timeout`` the number of seconds to wait after a connection +attempt is initiated to a peer until it is considered as having timed out. +This setting is especially important in case the number of half-open +connections are limited, since stale half-open +connection may delay the connection of other peers considerably. + +.. _connection_speed: + +.. raw:: html + + + ++------------------+------+---------+ +| name | type | default | ++==================+======+=========+ +| connection_speed | int | 6 | ++------------------+------+---------+ + +``connection_speed`` is the number of connection attempts that +are made per second. If a number < 0 is specified, it will default to +200 connections per second. If 0 is specified, it means don't make +outgoing connections at all. + +.. _inactivity_timeout: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| inactivity_timeout | int | 600 | ++--------------------+------+---------+ + +if a peer is uninteresting and uninterested for longer +than this number of seconds, it will be disconnected. +default is 10 minutes + +.. _unchoke_interval: + +.. raw:: html + + + ++------------------+------+---------+ +| name | type | default | ++==================+======+=========+ +| unchoke_interval | int | 15 | ++------------------+------+---------+ + +``unchoke_interval`` is the number of seconds between chokes/unchokes. +On this interval, peers are re-evaluated for being choked/unchoked. This +is defined as 30 seconds in the protocol, and it should be significantly +longer than what it takes for TCP to ramp up to it's max rate. + +.. _optimistic_unchoke_interval: + +.. raw:: html + + + ++-----------------------------+------+---------+ +| name | type | default | ++=============================+======+=========+ +| optimistic_unchoke_interval | int | 30 | ++-----------------------------+------+---------+ + +``optimistic_unchoke_interval`` is the number of seconds between +each *optimistic* unchoke. On this timer, the currently optimistically +unchoked peer will change. + +.. _num_want: + +.. raw:: html + + + ++----------+------+---------+ +| name | type | default | ++==========+======+=========+ +| num_want | int | 200 | ++----------+------+---------+ + +``num_want`` is the number of peers we want from each tracker request. It defines +what is sent as the ``&num_want=`` parameter to the tracker. + +.. _initial_picker_threshold: + +.. raw:: html + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| initial_picker_threshold | int | 4 | ++--------------------------+------+---------+ + +``initial_picker_threshold`` specifies the number of pieces we need before we +switch to rarest first picking. This defaults to 4, which means the 4 first +pieces in any torrent are picked at random, the following pieces are picked +in rarest first order. + +.. _allowed_fast_set_size: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| allowed_fast_set_size | int | 10 | ++-----------------------+------+---------+ + +the number of allowed pieces to send to peers +that supports the fast extensions + +.. _suggest_mode: + +.. raw:: html + + + ++--------------+------+-------------------------------------+ +| name | type | default | ++==============+======+=====================================+ +| suggest_mode | int | settings_pack::no_piece_suggestions | ++--------------+------+-------------------------------------+ + +``suggest_mode`` controls whether or not libtorrent will send out suggest +messages to create a bias of its peers to request certain pieces. The modes +are: + +* ``no_piece_suggestsions`` which is the default and will not send out suggest + messages. +* ``suggest_read_cache`` which will send out suggest messages for the most + recent pieces that are in the read cache. + +.. _max_queued_disk_bytes: + +.. raw:: html + + + ++-----------------------+------+-------------+ +| name | type | default | ++=======================+======+=============+ +| max_queued_disk_bytes | int | 1024 * 1024 | ++-----------------------+------+-------------+ + +``max_queued_disk_bytes`` is the number maximum number of bytes, to be +written to disk, that can wait in the disk I/O thread queue. This queue +is only for waiting for the disk I/O thread to receive the job and either +write it to disk or insert it in the write cache. When this limit is reached, +the peer connections will stop reading data from their sockets, until the disk +thread catches up. Setting this too low will severly limit your download rate. + +.. _handshake_timeout: + +.. raw:: html + + + ++-------------------+------+---------+ +| name | type | default | ++===================+======+=========+ +| handshake_timeout | int | 10 | ++-------------------+------+---------+ + +the number of seconds to wait for a handshake +response from a peer. If no response is received +within this time, the peer is disconnected. + +.. _send_buffer_low_watermark: + +.. _send_buffer_watermark: + +.. _send_buffer_watermark_factor: + +.. raw:: html + + + + + ++------------------------------+------+------------+ +| name | type | default | ++==============================+======+============+ +| send_buffer_low_watermark | int | 512 | ++------------------------------+------+------------+ +| send_buffer_watermark | int | 500 * 1024 | ++------------------------------+------+------------+ +| send_buffer_watermark_factor | int | 50 | ++------------------------------+------+------------+ + +``send_buffer_low_watermark`` the minimum send buffer target +size (send buffer includes bytes pending being read from disk). +For good and snappy seeding performance, set this fairly high, to +at least fit a few blocks. This is essentially the initial +window size which will determine how fast we can ramp up +the send rate + +if the send buffer has fewer bytes than ``send_buffer_watermark``, +we'll read another 16kB block onto it. If set too small, +upload rate capacity will suffer. If set too high, +memory will be wasted. +The actual watermark may be lower than this in case +the upload rate is low, this is the upper limit. + +the current upload rate to a peer is multiplied by +this factor to get the send buffer watermark. The +factor is specified as a percentage. i.e. 50 -> 0.5 +This product is clamped to the ``send_buffer_watermark`` +setting to not exceed the max. For high speed +upload, this should be set to a greater value than +100. For high capacity connections, setting this +higher can improve upload performance and disk throughput. Setting it too +high may waste RAM and create a bias towards read jobs over write jobs. + +.. _choking_algorithm: + +.. _seed_choking_algorithm: + +.. raw:: html + + + + ++------------------------+------+-----------------------------------+ +| name | type | default | ++========================+======+===================================+ +| choking_algorithm | int | settings_pack::fixed_slots_choker | ++------------------------+------+-----------------------------------+ +| seed_choking_algorithm | int | settings_pack::round_robin | ++------------------------+------+-----------------------------------+ + +``choking_algorithm`` specifies which algorithm to use to determine which peers +to unchoke. + +The options for choking algorithms are: + +* ``fixed_slots_choker`` is the traditional choker with a fixed number of unchoke + slots (as specified by ``session::set_max_uploads()``). + +* ``auto_expand_choker`` opens at least the number of slots as specified by + ``session::set_max_uploads()`` but opens up more slots if the upload capacity + is not saturated. This unchoker will work just like the ``fixed_slots_choker`` + if there's no global upload rate limit set. + +* ``rate_based_choker`` opens up unchoke slots based on the upload rate + achieved to peers. The more slots that are opened, the marginal upload + rate required to open up another slot increases. + +* ``bittyrant_choker`` attempts to optimize download rate by finding the + reciprocation rate of each peer individually and prefers peers that gives + the highest *return on investment*. It still allocates all upload capacity, + but shuffles it around to the best peers first. For this choker to be + efficient, you need to set a global upload rate limit + (``session::set_upload_rate_limit()``). For more information about this + choker, see the paper_. This choker is not fully implemented nor tested. + +.. _paper: http://bittyrant.cs.washington.edu/#papers + +``seed_choking_algorithm`` controls the seeding unchoke behavior. The available +options are: + +* ``round_robin`` which round-robins the peers that are unchoked when seeding. This + distributes the upload bandwidht uniformly and fairly. It minimizes the ability + for a peer to download everything without redistributing it. + +* ``fastest_upload`` unchokes the peers we can send to the fastest. This might be + a bit more reliable in utilizing all available capacity. + +* ``anti_leech`` prioritizes peers who have just started or are just about to finish + the download. The intention is to force peers in the middle of the download to + trade with each other. + +.. _cache_size: + +.. _cache_buffer_chunk_size: + +.. _cache_expiry: + +.. raw:: html + + + + + ++-------------------------+------+---------+ +| name | type | default | ++=========================+======+=========+ +| cache_size | int | 1024 | ++-------------------------+------+---------+ +| cache_buffer_chunk_size | int | 0 | ++-------------------------+------+---------+ +| cache_expiry | int | 300 | ++-------------------------+------+---------+ + +``cache_size`` is the disk write and read cache. It is specified +in units of 16 KiB blocks. Buffers that are part of a peer's send +or receive buffer also count against this limit. Send and receive +buffers will never be denied to be allocated, but they will cause +the actual cached blocks to be flushed or evicted. If this is set +to -1, the cache size is automatically set to the amount of +physical RAM available in the machine divided by 8. If the amount +of physical RAM cannot be determined, it's set to 1024 (= 16 MiB). + +Disk buffers are allocated using a pool allocator, the number of +blocks that are allocated at a time when the pool needs to grow can +be specified in ``cache_buffer_chunk_size``. Lower numbers saves +memory at the expense of more heap allocations. If it is set to 0, +the effective chunk size is proportional to the total cache size, +attempting to strike a good balance between performance and memory +usage. It defaults to 0. ``cache_expiry`` is the number of seconds +from the last cached write to a piece in the write cache, to when +it's forcefully flushed to disk. Default is 60 second. + +.. _explicit_cache_interval: + +.. raw:: html + + + ++-------------------------+------+---------+ +| name | type | default | ++=========================+======+=========+ +| explicit_cache_interval | int | 30 | ++-------------------------+------+---------+ + +``explicit_cache_interval`` is the number of seconds in between +each refresh of a part of the explicit read cache. Torrents take +turns in refreshing and this is the time in between each torrent +refresh. Refreshing a torrent's explicit read cache means scanning +all pieces and picking a random set of the rarest ones. There is an +affinity to pick pieces that are already in the cache, so that +subsequent refreshes only swaps in pieces that are rarer than +whatever is in +the cache at the time. + +.. _disk_io_write_mode: + +.. _disk_io_read_mode: + +.. raw:: html + + + + ++--------------------+------+--------------------------------+ +| name | type | default | ++====================+======+================================+ +| disk_io_write_mode | int | settings_pack::enable_os_cache | ++--------------------+------+--------------------------------+ +| disk_io_read_mode | int | settings_pack::enable_os_cache | ++--------------------+------+--------------------------------+ + +determines how files are opened when they're in read only mode versus +read and write mode. The options are: + +* enable_os_cache + This is the default and files are opened normally, with the OS caching + reads and writes. +* disable_os_cache + This opens all files in no-cache mode. This corresponds to the + OS not letting blocks for the files linger in the cache. This + makes sense in order to avoid the bittorrent client to + potentially evict all other processes' cache by simply handling + high throughput and large files. If libtorrent's read cache is + disabled, enabling this may reduce performance. + +One reason to disable caching is that it may help the operating +system from growing its file cache indefinitely. Since some OSes +only allow aligned files to be opened in unbuffered mode, It is +recommended to make the largest file in a torrent the first file +(with offset 0) or use pad files to align all files to piece +boundries. + +.. _outgoing_port: + +.. _num_outgoing_ports: + +.. raw:: html + + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| outgoing_port | int | 0 | ++--------------------+------+---------+ +| num_outgoing_ports | int | 0 | ++--------------------+------+---------+ + +this is the first port to use for binding +outgoing connections to. This is useful +for users that have routers that +allow QoS settings based on local port. +when binding outgoing connections to specific +ports, ``num_outgoing_ports`` is the size of +the range. It should be more than a few + +.. warning:: setting outgoing ports will limit the ability to keep multiple + connections to the same client, even for different torrents. It is not + recommended to change this setting. Its main purpose is to use as an + escape hatch for cheap routers with QoS capability but can only classify + flows based on port numbers. + +It is a range instead of a single port because of the problems with +failing to reconnect to peers if a previous socket to that peer and +port is in ``TIME_WAIT`` state. + +.. _peer_tos: + +.. raw:: html + + + ++----------+------+---------+ +| name | type | default | ++==========+======+=========+ +| peer_tos | int | 0 | ++----------+------+---------+ + +``peer_tos`` determines the TOS byte set in the IP header of every packet +sent to peers (including web seeds). The default value for this is ``0x0`` +(no marking). One potentially useful TOS mark is ``0x20``, this represents +the *QBone scavenger service*. For more details, see QBSS_. + +.. _`QBSS`: http://qbone.internet2.edu/qbss/ + +.. _active_downloads: + +.. _active_seeds: + +.. _active_dht_limit: + +.. _active_tracker_limit: + +.. _active_lsd_limit: + +.. _active_limit: + +.. _active_loaded_limit: + +.. raw:: html + + + + + + + + + ++----------------------+------+---------+ +| name | type | default | ++======================+======+=========+ +| active_downloads | int | 3 | ++----------------------+------+---------+ +| active_seeds | int | 5 | ++----------------------+------+---------+ +| active_dht_limit | int | 88 | ++----------------------+------+---------+ +| active_tracker_limit | int | 1600 | ++----------------------+------+---------+ +| active_lsd_limit | int | 60 | ++----------------------+------+---------+ +| active_limit | int | 15 | ++----------------------+------+---------+ +| active_loaded_limit | int | 100 | ++----------------------+------+---------+ + +for auto managed torrents, these are the limits +they are subject to. If there are too many torrents +some of the auto managed ones will be paused until +some slots free up. +``active_downloads`` and ``active_seeds`` controls how many active seeding and +downloading torrents the queuing mechanism allows. The target number of active +torrents is ``min(active_downloads + active_seeds, active_limit)``. +``active_downloads`` and ``active_seeds`` are upper limits on the number of +downloading torrents and seeding torrents respectively. Setting the value to +-1 means unlimited. + +For example if there are 10 seeding torrents and 10 downloading torrents, and +``active_downloads`` is 4 and ``active_seeds`` is 4, there will be 4 seeds +active and 4 downloading torrents. If the settings are ``active_downloads`` = 2 +and ``active_seeds`` = 4, then there will be 2 downloading torrents and 4 seeding +torrents active. Torrents that are not auto managed are not counted against these +limits. + +``active_limit`` is a hard limit on the number of active torrents. This applies even to +slow torrents. + +``active_dht_limit`` is the max number of torrents to announce to the DHT. By default +this is set to 88, which is no more than one DHT announce every 10 seconds. + +``active_tracker_limit`` is the max number of torrents to announce to their trackers. +By default this is 360, which is no more than one announce every 5 seconds. + +``active_lsd_limit`` is the max number of torrents to announce to the local network +over the local service discovery protocol. By default this is 80, which is no more +than one announce every 5 seconds (assuming the default announce interval of 5 minutes). + +You can have more torrents *active*, even though they are not announced to the DHT, +lsd or their tracker. If some peer knows about you for any reason and tries to connect, +it will still be accepted, unless the torrent is paused, which means it won't accept +any connections. + +``active_loaded_limit`` is the number of torrents that are allowed to be *loaded* +at any given time. Note that a torrent can be active even though it's not loaded. +if an unloaded torrents finds a peer that wants to access it, the torrent will be +loaded on demand, using a user-supplied callback function. If the feature of unloading +torrents is not enabled, this setting have no effect. If this limit is set to 0, it +means unlimited. For more information, see dynamic-loading-of-torrent-files_. + +.. _auto_manage_interval: + +.. raw:: html + + + ++----------------------+------+---------+ +| name | type | default | ++======================+======+=========+ +| auto_manage_interval | int | 30 | ++----------------------+------+---------+ + +``auto_manage_interval`` is the number of seconds between the torrent queue +is updated, and rotated. + +.. _seed_time_limit: + +.. raw:: html + + + ++-----------------+------+--------------+ +| name | type | default | ++=================+======+==============+ +| seed_time_limit | int | 24 * 60 * 60 | ++-----------------+------+--------------+ + +this is the limit on the time a torrent has been an active seed +(specified in seconds) before it is considered having met the seed limit criteria. +See queuing_. + +.. _auto_scrape_interval: + +.. _auto_scrape_min_interval: + +.. raw:: html + + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| auto_scrape_interval | int | 1800 | ++--------------------------+------+---------+ +| auto_scrape_min_interval | int | 300 | ++--------------------------+------+---------+ + +``auto_scrape_interval`` is the number of seconds between scrapes of +queued torrents (auto managed and paused torrents). Auto managed +torrents that are paused, are scraped regularly in order to keep +track of their downloader/seed ratio. This ratio is used to determine +which torrents to seed and which to pause. + +``auto_scrape_min_interval`` is the minimum number of seconds between any +automatic scrape (regardless of torrent). In case there are a large number +of paused auto managed torrents, this puts a limit on how often a scrape +request is sent. + +.. _max_peerlist_size: + +.. _max_paused_peerlist_size: + +.. raw:: html + + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| max_peerlist_size | int | 3000 | ++--------------------------+------+---------+ +| max_paused_peerlist_size | int | 1000 | ++--------------------------+------+---------+ + +``max_peerlist_size`` is the maximum number of peers in the list of +known peers. These peers are not necessarily connected, so this number +should be much greater than the maximum number of connected peers. +Peers are evicted from the cache when the list grows passed 90% of +this limit, and once the size hits the limit, peers are no longer +added to the list. If this limit is set to 0, there is no limit on +how many peers we'll keep in the peer list. + +``max_paused_peerlist_size`` is the max peer list size used for torrents +that are paused. This default to the same as ``max_peerlist_size``, but +can be used to save memory for paused torrents, since it's not as +important for them to keep a large peer list. + +.. _min_announce_interval: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| min_announce_interval | int | 5 * 60 | ++-----------------------+------+---------+ + +this is the minimum allowed announce interval for a tracker. This +is specified in seconds and is used as a sanity check on what is +returned from a tracker. It mitigates hammering misconfigured trackers. + +.. _auto_manage_startup: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| auto_manage_startup | int | 60 | ++---------------------+------+---------+ + +this is the number of seconds a torrent is considered +active after it was started, regardless of upload and download speed. This +is so that newly started torrents are not considered inactive until they +have a fair chance to start downloading. + +.. _seeding_piece_quota: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| seeding_piece_quota | int | 20 | ++---------------------+------+---------+ + +``seeding_piece_quota`` is the number of pieces to send to a peer, +when seeding, before rotating in another peer to the unchoke set. +It defaults to 3 pieces, which means that when seeding, any peer we've +sent more than this number of pieces to will be unchoked in favour of +a choked peer. + +.. _max_sparse_regions: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| max_sparse_regions | int | 0 | ++--------------------+------+---------+ + +``max_sparse_regions`` is a limit of the number of *sparse regions* in +a torrent. A sparse region is defined as a hole of pieces we have not +yet downloaded, in between pieces that have been downloaded. This is +used as a hack for windows vista which has a bug where you cannot +write files with more than a certain number of sparse regions. This +limit is not hard, it will be exceeded. Once it's exceeded, pieces +that will maintain or decrease the number of sparse regions are +prioritized. To disable this functionality, set this to 0. It defaults +to 0 on all platforms except windows. + +.. _max_rejects: + +.. raw:: html + + + ++-------------+------+---------+ +| name | type | default | ++=============+======+=========+ +| max_rejects | int | 50 | ++-------------+------+---------+ + +TODO: deprecate this +``max_rejects`` is the number of piece requests we will reject in a row +while a peer is choked before the peer is considered abusive and is +disconnected. + +.. _recv_socket_buffer_size: + +.. _send_socket_buffer_size: + +.. raw:: html + + + + ++-------------------------+------+---------+ +| name | type | default | ++=========================+======+=========+ +| recv_socket_buffer_size | int | 0 | ++-------------------------+------+---------+ +| send_socket_buffer_size | int | 0 | ++-------------------------+------+---------+ + +``recv_socket_buffer_size`` and ``send_socket_buffer_size`` specifies +the buffer sizes set on peer sockets. 0 (which is the default) means +the OS default (i.e. don't change the buffer sizes). The socket buffer +sizes are changed using setsockopt() with SOL_SOCKET/SO_RCVBUF and +SO_SNDBUFFER. + +.. _file_checks_delay_per_block: + +.. raw:: html + + + ++-----------------------------+------+---------+ +| name | type | default | ++=============================+======+=========+ +| file_checks_delay_per_block | int | 0 | ++-----------------------------+------+---------+ + +``file_checks_delay_per_block`` is the number of milliseconds to sleep +in between disk read operations when checking torrents. This defaults +to 0, but can be set to higher numbers to slow down the rate at which +data is read from the disk while checking. This may be useful for +background tasks that doesn't matter if they take a bit longer, as long +as they leave disk I/O time for other processes. + +.. _read_cache_line_size: + +.. _write_cache_line_size: + +.. raw:: html + + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| read_cache_line_size | int | 32 | ++-----------------------+------+---------+ +| write_cache_line_size | int | 16 | ++-----------------------+------+---------+ + +``read_cache_line_size`` is the number of blocks to read into the read +cache when a read cache miss occurs. Setting this to 0 is essentially +the same thing as disabling read cache. The number of blocks read +into the read cache is always capped by the piece boundry. + +When a piece in the write cache has ``write_cache_line_size`` contiguous +blocks in it, they will be flushed. Setting this to 1 effectively +disables the write cache. + +.. _optimistic_disk_retry: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| optimistic_disk_retry | int | 10 * 60 | ++-----------------------+------+---------+ + +``optimistic_disk_retry`` is the number of seconds from a disk write +errors occur on a torrent until libtorrent will take it out of the +upload mode, to test if the error condition has been fixed. + +libtorrent will only do this automatically for auto managed torrents. + +You can explicitly take a torrent out of upload only mode using +set_upload_mode(). + +.. _max_suggest_pieces: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| max_suggest_pieces | int | 10 | ++--------------------+------+---------+ + +``max_suggest_pieces`` is the max number of suggested piece indices received +from a peer that's remembered. If a peer floods suggest messages, this limit +prevents libtorrent from using too much RAM. It defaults to 10. + +.. _local_service_announce_interval: + +.. raw:: html + + + ++---------------------------------+------+---------+ +| name | type | default | ++=================================+======+=========+ +| local_service_announce_interval | int | 5 * 60 | ++---------------------------------+------+---------+ + +``local_service_announce_interval`` is the time between local +network announces for a torrent. By default, when local service +discovery is enabled a torrent announces itself every 5 minutes. +This interval is specified in seconds. + +.. _dht_announce_interval: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| dht_announce_interval | int | 15 * 60 | ++-----------------------+------+---------+ + +``dht_announce_interval`` is the number of seconds between announcing +torrents to the distributed hash table (DHT). + +.. _udp_tracker_token_expiry: + +.. raw:: html + + + ++--------------------------+------+---------+ +| name | type | default | ++==========================+======+=========+ +| udp_tracker_token_expiry | int | 60 | ++--------------------------+------+---------+ + +``udp_tracker_token_expiry`` is the number of seconds libtorrent +will keep UDP tracker connection tokens around for. This is specified +to be 60 seconds, and defaults to that. The higher this value is, the +fewer packets have to be sent to the UDP tracker. In order for higher +values to work, the tracker needs to be configured to match the +expiration time for tokens. + +.. _default_cache_min_age: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| default_cache_min_age | int | 1 | ++-----------------------+------+---------+ + +``default_cache_min_age`` is the minimum number of seconds any read +cache line is kept in the cache. This defaults to one second but +may be greater if ``guided_read_cache`` is enabled. Having a lower +bound on the time a cache line stays in the cache is an attempt +to avoid swapping the same pieces in and out of the cache in case +there is a shortage of spare cache space. + +.. _num_optimistic_unchoke_slots: + +.. raw:: html + + + ++------------------------------+------+---------+ +| name | type | default | ++==============================+======+=========+ +| num_optimistic_unchoke_slots | int | 0 | ++------------------------------+------+---------+ + +``num_optimistic_unchoke_slots`` is the number of optimistic unchoke +slots to use. It defaults to 0, which means automatic. Having a higher +number of optimistic unchoke slots mean you will find the good peers +faster but with the trade-off to use up more bandwidth. When this is +set to 0, libtorrent opens up 20% of your allowed upload slots as +optimistic unchoke slots. + +.. _default_est_reciprocation_rate: + +.. _increase_est_reciprocation_rate: + +.. _decrease_est_reciprocation_rate: + +.. raw:: html + + + + + ++---------------------------------+------+---------+ +| name | type | default | ++=================================+======+=========+ +| default_est_reciprocation_rate | int | 16000 | ++---------------------------------+------+---------+ +| increase_est_reciprocation_rate | int | 20 | ++---------------------------------+------+---------+ +| decrease_est_reciprocation_rate | int | 3 | ++---------------------------------+------+---------+ + +``default_est_reciprocation_rate`` is the assumed reciprocation rate +from peers when using the BitTyrant choker. This defaults to 14 kiB/s. +If set too high, you will over-estimate your peers and be more altruistic +while finding the true reciprocation rate, if it's set too low, you'll +be too stingy and waste finding the true reciprocation rate. + +``increase_est_reciprocation_rate`` specifies how many percent the +extimated reciprocation rate should be increased by each unchoke +interval a peer is still choking us back. This defaults to 20%. +This only applies to the BitTyrant choker. + +``decrease_est_reciprocation_rate`` specifies how many percent the +estimated reciprocation rate should be decreased by each unchoke +interval a peer unchokes us. This default to 3%. +This only applies to the BitTyrant choker. + +.. _max_pex_peers: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| max_pex_peers | int | 50 | ++---------------+------+---------+ + +the max number of peers we accept from pex messages from a single peer. +this limits the number of concurrent peers any of our peers claims to +be connected to. If they clain to be connected to more than this, we'll +ignore any peer that exceeds this limit + +.. _tick_interval: + +.. raw:: html + + + ++---------------+------+---------+ +| name | type | default | ++===============+======+=========+ +| tick_interval | int | 500 | ++---------------+------+---------+ + +``tick_interval`` specifies the number of milliseconds between internal +ticks. This is the frequency with which bandwidth quota is distributed to +peers. It should not be more than one second (i.e. 1000 ms). Setting this +to a low value (around 100) means higher resolution bandwidth quota distribution, +setting it to a higher value saves CPU cycles. + +.. _share_mode_target: + +.. raw:: html + + + ++-------------------+------+---------+ +| name | type | default | ++===================+======+=========+ +| share_mode_target | int | 3 | ++-------------------+------+---------+ + +``share_mode_target`` specifies the target share ratio for share mode torrents. +This defaults to 3, meaning we'll try to upload 3 times as much as we download. +Setting this very high, will make it very conservative and you might end up +not downloading anything ever (and not affecting your share ratio). It does +not make any sense to set this any lower than 2. For instance, if only 3 peers +need to download the rarest piece, it's impossible to download a single piece +and upload it more than 3 times. If the share_mode_target is set to more than 3, +nothing is downloaded. + +.. _upload_rate_limit: + +.. _download_rate_limit: + +.. raw:: html + + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| upload_rate_limit | int | 0 | ++---------------------+------+---------+ +| download_rate_limit | int | 0 | ++---------------------+------+---------+ + +``upload_rate_limit``, ``download_rate_limit``, ``local_upload_rate_limit`` +and ``local_download_rate_limit`` sets the session-global limits of upload +and download rate limits, in bytes per second. The local rates refer to peers +on the local network. By default peers on the local network are not rate limited. + +These rate limits are only used for local peers (peers within the same subnet as +the client itself) and it is only used when ``ignore_limits_on_local_network`` +is set to true (which it is by default). These rate limits default to unthrottled, +but can be useful in case you want to treat local peers preferentially, but not +quite unthrottled. + +A value of 0 means unlimited. + +.. _dht_upload_rate_limit: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| dht_upload_rate_limit | int | 4000 | ++-----------------------+------+---------+ + +``dht_upload_rate_limit`` sets the rate limit on the DHT. This is specified in +bytes per second and defaults to 4000. For busy boxes with lots of torrents +that requires more DHT traffic, this should be raised. + +.. _unchoke_slots_limit: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| unchoke_slots_limit | int | 8 | ++---------------------+------+---------+ + +``unchoke_slots_limit`` is the max number of unchoked peers in the session. +The number of unchoke slots may be ignored depending on what +``choking_algorithm`` is set to. + +.. _half_open_limit: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| half_open_limit | int | 0 | ++-----------------+------+---------+ + +``half_open_limit`` sets the maximum number of half-open connections +libtorrent will have when connecting to peers. A half-open connection is one +where connect() has been called, but the connection still hasn't been established +(nor failed). Windows XP Service Pack 2 sets a default, system wide, limit of +the number of half-open connections to 10. So, this limit can be used to work +nicer together with other network applications on that system. The default is +to have no limit, and passing -1 as the limit, means to have no limit. When +limiting the number of simultaneous connection attempts, peers will be put in +a queue waiting for their turn to get connected. + +.. _connections_limit: + +.. raw:: html + + + ++-------------------+------+---------+ +| name | type | default | ++===================+======+=========+ +| connections_limit | int | 200 | ++-------------------+------+---------+ + +``connections_limit`` sets a global limit on the number of connections +opened. The number of connections is set to a hard minimum of at least two per +torrent, so if you set a too low connections limit, and open too many torrents, +the limit will not be met. + +.. _connections_slack: + +.. raw:: html + + + ++-------------------+------+---------+ +| name | type | default | ++===================+======+=========+ +| connections_slack | int | 10 | ++-------------------+------+---------+ + +``connections_slack`` is the the number of incoming connections exceeding the +connection limit to accept in order to potentially replace existing ones. + +.. _utp_target_delay: + +.. _utp_gain_factor: + +.. _utp_min_timeout: + +.. _utp_syn_resends: + +.. _utp_fin_resends: + +.. _utp_num_resends: + +.. _utp_connect_timeout: + +.. _utp_loss_multiplier: + +.. raw:: html + + + + + + + + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| utp_target_delay | int | 100 | ++---------------------+------+---------+ +| utp_gain_factor | int | 1500 | ++---------------------+------+---------+ +| utp_min_timeout | int | 500 | ++---------------------+------+---------+ +| utp_syn_resends | int | 2 | ++---------------------+------+---------+ +| utp_fin_resends | int | 2 | ++---------------------+------+---------+ +| utp_num_resends | int | 6 | ++---------------------+------+---------+ +| utp_connect_timeout | int | 3000 | ++---------------------+------+---------+ +| utp_loss_multiplier | int | 50 | ++---------------------+------+---------+ + +``utp_target_delay`` is the target delay for uTP sockets in milliseconds. A high +value will make uTP connections more aggressive and cause longer queues in the upload +bottleneck. It cannot be too low, since the noise in the measurements would cause +it to send too slow. The default is 50 milliseconds. +``utp_gain_factor`` is the number of bytes the uTP congestion window can increase +at the most in one RTT. This defaults to 300 bytes. If this is set too high, +the congestion controller reacts too hard to noise and will not be stable, if it's +set too low, it will react slow to congestion and not back off as fast. +``utp_min_timeout`` is the shortest allowed uTP socket timeout, specified in milliseconds. +This defaults to 500 milliseconds. The timeout depends on the RTT of the connection, but +is never smaller than this value. A connection times out when every packet in a window +is lost, or when a packet is lost twice in a row (i.e. the resent packet is lost as well). + +The shorter the timeout is, the faster the connection will recover from this situation, +assuming the RTT is low enough. +``utp_syn_resends`` is the number of SYN packets that are sent (and timed out) before +giving up and closing the socket. +``utp_num_resends`` is the number of times a packet is sent (and lossed or timed out) +before giving up and closing the connection. +``utp_connect_timeout`` is the number of milliseconds of timeout for the initial SYN +packet for uTP connections. For each timed out packet (in a row), the timeout is doubled. +``utp_loss_multiplier`` controls how the congestion window is changed when a packet +loss is experienced. It's specified as a percentage multiplier for ``cwnd``. By default +it's set to 50 (i.e. cut in half). Do not change this value unless you know what +you're doing. Never set it higher than 100. + +.. _mixed_mode_algorithm: + +.. raw:: html + + + ++----------------------+------+----------------------------------+ +| name | type | default | ++======================+======+==================================+ +| mixed_mode_algorithm | int | settings_pack::peer_proportional | ++----------------------+------+----------------------------------+ + +The ``mixed_mode_algorithm`` determines how to treat TCP connections when there are +uTP connections. Since uTP is designed to yield to TCP, there's an inherent problem +when using swarms that have both TCP and uTP connections. If nothing is done, uTP +connections would often be starved out for bandwidth by the TCP connections. This mode +is ``prefer_tcp``. The ``peer_proportional`` mode simply looks at the current throughput +and rate limits all TCP connections to their proportional share based on how many of +the connections are TCP. This works best if uTP connections are not rate limited by +the global rate limiter (which they aren't by default). + +.. _listen_queue_size: + +.. raw:: html + + + ++-------------------+------+---------+ +| name | type | default | ++===================+======+=========+ +| listen_queue_size | int | 5 | ++-------------------+------+---------+ + +``listen_queue_size`` is the value passed in to listen() for the listen socket. +It is the number of outstanding incoming connections to queue up while we're not +actively waiting for a connection to be accepted. The default is 5 which should +be sufficient for any normal client. If this is a high performance server which +expects to receive a lot of connections, or used in a simulator or test, it +might make sense to raise this number. It will not take affect until listen_on() +is called again (or for the first time). + +.. _torrent_connect_boost: + +.. raw:: html + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| torrent_connect_boost | int | 10 | ++-----------------------+------+---------+ + +``torrent_connect_boost`` is the number of peers to try to connect to immediately +when the first tracker response is received for a torrent. This is a boost to +given to new torrents to accelerate them starting up. The normal connect scheduler +is run once every second, this allows peers to be connected immediately instead +of waiting for the session tick to trigger connections. + +.. _alert_queue_size: + +.. raw:: html + + + ++------------------+------+---------+ +| name | type | default | ++==================+======+=========+ +| alert_queue_size | int | 1000 | ++------------------+------+---------+ + +``alert_queue_size`` is the maximum number of alerts queued up internally. If +alerts are not popped, the queue will eventually fill up to this level. + +.. _max_metadata_size: + +.. raw:: html + + + ++-------------------+------+------------------+ +| name | type | default | ++===================+======+==================+ +| max_metadata_size | int | 3 * 1024 * 10240 | ++-------------------+------+------------------+ + +``max_metadata_size`` is the maximum allowed size (in bytes) to be received +by the metadata extension, i.e. magnet links. It defaults to 1 MiB. + +.. _hashing_threads: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| hashing_threads | int | 1 | ++-----------------+------+---------+ + +``hashing_threads`` is the number of threads to use for piece hash verification. It +defaults to 1. For very high download rates, on machines with multiple cores, this +could be incremented. Setting it higher than the number of CPU cores would presumably +not provide any benefit of setting it to the number of cores. If it's set to 0, +hashing is done in the disk thread. + +.. _checking_mem_usage: + +.. raw:: html + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| checking_mem_usage | int | 256 | ++--------------------+------+---------+ + +the number of blocks to keep outstanding at any given time when +checking torrents. Higher numbers give faster re-checks but uses +more memory. Specified in number of 16 kiB blocks + +.. _predictive_piece_announce: + +.. raw:: html + + + ++---------------------------+------+---------+ +| name | type | default | ++===========================+======+=========+ +| predictive_piece_announce | int | 0 | ++---------------------------+------+---------+ + +if set to > 0, pieces will be announced to other peers before they +are fully downloaded (and before they are hash checked). The intention +is to gain 1.5 potential round trip times per downloaded piece. When +non-zero, this indicates how many milliseconds in advance pieces +should be announced, before they are expected to be completed. + +.. _aio_threads: + +.. _aio_max: + +.. raw:: html + + + + ++-------------+------+---------+ +| name | type | default | ++=============+======+=========+ +| aio_threads | int | 4 | ++-------------+------+---------+ +| aio_max | int | 300 | ++-------------+------+---------+ + +for some aio back-ends, ``aio_threads`` specifies the number of +io-threads to use, and ``aio_max`` the max number of outstanding jobs. + +.. _network_threads: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| network_threads | int | 0 | ++-----------------+------+---------+ + +``network_threads`` is the number of threads to use to call ``async_write_some`` +(i.e. send) on peer connection sockets. When seeding at extremely high rates, +this may become a bottleneck, and setting this to 2 or more may parallelize +that cost. When using SSL torrents, all encryption for outgoing traffic is +done withint the socket send functions, and this will help parallelizing the +cost of SSL encryption as well. + +.. _ssl_listen: + +.. raw:: html + + + ++------------+------+---------+ +| name | type | default | ++============+======+=========+ +| ssl_listen | int | 4433 | ++------------+------+---------+ + +``ssl_listen`` sets the listen port for SSL connections. If this is set to 0, +no SSL listen port is opened. Otherwise a socket is opened on this port. This +setting is only taken into account when opening the regular listen port, and +won't re-open the listen socket simply by changing this setting. + +.. _tracker_backoff: + +.. raw:: html + + + ++-----------------+------+---------+ +| name | type | default | ++=================+======+=========+ +| tracker_backoff | int | 250 | ++-----------------+------+---------+ + +``tracker_backoff`` determines how aggressively to back off from retrying +failing trackers. This value determines *x* in the following formula, determining +the number of seconds to wait until the next retry: + + delay = 5 + 5 * x / 100 * fails^2 + +This setting may be useful to make libtorrent more or less aggressive in hitting +trackers. + +.. _share_ratio_limit: + +.. _seed_time_ratio_limit: + +.. raw:: html + + + + ++-----------------------+------+---------+ +| name | type | default | ++=======================+======+=========+ +| share_ratio_limit | int | 200 | ++-----------------------+------+---------+ +| seed_time_ratio_limit | int | 700 | ++-----------------------+------+---------+ + +when a seeding torrent reaches eaither the share ratio +(bytes up / bytes down) or the seed time ratio +(seconds as seed / seconds as downloader) or the seed +time limit (seconds as seed) it is considered +done, and it will leave room for other torrents +these are specified as percentages + +.. _peer_turnover: + +.. _peer_turnover_cutoff: + +.. _peer_turnover_interval: + +.. raw:: html + + + + + ++------------------------+------+---------+ +| name | type | default | ++========================+======+=========+ +| peer_turnover | int | 4 | ++------------------------+------+---------+ +| peer_turnover_cutoff | int | 90 | ++------------------------+------+---------+ +| peer_turnover_interval | int | 300 | ++------------------------+------+---------+ + +peer_turnover is the percentage of peers to disconnect +every turnover peer_turnover_interval (if we're at +the peer limit), this is specified in percent +when we are connected to more than +limit * peer_turnover_cutoff peers +disconnect peer_turnover fraction +of the peers. It is specified in percent +peer_turnover_interval is the interval (in seconds) +between optimistic disconnects +if the disconnects happen and how many peers are disconnected +is controlled by peer_turnover and peer_turnover_cutoff + +.. _connect_seed_every_n_download: + +.. raw:: html + + + ++-------------------------------+------+---------+ +| name | type | default | ++===============================+======+=========+ +| connect_seed_every_n_download | int | 10 | ++-------------------------------+------+---------+ + +this setting controls the priority of downloading torrents +over seeding or finished torrents when it comes to making +peer connections. Peer connections are throttled by the +connection_speed and the half-open connection limit. This +makes peer connections a limited resource. Torrents that +still have pieces to download are prioritized by default, +to avoid having many seeding torrents use most of the connection +attempts and only give one peer every now and then to the +downloading torrent. libtorrent will loop over the downloading +torrents to connect a peer each, and every n:th connection +attempt, a finished torrent is picked to be allowed to connect +to a peer. This setting controls n. + +.. _max_http_recv_buffer_size: + +.. raw:: html + + + ++---------------------------+------+------------+ +| name | type | default | ++===========================+======+============+ +| max_http_recv_buffer_size | int | 4*1024*204 | ++---------------------------+------+------------+ + +the max number of bytes to allow an HTTP response to be when +announcing to trackers or downloading .torrent files via +the ``url`` provided in ``add_torrent_params``. + +.. _max_retry_port_bind: + +.. raw:: html + + + ++---------------------+------+---------+ +| name | type | default | ++=====================+======+=========+ +| max_retry_port_bind | int | 10 | ++---------------------+------+---------+ + +if binding to a specific port fails, should the port be incremented +by one and tried again? This setting specifies how many times to +retry a failed port bind + +.. _alert_mask: + +.. raw:: html + + + ++------------+------+---------------------------+ +| name | type | default | ++============+======+===========================+ +| alert_mask | int | alert::error_notification | ++------------+------+---------------------------+ + +a bitmask combining flags from alert::category_t defining +which kinds of alerts to receive + +.. _out_enc_policy: + +.. _in_enc_policy: + +.. raw:: html + + + + ++----------------+------+---------------------------+ +| name | type | default | ++================+======+===========================+ +| out_enc_policy | int | settings_pack::pe_enabled | ++----------------+------+---------------------------+ +| in_enc_policy | int | settings_pack::pe_enabled | ++----------------+------+---------------------------+ + +control the settings for incoming +and outgoing connections respectively. +see enc_policy enum for the available options. + +.. _allowed_enc_level: + +.. raw:: html + + + ++-------------------+------+------------------------+ +| name | type | default | ++===================+======+========================+ +| allowed_enc_level | int | settings_pack::pe_both | ++-------------------+------+------------------------+ + +determines the encryption level of the +connections. This setting will adjust which encryption scheme is +offered to the other peer, as well as which encryption scheme is +selected by the client. See enc_level enum for options. + +.. _inactive_down_rate: + +.. _inactive_up_rate: + +.. raw:: html + + + + ++--------------------+------+---------+ +| name | type | default | ++====================+======+=========+ +| inactive_down_rate | int | 2048 | ++--------------------+------+---------+ +| inactive_up_rate | int | 2048 | ++--------------------+------+---------+ + +the download and upload rate limits for a torrent to be considered +active by the queuing mechanism. A torrent whose download rate is less +than ``inactive_down_rate`` and whose upload rate is less than +``inactive_up_rate`` for ``auto_manage_startup`` seconds, is +considered inactive, and another queued torrent may be startert. +This logic is disabled if ``dont_count_slow_torrents`` is false. + +.. _proxy_type: + +.. raw:: html + + + ++------------+------+---------------------+ +| name | type | default | ++============+======+=====================+ +| proxy_type | int | settings_pack::none | ++------------+------+---------------------+ + +proxy to use, defaults to none. see proxy_type_t. + +.. _proxy_port: + +.. raw:: html + + + ++------------+------+---------+ +| name | type | default | ++============+======+=========+ +| proxy_port | int | 0 | ++------------+------+---------+ + +the port of the proxy server + +.. _i2p_port: + +.. raw:: html + + + ++----------+------+---------+ +| name | type | default | ++==========+======+=========+ +| i2p_port | int | 0 | ++----------+------+---------+ + +sets the i2p_ SAM bridge port to connect to. set the hostname with +the ``i2p_hostname`` setting. + +.. _i2p: http://www.i2p2.de + diff --git a/docs/stats_counters.rst b/docs/stats_counters.rst new file mode 100644 index 000000000..28520bf2e --- /dev/null +++ b/docs/stats_counters.rst @@ -0,0 +1,368 @@ +.. _peer.error_peers: + +.. _peer.disconnected_peers: + ++-------------------------+---------+ +| name | type | ++=========================+=========+ +| peer.error_peers | counter | ++-------------------------+---------+ +| peer.disconnected_peers | counter | ++-------------------------+---------+ + +``error_peers`` is the total number of peer disconnects +caused by an error (not initiated by this client) and +disconnected initiated by this client (``disconnected_peers``). + +.. _peer.eof_peers: + +.. _peer.connreset_peers: + +.. _peer.connrefused_peers: + +.. _peer.connaborted_peers: + +.. _peer.perm_peers: + +.. _peer.buffer_peers: + +.. _peer.unreachable_peers: + +.. _peer.broken_pipe_peers: + +.. _peer.addrinuse_peers: + +.. _peer.no_access_peers: + +.. _peer.invalid_arg_peers: + +.. _peer.aborted_peers: + ++------------------------+---------+ +| name | type | ++========================+=========+ +| peer.eof_peers | counter | ++------------------------+---------+ +| peer.connreset_peers | counter | ++------------------------+---------+ +| peer.connrefused_peers | counter | ++------------------------+---------+ +| peer.connaborted_peers | counter | ++------------------------+---------+ +| peer.perm_peers | counter | ++------------------------+---------+ +| peer.buffer_peers | counter | ++------------------------+---------+ +| peer.unreachable_peers | counter | ++------------------------+---------+ +| peer.broken_pipe_peers | counter | ++------------------------+---------+ +| peer.addrinuse_peers | counter | ++------------------------+---------+ +| peer.no_access_peers | counter | ++------------------------+---------+ +| peer.invalid_arg_peers | counter | ++------------------------+---------+ +| peer.aborted_peers | counter | ++------------------------+---------+ + +these counters break down the peer errors into more specific +categories. These errors are what the underlying transport +reported (i.e. TCP or uTP) + +.. _peer.error_incoming_peers: + +.. _peer.error_outgoing_peers: + ++---------------------------+---------+ +| name | type | ++===========================+=========+ +| peer.error_incoming_peers | counter | ++---------------------------+---------+ +| peer.error_outgoing_peers | counter | ++---------------------------+---------+ + +these counters break down the peer errors into +whether they happen on incoming or outgoing peers. + +.. _peer.error_rc4_peers: + +.. _peer.error_encrypted_peers: + ++----------------------------+---------+ +| name | type | ++============================+=========+ +| peer.error_rc4_peers | counter | ++----------------------------+---------+ +| peer.error_encrypted_peers | counter | ++----------------------------+---------+ + +these counters break down the peer errors into +whether they happen on encrypted peers (just +encrypted handshake) and rc4 peers (full stream +encryption). These can indicate whether encrypted +peers are more or less likely to fail + +.. _peer.error_tcp_peers: + +.. _peer.error_utp_peers: + ++----------------------+---------+ +| name | type | ++======================+=========+ +| peer.error_tcp_peers | counter | ++----------------------+---------+ +| peer.error_utp_peers | counter | ++----------------------+---------+ + +these counters break down the peer errors into +whether they happen on uTP peers or TCP peers. +these may indicate whether one protocol is +more error prone + +.. _peer.connect_timeouts: + +.. _peer.uninteresting_peers: + +.. _peer.timeout_peers: + +.. _peer.no_memory_peers: + +.. _peer.too_many_peers: + +.. _peer.transport_timeout_peers: + +.. _peer.num_banned_peers: + +.. _peer.connection_attempts: + +.. _peer.banned_for_hash_failure: + ++------------------------------+---------+ +| name | type | ++==============================+=========+ +| peer.connect_timeouts | counter | ++------------------------------+---------+ +| peer.uninteresting_peers | counter | ++------------------------------+---------+ +| peer.timeout_peers | counter | ++------------------------------+---------+ +| peer.no_memory_peers | counter | ++------------------------------+---------+ +| peer.too_many_peers | counter | ++------------------------------+---------+ +| peer.transport_timeout_peers | counter | ++------------------------------+---------+ +| peer.num_banned_peers | counter | ++------------------------------+---------+ +| peer.connection_attempts | counter | ++------------------------------+---------+ +| peer.banned_for_hash_failure | counter | ++------------------------------+---------+ + +these counters break down the reasons to +disconnect peers. + +.. _peer.num_tcp_peers: + +.. _peer.num_socks5_peers: + +.. _peer.num_http_proxy_peers: + +.. _peer.num_utp_peers: + +.. _peer.num_i2p_peers: + +.. _peer.num_ssl_peers: + +.. _peer.num_ssl_socks5_peers: + +.. _peer.num_ssl_http_proxy_peers: + +.. _peer.num_ssl_utp_peers: + ++-------------------------------+-------+ +| name | type | ++===============================+=======+ +| peer.num_tcp_peers | gauge | ++-------------------------------+-------+ +| peer.num_socks5_peers | gauge | ++-------------------------------+-------+ +| peer.num_http_proxy_peers | gauge | ++-------------------------------+-------+ +| peer.num_utp_peers | gauge | ++-------------------------------+-------+ +| peer.num_i2p_peers | gauge | ++-------------------------------+-------+ +| peer.num_ssl_peers | gauge | ++-------------------------------+-------+ +| peer.num_ssl_socks5_peers | gauge | ++-------------------------------+-------+ +| peer.num_ssl_http_proxy_peers | gauge | ++-------------------------------+-------+ +| peer.num_ssl_utp_peers | gauge | ++-------------------------------+-------+ + +the number of peer connections for each kind of socket. +these counts include half-open (connecting) peers. + +.. _net.on_read_counter: + +.. _net.on_write_counter: + +.. _net.on_tick_counter: + +.. _net.on_lsd_counter: + +.. _net.on_lsd_peer_counter: + +.. _net.on_udp_counter: + +.. _net.on_accept_counter: + +.. _net.on_disk_counter: + ++-------------------------+---------+ +| name | type | ++=========================+=========+ +| net.on_read_counter | counter | ++-------------------------+---------+ +| net.on_write_counter | counter | ++-------------------------+---------+ +| net.on_tick_counter | counter | ++-------------------------+---------+ +| net.on_lsd_counter | counter | ++-------------------------+---------+ +| net.on_lsd_peer_counter | counter | ++-------------------------+---------+ +| net.on_udp_counter | counter | ++-------------------------+---------+ +| net.on_accept_counter | counter | ++-------------------------+---------+ +| net.on_disk_counter | counter | ++-------------------------+---------+ + +These counters count the number of times the +network thread wakes up for each respective +reason. If these counters are very large, it +may indicate a performance issue, causing the +network thread to wake up too ofte, wasting CPU. +mitigate it by increasing buffers and limits +for the specific trigger that wakes up the +thread. + +.. _ses.num_checking_torrents: + +.. _ses.num_stopped_torrents: + +.. _ses.num_upload_only_torrents: + +.. _ses.num_downloading_torrents: + +.. _ses.num_seeding_torrents: + +.. _ses.num_queued_seeding_torrents: + +.. _ses.num_queued_download_torrents: + +.. _ses.num_error_torrents: + ++----------------------------------+-------+ +| name | type | ++==================================+=======+ +| ses.num_checking_torrents | gauge | ++----------------------------------+-------+ +| ses.num_stopped_torrents | gauge | ++----------------------------------+-------+ +| ses.num_upload_only_torrents | gauge | ++----------------------------------+-------+ +| ses.num_downloading_torrents | gauge | ++----------------------------------+-------+ +| ses.num_seeding_torrents | gauge | ++----------------------------------+-------+ +| ses.num_queued_seeding_torrents | gauge | ++----------------------------------+-------+ +| ses.num_queued_download_torrents | gauge | ++----------------------------------+-------+ +| ses.num_error_torrents | gauge | ++----------------------------------+-------+ + +these gauges count the number of torrents in +different states. Each torrent only belongs to +one of these states. For torrents that could +belong to multiple of these, the most prominent +in picked. For instance, a torrent with an error +counts as an error-torrent, regardless of its other +state. + +.. _ses.torrent_evicted_counter: + ++-----------------------------+---------+ +| name | type | ++=============================+=========+ +| ses.torrent_evicted_counter | counter | ++-----------------------------+---------+ + +this counts the number of times a torrent has been +evicted (only applies when `dynamic loading of torrent files`_ +is enabled). + +.. _picker.piece_picks: + ++--------------------+---------+ +| name | type | ++====================+=========+ +| picker.piece_picks | counter | ++--------------------+---------+ + +counts the number of times the piece picker has been invoked + +.. _picker.piece_picker_loops: + ++---------------------------+---------+ +| name | type | ++===========================+=========+ +| picker.piece_picker_loops | counter | ++---------------------------+---------+ + +the number of pieces considered while picking pieces + +.. _picker.end_game_piece_picker_blocks: + +.. _picker.piece_picker_blocks: + +.. _picker.reject_piece_picks: + +.. _picker.unchoke_piece_picks: + +.. _picker.incoming_redundant_piece_picks: + +.. _picker.incoming_piece_picks: + +.. _picker.end_game_piece_picks: + +.. _picker.snubbed_piece_picks: + ++---------------------------------------+---------+ +| name | type | ++=======================================+=========+ +| picker.end_game_piece_picker_blocks | counter | ++---------------------------------------+---------+ +| picker.piece_picker_blocks | counter | ++---------------------------------------+---------+ +| picker.reject_piece_picks | counter | ++---------------------------------------+---------+ +| picker.unchoke_piece_picks | counter | ++---------------------------------------+---------+ +| picker.incoming_redundant_piece_picks | counter | ++---------------------------------------+---------+ +| picker.incoming_piece_picks | counter | ++---------------------------------------+---------+ +| picker.end_game_piece_picks | counter | ++---------------------------------------+---------+ +| picker.snubbed_piece_picks | counter | ++---------------------------------------+---------+ + +This breaks down the piece picks into the event that +triggered it + diff --git a/docs/todo.html b/docs/todo.html index f6e83d781..4f7cc7afc 100644 --- a/docs/todo.html +++ b/docs/todo.html @@ -21,3502 +21,9 @@

libtorrent todo-list

-2 important -6 relevant -13 feasible -46 notes -
relevance 4../src/session_impl.cpp:664in order to support SSL over uTP, the utp_socket manager either needs to be able to receive packets on multiple ports, or we need to peek into the first few bytes the payload stream of a socket to determine whether or not it's an SSL connection. (The former is simpler but won't do as well with NATs)
relevance 4../src/settings.cpp:43eliminate all use of this mechanism
relevance 3../src/torrent.cpp:6244if peer is a really good peer, maybe we shouldn't disconnect it
relevance 3../src/web_peer_connection.cpp:580just make this peer not have the pieces associated with the file we just requested. Only when it doesn't have any of the file do the following
relevance 2../src/gzip.cpp:132it would be nice to use proper error handling here
relevance 2../src/policy.cpp:153this could be optimized if SSE 4.2 is available. It could also be optimized given that we have a fixed length
relevance 2../src/torrent.cpp:634post alert
relevance 2../src/web_peer_connection.cpp:633create a mapping of file-index to redirection URLs. Use that to form URLs instead. Support to reconnect to a new server without destructing this peer_connection
relevance 2../src/kademlia/node.cpp:66make this configurable in dht_settings
relevance 2../src/kademlia/node_id.cpp:135this could be optimized if SSE 4.2 is available. It could also be optimized given that we have a fixed length
relevance 1../src/http_seed_connection.cpp:115in chunked encoding mode, this assert won't hold. the chunk headers should be subtracted from the receive_buffer_size
relevance 1../src/peer_connection.cpp:2587peers should really be corked/uncorked outside of all completed disk operations
relevance 1../src/session_impl.cpp:5550report the proper address of the router as the source IP of this understanding of our external address, instead of the empty address
relevance 1../src/session_impl.cpp:6339we only need to do this if our global IPv4 address has changed since the DHT (currently) only supports IPv4. Since restarting the DHT is kind of expensive, it would be nice to not do it unnecessarily
relevance 1../src/torrent.cpp:1031make this depend on the error and on the filesystem the files are being downloaded to. If the error is no_space_left_on_device and the filesystem doesn't support sparse files, only zero the priorities of the pieces that are at the tails of all files, leaving everything up to the highest written piece in each file
relevance 1../src/torrent.cpp:5505save the send_stats state instead of throwing them away it may pose an issue when downgrading though
relevance 1../src/torrent.cpp:6412should disconnect all peers that have the pieces we have not just seeds. It would be pretty expensive to check all pieces for all peers though
relevance 1../src/torrent_info.cpp:181we might save constructing a std::string if this would take a char const* instead
relevance 1../src/torrent_info.cpp:405this logic should be a separate step done once the torrent is loaded, and the original filenames should be preserved!
relevance 1../src/torrent_info.cpp:441once the filename renaming is removed from here this check can be removed as well
relevance 1../src/kademlia/node.cpp:810find_node should write directly to the response entry
relevance 1../include/libtorrent/ip_voter.hpp:122instead, have one instance per possible subnet, global IPv4, global IPv6, loopback, 192.168.x.x, 10.x.x.x, etc.
relevance 1../include/libtorrent/web_peer_connection.hpp:130if we make this be a disk_buffer_holder instead we would save a copy sometimes use allocate_disk_receive_buffer and release_disk_receive_buffer
relevance 0../src/bt_peer_connection.cpp:641this could be optimized using knuth morris pratt
relevance 0../src/bt_peer_connection.cpp:2110if we're finished, send upload_only message
relevance 0../src/bt_peer_connection.cpp:3362move the erasing into the loop above remove all payload ranges that has been sent
relevance 0../src/file.cpp:1403is there any way to pre-fetch data from a file on windows?
relevance 0../src/http_tracker_connection.cpp:97support authentication (i.e. user name and password) in the URL
relevance 0../src/i2p_stream.cpp:210move this to proxy_base and use it in all proxies
relevance 0../src/packet_buffer.cpp:176use compare_less_wrap for this comparison as well
relevance 0../src/peer_connection.cpp:986this should only be peers we're trying to download from
relevance 0../src/peer_connection.cpp:2750this might need something more so that once we have the metadata we can construct a full bitfield
relevance 0../src/peer_connection.cpp:2881sort the allowed fast set in priority order
relevance 0../src/peer_connection.cpp:4547peers should really be corked/uncorked outside of all completed disk operations
relevance 0../src/policy.cpp:886only allow _one_ connection to use this override at a time
relevance 0../src/session_impl.cpp:1765recalculate all connect candidates for all torrents
relevance 0../src/session_impl.cpp:3238have a separate list for these connections, instead of having to loop through all of them
relevance 0../src/session_impl.cpp:4306allow extensions to sort torrents for queuing
relevance 0../src/session_impl.cpp:4471use a lower limit than m_settings.connections_limit to allocate the to 10% or so of connection slots for incoming connections
relevance 0../src/session_impl.cpp:4505make this bias configurable
relevance 0../src/session_impl.cpp:4506also take average_peers into account, to create a bias for downloading torrents with < average peers
relevance 0../src/session_impl.cpp:4616post a message to have this happen immediately instead of waiting for the next tick
relevance 0../src/session_impl.cpp:4650make configurable
relevance 0../src/session_impl.cpp:4664make configurable
relevance 0../src/storage.cpp:322if the read fails, set error and exit immediately
relevance 0../src/storage.cpp:356if the read fails, set error and exit immediately
relevance 0../src/storage.cpp:645make this more generic to not just work if files have been renamed, but also if they have been merged into a single file for instance maybe use the same format as .torrent files and reuse some code from torrent_info
relevance 0../src/storage.cpp:1280what if file_base is used to merge several virtual files into a single physical file? We should probably disable this if file_base is used. This is not a widely used feature though
relevance 0../src/torrent.cpp:1233is verify_peer_cert called once per certificate in the chain, and this function just tells us which depth we're at right now? If so, the comment makes sense. any certificate that isn't the leaf (i.e. the one presented by the peer) should be accepted automatically, given preverified is true. The leaf certificate need to be verified to make sure its DN matches the info-hash
relevance 0../src/torrent.cpp:5236make this more generic to not just work if files have been renamed, but also if they have been merged into a single file for instance maybe use the same format as .torrent files and reuse some code from torrent_info The mapped_files needs to be read both in the network thread and in the disk thread, since they both have their own mapped files structures which are kept in sync
relevance 0../src/torrent.cpp:5372if this is a merkle torrent and we can't restore the tree, we need to wipe all the bits in the have array, but not necessarily we might want to do a full check to see if we have all the pieces. This is low priority since almost no one uses merkle torrents
relevance 0../src/torrent.cpp:5562make this more generic to not just work if files have been renamed, but also if they have been merged into a single file for instance. using file_base
relevance 0../src/torrent.cpp:8176go through the pieces we have and count the total number of downloaders we have. Only count peers that are interested in us since some peers might not send have messages for pieces we have it num_interested == 0, we need to pick a new piece
relevance 0../src/torrent.cpp:8821instead of resorting the whole list, insert the peers directly into the right place
relevance 0../src/udp_socket.cpp:292it would be nice to detect this on posix systems also
relevance 0../src/udp_tracker_connection.cpp:548it would be more efficient to not use a string here. however, the problem is that some trackers will respond with actual strings. For example i2p trackers
relevance 0../src/upnp.cpp:63listen_interface is not used. It's meant to bind the broadcast socket
relevance 0../src/utp_stream.cpp:1606this loop may not be very efficient
relevance 0../src/kademlia/dht_tracker.cpp:426ideally this function would be called when the put completes
relevance 0../src/kademlia/routing_table.cpp:291instad of refreshing a bucket by using find_nodes, ping each node periodically
relevance 0../include/libtorrent/config.hpp:333Make this count Unicode characters instead of bytes on windows
relevance 0../include/libtorrent/peer_connection.hpp:725make this private
relevance 0../include/libtorrent/peer_connection.hpp:806make these private as well
relevance 0../include/libtorrent/proxy_base.hpp:166it would be nice to remember the bind port and bind once we know where the proxy is m_sock.bind(endpoint, ec);
relevance 0../include/libtorrent/stat.hpp:113this is 4 bytes of padding!
relevance 0../include/libtorrent/torrent_info.hpp:123include the number of peers received from this tracker, at last announce
relevance 0../include/libtorrent/upnp.hpp:108support using the windows API for UPnP operations as well
relevance 0../include/libtorrent/utp_stream.hpp:378implement blocking write. Low priority since it's not used (yet)
relevance 0../include/libtorrent/kademlia/item.hpp:61since this is a public function, it should probably be moved out of this header and into one with other public functions.
\ No newline at end of file +0 urgent +0 important +0 relevant +0 feasible +0 notes +
\ No newline at end of file diff --git a/docs/troubleshooting.png b/docs/troubleshooting.png index c2cbceeb9265d4ab0ed57cebef627573c836bf79..af0d74fa6cc5d69db3d9759aa7ab58f1b09efca2 100644 GIT binary patch literal 278213 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∨b26nU;}31T(TX zQv|B!JKw)^#|`nNn@=v{JNk~-b6P~~oJWBqE6z8tIA36KqsNVN!;l5+`Y&l|H-sKS zjpeE(@m_o~#6|qj{82Qnl+_>%#E&1<)fvoJ@DY3V>V;A#m5Ckw1c|yKkxZ1Jxw(fX z`2JyFcLBo!r%t_sug=QK;_!Fw-TV0!193m{Wt*^b%4S4;tmo=Z|IZMd<(4g(B67Cz zZK@vf+fnb+L6G24XaEC@7(MzRb(E|QV`=}=$GZKXF1bO1?v_Y8%m=w`CNHUCY2@Jw z(F*WHMetZ;)Z7=2mzR@6+5na}5qgd@rm89?U#Pqzx+Bq2V;c_b)uFk&dCbA14^77A z9X^v_B<7TSd|C+-S(1MM;C%#iFn5I-K*x7VL+yP*zGQA%@9CK>!sAZut6%dP?@ARv z5n-iJWHQ46JWPMt1ZXz$$_kQqLOJ}s;^AAdf2~aiu#9?k?}0_tpmX7pEv#Ch#;;KV z6}tSm2lWp-;f4DKnDq22BlnpnjC^+P-8gSSV)B6=aspw2UeGF-@g~-9Pd+9 zS(Sq^^XDB%QWIa>wcvyWU?rAdaS#`%N!<=~87|4MqFdrKus2%>w{&!_(IuNI2i5q! zBBGVgF?ZFU+;1K8+vw4TCB;pZ=>w>V?rnzh&xdn)H>9YH_6x!`?cTp%fSw(qho1v|wgbr)aU8NLci_y?Ta!Y}=$w1uh} zWQ-_SOhFGsMY5m72n{s2%*GayAp2(BpsIxkz}S3K+Vdg$P2TdZvN8kZFhem2*zZ6; zR{B#kGrF!u>4tnpn#t0LKeJ94F}9+cy+We`jE2V0I7B{b5}pENZT>W3GVD7}H;8Qo z_xobYRIxVpvFtbOnZAEGf_W)J$pp3`3n%xPwRz9TxG|&ZF85ZBGEOlmz2cH0v*9tb zky+t@^97D?g{w+kzq=sO?N8+@YiWt8 zUBb9Z{R0}I$7dEUU%3)JaRD6I;GshwdYPRmsFp-esjrx;2M%fAY_MseOImO`mX7=moM ziV<=IWIPfIS!4G*%zvW=_Ta2y^5Js4t0cm_`FcC^y|Sx;wz>Z#+uU9=tgVXpEeaZC z4Y7Gwa8!_!xye6)YX;&S=)b>xL@s#V`cavmbqII*QFMo zGujH!=JnjPL!X8Y8nn011PpF70TO_617Gglzu%S_BlOykZ_vW%)1&Pdb@)=Rdm-{) zEx?yAd>*l%EGOs4q|Fz&R=Doa`~t$opUrk$yqMFzAGR&N?H}-9R1Q%g#8;ZrxTq+5 z@M(l|2aX(xeU&L79P&d~9VI!Lik#i$>S_MIHAxe#E4WCA2Jo1tp< z`bYj6p4v*TA?nM;r<_e&uX-2kpV;QgunSv7KljK7^VUOFYpL zR?N*inq1|Vky#WH8cKn3uk*ke=(p1|GFTv2he9@^tyKScN^&$8su~(9wb9%p+KbeF z!7WVZQ~PuqWpfORTFm!E3ZUo&^_(~{6F+qYAwg=)Ia~^=mk-oi$!}Uq?ue=#!A%}m zJdWE%S*LH@x)uCeeaaLsUORQvD#YG>-&TB5%QYc|wVgEE6n6J+uCPcDmq~ev~snf)4IN zGh(LX-np{^3sI^;rk_Jp77p>D!#f_Q+kF9oesuPlz?-Ld$0Q^3{YL`=22NVwj3Jj0 z*>BwVUF;6^f%QDEzK-Z^aEp+1LAH#KDz2gIHLx#0*GEn^%9)!-bU4Q?L6L{-TTvIi z|M&8UtKcr|${IYloU}w)*hL4MWwZ5vtr+z9upfXvi4>#tKO9^BYCZ6XlT+-vd4Zar z>^oX*kB=}u8nBc3O<)R!1ENl!W>Uz~ZhW9-xxPwe_59;JAN7MYv-DolisGo+slH$@ z!2H#^5q;-%*9eyD+I67o$SYSMdLf^B^y=mM{?TgSNMWlil*!-hXF+s{>i}t)Zr4(> z^twx977OIZb)cQXnGO>TZRyGUL#O}Vy3$eJ z)Ny@*szbpb#TzJ7&6LgN%-MI#Ic8n1)<{PU|DvJ(LgpC~GSr9ljj{W#ZkQP)uOJPQ zH&2JRRX(Scx|~p2&`-h9qEgdv4;nLibkh0r6LIKz-m`_I+9>AF=DUr$99f--p1+^K zCm_o!NVBTUKXIK>f+<(x!=y!5yg`EyJtKjgop?-=FCk6y_~QUSMLK;zF;Btd#Y_dy zS1eh%bmF`xXd7goim?c|MpBPyBOLs?Xc$ zCyWBO#7;7^w6r9XacTw@mA{+3QpcCBsS03~`$6$~GNH9i*x-nyCs=ZTlQ^$mkH1dt zuRR5RItJm5k;y3buHH0}3vsD+cDCg7xV!Z^ZB=@*TY17{m3ZyV9O4~2#DX1L%|)Nt zigz8}m2y>DQ9;8|&<6guQ(DHDarh}DCOE(Ly`^^v7$Y{;fgCB?p4>K)1AYgeH?khu z`^9Q0Al`znYt$!aaL9O&M1hS2UYaZF*+8nZ7(4JU;ei|UPN^I?sCq`+Htf6TSJec~ zNBx7EH}RKgOv_kp-MHES=(Bc;csa%W8TzOz5~|AkGy8c& z{z`2w|3kO!YD zs?ti$`OD=^m$y4ucRe7yjYW#d{P?Gp5Bs+FZi5z`(sQ^RYPHdzf!`K)(|++2-*7^D z-tQRtH}pOXofk}7)k4ytZ4<-SpQqVZR#w$MHxN8G_(#eQgks{tYISTQQ4*vi2}qrF ztPN*A#*AVZQgF|ZR@F}xa;V8yUaFQ#y>a*MZ0RI?wJXXgY$i^e=(zNsIFR<9qnF_F zI|EBbPyy(|=wE37cp2s|k1_TrJ$h61(u$~kDMMDjrGCkXiSdfnM~&K5ConyPY%8OP zypGr$5FS1m6$0f~Vd0P9SgrU7t%Y~!#&*cUz+To}AaAOX{LyUnH-}DbBF>XP82aGL z@c3lwsu9}Tzw+@{)|y5cLl=Wg%#JmEv|y0L`$JO!m7kC2(>^W?w^F|2kz>cU&>L&{ zwvy!QoG@aooAy+(`TkRApuASvs1>NWJN%Q&}XlBub%a9+k_=0IRT85=&9Nq zGoQ8zs-`j}DOG$z3jw-ldb=~8mrGZz$^;d=Kl#@OiUWZkrT!jg9=mn8L1py2QC_tM z#FX>q--6x(4}obh1OI_cvha-rI7!DB6coZX!7>%3(Sa;dFjMPZD3F(|SQ^Z*^+Ia0 z!kN|x!sQEs5hg16h_!sc*paT0Ck+*Eus~?Gp&`UNVeCVE~4W1 z=}1Iv+^)wq`$6@yPKo)Pnk!gjy`HSmru|mpkEv8i@Jkpm<_R^{uJ%0(NF%VA%$;kpi8wiBBPN7IHR|&v zpqNxgqVQy99u6lyr4@=ws#BbVKzV!g>-ULToEh5Jpvbm{iZn-$4Q z=U)$-w5~(qw(4%D+*SSW>Rks>n7eZ2d!De^YYLWWk?I)a?m7He@<>runwJ_nbc!BW z@x?HxPo66NSyT?e7mu~*(Pq;4ptz~ee@G1y=)z{#Rz}t`@>$h9LG4a}CxD*|>r4LD zwg%OE1b7m4So)gr-Yl3y3@z&r-0;VEVAUqj6Ig%SU`)=h!0RWodRlZ4dcHJ=*^+X+-cjb`dSe z$`6Ho>cOUi$Mi9|VJD~BA>-v+Y;ET8#osi3`CUj2A5D88NyK!GKC*<%0UDTMAf*7?kZ7n zt|0QYh=|3|SXF=bw`6MY2|Opkg|M1{n`5rxAe366%=BSqN9RbbS`b&<&HOIdgIK{1 z)f*ZbVKw_iRf$1Be`6eOx%lvUFQe`3j@kaqFi>+9yBlZavN7+)jPFl@2)M zK*YjebnG`d!8rEyoYP&ePdNDqd0`r|<8!0~m+$^A8-31x8n-`gn3ej8j)B>_OS-o) z8qzhrv8a>Z%(=5+4xaE)k4g>_vknLh+YTTj0D2+FU?GRF4WYi%@#Q6Fxh|Q8>tm58CvLP~a z>Njyd=`6*hHCaIP(aRIOOZ&`QX+|S0)2`k7>R#_ZNZXb;^))j|TcOiO$z%78k?kW4 zYwS5vC};qP2bbLWz0Q1`^qCrR{0xSB|G!z#!BvF=Lba;p~d;+F5TWkDi-aX~B zM~@nHAJ!>P(uAADRkw-{&i!0?hw9wk%<<eIx(2=v)c)})O0VGBKXX6FrC&AQ zH1y1eZD-ybNzdHS-XyHu02Td_83lSv$NznD2FWn~{zwAGa#k~=p1j;p{|SdT;rsz22Xm>KojrIF=$rbgLNYG#0G-wOqn z#?RW#KRe&b{(9ld)(34=x)p|_GNPQQhqZvQs~XY4RI22$!aqGnyq#C==za8<=bV&3 zjpx1jx1}=R691)0EjRSrQ5uvkI!x!rmxxO%xIyRH8p#J4e^wyw+tYt(= zASO2%%Ml?Nw;(S0P>)kJ{Yn*3t?~>9`!G<{ z0~3Gzg=rkdu6b_prX}mK+*xph#W5Fvu*Ui_015%rmFnhhnSvGzJrf)ueS$EHO8N1v zdgrk9+w4a!tT+YxOCwJsgI4FF{XNSgxmJZyA!n-IpTD*v!M;4WulcWKCN>h5w18Zr zz~eKB9J#B6Yso^snu|5VpAy_$Z0AcVHPS7EbNj~Tu98zOGKRJxg0E*t79}ZT)fC*5 zJ%79H7!&hnqszhs596p|e+*u&)!T75zKx1s%#6@$KY#vYj03Axl(1r)oc5r&cs$rM zm_d=bZR)vmB(&RvdY2rN(jUR;p(_sMniRh{m-%t(k24oDw@-5!>+nL;ZTerdFS0uA z83E6nWNK>Hc$N>2S)VoOkweVW*7G(!v&ormuB8|CXc4we0;gidip$xk$&aRX605VM zzaVc8-eR)(*@@^pC+)n*^ZgQXPyOngx-=8aQ&E>PQsaA#%-Q7lbqR@urAdE#oT>Su zn>Xyn{|5%;f0lI+!?N65GXILmn`c^B+BrM(wfr0tuYHxrW)}4w8G8PHL}cdn-N91M z_REc?MUAwH+vslf(n3DmKk%Y+s?~s`c2Y`51%Hw+1t-Cme8*r|GM^P_Eveznl#3`5 z;OqcB$?SFP0uR#vM*5{RK?!ikPVY4E-~RytIXgKeB`53a>520QP3|L$Dxq>-uHOS8 zl$6l>2|3$2auEktRAl4zEB703-RBvpEU`L|WKK7CclRGsa}@ph=TFP|51} z?w?#Y>z8is=8}Q}$Z0)vkm1qBN2uUqWH>N=Cv05Bxdl4`(g0jJ5*TReegh!`!)jyDy@x~%@}ZwAvY#b! znRYuX`L&|Xt>7r*nQ>~pcIc?@!-L&#CLb#Fc8*u$dY34s&u8Fno|3^tKtm~olDNC`pquIvo@?fR1-NV?qiRj zrycU1_1#e7p4z*2T&CXp?5hGyWk5*!i&(-{$A2>uu|F`E@|e{w8w>PRo>U$`^8Ra5 z;(28&*`SLb9TEom4+^r}-YD7cJ>L0s){mE6b?^nrgzC(=Zz@lHJl`?BXO|amv;IEA zx*r4S2lzsNn~-69*wn3e%sOMsvFX*-?>%)vyb@|;vA9unI46c>PZ;4)tP}M$-AI04 zQP8A`j(@uyxgAiw1kgozQ!<>$vxE-}=L5p$-&%^Ak&fd$EenG6ih(cIwtVL%cP3?E z=GL&QV?P^g?(^xFgSqn;=Le=Q+XtJzys8`Z&D$!rXQ5H7Ur1b7-w@5qWPUewnBzDYr>^*gk!36lqi4#Tv&l#QzzC+y!=W z6*B330>+4>g<-nd=@Iphv*~)&_voC+?=sD!s1fg)rKflBrLfxHbbY0vkwM(Wm+l7A z*qoWcaj_pv(Fae$;2spp%0ZR90Uha~Lx+Cs`asRe!I4H){+?UT;thHWor~v$s_RKR z{rtR`f;AxERK2H>Ub%{+%5Z}`t)jsjH9$wt3nx~AQ&_WnsC z+7kl_RL&s=v1>=4TbQKnar%|bg6c&|c6r9d1I>Fd$<1!8Ux(O=_?l~gtLe2gb z_8hyksD%Sm?#AExtkw5#v!azUMJwljE3v&@GzukiZ{Y? z{od=xUu%8P^2jrrO?n$_w-+43i7#Z&75D+u*zs}OcK#k{uMwXBC@|6oc-!pqsm%+c zy4bkbB|uF zUNNO@{*zPj>l*a!9&Jc>i&eGRF*E3pox_w~!`6PObaR+rY@If-Lv5=zs)Ih<67jEH zM#<`0goQg~p#scfO@((2@hEC?EhPdqD|RroOr<;QJ(;aT&giBS_^06lznWmNLCH-z zEqJrYe$JS2<19`XAw*(+#o+RsAkEBMtx_YYKK*{$LJ~f;i!qI3{8mwyVl;KGZr+W& zOF>32H7gHYs7d~a!d`X&%jbTK3JSa!6Mtcj%Yr-Q9(J2vIhL$apA6!CMs~oIDYovz zUP$3xygFwdRpG}EAJo?zTsgFT)%+1gB^7Dr!4rGrK1nwHKFjRQ=uBgk={Z|9W)ylF zsjUB5JtWHGd4W{OmYJtE75y{~*}m!9(QAjKW?kMFZ>#?vj}?oMiBB(^|C(W3JVbBq z(^F6P&5M0uWbD4#s3P;5+?Dj4IP6itMyQ3Y2rZ0$qF+kY$_?&6lP&h}v!VK%JeOBSi_hMQE~ zW9mACqV|p%C9U$mC|SDezIdDvT&(%e7>kh~%~Fl*Z%#MaG{(Zxe!oLd*i@C2==!oK z$1l0P41N{Q+T>>*VvwTz;aa)FouR7Uii+upDCZ=8TU_QyyFB;8xLjsFr7}^=WI3K{}Rt6x5uTueq*)P-8m-^U)ZzsQ~+T`wAV`E7l%r*-9oK0oyRx1X?5F|gln zYTx&|%i3Y*mnWKv;wY%jCg{-{vsWf$sOV4~mGzdf|NGsG%IBAbLhgj7S8o!epStX> zg5nJdarBXBz9Rl)oyjaWNew%1+;0A>Fy-D;zWuf^x3nMSyP@R!`4JmGt}p%cX_Sw! z0SBE^_Bl-39rY$gMxlCtU{OM{)~4!6*(F@&O4kkPs>Ag1oFa@j2Yi3!{e5fdsv`7D zaykrJ(j%fXjGg)5i&aeSZO0d#_RCkTv|KW4{lT#MhT=>2y_FJguKs6vciGlcjgq`& zgOp@i&d?rauvccZ%}s@W99#L%I2X{WbJEr)s*ZAY-A`SW=`s7pzOa-1`zwShZPe`? zJO62Rzcfe)-E}4Woygk+`gpg#k4eBJ;whr5TKbWf%R(@LW*Crq;*1cX=WF|f)o-0KE;L^fM9)Ay+K-5b+w z^doH7h5of?;YCpA0YZnTH^s#BTD;|=7y9Y5qil1ot(VgrS-)oB=ix5hU%+w1?k^0e z6dIl>x!feoRFpjJeZ&1it@pGxaxv~3bZv*?Kn=fy!9jP_tm;CyO-@O08GSeM-EH~4 zhojx)e+GHV4~tWdTwr{pI{$u%=g@D!KGS?hdfIHE`^w3lFMO3MS@)9 z_1m}D^s2n?k`p($At3o+RsuBTbm{SU`+n->FvsxcgFq>@KC`>{O$goU@VyfCbAW8w zyLT*0`2Ya(WyZl|l|ilxR6M>YeX%&Td+Mav!DGh;*JYnSzkv$w_jLxw_Q@7NkU=0g z1Y5tJf52jo%zmZ%;3(TITb+ucTl<-8Uh}XfxvVTw&v=?nw%Y7Rts`yKL-$wT?5f$# zW2Nr%%AhxehWn;j91DK#-NI<-szzie!jZ|CS<>S%xq-NeU3&{#R_XER5?xC?hCYmB z$*i;@;P@b)&*4jEOFt+qeEny$&uyEl~ zSz~kanPc|esWg++D;v9+DRuVfTUT4Kc8ICofSmIoyxz!}CgapSLxPv>8tQ{w1%5(S zXU_4-vsDaC4*dEYa`w0B&Ez@JT?Y*Nk##FD0|1;EGtR7ynAtIIm z3kTiNuU7D-0{WY~JDkA}sp zFT2?edXbv}p;)&&-_R;_eXG*NH)b0>&tNa7eM&LV5ge89Sd9Jaf@9Gh&upua=Fj80 zSzI(;W8yGBIc@x&ULl?rJO31(a3oUxJP!n&WKS?HMA)rt>aUcp8`pc^4UOyjV_tMP zD~X#^Hm_E&LgtA^J4#U)kpAv3x;tQ{-r(f21KH6L_eXeGk8m=+hOSFmkkH&M40>5YvUY~l{x#j9avT@2D87EO?MoVg&lfkCY~=I7*OC|O~VJo_gl(f{gMIN zR^$6S8Q1IxlGN&(jtsd3<*9Rr2I1%FA@&PnM%$+b-W=%Bd1k+Xg9q<$cj*qf23Rn- zuwY&ZBaFQ<8<0JD`m`&Dd3WAO)s@2x=B-&D^x5&@wwtk&E7VCRzh~Kl6_az#U+?Ml-^m(sTXDo3gEdzqFFw>0Z%S$Vkz8&v+R#Y}lP zlB!b0-8~A=zP*236M95d_4Ai6ETekBtSK(a(9p<%>0e->HsU?B`(X{M&7Ln$XSMJP+VScR1P_z-`WDu;O;K+2x&kbwej&wF_m$0znV(g6|!xX3K z`wT8WlWwSkwgOJ8eE(id&XeCW8)QC@)6yz49^oAq;AXlfq2uAW1xuA$9`$d)ta1HUqNP9oQTa-C$F>aM6qIkeMA zP6&>6lXFe@5;9c19pY_lp5o7(xm&u`#k}+4a1}V~PN_k+jm!GhUGJPUB*0>97fBl@@gjq%{lnv@>_QnT{24rO`0yt5V|!~{KM6EF%dg@E((4YG4{Tfa5*tkQ19kl;y0#b*vF+1=Nqt0-fX zW|mv{0oG(%T5b>@l%e6cKUUvH)!1;ur~bbeSgzgYQuoXF5-F`X+wWWZml@G)EJI0~ z4g%3%;}FLTn)3@Nvu?kGvKa6>I78)h|@3V|M}q`gnt*`f#6GVvTp;$7I*)BQL9}e;sT5*30hA@S-k@LRHPIXP=c_ zxHtJ+YNzTWfqo_Ou>nU`#2Pw&?Wq-S`$crx>fa-$qN}%?8#WII_c1-fc3Lc4sHYlH zI^)igwPaH05u{PtUc=09Qm%kld`l8ilEX!!brC}p8;Uc0%6|^)GBk0mY=(@OmR6Ri z&y)`{$KAH>wcED-u2TC!7(D$$SIM|>x^`s_Q^k3dUDNir{e6~xU3lwLrcK+!xLZ`w z^VNxnBoq{U=FDNxt^2A~t1K)EAoE*VRz4M>xmKUJwY;LDfoT#C8CNY#TLI`a9Z&(vI0^nDGyZW4shg~8~DuxRS(6&;0l5Z=`T5OL}by#>kF zM2c-{;xEs%vANRsDdZoM*^ehJ_P5ky1Kn*7qo77c?k05VS=z9=>20qj`_Rx(RF_Rn zO$=8JUA$sNf73N%=#PyZdr26XOVD5cNK23|@c@r>OuH;Q%A@XTVbMMIH#Rk~#NpyM z>#iL{(OUvvsvX}d=QJZqXPfunRR6cqF>?xS=ie%6*?pzIX+)6Xso@jro~u|o zUSr{t%1?8@Z8K79`gnCu5C^r{G{;$BE@aKwB3@)VZ*6X>)ten%N6nD6W|SBznuo$j z#I;{LUp+|aJ+1!soNQ;PFpUVt$*SZ%uuD&5p8N9UHH6AFT!gg`WoSuoSy?g0N_vnuD^677>MbO9I*wRF!2*U>WZVdCOUbI!~5xyGL za$D@}XI>6%=5GOqHPW#zP!Frwzk86L-bYvvwTO|UMt$xvG*GP(D8+HbA?Z0UjKT<+ zEps;H2-T*er%oAeDyZwO(`f7Ex>(3|zJ7kf?jtq`3JW}C3{WuZ?Ow%Y;18?P$j#vL zsbTxC)dfnaBGM!O(H`(T^-xXG=5c7pPCXU(6u-pbG*RXY))|v`MJ|qr1`wp%lc6V% z8EiR2&miuu+O)Zek$x@b*B{yG@~m%beo^V>%a2kMRh;ZL=nWh5N5M8E?#9a?M%-`I zB=eTBNK%Oa*mdgE3E?vnznHBtK0JL)i&A8!OkCQyodY-KebD@(lIn5Rt>L?VjAYWc z3zEb2RO_jL>VnQu)6f}=h(`TjY-AL8?lIF~)PxHkoLt+-J^7eczpXy*-)>mWol$|| zYwN4hzLdZL8_E}TZOQ(rsH&-HIcd@+nV@G^#NHmheOuV51B8fjyMjtiPH>}R5t66* zWR$$Em_9YG>9u@hd6xbbu|B7|cHXrrjLK&p?h!OP*%Ac_!g)k6|MPH9Wo4YY!g`ux zsdBCFw3bHNn)8iEBfrkeOIYK5d)6?<%_ns%G7OtHZ`$dZj(TzVpO~LD|mnm+NhtruGwQjoc~-EX}ktCJa4_Btga; z@sz~n!)3>hgw4Y{T*e%Wo+Ah7>rZR_GvEPVrL!uA07kW6(}4zBtB1uXj>ABPB-2^- z{Q2`n9qjKbhV0JH_;s44csf;C%~h2;eR?!WNg!v{+dXZp-8;xyH@zPm%1V#Z21Z62 z5j1xaT1GO)?XIB^r5PmO+T5kv#GV&2GB(S2&Y7b+eem@owTGOsl>D3_?{ZEm)>f`K zAZF<%D@&v4PsZCH?_xvL>J=8}@p!97YV%pK-MQw9O71O>>{T86hlK;E^>)v+v%A(t z>u{$`B_Q1YdBqJIhYcATO)**eKhG*URgc$rc~$H8h_(@GJ&Y2U3Bb{O^8EQaO^Y!X zj)*4d#g4EXDWyMbSoH8v7y-oUi1lv_nE3d1?1kC$sUIAgyqylm_h?I-(0};v2@3PR z+?D!WJ9&w?p24Y`6L;rzmHl0JNZTxJs6>RXZcgH$j{#c)UrabtR_ibG(r>DEbm6-- zZyiQzwfV=~Y_2SsJTlH)ZxDQhy8f}ecL&sN{QsO2CQ=&9nwvi#n6;bjrRL1%PhU+p zRlUvB!oniy(>Ze|{WR6kI*Z4{W$PB1EdOXV%OiQ5ol|fApE|W!4T{@}U(#c0!yF1k z^?IA1r>D6Tgv++P&XGNP_rB$sjcG)GMa4;fUhf#u@#&LQMqhg=Z5{ZXwv4~tJ*~c} z%XWK;IqWMuXy^4(&1T{houift`weu_w!SUz~EqV&mH47rVgH5Vx#21%dx3|QJjVQ3&`R8p$jI;$_Nz~u$W49#u;sc=mD?mzFn&2^Ch zyPDRJ)>p5-#HzxhM~4je=mO&Uqr333`1*?ZvTs%EtB)%V%dg0pJn`{--(PXnLF`2T49 z{{18EZ+3Igw_dum35_q}Ee4a9vmTrssth)Tg|%IuBsVe4&Bn{svYolMu?|zUThw)J zwgkGCh1f-r>(FVgg4eOLW43E|-V|F?v~Py^=gJrmo?1Qpr6rTDMUFG8`e1GVNg^8) z>~5i}-gMxf$k;kejd2m#(QnkXwnKSwN3MV-Hh?B3Y%dC{zUffZcr)0|=!rq35VvEF z$jA)jLM{|ga>l$Rhcs;tUmJhRI;wt1(&Vc7UDX<@Ctg1NpGZCNR)_NElOOiQ!j(dD z%8|lTSs62pJuZi8H;xUfYQg$A;*;fI(tAWSLn}|ORA2Ibe))A|{)X6%SG0Z)*l>7Y zWA~*8?)_!r+XO!rz1pmE&lMN&l_R2=@{!s)`OkqY#(y45G=S&$3Oi*CSPk}6{^zHV zo+CWgUiHhBS~}$rRYT=rPZ`?_eck`B+gGVr;WlNtZr8_CtgLM{4Ob72G2A)1Ww2ec zxgrdHl#y-RB>N}D^8f3~ZBbpgYO&=4^+f53Evv1bIk_y~!G3Y`o4}kf3{#k3Tg=Od zkYSe-(4$kwj-5|PYA^3Ur*eRzd?JC@zUf2F?iQ_=xiU)AMqVc}Fw-ZcO0H-2I6Kes zMbJ6YFUscks5LP?!J(GjP;DokOG~O9aY*a@Z`|$Mq$2rr8g21uh%(d{>u49SZS22H z{Zs4bGtwV-oI7oed_?BZ3oU0t{RV1!XAc|}U-)8OTi+z_v^!Q+Z&G{w`$|X6J8olW z{aC0zwgl|UuTr^P5o@Qi<^G@Q6M;65s??ez2fCc?IJRy})GsX$*Tr*h-T%JJS!bHZ zmyDH=8uBbR7v8X$tH+vVP1UsIWV8Ac)yhW^AVoyeC%5-FpO+_N4m0r2YNo-nxJ#u! zN_U)JFvQSsSIbX4xMfmSo%2##Sn*%lzpd@k79`mk5zJ0(`@Kqc%2XiCpa>-;C7Th4 z4yC8t)y@kD_+mXtKyU{iszDe>$e=oCI3*^UY~3DOwKKEgPkfh^varb3G;IE{{~762 z+gBqduk)9lE-77;Yt_1(_~KgeE^|ZLTI?a#P$VIgy`+t@!YiRGp$V}4Q?`^s7ALcuyLam1% zL#*;Yxb(M&|36sw+9j4xWHn`nPsNG6Q9gppIvwE3ToU5$Pmj-G&~Jdq7fF0ve7s%0 z$*57Qxg8hRb{rSE_i~=)!H+?7j!7Pv?u=>@HA3a{UUMGKho2-n2_weU5(tGg7aw^z z|2x$A(acP?;V{lh#oP3UcC(Z!mIl9>LGXVyWSCN^!o$49>Za3>7>+Zn}2Pgfo%UU@{0dSyv#thjxZI>SJToa zJa_(9haG1|UDqyMw)Y>4F=kcOh0tb$KYzj2j&(NeU~5O=lqoJnH(k{m6<0owkGU+A zhu7v=yM-+7xH)t~cw?63e~-_-=F#0#`%TyGxHII5^}a5Wnx?v`+DU#%3ONhAt`vV4 z|LWl1I&t2wDLV0ohOTh9Bqu5AB0X0K0ybR+R}@DUzdV2L9C7X}9BJFMx9?kU;e?@o zgFV!hDN7MBGJgl7L?rl>xh?P03a7yj=jX=8NDWxKL)^4;4=e2Wo9S@E6mVfiFMK@m ztzE|=WW4aq&&Neh8xmAO9kYm(IeYfA7cag-Nc^qinLP|T_n62Lj0@d^2R}ic%*moW zzf)Yi%jYV7@WMtzekA+F(689kqIQ17h+j#Gut!M%YgTUWVKza(_-9T1`lzF~S@p+u ze*RU4{BgI4Vy&bcS1*4Zd)et8%cVA@xHyB^{%?YQ?A=QikGH-#s+-jL%L&PtUgsMF zQXUEsC!&K*+aMX2U(>b@(%)$Q7sCk&Wy|QzZ|>`sj++>kyx6;TED}x#KBxxew=ci~ zSd8J=wZTce^r@P`mc}>VZfvkR`UaAPx;ucLAAAB`x9jg77aSV9%+cuw4e-K;yXq&s>FnVt{iY?| z-YoNljXIro41r*{ye^yGvR(K1Sqogp(QwR;lwF=x(NFPOL6qCXmWeS}3?kiD8F}Xb z=2`?WH^Iu#I;0&UJpS)ddUgK5BYElP%jRm1(oHozUo>3T>yd)o{T^-S51F2cIib>P zM11H1Q@sV*I`+2?b^_D|o^w-B+)>v~@oGMM`O;Eq%eHOaOc2q<`&66X#{9V;412n} z`lZO|*xgFOOzoH}^Bc#&rH1<~2kR&d88~pGNwUbS_v9sXSKN}DQ_K&m&TFrqb{5+B z4_@i!hUFLjJB&wvTaV)E|CQYL=_(4sDy`}J_ZjEL+N)O2epvBEY?Ra|<7?Y`#*Gd; zoC3}7fBaWUV0ijH`30tlQ~M9kC=?5qN!4D_?)W77=|uYwkugezBPUJ(roZ_lsJAK4 z60dIEyZ5tqEnDIl9E5zk`%F$(q;=}m3Ag&|A-A%Q$A%WAnoHcZn>Fjw?5pe8TGVot zNde07i|pTc`+}h|Mi}wi+oKEhzahTI&GG-GFKtF2@=d2M-0!QxUzlu(KmAaj~x$W~#ms#4)k(L{zGOv}D zZ7=d{c=wJyHr9y-8`eBaPEICxDKeUe^HF9d`!^Y13ZMiiRS+d>S`3Uf?@jmnl~OO}m-rtxKM@n+y<=C!x9nNINg-;(kHts#(DNUrEGBZ5X}#Ss zX~5v%J{dAIb>c?TywG6=N3wgLZ%j~81C{sa2kAliuAnf3;uafSIAm_Wur2kWxaf4C zUvqfBkbi0EGi$GjB~7+J{m15iqj1Z$27m4`h=`TEsf)5%!fjL^TmJV|uuu6fx$F|&OvewOi8 zlnQBUy|jJ1bd`3JH9ibgvLZ9Fe}A=-jhkC2po13J=<3x-#Wr~Mu(=BiRXsk}O1-aO(eBg;2}Wx`+iczUL$q^w=yD{4w#GhLe(MLiQc zOGMzdnj{Lpey#gA;X9Yt(>OmsQtRED6E<(rBIc?V&7VI86%e)vxK1sE5XA}aSVqRP z%o9{~faRc({mgIT%=H~37<>DIV6DU_7gy|(L7CYzeSJngnp!-sPzu1Cv8>B<)px6w z#jJ^?OP(<9jo7m=W@jG-7g-j6!vKnDHCB0rH>d=|An~gXrli!-iQ2!vEVy5%)L$e1lnN8542>MMMW#tSj#LtobI=2R}VBZdKn=Di@)}kxREIxS1@aO z)C!xEAOmWfoGk;NmX|+cn+FOQcx9ss9kaUX6WV7m9!_@}9N1Zv>1V6MKM&yhO;LH2 zel2Jes|YB`IVQGCLcY{@uG*w7GE!LC-b@I84&G$h2R^(jLPD4@fup@}{yfp){Kbop z@lhj`;)+3c5+o04+C@t0S>Q;fIOm!y-4*it=VJC50iDP^KK}l&fr@{iadFxKjEjGK`En`6YC3Bjv^Jm@%e|Ei5La((;Ou=~J# zGTXSKir6=tnP)4d%8zCn2>&*~$7f(u{#ht4+a+~N&!)C|SpKA=I zD9od=F?QuQB_?uEj3xP@iOH$FcaO!js{k!n%@LWtl28S=_SDpM4Q?WlT=MSrxi+I2 z2vnHmg}Axvkx7MlXIo0nrREf&K~Zjoc2}HRf>%wcr4luzjHEx10bUSoqG;(w71D=yEv9kMWJ1HH&$@lbwmw zM~@$OCp`g!>Gs?VR=CAu78Y+a6wH2O2$2!|P%x?0CUL(Y&Mg1)T~05)pfWNtW>Rq7 zN+ScGVZtP(3jD90jX4!iDU5c-IK zXr>thO&vs{PPRRAR&w|GV5}bX#;u6F;OJNs%xlPY_WMU5io>%=^h=n!Cj&x#@rNIJ z|K2?gkI$8GF|03WjKkMc_Td_3HOTu4`Hgbs0&j%B$pth}QJIY~$NXHA$4{|&o5ipt z#`p~Aq@OxH0ZlBkp<@?6k6;eh#-^Ovdj9{V!CIp5SGh>?gc-q=-);iN8gvXYjvW&& zF-MBxABW?24Gp(~Q{&eac@`BGV$h+loaWuk6O!ov)W^@yna{O*x39ndL+BwKF=6!S zn3!m3-F)(7pO5^PMW%z_su{P{R!=hCQ(7yw&u8{j_MKCC^Xd1*#aSARIg7}d8us_{4%KE0792}Vd?<_`VmO$--^tFdlKs{~0y8&H zTInuIj)=`)z{2=kZW1rzOu*d$=om=C!~HjQ5hXKhb6{+HP*2NqTgW?gbL-dC$qbPU zJ)QFxF5oDYPIM_KP$$-6GYN7aBw}WI=gy9Ty3<$mW>6Ynokk(V{33GjV*Up_<(4e5 z-=Q}{;!cidpC*-~REVtp#CRKF0@BDMUmN88o?+hR-2iH2Q1B2i}3UJ$5D+T z^HHp4S+(lIxpOb;>xU6dj<9ODr#Tzzul!Nu(jf{E=1;!9noe(ucH!d1`ge;WedM>K zg~dB^Ss3$T7CI>C3ok9L-%*5<2sVHrf`k{f=FXivW19!;5+z5X@I&?fE`8N36;tU= zr>G-H0_2+Wy?rD_Do5!t5^ZtK;O_|o?c4zliSmBQZlc?r?)-%lj9O7uoW@qbHO^Jh-{DTsVOTC&hkdM_#LIMkXTWjkb4Q`RU4;-jP z^FbtXojDWaaER*p(b+{F=vN>aX|OR7GCLsPws1NJg5lt^^T&q%6h?Bbd}C_Ka%^T1 z{2Vcihun&DS9p2&_m6i+H8JdmOyeOsySm!d0?{)^SMxCjStUF}Q#$*KhzF}CbEg}(&WEvT)ntzeX=eYrYehJZg}`0(zXJC9;zFu|3BiY0kq;IO6% zL(=UHKhfkN`?16&VdP>UOdTT`RAAJP)j3hU`1!G0Xyan$#!3N}UaUrAj)IIm#Uw5v zs~M$$+xQvm{k)U;RQDDuS}8Gb4W&|LmKa(O%9e;~qwzn~tE|O5)Y_k2Gz4T}6d6C? z&-u%D_{jGG^oZ}9pV4;d^N$P9w?sy&ML<&RlEL9q{m*mcrFJLonXGOx8jek2DxPNo zt5Nz@WypTsBxi0pag>x%I?6*NN>u9In@R^j7sh`D9iZyroG3gj=^*m+`Kxazm-~wh zzCIN_!0Q-D%y1f7YI^e{2!$A@wIU^QcYlh%9a}m8&QHm_>Gnfn_({CDWw`wCP6f*+ z#78KjILqHo=-6613?1TDX44;7f^ibjsfGnAU_?6G`T1eCwz5^Y9VQhru>=JQT1b#i z70oxUxqtr$QHv4H0#qPEK4zycCwD{VE?p|i%cXksXu~6qT@*&Np$JK~rv5aAfBoud zj!!ASLA_MVNI+rK_4rf0{p=7SL(De6$;}KIT2NVCoz|M{?ze_>2xtb1*?;&>mj9am z9sO$SmakzfB4y&?7Kut?e}DeU?Yh7;S7zu_-?PXBkVUTcFG~dZvg?94&1R{(wSl7K zdb|V_diQ4O+Aus%F){;c(p-I#*HK=Su;Fi&pnDB+n|gKtZu2i+HbG`#vGK1hG7Y9$ zCgKSB^Z`OXI#9vSHK^U4ydG=SW!|EUUK>=t*vX@C~3Usqo%7Ot%m>>!BHY)rX+0F~Ztx68_oXJvV1JCo$k{MyaOrVCaz#ql~P z0B?po9wDojc+xCY{q*iczR#(d_ojEn90}HC+CTpQ8hlK;Zk{o7CfgY=_Zl*T1PfO= z0)PLzwMg~MT=D|v5Llo{j~~~oa!Hy1gabAs@)E&OyAL0>wJi*vip;K>Gx)JK|4#L`nLL;7Qc@$Af82ZF!f17Nm?j>X zRKrrDMc{Mjpq$gEp*EVK-J;w-iONg<)^%pe*r}&MI}(F1pZ4+slbt<(KB?4)ndwdU zkFk!f&mY}-L4koli{Esh)aO_GpC4~3);HLcgrMo=*)o9sk$VuVko=VrRTzaNt6#o6 zPD@KmMI{;hPG}QF*OD2>5dkp;70f!UeErQyZ4bc0=z{Q_JfEetXtekNpY-hL& zl?L&2KjkKHGNDYojaoXn6YC*_-y#&{hFEnlKF8{ys=~sEt4gBsG1W&Fpj%aD4Gc?7 z;f^_G?Mf9Xk!y7P(ysy(34g?Jy0EMk5l}mJc7y>WsRJTNadUiqg~jJ=;{+7}4O!O# z!!NyQz21zR^DDj%Y+6@@@N5Xl8iEGbSKZvGUaAwQZk}yhn`t&_(jfd!M~|+-K;6g) zNsO_co^;zfanao-Y8B=g=1@;sN&35DvjYfc*UoZkAyVq{<*zR_bQ3A@>;DhICZ@cc zFG5+O{@hNd^q0MVAmlBE^K;YFH$K!5i41VF@BpWP3Tb@M?!!X@)Srksscy%=rCr8G zfWhbGN0AUMU%Heux8i4G%AP$lnN8=&(oY(`mF2OU3jAwwb*qjz+97cOr)?@&92A68 z?L%ZJ&15Q`1M7;?59)Wjl;seG_J~rCU_uSe@WxMO!&wlxyTOJ}w2B&vN?UM?Fz;XN z>**<{th|~I=FXjRfMoo+fqCf`VyA8XcL}NdUAayoo7md0Hq=euNzLBI8_~NWk;bzN zN1wv^toj*+e*>0%>1k<#=jVkBOGfTRz6^vDRS)?4UA2xtf-xJ)AS(-NmMr;7u*2gB zH3GR6#P{LJg%RyQGcx}jtq29auORVce+|B1d=bC-^YpBwir^)W9mo;sXz zRxs2cNFaG7Cb6(jS6h3IiM%Mh8-N|HkJI%7-^zuhhN09HSWEmX0yi<~vqM9|Zw*Ot z4bdJs6xCf~nYbw65RieOhXaBC-OOH9E|VGD|S#ax?Hti!k+!*u& zk+LP*dfK$P{MHP^-xmaDJTsSzq0KwFs)F(9O+S(rRmz|UzCZKFskm=}I$(1zE_4Jk zSRJ0xlkCz%N@|#~v3ri!Ex|VM$pzMT7uN+QlpS*8n=YlEdk1TV;o}c56*vfV-JSynJ~cFq;y6Pu36zWQ{zb_m78!yVn7Xl7mThxr21dAZ^mdxH2rk7?O8 z3Hdmz^)YG?RxAS>gn-_(_Ah9eo^k$s3q*~e$7P6!DT(Q0j|r2voH#z|IA*#ayhvbd zoB_n~6B|!w`tZ#+;R4vTYZT+FN`3lJ;60_tzkmO1$IFqEXT0$~WGpZbgiCIl2iqwh zJu(rL`n)lZfp$$ z1@|;3Q-SyhdK?ZOrtj?8SAt6v-V$-KMqo7s?fE9by|!#$4Lh^aEsiJ|?-4eL{mti| zAOduNt;7R9noy`Qr;8XnI zPgm;<&&P0gScWk$c?d(+*RMgG7da&Q%2PG{0|NSw44jRbt(Byhr3t%Rc@(g@Y+(|{ z!u6FUDZ>Ow87$zwxVR#p7vtZj&Z6ZDPDoCk&D(}jVhnArNsIxzw+J`AtF+OIBqSz| zUGXKNL0*(I1?QD(6Isz|KjQZvs5bhpZv_fR;gA3Y z$!1utLAD(y(r{>tJoU*}B2ghg9_u-bH5l~R2mIiE)>c(5K}K!mD<-lGsJoP<#cLuv z_2}N6pYj3p4oKv#-4J4>{@+-sa+SZp#M6;+j<~BqH)}+&{13gB?32t`h26zt0gBWf z$eD1!oMI9e&%rjCWQ3{>FdpQ<$*KdsOU!VURf@4HMpP1?;J(OfH5Js18*5; zQ5nK%z;c@}!s+L%d(N9z9IVC{kx5BQD||hK?@#-RF^JED5{tl62jx)4sE_d^F-zwVSzapA_F7y^pHRz_5$5}J3=8+ATQo|2>D%;9CK@84&H zBS|p&=Ru9?S0?mI*R2Z(2uR@S>DpqO$i?a1r%$Bb3_Gk)Ex^J^ZqX{zQlM&tLlT%f zKHurEwl(~=xDbced`EH`g`CyJ-91IsJC7e<$mTP4Ay91J&vFsiT@K|(E{oM`0ATpv z@rP*Gcvc~Qbr*ML^wP-*DH)+E45g;@rWenjuRHE=`xC_#h4c2UTSEa~}@7^fHnNgw#lPI0KGSBVf+76PG*(a^9(Z^l$1 zhmRh;ee-6R za!;?XxSIn?1S}}nUlaN`0kBR9(uc}SmcE@Zw(t4|mGXXsTX`iVR;d^x*8_r)W|Bx< z$*qFM8r01ZAErFvCBC2)<&=HYviF57Xq zzWofmq_|PZ*?jITOiWw^18@j9u7W4B=F`tzM5R62!G_raIJ{xn+FMq3ekLqUyu6x8 zmjw{JKLi)4F|AHZS#|stQt1gBjN{|)2|Ex<_uMBZk+t%{?+huI956k=7Yc=~{f;1> zWjpBVA5(2Lj1(o8VYbYY;bM9Nc^CNHmebwXT?_2vjJs3mavg;GEhOY}Il-`k+fP>^ z!FWNRDi<$f|H?EjDh)-tKtgir)A3SPT5S6Six8Nw&x3tAxJO$gW!4_FZ3Zw0bdl|2 zd{@Ni!sV*Xx0ceU5kSj?&h0Ba$Q7_HKQRw+i1a7k~k|L-*@;u*?~|zhNBEN>dD}{GyAO4^m4?lWtaa+P-SIV>;fP13eoiV-K&rqvBG zQByD|AF7HY9ND1qcH*>YLnvEKO~dF{(qdJn5oy}?n=UHVvO+}h%a7nHa(d>2qDUFf z*|SeFBS$3Wsnfg=iAsHW_@6(2rXvMo2@?dqr9~_hM_L{KHpLyUy$o9N9|XwuB<6+O zX3gR>|E3gY|H!{_ePz>N;ySHAsQ-EQw>R8OR^o;U^>JVz>n~biT)>e8WN>Oi+1=;= z4ed(F3>c$J2Zg~;2sG=1?jp0gc)eLB^B)Y8-c%4Q(^O68m}(x%gB;mSX&B2i^G~0S zZI2(i=ANF<1z2CU_g1h>bNBvZLTDqfl5(()ju&sy>=c$==Y+XubMqA|n#l^#K>VdE zJfhi3Puo{*+&-R`ra`)f*`_%yGpMg6W_|pydU_Z2y;WhGP6s#lEVJ(?tN%bY}%j2{3Q=9m@;t66pC|Clc=P5 zetsiS?UPJMS9FlTlSflhgknlTLH_rDv;ZQ}(pN8DECI2w@H2hA5n~|7D4)2V!aXlG z5Do6KsN4K*Fp1Pbv~r+?)=&X*ZAR$p3vY5*2VGUpyNwB)&E%<5iLv`OZQ6tvFTanQ zqpYZSAFvPQr7>_Iq#h~GerT7|IXUKvLzXTD>avCG`mI|el5Zezn0uCijX3^NAHnL; z+}!vE`73E;eh?YclNa*xgh>kF7D|)Bh`2|l9%#^cy94$KZ&obt;DCrZ@behDd-37m zbP${nS{}3~$4YL5D*%Y;K`{9o-k+$0vratAWm%#Cfr|Oe<5RxYgl3Y4K6`JJ!rN?x zp5Rv$B;di2OX*Crk>(NbDw=PG8fof(g^t%-%eDQa4+E&JCT&dbgquCUIuK0`)nA|Nq|#^9TjP(l*mZM2t+vpGK|+`<|iJgpZQ> z7Wp<0>-zdEC01I;?AmpThYtz*{oVbcdV1U2V}HPNVn2*fK;wC{{>FPQ5fzynAj$}s zBYY0Ql{!sm_tU0;Q-F$KG0P7y7A+yOT?Or3spKP}SOB6LuCB@ip$nHwifu7fJo-`n zwj&A!sL;Pp*xtD$id7)|Klu=h8=hfk3x#uf&$Z5cYI*UnlbeM%np5FG2h>UVc8R zbvk2&G9tQAkMstoO+U;yBL4$%!vHk;7YQ=UFOZGXN#C3~vlv{l=~j_pd!PrI*ikQ3Q<+Du&YDJOyDFdK75Sh_Vf^*g1C#3-q`l;gJrbidwi{xpv&7nun zccxc)fTIP%HH+&vdRa+L%`eW;oAQHWh+Z`Re-n){0^P7_(~ES#lc<^Mj?Rfdq5zt# zd~Jv6?G`kioLwJG_K=4VTvT*XLj+)Nhkwv!s%sDynU(kcaQ;Q-Zf4!kV&6`?Oe%jhcm?U9+J@*UOZrT|G&O3`~s;ke%brT7^ zH*uM~gflWJTN8&rBCUiNjwY0rY!yp1@D}WZ5;=8)Q5va28`sqIrK-6N(xr=_+^ZLv zTNWadtqDx>wxjoq{jh=A58pF>sjocYv$GB({HTaO6aNKnF?j+etxBxgv9W2y@|W)N zlY93RhI&cbO#53i8ek|+7leIBI7kFkrEg>1HJlj`f_+Od2?@@Qj!iH$aG1ZpRauk2 zU_Gr~cYL`sF|py}$A#Fx+S{{HDhMZyZzt?j3AZ43tHUDi)2w0Q2EeklLA?m1SjI+B zEXy&ooU_+8sHklIPZ0lvH!hdEA9TUgsa=Fdt;nH-w|eQ)rMpc>qG_nq;Jk{kVdaNM z!v!`}OA)~s9ce&wE!YS=LYJ1c?LGp|ko%j1cLMx{)+(o#8>g<$3cNh1Q%*41x1TgF z;zbW0Bs6SI7UwsypD72L`C&~BDg@SoZ)=#KV#t!ENRf9EMYbPutiLVet6^AQ4XA6UR+#US@{K(2h=P3GA$H`^b)ebC2Al&x4b~s zV*o!rc_CV7I6JT@5>SWn}Qu30}H1v8-!mN?sm&N%A@RfJk8l5%H+S>4?!qeHo3^ zr+r7VVGenTAu%@Afoc}w4|yQu3nDL1BCse>`{* z5L0?jz={_0_{*UZFJs)(uG8=jBB`RHC;RasK{CQAGYOfSY>-E?m_2*;nl-Id*#!j! zFjgg`<>=n+9h1{K{5ujm|H$D^CU!|YRIHQ8E&>@a=>~^lFE{23)evPp_5w4UoD|`< zSACz)n@hJ?>&cJ>fBQAfa#%laCwu!B?J09teEdemVsVjB+@MfhMg2i@f}-eV199qa z$A1sqKsEuO>~H67lP9$N__S!=ykEivQ%*}na@Ip>*lchBkAc>zs2u)IvjPhMBC#I5 z>$Eg4I2byM*R|PD{g>QpLZlSLxunRhudjzmq3mOe0HFQ>vgLw0d?#Uk4ED)Z)3>w0 z`-Bfp=)Qbk1f@$>%UMGU5SJO!3+ye9?}0bE{+?zM^5Y{T zCy)L9Yi|DT-H)#ZU0GcGWNd!^gG(2l`1)=<_vFGkm(jVWHkS!vEx2O}9pK+d?nzRF z?F{9q@Y1wbNj1!-L#wgzGB!5$0f}4us@4!cFC+9yCnBixD55oA{0PShWrvHfjqda4+W;+N{%3Aqa8qh=7WlwBma<1 zkbdRK$O8WRWy-0p8FV|f^xcf^-M4XuLirItMctEM6znMByJ$>+5+<->*-aA%V#cd| zF)U_dbuV5Bqw9o!dSrlCWVO=63vY8oo<4PNe7z|m0=%q+%a@W8Ls(~4<_kr{*@eRm z4d*o}NW`+Ij36P_p;Pq<{|40&)$#Xpk5cw=O+b!ZkI^Kbkt6dgx@YhF>2t4~K(!ny zgHOM4$R1r`H!6Y@4CY5%Sj`GV;n<|h%N@ylC>?d~^EVoAP%xlQ(z4>>lQW_>D%`Es z|FT^9_a_gLxh!kWTK}xde$&%ThHOkC0OQdtq5}mmN8T`B2-&lz!{*a6WmoI8-1VE2 za&o@Fi;#5$m#l#`Pq z99@4IB$(yZN|y7GSkw2S?IX>Yq=GuBkRRDlbCmDgKl~Xy0c&)iVFHfToY=Y3&e5@# zYTBhc8~D~>tNVeIbWObaFIcf)^eqHKtR*1U&0DbG5-Au##mRjcvU0U0Nl*MwX{w;w zn>llu#O6ML^Zxz!@7$@Dc5#84{%z6T0~U1TJ>>tu)lHhz}mJy zU6VgRy;XzrKxrJ-eIImGyyPwSjauRZ|3cbCN{9^U7*Vc9KpYc3P`P*j|J~j9BYm%N zaemg&umT{5*q1oU12a<}w%bv1_nti$=w1SvfjSGJOEK4*KS(_M`kYuayWt~oX&}fQ zTGSB@{T~ddr8l9cg&&_cSNKzP!oxJzF!vx8E;V(no8ImM*=wPDsWc~Oc=SJ8wH>Je zG#C$k!vzFj}z_ks3ttn*-hd~g70w>p< z0k~r0yY7R)XQ}aw?s5bfI6bKxp$uLUrl*!HDY%p(W-@SVj$Bt%y@YO<@7~ngM7@pd z+Fhn5dbVM-P;v$bQz(**(OFf&gQXlhHrDH=43#!doPz?~=i-wrxM!G%O#Lo1#9V5u zpP5#zZ`6gPq&~}+|A3#E@!j3g@eJfC01m7|C?BG$TUu0PX|T~**67qLcrYl2oPDXo z)6#q%e(0z4{>g>=SFX&alHzdB_m!2__O}(8(`w`Mvk~$T$&?(4S%$!l|GsOPZyT z?#mkF-(APaQYXCGj2@6|0(vEVqFiBrD}5L34jtC>b9{Y#zv4wxo5G@@OS7d1#A(QD%Y-6~UW#@95C)Ua@-R$`)_|7m>EXFgPvP1)mPA z{aWsPolk33w(3T{R_Z+W z{?!vl*o|C1H2*hbBn*t@&@Pol!<$SyD!cI;`0dP_B*w*g!vwx~VNXlw`U2zkBB<AJ+9nL%PJMuS7_hc<}O*!a^_TlFHw?bjy(C2#V<~&1<`dMtzKsHoJEVm zJTP$a=Iz@Cn09^tPVQLR_w1J9Kv9Yiev&&p3pz)od>8 z6hRM-=$pJ|V2Ax}M>)@B(4N^i;#I(IBr-6!+>*|YLD^DM*kmjC*`fOAjd>*Y%< z;MJcUEiai+UcxeXq7Z}yn}mdiw?wM@v@0Mt89AU8F*G__21YmR85_Kw2acqAX9MHw z+WYW|Xc{J__*mMVDv`YFFvoaIW&Eo-dhzL*Q?6XMu&pNN)A}P7DoHEoX8v)NzoT!k zTzSlm2M)F!q(G7r*sDb!dQJDQVZFb$$#0G#KZC4Mk75a>43qXT&-FD07FBXYG+C=+ z9-ZwI85to$nUYpMfAC!UYk#`1WnrOWJN5J4kh%DCFUj>1g}O5h^NIu3lZ+I?kE$7d;T7IyR#- zfuVNO?0{pHM+kf0p>1ze3p;-{- z(zoq!vc-x&EoX3f+*HsTaX-Ij=B0gXy`+#v5TPr4B(B5b`fD2hi z=Oa2b^*MD)gQa#@jVpWJh}D?))QlHB^N>;nS;T*lo@#{x03i;s?<^cI%9nZMg?G8@ zg^PWq9ZkmwS7h_%MwA*SPbMEeILych@8pH-jR^`e_|ufBHT<{u8NnGSm4i2wKJTvI zdr+eltc?6L1#jExF?$iwUEjjbI`I&ca-&Tv@zMtuJ0SS(}FzArzj+dNfyO~%N zGYS#WpwjXvsN8Rhde*GjIwp!^)m@PxHz`#r^ljr3Wz^Ok=pGjvt3#jA-Tf)-m5xr< zQbU>K#EP)99)|BK4oa$~L6|&T-$hzF9<{%bQMVpFULTp>GKyXiKh671)U|?wTVXfW zCHpkwTi#pS*w5U4!<5!1qfTpCpDL>K8{YXrh+lJaGrS)_t3)zNL35pg=HK^Yc8d28 z8;<2p;i>&~`B~fA?90W-A=|L{E~qQnH~K6RkX|!GsH=Gpn+m4<^G~|g?YnpRH>dyi zR|G|ZWbv3s?3c60gv#>m-FYad=1Yo&hp9&F9k<%`_Gqo_8Sd`=<>k{L9#5PYASYKD zd16;!Z)p{IC4p8c|M(`x5v2lg_S?LCUy{TrUNt%9Rhy+b9KnHr~zwopP;6 zPiG-Yt&E6f)y+zLF5_j)@03*7kKJAB%I*=ay%pU!@8{v^vy$F#| zTL=N9S8tlsS)qUbg)}j6`PFmD(HJzyxB1IUgstWe4U$||85yp;w{WH0C!cM!z@ctH z#uvldE+ezPa}b%tzz7Ce){e7V!y$-}St(&OIYTt!^LC+f@TL(WMH#xyaJ&HA;v z4JW8)`*!FzYOxd!I@6s=Ym z((PR(!=)#4Efya>c1&4bUU;7DPtXYO2z;V0iqpBwZ`?)X6I5tl<{ErVLLSD0}E z^R3oSMIT;YkCC(dfC06U0|KHttsZyfWsr6xA6-2{$My8)HGcf6h-d}fKN7F}?HRq%MGgKJpl`#(st*V?=<Bdhy3O1;+ z!6)hB#ott^&1csuXy*JUW{C{pUbz~TyHvi+{yB8si#GY-Q1CC>CLH}njRK~0ZoP(d zh}b%02=iH0fvXQbkuOMO20Q#DO$cmhIIeMRs^pTXR#vfbaSVlO3Av;#rmf7|a>{B+ zO?_x4zM7OOEH!au!T098Zjv%xwhH54ewQc>X<8>ItDtxx$Cyv%B=fiFL-WsOE_Rw< zaqv#sUt#AwW{w#2!=I%k1$jJOGRize4h_&XpfkEjbDgcv)D8JiQexGrYxFB_8QTkK zv?V6GpoT}dgenRm4p#P!u#vM3sY&DcL&?=A~n+9Mi?z*s!Xn`6|J&~Qfk#%g)2wFpiFue1*);`CUqLvpY2v_d3 zOuTR;y)2YC7D4yN!QH);Nd3D<-2#C`HX1hUCyHraaCmC#*xOx{V>QmL_?iwY!YBba zP5XpwD$w+(4+WTMvwaJC9)2tNTQM+UD+d7>`2$u_H#i)DwK9jiq&ErqO5XE-rqWK>! z08_K}QSGG2bzl^Ygh`Wpnc3t+j`_^!BAq1u@0%h9_qm^h!>%F0rTx|0JPT?j8ULz> z9N+^1SPTab&a{mAN#J__=nP8`E&vh)+nzx?Wz~#MeP8G!Z%ILr#eXG4Qx6yz8$*d( zxVrXf8!u~4?MIOdM>)_cp;4HudJX>-l`qq$)4+)GzLQELb2|8clj);dGI6xz)vv?k zryabj5%Qu=km|wAlHuZg?fq=kR!vKl_-OKG_;g1xQ3po+8H0?B?9#d^>BDoS*288y z9UL7=YSRcT=n1c!(*q8J+Y0jc=kHOQzPFjTC>4Vkr)}7VmEEN&0RimIiAhOn-N8i_x&GCl!ukkvGO@9RFN2u7 zq1iqUQ_^|!rq!NswW=ss88}X*RG6seM$j42L5NUWOE1m-%>^^VT^iH^hQ@*x=`(Ew zn^WZz1%)n_Bs7y)1~T45Ol2{eQPSg&Dr-|+_lpT1bjmB}$h@U8v0 z`S>Xryl619q=W?TNmzz_ZQ?`7t5y?p%6nedVz%{?%ONmhYAw2=mL818DwxKG_ zM&f}mc64&!>32?#y8HS(SJn4b@m#%?VG4vL`Jv4@L`-YbKh^nRZOb0XCb{%onlYC? z`yqtegbkwWhNK1R{v#*)V1M-ZacFt+cn+n;n<0;^`fQgiET-uX2Q3^$y6}+7A*@#0 zzhj3rj>C*o883Q@w^;Gl2+%LYH>ur=Q=X*($NVgt=MOgvFN1N!{!NX!mPfO)j1ZD> z%c(FQ3XkILB$t%+JLhwu1Z6v&K>FP1MgG856O?n5yO^cU7TCWlZHyHNU16RDMLD`b zI|ql;XU`sfoz+L*GXj#QMr=cmo|`5g1{r2iP$I(V(5c%4jHcGFx47_8y3}eX6JF`O zYcBgQHrK6B+K@Y%t~L+`LBL4Zp3_>QDPQ=yJX~0BUjgs>iY;d82%@LOoNSgdkY`{; z%WEak=#5$V4a?Dm3tNGJq3i|`DUbK|X5OTgz6;XSs=%>Nb3+&q0nMxH>6Kl(w(f(D z$PUwibOg*p-7l)VNlr7fwXHz)M{`i%1G#f1qeq|GCuJ7YD1DqVH`!$=1jsODZWb zE!j7b!Mh2i>ujuOh|?ILtT&4ij~<4A2XX6b9?%16anFqfbqL;QJ(bWAh0%qIL+8_8 z`VgzAJ6rgMKL?`$Fxd>O6l9=8<$9MLXLb8?%5zVi#5>x1w3JAONv2>MJ9Ee@xKtw?|AncXU^g6oRC5nk`WD zD6Khlp7_GC2H5-W2(nOD0>B>#ZIQ;Iz@(2TLl`}NAg$Jk(qb%nC-_91g>&|GG+TMM zRzLcKib??g7U0eN&e_9bUo({W>=seBRUi~PoN)%;8_8ZI0!j}>6zrjim zFbTei#RB7eJPPm8vYU@$H$pRkIVuLHxLx(g?(_EV?|*r(C|ZfeGhYL}`OgJLr=L)g za6hoo%?r3YI4mq|rR#JK-91i7cXkW%N&wAXqoYCsgMx0ez~0KL9%ZPqXxZ%8IHQAF zZvnKz!c~kw_^S+6Lv_)YQaK(t7~7ql?*Se>`yaHB4Gf?rcXPwnthu~#YX3~68QSp5S@*AOtbF*(@~_8lJODNTh5OsRFf4 zp&`)K>lb#35Z0X{g}R$5%mu!~ZqLt8t43(*OR3g_MF_V^!{&(Bj4!-SCSoL?pk2HS zXD5FRb#+4qp@@~}Ccvc(BM0&$5a-N)EiN-HL9p@BC?)bSFX?XT8%JC^c1-tGmUsiq z<2n4%$%$YIMsg>QAHSBiT4zYy5tJ9SZ*$j&*C%V;MwD7u$SUxy+}H9Moale1Ou4Gl zQ6-q1Pcbr%6TwWVAo%|E-Y}OoarieztQh-id$nhgc!v%T%hdkCd?JqHymRL|YdP#=tVda2h!zzx18XmVW*1F9!oT6VuW7pLG+$EIkb_U8g*}o0 zY>Cp6+7q(}`aa_0Zrz#2%vjUrK1r0``^<1CoUIe-gsSo|BcCj*6HxT5SufCrwfkSu z)oDR)LzLUzcf>!_r*Hd^CQ`%cL0L%&PhWwkmz7;iSOXREBQ?5AbQ!;jIpu!{*utm{ zXu)g`FX}2u3T+~XYRhn%{saXAXuq*5tKnKuwHCi5Yw%&qxbJMZ;*;E2CZkVxD4#(a zge27Br)bxjJ!*LK1_+EsBMrO<(}>|)C~iH3rNRyZHyA!rTUVF)L?q34QgY5frmoa5 zM~}{9G=my|{tyEi>tY;LO8*YQv0~;G(|UXNGUHOROfA}sV*&c~9NlqpA6k}FgWUQ# z-3EmJTuFo?nQ>TX7x0pHTCtLqaaIIQaD+5)`oW}uM<3ET7TMJ!P(|GC+TJ-StfciE zja;%%pWDKXP>F~pC*p9y#t7_Jy7x9HB;lBv9sofoD+^!5Y?6Q=(Dwq}gbu>K(RY%D z4^dUUT7=oRK~7ql#PSXj$-^&Nq7j-3MUBv^CdJHPj@}TzK~JAPY6%k`V z4+QO&w7A{)Ns|VR966H78p0QU3kqq(h~4~7kxDN#Z!}FtprWQbdWf?;lsO(`KJ9%Y z9&8&S93u&@%#(M*L_K3;7q`b zROim5DngNH4KR@>w0-+_KH2nt{<*FzKHh8C?q2^7U2g)HW8bd--t|Z&!(EaoR76Pw zLWBklM2L!tjHMEZgbXE7iZn`@8c{MdC{v~+WGa%RWQvds@l=NP_sYB8z4rd^?eng8 zJ!?JWzOU=|JBQ;q&g0A-prv)3&rPwlkLUPc1T_VJyz2C#f5;Yo=?tlM>rUZkWxZBxfE*nA>Df-&Z4` zYX_ln4v^xb;Q!~1+_5s}XhDJC5+kch2p2T&==FQ|>4OMqyVD^F{q2{`6ES(&9Cn4& z0j!wSuT!cF0q6qrQ-A1Ipc`&-+uY#WW5dEqI7=TI`<;eq!UB&v<#gZ9iLk%)0<@Z& z_MeV$6qm4wamQLLMa*^QSo47^(=v|#d+3&k!o}=H?QZ6E%k#Qg5%-JG9Y|UIAg-(A>ECv zDoOY$Hi<-@8!Ra4*KY-5^S=54ZD^0aA&3^@_@P(t*(&~W)n}*K;3&sQR2)`45L}qA zhGC0!F%4kK4 z^*&)J1A1mNA!Xz+Fz{8{6s!RqIFFU-suD13w4Rlt& z4#tZay`8>KZ|k4P&fS3hG-n?PnWzwQ0c|`fiKTZeF2)b4^y$++4?AvCye4o|ZUxLQ zwDy(U@}qJYcQ7b|ffNk%E@P|2Xk|%>+<*z9Tt^3oc!bXk9Kj^L+fYllKy4miWB=e2 z@-f!u_@Suy`#MLAKvuWjp06oyN+-y#N*Spu#Pb_u{RtC(GkRA^X~zO!$l&(_s+pDM z-w`l4EOYGbZ!DgUj^Z@IZvz7a)0V3H&|97*juFtg6)IU!-S}{q#(G@Hg9ubBJY22+kv9|fPwG}b^~FAReS z2-5uXO~C`Uprl`DC~thBa2{@>DiNT+tvdpcfTDT^h3dn29GWmL5BRxY{71dNB&0{c zOyrdep58w&7Gjw$dl^EqUW=vrg<{VEVm?A4@!*4_%9P4W=$Joeq^I);yWF=zHqteC z4F)@%vjW3Uk@5wyhM&(nCpZtWPOvo^;{@X7>G_cvkSErphJIa`Ltg(mF*sOy$J{g0 ze_(Rm=f^1OkD7))me5Quo%tu2s7*t61j z78)+ZTAGuEw|R6C2-}0wd1NK7(AoJ54ZYW_dHCoNQ?4q)JA6z}LSy=&XAeb1#@_h! z4!b%iSa6I>O0zoH8y&=yQgy`nJF~0w4|xRhWb2A z1qT6wm6H9d7&$5e)PcrXrC1K7r|b;d_DJU~`>ic8f&w|tC&`+ETy^Aak%s>O55!0=Hr zRjg8!0X&7nWR~XlVoYSVsQ%Mn!YCmM1vIZ1Q~(dKZjQr7a&qh09Io8nL-UAe=kcKt z*hUrw0#(x?P{?payVD0KDsn85h!y)z6Wwb1(cpv+3&kB;Fu*_Z&CQY$!gk^mo>cO1 zDhN0tICxId->>%PyfGQk#%B145ej4Wi@bg4XE+aBbBh57QHl~x%#~#+h7$qv=ATN1XUp~4{bW91vYx6jEssT3=Uxj^zSUVgdm~C$%0*g)ad&E zq+}ZF16Pxbcgo(77?(he?C0!6^rwLqPz=fGVoMSsQrl#Wj8aR@P%b1hfY)L|MWR*j z9;o7O%cIWeXBI^p4LOC;O&ry%LJ5-%hjgZ!m@EP*Jv&D>cMR1o002t&nzP<1)P^(| z0!R@oDelvRENRvdf&u_nX{ilvJyg4DY62$G-j5Axw`RP5?V99}N|73SI&c%XNjUf0 zumQB^#Y96D9dITyR~|Y=CF&+}aX=gX?Hg#S!8eW^siQaL$^uC}=mS*Mq?_@<|Ka^@ zC2u&Ngvu8sAT91_PBD1VV_qF`Q(n&Gv#6OsD0JI3!J?>JH~=Ba4T^aG-h-ixyjKCX zkfv1mQ>?A$GqAz+;E2(NK7G_}zUaE#B{OMJL?=QxL7w-9xhi~ER~eZHWo50Htlhz$rEW{gq}Gl2(^&$2#OX4QcOdrrPxkTN?v0H_ZoCJygjpzHDO=>!hW!F!1XtUyUs;V#?wLo%{he)!qX5IG@Lz)qWa8c znk8+u%q^W$D``duX>iTJ&DxlS@E++C)DEU(3Ljj-IHLP$+$N@>*I?31PDX}dhzdFa z$^|VXH3>neJr)^qnRA_~$T6KpHycw+T1GOCiZ%JS5$*BS`wfkRGBB=)x{H zzYOEHpSy5@96*&aS;WhX>^;iEmgyqM-WQA$94Z2W#B=t1{r8hSqq-xw3zo@- zAwIpwtegiv)=&C+(Vog~Y`oM(@%YkL16yn&nAAXxYqdXJ^xQbr^8a$?j z%pzig;3*xc!vsA&C28jB?r(sR*CSLXk^7ht6JtGuv<}HME8Bz=+gm6`9wfn83?D!K zSyj~q&`2OyG8iHu{AzUZmVmzgBBc0kRlOww=4{w-4tHMq#O&JkpmnX6TTL#IJ z-C|IqfVeVh1C=9uGSRFg2pMRteK$jU;QW|)G-w%>rYIylCua@5L+p7)#S3LCYU*o813mPi@UMK!VG=t9{ieuF@KQ#d ziy)o$pT6+FXpm5A@#zASkHLbkcy_k*_U#O6ZR#|hxY3{HB|Lww!C$T-c-xk~iLa=+ zRCjV%Bj)3P# zX67AVCCyQgg?QwEjtV7gz%gv|gLmw3MpjI*#Tz5R@2lxQPGyr*H|A?LkABdbuPK^h zW@1aCPs>?P%6$%W!lMvZQ79Y6j4_N^JmJZIM`=`)Mv6k>XdC&1TYi79 z;7mJ{^OH5+Gl$0iVQjF|Luq>Qph;Et{hy}HeR}Fx-<9!iTOFtA<{QCUFcqfll-EI8 zngQVhY@47;Zc{7v<*{nwvAmHWOB+~x{W|82;oKrJDUcxsEsG5Mh;jvQ9gMPkfYBgZ zDG!pc%MfzKIhiv8U6B*_0RkGWl3*FpcuK^`C2LtR8<|yPm`( zIWb3DeWznB+5l#m4f!*Y9t%RA*1VGaqpKU9dOB-uL?`C*e&aC*hG~F;n&yi+7P^K* z)sy+^WsiG_p1vcS@O}~F3r~THAOQ-JNXO;8@2eym6s_UP)38Tw$LKx%%TvugGd~}0 z%hmVYnEUO=&*$!ET@HO%{-xj7yV0XMFbGkagwZx5Rw|)O`#$hG0&OP0^)t!0gMFDYjF!nuyv|{wwa4Uuyo4 zm6lbNa&XKu*PojcuCCdv{VA>rnuy>>6h!oro0Wx14+-s3=qVx8=3eG95s~m2#7F3E zO%0~Vub=^VG)QZh9-+G?^m{Jr5TL}YM1Lkh^7H=%mO#i=yt{8PajEp`JbRVJTtm7| zk^oN#kt6hu+j|>ok=6kk`R0_-7cOk%gH(+v_w6@yT0wYxC*x}&M~b@-Y$*NabF3zF z()LAr6B4XsUaY!aH+x;)ZTnsC6;toh8e;4PoOM_D_Sj@im{33mkloL$taebS^z>xM zB>p8Gj_I>RH=koq`ej6hNCC%DIWn!GI&>%wlaMNZIjq!#C-WL5L?H)RZ4P!tyMWF` z=uStCS_>jc{{+0vlj7UM+X&zIF|7bOZRGIbFjv^&l~STZY6wYh7F7wa9e_}?On|dD zw=z)8=UUDG!U~64tA$9@rD7S=EBo|JXb7uzqlb+8H+)|GDAcqXO z@R4cpQIU~0ZNI>VIGc15r!ki!L?QUKKH=%nNK+*YFk$$FX)12hOhNz|_qwJZ1HY}T z?74C!qm05ZC>NE(&Hx*^pCm^UPxPzEKh6(kkPnN4vM_Y6z?+O+V8G~*wb&dQBS5{< zb7-9-x8N#PE))Ytz|T`sMlz)V<`dtMMt71-X?gjZbjiBVm4VAa>`QJ{LJi=k?8&*+da)M3g@~himlLnw{9#W6}7w7klmoyj2W8tP&UBxoYXj zxYkKWE>H4Yzj^b2hLL6$_8h(6ezAd}A^ScszPf{<`?YIo}VXi<)XGu05zI%qcujs_W;? z&Mo7`oBMySexztDsI8C>-f`V0iL$daev8iurW6anUDbh7HkkI@Zch7g-Mj<)-tJLr43%#0ckpO)$rDB0ZuI>=gJXUPO)iwmmoQ75Mbubn;{K~Cmu-{>6|)o zBCYY~l;;Y8dBXV5j4wR4_VFKgA34&<_pqj(Hw4|R+V6qW*R5OE?y~;1*zIi8qQ<6Z z0n>MA0(IKosc_XrMRQhB{O{2oQ&M<)W~Q={H{H>t@8I=QIvomaJJY4Kdo9LOGZnw{ z48?-6etj?#bhd3p7Xx}!JleY>bfA%aFbQ70xeiS)z{?wrk8w1`h^Zg(@f6G{TU^B>71UI zRm8Q_Wo3MpKIS$AYwO>89(m=N_o#NOvycngF#GOf_r6rHxq%lh{G=6nGF_r=iHFDg zSFZ|vJITw(l~@qFX89`=72?n(Ud*&B;@Fg-bg)f7?8TcCXFGZ| zCH8t>gqlvjxuyI3n^c=43!2fk!hlm694Q;2o2O-SC;*qYgnj#XA{~%?*xH7;9u?^e zjh|Zq&eae+wBiqz9j3%YZXFgLenhvM$T$i@|1;}Bm^kVtE1Tp$LQ>@}@b>6-7}{)W%&5SlFa5O{X7iz! z1=Kj{@5*8&RZ!_*9S=Y&?xDUDSZn^Bl(mL0pFbt45Pl4lr!($kZUY%dLlYPyyTRySFX>zK1nkFe*3$?+6YadHB7Y zbH$w%rz-2JKYK~M8m^p}yS7SygEJT8HP!g|Cgq)r|_gd`{-UfuAL4F;Lqd4P zqtWUg2ZaW2-74Y!dG@qFPdZmRt}`?>;CNRAJ0j1@9@T%Y{(DcnqxXd3 ziTCB|51V{jvv#d|5X@C0xMNTRT!Egn+uZio4dBv%H$N*`ym`>%j*3&b!3m%bP%AQ1 zblnK?Qc?!~jtDSszxA6fb7^#1T3aCjPaw^u%%_d>iR@&0<(Ph5g2&nm9%qzBf)Ni@4Q^s==jL(FG0WNa;e7g2Pn&Nm-7g;T>+k;Iv(&IG&>}Ij zaPLkG$t?7YsrAy4^vKYwcQ>WzlX^CNqiwFr{%b{}d^8}|qhn(aGTc*TQtJ-_$)iRt zN#R?+Jz66$TI1@h(vkAHwjaVL&wXojVsNJ1m>qr$?YeTZp)&@w??v*3--2?WCay-P z3*P$|0r@ViV#1vT96lTsRYsYG6%-YKZoybd!kwnPim7K z#eBjR>y*pR>TQSi1^cM()r^*`75=AG_4TGl(|G9&t`u8wsX6{P%)8#a*a zhkKsWPG9egZ{<{(%B&^QZNhg_588!a15#DEu7)G8xm)U1?*ZT=X zFM$O#rFa}z8{SK4e}=Wh{xSYfU0;3qcYNo*F*~|mu2e`l8k=$X0X50ERkg-bsW^R$ za*k+w>Fhq~&dW480OsKFe*5_SD{Rc=4#oc`Jj1C+LgMjPg7QJptcOvju;0_5n5@E$ z{k43RLixwP7dAB1sxW2AO1;3iWm?YV$=?pU4!=0>bIG(FD@Hr%A}Ur1!m3nQCrl^b zhLl@ImDnUSIcGe2tZ}R>@-2EP`vj^1{<6NW%;OpTq#sO2A|x^%c?fwh#TGgtCr3xX z-aSNUL)Z^MUd}TvaVCfJB?4Y+YH5A@@q^`mAIT=Pb^?G3*Rd60fOrCfa-10^bm%%q zU|?%{6EWDkB7Vfvs;X~BXp+KP8-smbU;egA&~Q6rSD@)${#se`)4{R3#1mk%wBF*c`Fp%o*V`FXOcAZw{Tx5p|udf*)uB<6wm zIR`o%>0j{ec!S$6IO+xOR~@T%8YgEB&y^D;rmzhwG$dr7;X0k)RmbBg5}=t;FVA z!g_8nf>E1tUsy_vA0S7sfdK%CpbalIJP|N-Scg+i+Yhv-GOFlDE3jUpf*d+@1j^@; zBd1zfVLOV?T_zhMpvY1-E}eVp^tv5)#xH9AGJZis5vWCIGL;Sk^>(zU?_9U<3w?X0 z>C^#%?GT0xkUw48b_K!gJ4=Y4$ueL5{nzX4C<(3b4JG3p&!-%jxELk`rl%N}jy~0L z{_(Hhz8z`#w=kP)ptvM$rXox8rpuf|{f`E)-m>qXk7v#A?z(jO*Z+G8%=KaDpGo7r z<4ZEiC-g6`U2v%Ig^)`~7ENX=>Hn=8rhgV{eJRhHE^SJB{r&G3u4-0z_Cn4%iG@1H zE_-)VO{IPVA<>?2=g3pLl&cyNHS-)FRp}|#@k8L@8Bc>LM|V-*bEt-f{`rS{?e)9u zpxC#~S^Lxi8KC1*8fr=sS3@a%H6x=1&_;dg_VxU~OSdHi_8>85o2dlu#OGpHgMvi{ z$@UjB%-OVj6*5ZoAGsylJwAwl-rMP$Zk^}a)S z9E0Py&eCu8i^Cm^`v8qp2BPeR?!)t_TNio%dDENq#$W#y1gwDE5A`9jVa%vrZ)l#u z-7yl_6&jjc0l)FT2ffa`xN@*{{wf3Ix>)OmgJVbf?>iD2n5@ydt-00wkmUv?1g`a$ zsRNjhC#?(9cMJcP^5{ne2_nQS_in+(^}{!}B0K+1>)h@!fddcYEnhunM}#h^8L>yH z;aGa7G!#)nRxB;hcO2Z=rf;=S(9bov6KiT?i);<4Ap|d61tEjc5mtMYx~;EswXk`F z>=oSs)F^b4(y+P4d)~WEmCf(&-P@&T=JGq`w{yxU2dMIdDyAfo}+IUP5gD_<#4ZPg=iu0q-YEsF=9234ib+y3FH3#gVfkN7L!I_I8v!I zHKN9X>R;nMeH~0JemZPTiUGLe^7QGyWRiAA|z}M(4ep5Vvfg!Op0F=wmyurp4P0Jp@|&96x(H+9_^xE=T^kCcwMb|e{qI3@M1%?vA__-^14+w_~)D1d3A))0=Myjo$48S^Ia zoSmoBejf?qo^KUK^7@?7F#zKIY>*sPJ{|Q+J9yA-Wsb?`9&{%ylbG=3sqz_wMw;w0__is4p~TW2MXjrPE?hI>@ltO zd;W(kT<%=p*8>D@*)qVkpX?NpHzfwK=dd4gn740(RqaKy-Vhh4fdyI@W(Ve;sf|Pj z(5b&DV!cq7F>$b|yR6!(udk={?Yo~|3polB5H2V-+F5;TiqAuqFx<-*&wH*jbGAsu z;(?2V&s<~!hY9IB=B}NKlYRvAx*#7_P>#k2K&xOXkpWXmwGG(l)xVc{bZ4)b#J3*2>E@6FoAP`5jp{`2O2d_3uAr96jXse*K}7^=yb> zoMbf_y2eyhNAgp)sW`PV5=$zcGP#k8yMTRmlyX>(d?88*TUx=czHad9V3N=S=z#7< z&;|)x?-;j>*)&qoL_BVAbb4~Ksh4I%ojF#zB7EF+{p14APK0I`2yW0t&=(FX>=j5D zzNJG8-mLC}XMdND+iorOU`^5pm(Yez*Y=uWsO z5%H84;XjXy&9u*!bSm9WZCQIEQ*000Vw-VsTm?h~JiHm;@v8(J^WcK*rU$w`6F$<12j~F#O9YqS9Xl{gS02B96&A3- z_*fM#VC`(mo{6FRkBd7<1OUzxD-l`rL*!8u+Jo=xH#JCQx-1vRCy6fv)miS;>F+1& zU@~tj&IJ>HH$9|jn>S=4 zK6(^d4=xi=II!Mr9lIc_Y5DoQ?GQp7oFu3wCw#p*&`&NzB_w4Pgy*8d9TP|y_b2?T zEIhBw>v;CJA6eErLt^pKCO9xD$U$?&yP{7YhJ`y76lKZct>AEOwF~2;Dy?u(Pu@7( z|8~8_Sj&z-y9S=dGc2BiOAN8|G7%5P2mmQF>E?L8%H246=XEt3hKv|7%gZFQXlGr| zoT+DB17o%9q!)OU6-|2nz&CegHK(DR*MR;wG&~$$FwZm7kk7%@{n4|V_o`>#l|;N> zQw-XL{~sVLR5}Y;j9TBNa1{Mo>yI)D@?EKmiP&-}(*mb1wR*5gl}4g;q>1>$)%L@r ztUM3O%gO1(n>T6s!^TA%!fC9viBhPk@ww4oEj@oOqsG*I1_cP-_Pa-*L!cUdz9Qz_ z?o~H@D^le-S%Tlv*Zm)F)Ea~vje=4|IlIp}^xLH$&o`)7i1&Au^3fll;gc!1XJC^s za>#&`AEkw86~H7GZBd_gr5qTzZL_q=lghK+OVC_`_$215$aF^vwFicu8~o>M+~s%L zQ%4qkTz$=9RdP#_i_c2+$CzP(JKL2p!lFzP%TxlWy0KP+hLB;pjY*$bqT-#mI{mZ{k6 zHY4$)nH&<(nf;@?ELgN%#qq_54^EhG=PORm83=Xv>MU{nop#8?UyW^lbVZ%9|JJyE zOiq}KYS#j22y8Mv|F#;1%J#8k7iR=m7w)VG8ku)t@a}yvF{wW9ODzYuXud)Hgy<>P zS;1+xg9CwY#-h-i$t!1jj#GS4x+(ENnvU9q)j@yCa4KWeE$X>I=~iZ|w>0noT_zhE z{&es?-KZin5O_k!;tSeZ=e#)nT`VnJJt!t9!g8kw*U*L?F3Sg-$bDM-y2ZRf;a=eQ z??odPWjdrk^-Fh8^Sc{5B=f?gQE6I>^m+-BGGZN9ruE$0Lw(UZb3bmT13$fe{aSQI zPWK|gLUw+UnX+7aI61^DH!6T(7$BWLpRk2_+fq^y71Fbl^9{2Xtf#dnVbt(HWE{M? z8q{5`hv%bOJ9<6B-wQ#W4ZPXcVX zoB~)t#=I6qJ(CxGy)M4srxLF?I1zX4r~`h; zYfDFXD+iy-G5siP(xXz4Ys{wQnTkY*<$Uqem`;Nl{-wF?>P(R3ExY=Rvhr&bVwtw1 zB1=1&M`@QBw1M-m;Jwqx8PlgD&HD5t>lM?v_4RZ-JTm%~x2VH~XxShgi5HXsWY>IV^%6}_;)VOdfZUFEl@77BDQC*Uh8ohe6gDkiuYCU zt);E>=O=7pDg%Rtz7f0Z+3(Fr=44at5|HM^b#X>Ks#1AI7YS;op4#L?T z<2-g8F_|eyYKYwM>0lMOLSqu}n||zCrdh_uueXNWiZ;EpH7IKNte_L$PiZ$L8K-MM z@|F0pHPBf{qnn@W2es>gkPJ+_Vgcq9BeX*pOY}{2s;AF;zhT6PAgkBX7BLm ziEF?1l=CkMO}ZU4%x#2KipIe-TWhOt9}Q&|ccIh~K$<`6D$cpF^A80F-nB{OfrA7Z z8W5ImT>5gO?=>IwF|xLKu4|5*3Nl~2Oj9oF zkY)c3uUUB|pmRWMc+58+bEL$lBP9t>j#iafMMm}$A~HoOUI3y1cpH`{1uX*>JLgdj z?Ylmu6ay|p)(fHXMcY-cu1yjbWVM)MP=j4Yqf_8wBCdE%7!$ELg&0nRR8gs=hA4CD zw3h1^Tl5nXR+-M7nSRyL)I`xB+oj727AR9xHOswoz6g1DP6e!pL&vzY=rgkj(K*D6 zl@%we={DKi@O;xZYmv(A)LG-&`XouoDN!B>OYT)wB}BrQm5ogn+s;!;HMA^zd~z>e z_Gf&FZ%JV)p~5C0jT$ue0#`1A?ff;LVIki&YtVo}>$0vwv*3u2d8wR2#utwIYxOwP zR)ddn&yeQ(etM><-2{R+WI#S8bKfa~xpL=@g(OtFbQ`-QwXK@gQ#?9}$X5f4y`dSd zmkXnGK#9fPNVrjLAgPV1JM}2rW5tfLkX<(>ncNz*e*bgYO3@&7^)E-8gOBWz)R;dh zKcD&cZ`Si>&B~@g5xskUxs6>|$i|*;Y@8rk|1qoh-OwK8(>2ZwHlKT>4N(%&IpK{} zmv2ekq+;@BR!KdP?&Npwz}Lj19&>XRzScL~H+kyPkGihk@eS`NVr<0^WK2Q@Kl=2e zhekXbGbZyNJBExFX$Jv`$4oU)DD zpY2oUzFN(Zfuf_NS0__(?m_-&LUy%A+6Sj_m`kFa4IR`=1`mzBcS(71(`?%lws-9p zJtti~#wm+K-%q_&%;}H@dInWo8*iT%%IM2&leBGsm zDJ4-Y+DS@QeM2EDJ$CY{*WNy^SK*u2=e~oq+*EBD@{X(2B6p|bqnlELo<+yU%ZjL( z1|7u$K+1poB>q+%5mF4BvX*D~!dP`>%uj+fP=}SlnG#OOx7;7F;rqdv`Kt7Qh(dx6BmZ#uR^R4$IGh9-x_!ho8-KbGJqFamD>GL%?UG&*K4o8g}2+#n`Oikt|mpoy5 zZb|4cvs+V(1paTj%_g<}X=^oILSqgt+x{u`owMA<9-5EO9zfnI3ZM>(C`34jFn`2* zOZy%%>IYmljCef#*wRX~9Ou)OZ^Rc4t9j_QdIW7b@;fIhUi zv%D6`ckrIb{KrFbANUO2(@ZZ1@%5q>;B$#uMrO5jbS`m?#6Qzp1*xubL9+kEC5`K4hH;1VU=;$4BYWZc?8b8)r<3Q_A=I?!kwk zxF!GY->e2&mB)Fb)Sjy-j`@ApdmdL?l$e?tly)RTF-_4%9qG{Tb;t@+N+yrAHO{v`e6p|2L%1nL*0r^|}W+P2i)&z4E_**$b4`mOH$71C*IZYD*&*AVilOKXKo zT#wBfiTT@~>ZshA=dtjbV$SF*x4QUc>=>0c?cM4!>F%NPOMiw{uDe9oEade002RgW3d{``Nwv&luTgXm$3+q={zT5ehut@b(~;SX3cH z1!vd-=>JyhrO$ZiBjQtT%`CS)-5aviE|a@tML=Te!HS1NEZyf1H8f8+xXh*T%oV4| z#djrBK>b8BF$lXIZElpEQTr`Uh(iyvZ*J3%ER|EL)==D0JtNZ9bhvCz)YRFW8~133 z133;Y^)6#&PG*TVHk_%r+1J@+c(2aCmj8-y4NIBvL?-a80!k&ysJ#c{Y57u;lgS)A zy-tGWj37<1qqTzwf&&HULnq}9-qsWO%-9ccbSHkk^3Sh0{|)Gb6Y~Qm=PU#&$hb7^ zwT#}5lIrT}7ke&Ctx;Y_^b&zr*6Pmev3X#(%V_a$s@9)oq9EBRrpzS_L$kJiefo3~ z5sc}iqGFREI}pOqtTP5ny?J2Puvsx<OZH%jo3utQ8PJTVAKtQBqL2W@+To z+hCQ72*O;@u2NB{we&7OmN(O3{*)5jb-Q%!+A#nY3zU_GsDfBrv35^EWaOGomFQ@l?@?N1x}xX8 zG5!6{S#3L%vFL7gWOPc`!9Ev~=frOjaJlmKu9nhs+JdgbTQ$q3X$)RuzuG-?l(x=_5n*daYZ(;`lfEjYw2H6KgZ?+DjIMxe^_DK+&&2H$2{MZk{{qo zSc&!zgaC8+^PL3H`|kG@l_?KG_tVm03I^SDP+0rWOtDf823UYoOEvy=#3jQ@-6 zJ>l)|A3bhI5pO>sr3uD$i- zoCZUmd9|tol6$~&U*F5?DRpc5`V|Ao*O#9M8T6yf9NlL(BMqfa;@#fo=i5`aiLlY^ zwtBKdNNB{#spiotuf}U_JXKT^DqgPC>-CAlzmBe_Bn5iBd2&F#$JSYUT{bu>ZHO2= z!tbhU+@xV!LiM|cPT3bbFJZBD*wp1qz3;icIU4sy-D00Igx*wMp1+N4$tui-&wPII%G7J z30D^iT;+}H`<&?)z4}<{IKJ%ML&&AbmzD~m^~`fVf1Q+n2z1g&o4bb?Q*jbXB9Le=KW$KO-Zt?b&mauH!wvo z|HGuR{&SvGlXF~;4^FN8(Qlm2=ZJ2lp(OQd{*(U5D&sfB^*(dKT;4L#zwOWTfBS97 zF{su#4stAUg`w@6szozi*f_?84mP>5fprZ3DPm}YL~D*HS4El^0asV6LzLAsCDo0c9CX@$*Op%xu}Ye)V6)u}GWnO07!SRCqJBV1mkz!vrG*~MTAgU0fQDGBhA!r>J$jYb2>w%WRBvn*Y zgq3yaScoo{HFKxJY(q-A1GQuBfIxYIi(W5s{;gs|pA1=u~dPHV?dqz~d#%#JNh1Nuvq=}v}v4v+OG_zycY`7sVTxqSS4S|7ohK2nTz~n2?+bNLm?AtaTACR}8&xk+ctgc$_JMA{5 z7e_abbBCYGN_aqMqf0Zt2KV>7*{a6=r~73a%sCkyMK`xtFP|CPZL28Wb za4o<^=0|%5PSoG0Q5jqLNA1kG(jwtJ0Qe$}cX@6qZ-1u*q#XEL%{JOsU?%fkfNhawk7XQi7WN{@R`pV!&yu7zY4b>gH#F)RE`lH9d>77!lj_ER72Z;*^w;+gA=8Hl69XI!w4zdsWU?NP3w&S}&h>51wDhJas+$QZZj%^}>`#;?OkkMAw30 zC0}!geO0ku+7zf+lEhy`Hvgz)m$osjq{kxC`QLk`x>g9)Zf8Y?DD_xxv3T>ugwNa- zVOu1KA9azohDPnA7y#sf&K+M~W6o24%#X_tJTLDpP}#Ud9wJ0lSRz?dI1;W3aZ2~u zL?aqCgl$Z&iux$Vy^&*0OE*v@adu^3eiuFIvCJ!0*0;Q$fi@Nt z6i}ti=&C#KY;8lsW^}%O@>bf%Z@U`2v9KMghV_f8>el)Hs7*MXKPv3|tdolx_g#y$ z^0S|^7LbOO_U|YH8767c`4F}+{gUGR8keH@zMV8ym@ND#`SHe*ym@IR_dVwa%^km5 zVX4qj#Ge@Pd+}F4SrHpAy{NM{Z4!{b=GB_N9Y;^HDwvybD?TR_+>OZ*C|)`iD6M~M zHj)eX%<7aP>+pDKvW2|rgyoNGx9dk=dPs}GeV)~%a^1x_Enw~pKfm)Io#Me?_wRSI zu-HLimhl%c0MDjrRiFMv=Qv5MiD${MEzB2e)PZ+}l8Q##MV&s(DjnqJd#DGPh^+02 ze6_G5ogu~Eh&})eeo%)@o-9^cWN%dVexG(p;P~FlhuCI&CeNz(iQj%rqjH;!&0yEH zrsv{j^$c@5aOujD`|;*ma$Lqr_R+brwC&MWl^?~7?0Ou&_pgrDuWp?J4Rpy_lm-JY z(`wxE{R3BuD{<7jw{I($=B|Fd#u4y}WOM6th|0-g!V1I1w=6e^t}N6;aOOd?AcS}9 zv%d7Omsz~bAp8TW=AAO1chaE)!(yL%E<8~6q%}jxY>~#1K?A>ySG&j5B#wXNBmVf~ z=+eCvTUBO}7Fu71=|)+OIyqk>@N`NEoU7dFwuN9@p|tI2V3@OuYFot-x^(*VgCpBt z?%OFA#t(5<`h_@YSkG6@6!`2UXS6-$Qt`codcqd)=He|w)W?tC#P}_4HgL#ySB1i8 z$pk(qT_v$&&qR4kV|U-Zm6!LZ$SfW>e*E}fZHchw-6k|WnAB~;BXZI^qbu5wKq1E% zJ)&&F^k$VjJvnAOaIU8toH4C30S(6o@sY4MHLQ*{X5>qfW{rPZKRera&>32UeWLV`6c;u!(9V1x` z68v@s%@rJh1>xG)uT4trv~Pr~w4kEkpRb$Tx^oA+l$HVYBfoR@ghl~c>UaO8S*kiSFthHz@#AoU3WI{;fGfQ`O=FK{@S(R#YFLM}W$M4%$)ddZ8AzUGfz zo5UV0%K@8xINWDFeLdjm;h?^(y~+9w z@t+5n`Z6;&?)J97Ur#0E6@%L5FQ@YS2~h-~!O>H^lEhtbqHTui0c&O_pCB-ZsFV7i z_xYlkYZ1@*-+~3Y)AW0Fc6N3aJs`)>5bl}S`i|INF~zup$wGMV*UzsodPUN$a8gcn zsI9B}0f`uGbg=L48z3DhUaa`b+J8Q$+Kngi{K*qH{XWd=qugh3UmD6Qck8Cvf1pZ$ zVTBJp`P;Qo>l}Lb61I?Ht8vP?;G^bTG8v1lti#T(esdc^3P{k3hqIOnj<4Xig@uJ@ zZqF<}|9P^Oi}k>a^A#441(`8`FCr>6eSIJJ;cQKQbn9xWF!TqX8&^YX@y%#0{QrDA zj4XZ=N^X8?1I;gOHrJ_qj%^3`dw5+ue*S!b$YNKor2Ju%M=r(>_qEqMiugqP}HzuVgb%OBWaQVsGW*6*U)IA}#xuIw_ltWEcqAMo=RcNSW9sHgbyzyinueEgayo8rPpJ6KWGU) zgRZ1MP!E;-JSV5+%hELtPbdpT8u+s!Ogvyf8|BUNeM#YKUqss9POz|DdrCV~9SH_c zh_;yG&EL`SDP$i!rkSRo45)jfc2BU^^Jk+w5i7xvxpefgD_NAbX3cU>PbRLy*Z~cY zNGhThkfX}T3bjJ@1>?T>dw z>Q${1rgrywoPRXW@k43qB_f3p-v%3aQ7ttm5pz(rZ^KF1cZbIr(qxMC5S~XxDKt5+ zO(lO5C%!c`J<_`m27D2Uj=TN(U~$N+l>a>ZS1b2z?r>`87^h*?qog-W6c@K1R6W^n zV$7tzc^kH^zIOvDFzn3yUq}Ay3t)Y@qX<4}d+@acEM&GOF0Y(CyZG)6x2M)((UiBI6}X2CAF$;Uyy_MPQX@CJtD)F0R@yOeLfmr*4c#!jummGy(>ytaW};{&Hsc9;s_{XRlj+d#zpW z`x5ZU-I6fh zidDZ%+7wN4+r>qxbH2{I;M+xn^sVoToN}q^YKsddyS8rG-JmpdSX!0L-LS`LI#+(k zIIlPq?)oe5vs8jCbxJ@>>5vqM(Z?;!{Oz@3T$fdBO1$@bkw!M9YpJgBygd-KL|vTf z9K1~a7&S$`72xWH`h7GtKN=+1%YVQQ=m}%LbZYJSW|^=q(((7d$5@O907-fOaL||z zCNh0|XUTKMgcaE2b?p&x!rZ#Yb5pP0f+S1!*}&<$N8JmXG-FlKV3^7uUWm31KELhPTchRsM$PkHzu=3%^%8EF?q}0AbCUYi@0&!DHQoUHz9U1ZFl_!m~Ek*Y_5S z^-zof7u<9{N99CR+c)h9eYvgQXi8ai^&;Bur6bag9Es}rIS#4}DzqzWq?V3|-_dP=Gdr2{l#I_v{IHVM{>CicKLKbJY?5LEgANDo-J;}} zK3h(R>3|Wqf+D^ZB&&^$MmP`>Xy8t0;|x`t$iB@NwY zY!frNmr_Cc3BwCBv~ji&MkohIYeHqm&T*D6(uuiqr_cSkBAM8yAzO5-w=0^YCuH{4 zs2mhl6*jpcLVac3r%yU7EOX>l#S3Cjs1#JwIcne{KR_UpG!Mwu=k$-7zF_a^`@@u`6(^?$ zec9Rn=eAhauQGPA8_oZ%%?!SDex~VppE1YnP9)3-(cUCsN?**JFlXuT`j9 zx0u-Rl%b*>UOhl zr`hgy?kS&rHz_7ZL+gfp_i7DKv+!4r`77_rv7PAuk6~Zhm^R1SC(GfkT++VXxic-! zFv|P?!Ckv9H_7MN;tz7Ws;aLDci(BV{;L+aH7jEB^vFBe_Jaa@j{1Kp8Xm^pn7B)? ztBj&?if%`b*ltL4mAWjD$!sWg5qD61a&pm)}YD7-yegHP=-@C!JtyXTe|h5R;sxw1zK<*K=+P{Jz97gkzH0Qied%5HN1dvqlAiyh zjk(?RXiWQg&e&>`u6?KDn_+T^w}xKz>+3qeXl%i62f04BvT2o-EiY>m8nZnxMLc#a zz^JCD<-SqNm5ja;+j@#9_Jh{VFYt?fvsir!tF7C)`cy_Y5>7}}zFRROKK1C$1)Hc! z48oail`oIG6GNw{8oczMTKbqd-lJfuNMuQGv%9A$Q_G} z88)F&jtq8)L;&t8aoTKC!-2_D`_5C*#!? znYuCk<8(F4QKr6Lwtl+Bb*3Zitsi%8er_6DR_*-r)#(eFF&}5zxA8scmk7%2; zj%vv!3%}NzFrl9a==YXjc`uZO>bk)HNHFIdj;IkKgQ^WA@Iwd1k2m~6BxC=U4?BaN_ir~YFQnvGiRUZXidM@ zqP}nK0JG%_TODr1p8h=MRn!wJX?t7m91nSC&GE-?OC$|)Ow@?f+vS&&_{nv%hy3dq z2cT(KLoc;`h0`$Sp>JY53u1Q7wCh`X*JT?kG3C=$Bw&dX6E<>4Yf+T zS@`m5mWtuFoJZ3Wc77~7=q$Hl{|wt>b(f4;Q6j?L=hoxx&a+FDektjeXI}lX&cXZt zBJ4fDdfwmv@mC5_BHEI+_MlQEZEbBTrM=OT%4njYy;ErGt+W(M6qN>*D3z6#Qb>}O z{2wpQIiL0WUjNT^ozHcya}IjHU(eU`zVFBV7^@T4Es6b5v$9jc@p#GNthV~@t6Ex` zn-_L>T0FT_g)htUV3LiR&EtL6)(?|5j@x%M{95{Qr#*mU^XA~`e(^zQU8Zq7rr*8W zb@;jbQfE$YXCwinziNG5ef_=78@DLL)d&AlBEZB;xCepF@USf*?>nIfEQ#dM`%Nt) zFj>Gm9EM)Pl9KxsKeB?1s~0>;{E0KPl6WA^hBlvl;Ur)RCzLIMf?>ma9yG0y36wYo ziwg|08Icu&y?J(BRTW=a+#+mftb$u+;1vH1~k ze)&bv;`u-no;TodX;M0z-&@U9vq9zu&q=Zrg}jU6irN08lq=VmL)9<(eN!dh&hYcH zTL$$U)8Efqx;{IRrz-npdE_zP2(>x`4aG z;*)a1^tbA4jWr^4@2iZTrbp%lf1lc-bo$kh;KNuSY3rmvgd*bi&b|C1xpSZ0eA2-YtDsxi5N@w95sXmW6;m!6x|U(KU2i zt*%XG;pPY(UUOYX$3*s=7d`bdYsKH}ZoUvBPa1D8uk=*Qta}oonqO3|tth(abYPY4 zz7{cEdk@b2W&Rc=e?4|f#_AvwvtybUE!7bo2fFSv?0m38c6(d|>eA}&DwK@ayGF5p zUj$Mbsnik;$_2*J&pd*&?bA~&e!`iGP=orq=XLv{ZtgYwh054VVz`W|cqBV9g8XfF4(?w#PD9`ZX5u zD+n~fn5t-q+rO5M4|amg0X|Ui-j&3XhOhDWg?DrW2(D`u8NV|j9%nT{nhKmHIi(l1 zGLQlfP9sTjMO@nTJ3kMB?uS(ns)!~2BK=^PbCo6cmAKE>Ra{D`uCK3`Qlqofw^E;T zrOh8~%PUBOaSxHP{fLc$fdO(Uky3u@>+6+L`{=4`i=)w@G%O5x5VL^tl7}i;A%tB{ zpkk*nKUA~H3joibV3V>Xv`vjU>SLuDXs@1qea8(eVsz&i=OD@{zwSDeaQ^&{uALvi zX6m@m@DC7ICOq;l;HyOen^Y%^d$|dtznxPms+$wZA;L(m+O>NQ%}EdvWRk;;IH?)Ud8=`W*yr;b)ZB9 z(L3sxTk0dMvEQFnwTWfzq12(fVF_V~Ub*_Fdv#@>Nr2h$8cr%RD{j(5E@y{^dl1N4 zSfA2$ua@QRZ3QC{+b^s}UVpuSISQ}^oVDBE--`Yw z9y^wAIZi+V3Af`)+oomVO$32uIji*o1VI3IVYrMA{We%51x%or_|(EI9{3)N`d}L% zDI&rl<7$ZNrBO~zG7P9}p)(tsg@y3S(_hq#Q(B8La%74BKE6HUxo;HxCJl+N@9RI< z1dcNsoV=2#;~l1%!W=T5@LV)$PsxG7u*sZsdpRBb*3BX%<0N0ZAvgldfMm&)k3H=A z7Y0N%VA=M*CpUbaZrYg+Lgr;5SWsgTksOwCoAD~mO=oOw!I8ti0fhl`TfS)QNj4*n zbua)T4`tX#_8p`3rLY(Rvs3*V)XV0^#&h_25&Be%RVzN!_491C;iZRDOW_R@-?A_{ zyXo-==cKB;78JOnBO_uvr$bz?ZXcw7NFL=L&f`n_*a(n##1)w+_IPh*&n8b9irB0F z+u3gJ+Qk0KsW5F~RzkG;PfwoaEg-h|aFTDslN}X05^VLmdF%G27OQFh)sGiZ-k(#d zA%j(64;QuM>nXRlYjt;e{$FQ2hE6+=hQjI9m96j7ak%`a_k&{=Q7J_Qzp(9EX9n}- zLxWYz34qHe$~s%^&i!@I;>eqtk{9iY7)QIQ-1+v-!@0Vmd=9E^t7g<9gtm9^SZf8H zO!bUk|ELa(11K1^ly=fFT zQRaG@`i$f{kASJd^oj1@*4iYaX=-oqvXJdB1H#8OE1YHn5sd}aU6s+q!oR9^Cr_te zsZ45x;vj1u@X{?recma<&W{ez zAr=xcSN4{8)0qg38a&hB4!8D;taZ3)yer1A zm(Wc%dXkixIaq(}2vS<0F}kAOo-{8g7M?FVcxRHsEpdr}Lt)pwzHE<3^+Zs$nA(r+n>X$JP$eQM$;!3b4DXg; zwwf$sdy|osiOWn>n%&tY4*`}o2k%c9SZi*yN{D{HdwwMA(bGnj(gfioA@L}u%Er)f z{w_Leao&QswK2tfe$tK`#0`PlX*QHlNZc;s)raZ#?_c6thGikdk+UxB4W?$XcQd!C z)+KA*NpCsLAC_4L;Es?LyEL{i#D4#|lYlWtjN2}7v5c+kqi9Pu$%wTj`(^;Xvy9;r zw_F|rV2Jgh(edjtzE)=?21P|v^|hJB)FLKrg8o2w9Yb6Kc>-1k)9@br2$nXqJdLFw zu0|Bz!utbanFH(_Z${TWjqxzBsVMvS>C+Ev#@dfm?aBkSM{IkTcz8Eh+*`pXPJfck zSbsCNsyj$tcEh#7%+rg`*>O(AWLapO(U42tb2(+o%yP$zaECzo!jx_K=1!jyC2;I* zg(EA!spH_gLP##_VGa-w1_ByN@j}KNCsTC;gFKq7YvJo@o+6jGJx?gn$&=B^9)XdE z=vdOtQ<(Du9zMTVb4exDFe_&b6XT|242eV|T8`g<`*B!LJ_3jDBj&2A;75-BegjmG zZKX(E4}E-GLi~`b%B5!z?LvhN)&$6`uP)dR(AJH++VAUkv3L1I(luee2he3xt(taX zs)cWZMOtottO3W>$4{IK3s&zjLGG2=pA&EqC%}EYvIvF=dbn^8p?byTo0auYYz!!y zca}P?OSi)c4}b`Ef*6DZE}nCP>eJ3HSD&t?m3C8q$Wg!J8m_WVww!A#w=q1cmu>Ps zLtEUqv{X|1O&_L1xRicJhy&o=-G5fK1oGh8Kk0W4O<-(HvlztMmu=Qb_<{y@JBvvuE{q{!maC7QdO8kT`hz z7TP|n}2w7QKhz{etBu2cj0lq%G?sWkY#a0n$S9Fy$#x$vX zQ&yx*Rg`Y30XXne%@oYy1Tm(wuIc|Ed&-9ZO&T4)+PRc&Anlca^i#{ zffxwu+V^>Lj!CtZyMaU5kiVo|Gj z4*D0j1tO_s`}XUI5Q6&${zKyj=YQ?^3MNOJ_W^%@xk2JOjz>|EVB9EHJzz~hV6VN; z0Wp&mOh!?8)nE>cdEt<$hXA8rvY$J44Tx9+R=Wfle)hyBgwH7y-<95$+QoVJSIo(gLM{3uxOQ#C?!qu(8IG9xLi zZQwi8nu;;*-i)Ni`3t>@KgyGI-YF!Bvfr|pjR|%4j+I_w-NOd0#YJ{yyb| z+CuadAB}fC`)lL&RjBi00DOj>F_>BcyCRtmKNc7P!($^8lX_TMS7dTsY}Ji1zZsp$ z@g^SC12yL-f3zsmJmMtb?I1}_mvsd<$>1K?9pcG@Q*BK{P}R}evXrH9)QMRHw7^{n zI~~%&)HXy-vG6-DVc$Q zE!+mXDIFh}zON?!#?W)0`2#eMDo(hEafxVLx%q@L0~m=gWq=t$=Vi^M>rJ)^B07Ep z;3QdCtRgw(BYP89l$Df_XOzI+XpgoJ$XZj&qYRU7;`HX_EhDfq{c3WK#lDv3D^oPP z3B58ar+<2?ta!^Rz0;%ebJyDd_4aPF+gvo+XScc8@du;8%0#pC%==4HmT|`=s_FL&I!)H* zk=sT>8mu;OdPRDz=5$Oa18#|cpdb{qa6 zBQ+H%;0JZY9XAOTwp$CGskMn8%uBs$cPu|*D9zob)-U>k@WAED2n%ny$bA{HT9Es< zIBo^rTO|})eRB32nrk8c|6{d$76&%9yiVdhHP$WB)tx}C1T8V*I1{C zEgHj*s2cbV7Nd$E>U=q${*7DRFj`{JNoHRi=PT%`(UH`m(Quy2)O~HCNhkem z-HFvL=Y&^Wq620eU7c5`o(bjoZv=ZXvtq5u{QAL_(uJoAQmE_YCUo5IW0BEam zqomU2!Ucnp<-1(f7}c!G&l+v)^%!^|RCiF=YR69&VYx$MkKSe77oB8`LcIa)TC4Bi zQe$h=8>l|9*IX+6VX(3;76LH>z`D0I_z!#}wJGu?l%G~sKfq=$2h9~u!xn)WN%fv1V6;pMB8zb zf`njEAj!%1aGy{U7vF*3NfomA(HDAC*hJ*?KcqA- zNP`KaE*aG+o26))Ek+k($_9XN2o|r?Wti0Kjsk@m$q}~ee`>g1&A0siR?gYzNiz^Cy0W8-ugPz@T9?k7Ul zmqg@ky@#D3lt4HA0xcS}09X_wJZ#{D6YD*dTU;x>^JiY|uZ`SiII@alzrDZthV$L8 z-SN8?20*INwaJbB{iy8ufB+w6TY_$ANL^iBFLUeZ6JgdzrZWCT&YNdxR{WQ(&bx#2 z=o0?e>RdHDv!nm?9?KHUWH6TM^r@b(@dw+BG^$`>nRxeZ6BpOg%i|wp<>ZFy44Z>6 zSX0@ea*lz(7~X6NF^3{7g^1xdZ}u$7khoK)jb@k<3820`_B~co-2S#= z;I1BiUkJ!ahb(OLel1+Tqo298<^Fv#1R34Bz|N}yY<-jO-yi;{m!6!Q4F5f}HE1{S zw*C0})lj0y!;G2Y=zMM6!Wk)#)~XdD85)NU3La4;m!!_{>XhL-az_f^Ga!!}inEPNTS5iv^r zq;)4lK=k2@z#VbJGdti32J>(#H`p9eVZ#aI8PRB1VALfbWe?XOmI_&6X$zIP9@6+VF%V^^Mjk_{LS5H{eAh1a>S(qFYqoH+OSa8W87 zTSW;GS-XE*UR{Xvz}3!ydryb6*R0w7rbO}&R>V_g2+Z+3u6qpYuNmV{iI0h?7TfOe-sw$Yx=j`cL2#WjKyFs??w_lO zmzaI5SlzcP=f`jGNL|Z^$>-InAL5mTc(fBB1NVAh7K5oT!gD_&i8vUDBuVf>|0p=OSqy! zN=XSF>jey8qup~R#C>lp+%TCT|4K1<=>!aMz&+f7RNw;#^sXu%earN#?g-(E!PUr! zF)^Y5p!>h$ap`30@OF+^fHc^lk}3>s2Jr7wT7&)pP!moFNQsN(m{ProcVN_KL|ZaMH4nm0wlcdGI*9c(bS4th>=xhdL^FsRAUc z7Sd6ZIZ#~bVumj4Y&VFWf*g9KM}(JJ*KCwS`W9MUz@(zkN?cg=LrOz9vG=>q^#q>N zWFxh0wHWhl_JxB2sYAZ}tY#Fbq44&&7?r-u7B1ZT#3SbCK0nL8PNnbOj!W=KT1(&} z!rh(&!Z}8a27#(|HSaSV%%3kA6q*ZAe7L4qs~rn(?%aEyG5aKsr9OLYMv%V*D60TK%(9AjPX?h!uxZfbEZTlx-y@OI9Bm6>1Yme!Ii2QBoj23@u zO|^q+ZvOs$<2v4%*5)*BF=Se+t4wQ4i?mW$FY6Xd-643uP0|p{aQygj%=i~LZEkoqJ$v>8Om6o+El0S<0hCmSLI4l6fRey*rowp9 zzU{?}29#XzK!7&qY8gBdY2apL%5?hRbZs5Bc|K{fINC7cD2esAh=+xR(3)H`-NbrO zG2nSgCWb(PLfdVrpqE+3*vTzO*`&+BdqqyPqLg83JX*p(b4k~YsXlfrPv|FVjWfVgCD9im^s}8uGL8GSYTKwEk{>z=k(>A&+3$(1KSqn_iA)$0Ih>P$Hi0M zr?8PJ&CLzgYHIOkgtA%;!>sQfU%fc9BiI=OdjC$5qd@8;1W^ z)D-_JkN^C({0mc-y%m)KiJna5gu8kk(E7NWo$h!eU?E>C(ZV91_)M$v#GZRC{krEA zU{URUXOvncD08aLq zJGBU(xdZ@RY~PN5JrZaOI<~HHo#b;pP0?;*6p=Wes$VSkBi%Jr@!Wug=UGc1`7S)a z{V7iDTVFcGh{!*gbzNyERa(qqEvEjiw74&Mt-yi4-8C8HnV%UnfjmcWU}I-4%Ub8k zy|aNR`k>_$u?@K>;N>~qp;Q!^~RrO`{otkgZz)~qA*KYFh z`IOR#S5uC^oZmnwiS7dJ$nnLm-b$oD!v6yUj2L-w`{YLQJ3U>9aw4#03tlNWPI*J3 zTGi4%A$EKmN?8()-4^E>FIZY0^Hf;mI)xIw z$c7+Cp&MJO=uXwebE~FqvG4x|5IN^RZMH&ZOG{Zjr=z#YC1}w{GYp;#G#vd#5jQGv z=T(o}V$D@oEZh;jtF1A=V|QV3_0As;SvFcQ8Kz3gtk?1V1le_G9DnVVuwJ2DChw$8 zqA4rZ4fYD>bj|E9Grt(&yuRN=_6Atg=b*WC+`62G=GI(?XiAe#*=fT9d(W*?r7r#y zjTvnO7huRo>{I@A?#RH3%2LP15ULg7JffnyM^rdDYCKlhW~7UInMJtXXq@oA`Z>ws zke09QiP=4wiK$PxaIa@%v~}|Bpb3G49q>@#@=70@H{5}Y1Y=up$ILQi^P_1mG)SsF z=wGYXu7y6r59jXAonIiKA(rcOU3kNLMUk%7Dd zi8|)Wfs7&fkc!n=jjr_l^gFq?n^Jt%TWD$xO64s7$oa711a*eNjn>V30vxS!FJGo5 zAmMH-hUYuYZ|mRmloXU77#7vKJ)S^W?4?HsIsWe58iJ&J3ga$95dquX@qvy|k_8_R zD{zE=(UajKg)~SMEEIG6M>M2K1*v_;jZIX*FT-b$RPRc$k^b`D&p`$N6=gfh7zSZ@ zW^5opEfp0vm2;VL0?hpHc3>r!B@l7+vf=!JMa&RBoKUa7d;OY2VDagm03 z)5Q4rH%z9jtyoeW19~GwiHV6FAx9@QSBV`0UicsKN|i z1?{5!rEuP7c_@;^@YGa$@jBM;_{ql-X8cJ^!}G#_dx53z67>(CkaYb`XC^=?+CA9W zR;v#P!TULWA(btrsIe!cNtjn742g&1x!WMipyEcjwsD=13**>96U1h7-pBP5`BwF* z8o*fE3%fAMFDI0~4paLC0DM65-q4U`A{Q1X@R+5#1ev_CcxB2JQ;0(f66j!T=5Fpr z*h;Z|HJQ;@-h4jb-tf|ZY};m_%+$)j1{^;yxAQfVIyu-)wAiofA?DJudmV zU6xmekvY*`C`(j8>qr#008hfY^*XAniXME3|LJ{d^uf%o;}Z`Qe7oAeFCAS-X?SXL z{9RT5%(e|F$;tVa(J{+_x|OgaL;>;`0bt)$gjd zzQ~hFmE_8my>5CA59_K$b!(GZ!gxFT$K+V%j_|DfSodNRb3@^>BBjD7mn*n_4QpQ8 zXJc~;a3ld?d@^OB`GlWE>$HqU!^%Qm6~2Oy3zpO~KRQX>|* zB4{HW|A3X839)DSMci{Q;f}GA$x(vAcjKB;_5t5a@vG617IdEMS8Hr~pRC|p*&4n( zREh6X_ndLt$Xjexn3n?b9^N5(Omkk=4@5rXp;aZtM3Ju?oY=xAz8_qC*t4y;^5fU1 zS*MPyg*@;{q&m-cbxxRJOn z>y6G1ZKFevNaHgwsMf`!(~FI$4*4gtmT(s2z6sN;?xaAH-|C3I%2rfjjUVud7W(j>y$3 zp(*xlV^2y?Q|>?P$q*OF8aXR6o|seEe(#`@aL`YaA@7E++kD=&*5*Z4DMfQFeAgye z$}^?xRKcG7QOLJ&Ic!G4nsIllO4lPz|NN@3Yhi5PQ;hTP2I)BUj`18h^q{*N0!$vS znfX2^o{ncnWiy@o{LG#+k!*VxYEI@82*2A&z`?eU$|*DQ-g8ZAj};3CoMW!=+V?NB zI`eYS(9CvRWoKEsOigZ9_(b^bGp38xw^Bg7q*jI_k-IE=%PLK?;^~jl`s?Rr8z6Y* zZ3H61Fv2aK4NKigzI zn2BMx+hGt$Kh1v3LP3}!u5+911(qitBSDf`{J8Rf$~s7i*AievUh@R7Ku3hHSW}0E z23TG#UH6i)Rmt)8#>@;w1{CP<-2k*LxC@B)UP23^{;n=~jOGN;1zs70pAVZrm2aA= z&krDsW-SiWuR#e|!y!Y2U@$;fF03y!H8iM5@c?XW;3{?xeTUau75@lpi8A+@Iw2z) z6y+NH1=*- z!(Xplq^2p(#|*E+|Nl&Tw4-9HEp&M>9Vj{ zZRCJFO!qT0Bj-xuUb#4qY0H&B#;4_>vTen}?+c~+X8dTNh)~|KJ{O6u>Q0^E={333=xiSRJFbJGxDH=_`Uva@ZJvBLRC=c3X5dwIwfugGa&X+g5 z!};6mo6pO7Xm*l1A`l9Q9@rzIOl$3Rb1wAQ?{XlUWZPmJV0z&vauNsx%q$%V{_zZm zYqOQs(_Ow3=K|C}53b^K5r!{2b^9-7$8@*1JRsl`nz0)@1z`>i0gA*9j4W6H z&rSd5fv}Ks_Ld8+qODHKq9%iJf3tmI9O~Fvn$@>^>0>--1JZNHj0n=nVuaI{X`@Ou z=a&Je@iyihtM^q~hvQ0iaMPEYth#gk@_|{8@3iN_`~rDUdJ za9fZWW(1`1swbX|Wr(!RJz@Unz&XZwDCKA3?hpyXdEr9M)1Om13ffJKj36M#Jx#`@ zKz$6e@e=pFxAvuDd1)Ape0gRb2D}eD0lC~tc zAPwdwICll5@MF#p;4*D__3GEV>#tUXU~E9P?8jb{EDK0mmnl0r6~;1KGz7^K3B4_n zvg_bQx{jSa5X%S>Mh4!-8oxzAfT|IAmbMS6JNyu`sGvKAq&H#_K6|tGDXP&$T&)PF znx3wQ;WvV?NkRwqMGUmGUy&sSj9$)rL|tjq zaBbM&P{Vyo{*rZe^+9=M3Wxots|;5sJ)1R;=|#Lg>Yn%ZLJ!L#{pKc3wyq;s=Ms83 zJ3a&b1FZxXBr-KC-U$pDds}78TJIQ}zo9Soi0YJ(6&JNxKiU2~dq4AZ-He^lVn?y; z^)^t5aQ5y4`a>|$^F;JNCEEjB9i}|(*j(QxhuG~h!`$`~-x!uQKtJH`1>8|eLatL) z{=6P@^~eK>4)}dUF3d!{oIJa2IK-Btb(*sg%!96ryN}c`age5Mwa?EfHFUBtSeLv>Ig2Gn7!Tw0r(;RUrfOC~CE z-xYK5Q>i40ijabCJLv@8;gC6r!)Lgf`S6sp#(OEPSLv*{{JXHAq7K0+#pWgR&ML>$ z^~p$pF=P(eqWwO#osTqffdM;unX&FKKmf<&^_}}lks6CBe_z(i1FT+8>Pv~?^G9wc z3Kr^B<>)N$0uK$f(xYl25ttgy_`pK=&QM;Mza#pf4UsC#|x$Utva@zO&s2|Uq zFM2+n_lsGsh?CAcq_JyIl9M~NlRez#`RJcAI$mE&^YcAh*}P!8WeRrT`IwPr^ZQ|GiK zi`vz?+|4cKPiV}4|NAmE{ZLhPR>Mu?pv-zr-((Mr+F#%gzD(KHb{M6T(|ozbg{(x0 zzQb1(+Z2Y{4m&pc%6}Hl$CdhZ<#nL#5Nd9HnI))Ee)=-y^qqFon)`9vI$QU)S~Gf-!4_XEK!jcXGZ`;8Hidqu8X{R=c!E2NYsPV0qx$!#!TaD*)UYFLd8|Gjw5K zhhv+Tk(|6?)20S!5~ggME{D-7kS;)^)$YobyYUtS#UK}EU~!Vxbw0H)mq+2kf;w@W z%=rc~6CC_&Y|L?pv%Slr!{y>h2y7bQ!*mKBPN>rcy1JcRT@z1WpM%L@_R}ZQm`@uN z(%$aw57+|K&0kWyE0k`8+uL*;oBk&cNXZd;J1kJ(f;4tynML7%_Q!Ur?iQWEg41!x zX2%ARoBI;>a`1c@5jM*-tB#J3zi(|`J4Q`__?alEvb6>aH{GGq+D6BRh|r!@n$6l`syoata}}g^rDZa9zkX7j#Ie3VnYk%#&vT`bckiq*wSlSz zXyP%`IyxPS8E#Ml_k^5rLCP&vZ5^QQCG)AckA34>mY zlPaIKVV92<tmq^N5XGY4iOWQPi{IyZ(F{nE;i^XeOUjk2OlUsngOkT`Nu-V zTTb`uDh?%#H~t!qm*e~M3c!p9uYiS_*=N*a@S%!HDekkut&g343|%|3%|E?JkjfT8 zRIu=e<-qgWL+9tk96)nnQGoMsjDNZFqlMN$e+%ygJ{4VSPs}hi zyBLP0e$OzQM84&yKa5$flM%P=f|?1#+G~S@YL~t^zH!99jUZituPpZlKsLC|?rsFM z8T-j>FaP^+xyP;K7~toqG}e^r!TpRHiUq`nnGDkyv42?P8~G|CLB8aj-&>Q(5k{) zkKFZNP}I(*`*G9x>f|kXW#@F?S?fe-W`K*Jz#MpP+Z1l0;%NYK4E7fFNISdF zkSQhnHr{~G0f`9i69)ri;J`v{OQ8C+%Xf$hJZrIw81VvG^;?4-rPgCs& zl6b7Bjo%@Eei@XM8Q1*!ipEUo)ZWqOI|(HV#!pxu*2BAxHiEnx1K(`=$bFCE>?zr^-VZO_ zeNQ^Up4lBw@b^3~Ke)T(kn0e2*bNr*z#LrcXh+k+=`={^uTG2WhpnNs15+JAtg!tD zN8Q<3(Bb6!-W&6NcX0AZO7MKIE`7I=EJNe$Q`eVik?!;N($+;V4T)2}Rjc_L&#!pw zID7Vk{O3!aMYu$8eM@h)NP73mt@Xa&@)hi^BH1k8UYpX13l5Kue@4gvoNIl-vrzQ0 zRK?c3z}u}^+iQyPrMlwee(|i%kR9slnsw%HGj;krpNI+dAE~_0#V3yS$CdBnDl{t3 z*Lpo+rA~c%rHgM2K-72eq38(ka@OsIbxG8*qX;k{U|3~nX-TGv4Q(+fCjQ>GeJQ?5s|m($i}Qwz*P$4#^xH{ zn=se;27w@c=?Zd7aK+q0565m4wqQJ8aQds^PJ^6Q0`6VAojOv`LLw6-WUTr=elRTG z0Dg!{NSyle;u!dU<#B?=mm}a#k<^{Hg`Yp6G{EnwMLHN@D2ULHVQs<*S1FW6bQKK& zAM@OiG|0ll#BaU`3JjbB%~pF2H&XqU5Jlhvz}2hMXIO^kV-~t|MEN3a_>t0e&qhIZ z4g~X3Oo|!WBMvMAIt&a5Aj8|w6%~|dSRSh#a%*ig7u4rku=YIgGbZ%ni8*l7wb4XO zsZ>>Wyy_^R*9>No4@ze8ukY~;`LvaN#rNfh)SaFhJ+&3ucyF+7(>7;%f_$7{WD-5_ z1%ezUt6$Ia#a4=P$MO&ywN+tWlRZ3}H*Y4sB7PC>4$D>0VLJm5f`?)oU$ui*yyS`< z>yQ1kzZAQF`Y3EnA2bQ@YCUW&xy^<+Ic{3ql;lrL1|OptAs29)x2kS9`vX{&YgRM{K}6LT+NA2ku!ON6&RtuuI!VT)cRYB!Zk;zY*y>Ev1EL zUY>BhveM@kxDwZ{F%e#n&?;MG z?hd?_lbA?FW+s@W27)6%aGw%X_zT|N@$JJtgb=x>7HaD^xmvEL!6r{D3l?q+1S~bv z4}3%Yytg@i7EEp`A}T6M(Z_t>zVy^os*pQ(b}Mw1`1)J7y<9GRKx^r}9A*l$8ll(- z&YNDZm+5bVEeKV}rk(z@VQ|J!?F_ZvWONZkJZq@;%EXrF3oB%2Nj4fuHPt2$^TP1ygk~N3KU~tc!TYA~ z=3p%Sv=?tM1A+Bj8kcFEGsT-=EVQ-c?}+2Qy6QRdT&7KY#1H8@UQY9y5tkw_K4tyIbs*ED>qU}Xv8HYI`cnU*ahgTN$?2L#A? z^n0T9=3l2Qs*!aMOHzcdD(>2ak|j=i7nhRL)hbg8iO^rRuCIMNh&}Q$Ww^e&-~1Qy z&L++1W^5vrl}4lT6}V}kcHVaAo02?x3sx;=!ie&eLg5{Sqg_{Z)zxW;l=Spu{=ueo zjEuRKvW1DRSLHX#y3>&R&@2ThirUpb=yk@#PPzFZek*Fg8OuX@dJKJ3FkZl~e5FtH zqZEpJBG(hs`b*CAp1`pmc`jRV^>%0UVpn(f%8=PvufFQ7r$g*h9LB`z(~B)M0){l) zs3{{QaT{eS6@K7G>65juSgGbz#$G4ziyps-gusOsesuhP^7He4gqDGUU^`laQ2q`@ zq{_k0Shv&upXZKbRautb?C~oOslhrn-r_MDLe|@|c3zr)%JxyzC)Ryl?FemH>>Nwi z;$W^qLz9d^mYuY9kfGcLxOCqSHh;`I!KzmD_j7#?D2tu1v)ez{z?&8i1N$I*3{K~ z$0`{O{Se9ZhnRXO7Lp$VF3e>?3#73g_lm9R*zBP&4}p%1x2?>g`iy16?tEP-_AxpW z-h$g>!2yJ_%%|ZdYVWz*Kq#aY$j3()_$cObANn6^g0Y>cZSz;HXO)jiyWn$&D1%Dc8Hdn(`>s5NQUo(pJ>Gw#d`<6{vN$l9PGG_H9mSI@S>97-Jn^lm!QH&T zIe+gCnqz?QtRaxQvH)=Iac?j17P^n!UK6W}=;qWf-r7ZS-`}*$c; z=db#je@y*MaDB1(LI#aK=vjTrpKkAXy1j$}?L{fb zFQwbkA@aDhgj76Ze1d5csv@8E8pJ6ny>qOTJ@veT*Cpt_(*SwjFEAdO1Q zKJZHRdY&lBppJb>7^#3%o4NNe=(Wf<;$SLbVK}>dfhzv z!{7edim|aL3R2UmHmh(jjgm;3@8Y*z()_%wcPh0t(Y~V>>pr*XTW@LCf1sb)#jXZF zUEOWL@WGIP#$$2f{E-X6OMxHd%Y*}MS#`FpsNL!|H@iKw9&ODXq7h?bGavmG^EjhL z*P*1}{gSfaKQE4;51?$2ML?`)VP-}&8ZS3@h_|%W^(-^eJ+!?1127KI^2t91KHWQD zZ-^9Wn_5ryLGRPthtD}2pntrJZ#&=4oyXB=;&rOET=&?pMzmU$ruPKmtCwJDbicWI z;DWfk{<{1#`JSiA@CDqLxO32CUf6!*0=3TA?rC~lO%HN*UdLSR?BrVdi-YaHyp_4- zwa~!`mn`2UbAKMa?OIm=MQtRIfMF1naNoD!n}^I1U3J;-cDl4-D<3Wfs`}chNyR>H z=qp@l#d$(NuJ!(W9_8<=^S})+Dgr|E8KX_rK(XfL=lR`LWD@nu4dn0N?ANoqTA#~G z-D$hC=0<(c{MWB+m5j&Nx^_4pecGj^_B_I&JeKO#MNU5sy~XU}akvynfzt!N{epxL zc8Su`k6spL#3Z+gEgf(DsIhY8%JWv&P9J@(ZsBD0AjJOzj7=~;9f!S4xbY4F+ur+8 z-#!IGQj1ny`}n&T^fUSD=J6ga15b&muL>9GN&?RsQRGzt zvpC^cCvhkSRJe;1wqO=v1%h}&aJP||o?YdqvTotSE6I}%fq$?{8WrweUo8KDecL_i zB(3ZCU*eo{Kh)Q&T5gh>yd@B6oT$S9RIy(wL-htz7kl!NM3YSB;;zA?!V(gPL5bDV zdjRuQm|-BU3<^*SL?79}Pn(X|391yh-@-3CJ7dgE1aSX=f({H0gexQu@jiV@G1E<> zz83*}S@kNW*5}|gt|Cxtum#Iaz5G;AR+gLg?mswm_%(72aJQXN{c8+(C}?Xtvg2Ws zt`dGq0)LbTbjjd{V#Y%HJ^qrvl(o1QnEQf66UO$TebiInsu1>{r{9@9tBhYB= z4Kkck5E0e=gejI~=l9(0G)Ddxn2c2_)N^^wboKof*O{8b`948M?<#bC+w7xVX=v&2uT!3J zCW=l<*LlrZyCWNnCq7)wF=Cz)dD^Rxe2jskjHQ_aN;B+0)O2?+ zv&6|4u^zfpm(dsIpJXVA>GBhtSK;!mC)p?DW|( zv|O-M1~vY~cn9;KcVCgISxhfI+v(nOf2NLO(q1pD&C%? zZ_KAXx1`l`O+fgZf9_z2p5mDjspg3pRtld;ecaX@^q-o>C038z<`)*OVnGD)JoeME zQ)$JS2HEnj-@G{n#aj~6m-qqvN%BZ<#Bu{2m2@O zHCMVa!`Yl)N5j+6@9mRS1X@E*lk9t?chkaBzGNwduyqN9E zEWl2=sE#8R7VCu~S!bK?he%4bo%u7QEKA8y29*u1C)8UQz5^qee0>)`&0%R-hUd&6 z@r13vE|$RtDL5g8G1iqjmM$LpC0x(jldF0-^d*Oy={Mc#>?je_DVJK1<8fNLDjUI} zd2`kMv{))Ig>I6rKA6by+xwNNyf}lZ-2hH|o<>Jf*9wE=tc-!Mg#<%k8=y*n*+_7~ zm#%7EHK(aAr?msfob{M(DaEDM0#Y=-3eQU0uMKnT?H&iAlz5Sb1vC2;O}hktcw8VD1i_3a->J#Xn!HSvM98W+tHMWds;d z_+M3lv2(5If1`uYi(C~>oteJd5x*uF<2z7K^5(%eURElmAIcSMSnq$xtruW7sV?sU zbXXa;;@U&k8^D*>a>`d=NrR?tH3GtvPTaV>(%+gbLGmu9>je}~-1@u+_wN(e8VtMC z|2`-&lU#>`_>IIOT^$`jL>ur)Hf_qu$vOFL&S~yoA)07V$@K!ujTHf9vDyUKp(8`< zzKxsBUIC{DoO(oLR9dc6Fc8h4!U>@bdm@8hln#|(W~K$K7f=FiY%U-_$~uRaR`rqs zW7er?30Ac}$xF;-Z^N|`cUb(5H|xAu$BxnUCv0KSP#?wYg3AaZQZjNDG<0#UcQ=d` z&+v(j{;~fl$2?$Y*b_oU4M!?Zw`Cl#{mDCZuFuHC!MMOlxU(;nS?Zn6+gBxT56Dt3 zT@!E$h)76Ge1y8u*w}dNW#HLj{{=exCthqSYacdIF_jI!evNh9t~m$L>Z@Wtukex) z@31phiD~RHEe@1?{U31pyu6F&{j?=fwWFNLd7c)n*?B|bvL5$$Kb&%Rg8#ZmKHuz~ zRyO7o5tDs;49#j@mRK~OS}{crt2Yvmfo^!|&aWT4uQ)ww4UePNDPDf5G_@np%V3w7 zMHLB9lAAZYkK#<>$!G`BW}pE_#ob``W|?QeLUFc@lP~A-GG1BT!l+&rpRD}49Ou>g zW);(U9uk6=>o&@`1@<>IdU`l{=)69WQhKGxvryZpw#2SDx{HxrO~DyG5W#E?MAa#Vw|-m7^*vbQqOxe8f>IK_kpIG_Deuz z6BLkCWE#LoB3w6?HKHj-E!y!M!cAmalMN`im_U*ogAB?BZ1y|oB} zhg5qSLN}P4U;svyH~>5ZFG*7M$eta@1^kNqNUMDU#b__}#xl=x_gl(9M~ZOLc)a zqfNwSmry?8BCoGF17asjcVedP(^{eG*q_bDAywe%I zY5vmx$JcwnbKSP@UUm4eNirb>H{*`90tN@8|Wrp4T&OKJU-_x~}s&&*M1Gc5p`$4^ zymm@YbE~%B$+!5@C)UbEDcpK;^&70D4=I075GK3NHXXY?be&MyYvcQopqJr;-qsj% z)L^z6wQbq)rzg);2afw}cJ8_m^K4~Ebkoy{sBgCBrumF-zp&<-5ZDqr9<%OetLo%U6cQvj zxwy}1YHCuGKvZ1k15c)Tt`24{Z|ndR^ghux+TPLQa9`=bn}MdpN^smVIq#w#db(TeBE*^FdMDtgB0$*s#P9(Q!bbN0 z);M)7`i23RtHDC^?aB`KWAiz0u@)^A?H1LoWs2+eDfam-?XLB+EUx-**jw??j8m)d zr()5Fabr1u2x`ltlyeIz$!2Wr)@=#@vuWqrqB+#z>ww`~WU8K?_xz)FRob0iWxe+H znv1xTc!5Gv{+MP#`j{qX(AuUg+djMRkzDE_j{}UC*LH1D>=dOi#j=qOiDp_9$qelh ztsiCM&DURvp^PoehvXhu)qB>&NKZCBa>P7JtJjAMAe?dFw?5z%`_VT=_@{|lL3DAh zasFq@hQYkBvLcf&6K4|K3K7>M8ywt4n!d2~mgnm1(%vxWGvnI+*3RzPfW{WIt|WVB zk2EAu1=tEazJ2W4Ze9OOIw!+}bl9mwKZR_JtkZ7M>uNSj#GPbjcsTor^wMF%3fRp7 zY3e5IC&;2;rcmkicK?(gZ>qF2SrF!A?jyo^QwF^>FE1G}oJH%3SR^kmud$@5g)F`G z{M9svR5pjyV-6a-vUzJczkdC?ZO<8@&F483j&7B&!%Eo%Hvz*y__G>$V2 zA`cY^oW9rVCa1;-_lgP>L5NBbyGuXaq?rkHP%CB2uBaVj+SGx|(K@BDxR{cJ61YD) zk<$0aXY1Ev`#vnG#tb`PWY*R5g!S%8VuQIfNr+DTv%e~BGR?~)zPP8oeV;f^HdC6vs#d6=t zBjVpbJBi#&me@ZK+@f_ceKpZJ?Zoz{)%WIxw=G7zx6tr-pSG40oO2#_Jf)m-rF!$y zkz6KA)2_6*EADxDx3v?$?C<%6P2+f<-Lqyv=G|OLiQ^~USeR*c${WsXeSAJm*?8F1 zr~bE2owa=C#)E%h%Ut^|l##?b>D0{D6Wn?0W!PS+x*gzgpN+;hPN-FslR}&&Ga?l4JZvC5s-+&;^HxICnZcup$H~K7XZzH zrDU00RgS&aS%jFSs|)*s%}9WM${Rkdak^P|!!*8-!g;2+YG2tE?eztB?vUR1k`0pm zrr%qz&~nK) z9=ToF!k;iwmcu?Rd}pXQsr7zXFmG*Ft!js(6IntCnmusMClSF$ZOyc`K`_(Fm(8T$jTo- zezWbs&;(qC$bx@%9GTniYb|YX;<@yn-7&l| zrz=r!Tb#Jo)fO$c-rMwtW~8I-UKVU>@nrg+5MsPz%ReC`J5S+Jy<47~onqw=n-KZf zyaDwTp4Bv7-5!_R-Nf$DPRQ?mv60SLpC;(Q(K{A{ePXObKIaocSA;|5HWLX_O^pI3 zyFrcj_uhO_SNi|KduYabCG@jC`x~%<#|#Y3oJK?>`TJ zDLb_hElis}{T(_sJ{fUv$c<0`7GWMYKWy$CE9=HHtkr+gIn|TSgD&%F*Nx!AYDoS` zuK`ZHxeOq8J#N-n+&3g{QPJov7?wyD40+MjcL49;7GolLdxF~z6(PyM{wdjN~lqN-ZJnJotUgt#7VG%#@*)&V~9ug#?Knc}g!uZTJTC25t zV8LxIRx}=d&Wlh8f=ZTj22+6fn04EAS~1Ack>J-AEtSssh4)TCxkMfe-5eK4h0M&O zu)766PMP|h`z;#6!DGG!M+5r@2`*`?112%TbKk#H1jFGj{HT)o+zVFS$;y4K)D27X z8%PkR9aL(%lb#;RA+t$QqhRRT=0>k;ChfF6ISSV5CmF)AFE;jj9b~q%6E5<-eW1rU zE3c^~woxT{&W}7ipZ?&}qub8!VZo)D{`#H32`J~r5&@ZRD=n`C^Ge7@yQ zqYp(J1b6y1vpKsvyV}L+%8Z+(6zV>;Zg+MG7ZA*~a6!JIxG6IIUaP$A(ZY)(T*-_j zQudCdl~FPBvT*Z$qJpG;??KmHYYn;3+qms54 zucHjB5OouzMOV+P2D0aB-AaI?AC=8_`js%)3{<-BL7@5UFN)b zrH#_-yZEZjoR8wTlLZ4~^2%@OSceKPl)GykoUPHU0*xX3IJ^QgB`y{RWj*)oS_?E} zy?@trT{kt_)t>8OT;>1J9LHrk>fyaa=zZPX3c*R?W*U043>= zMne~GJ+y5v7fK+7&!Bfyo)6QlXlJD1FzM_A=gjyF-HbU7PPn5)_D z;hQ21?eUs0wHYK4F`5l5ae#|sE_b-m_-n+)DAVUh%gN}GCHFIJJi@>L{4^S@ z9uW!6wAaDNgL+R(j0ec=WwZMAJosW75_7tZabHPYsZsY%BQC6vJu-e%Jq|dy`4c0} z+&RQFk}k@_>}OVhvLoTN)q1}4Z(3Zw-9EP0s$ljfMb9Vs1E&<9vbdRi$lsvY;ko-~ zy&ivMVVe9l{!c~1aXsyv2M2CHO0!K_kd^_~(OYqsCoax;7{(kg zR&*@Uk}y~Oo&}>N=7|1)_d8@CGgDJE1gM8A3ZldxSOEkr9AmxTe~~a+jdVS8Q!hYihP5lZA=r3q>KJJJ?EqjQ@JKU5Bg@v_bB{ z>yQxFqn80?am*I^`Rd6VuIOly@6_iWha&M^!$$jSau_)RnJQXF(ZKGYZP%w`)=Vg@ zfHOkWboTvqM_5u|tR5647eU|$%|4`>u90G-nEmz*-sQIJ-ymtyv1AmQ!3hu=vA&KD z@C1h2)6ou;`}fZV2L}V6?6r7U0iqx3P~}Zr?oZ&(C&0_w#FEEoc5%lN=L06M)6f(3 z!x_r%#?-ii|8#MoZ|vgVWhNQE@cd`L)c-*8@B{A@^wdQHSM1TS zm229Gh?9s8ZtNKZXtZ!|JhkZrnF9q4-YpDW60HjhVpBMc20jtgJ=nZ~V(UflBC+U< z0|||YB^A%wTx_qSYm(l3lh(}=L+$Uv&%hI`L{QKw^s(T#;?B%zm-+hA+RCvWZ+-Ri z>v*K8)@EZ$MKM)TV-M2BV+IUmwB~A!8*^`t7Hpz?cWGv5C`hJlhw$U1iA_2tx}m0u zD+D&uc_LePTiP+pr|lfKLbc@-$+jJ%EciaSc88?k+>Zw<}~$A3OLDw)p3~ zUAs?u8}Kc!Q{J}rl#(4wt#d-tJ=eDO$6P#$dO;!ek)bx$mrgjmkt(pD7#7%avIGat z(C|ub?j-aP+X9)FSM&3_xL}!Dd&j-TlrVbrprk!s_@Gd>T@qvSYbqU`x(VBo( z8`I7Ua*~AYFH&FV1l9L+eWbir(dhzJG}PNWH27NjHl_&OizS;oANeUtx|MZl2Ea{DSxA(v!~rBRF?DqIFa7&Y5!u`#3~w zSj}i`<<2FW=sq$jBva$)zWp9CS_#G>=p2h>?`WB$S~e1fMFgZ5KS5r=7z~6hs5dc| z_dsHRk7LA1c3fQC1zYYk|9}9bJ$IoCh?V>e1rT)fU{M9PNuAV78+LE0pbh0ti&j9r zpw7tuSXZvnjYy`(^3Aq0aPQnX4==M%Xj=m+t|M7*Um-UlHD+WxUW)Ad6eNZ~UIjal zZE)dAUAniUsd0|}BcDLIea97GQ)iK}oG5F3ysv$cX(KraYVUh^2pocr6^*QSP^hgE zi1itl&}$39y04O^4M?<-?voAAb=1|Hh-uhBaSABLL+8}AH{_%sjzXX=VRc_0 z&E^&sfw))@or9L<+SsDr!7)rNy(J9vdC_61zT6h=Vck1Iq+naDN<^dxaeJ&CPVS+}hnDjLG}GUC>{z@F1j-{yxZ10FSt@ zN-fP*&mlA>d6bX^*VQSF97t!}w;yp8*LYD<6aPbE4TGZ<(&ajK&2dKiFvGTz z5?$}numu`@aJIJJ@V*qFaWu5Jg5Lo_%FM(>bf3X@@2yar)KI0nC4zfl5}8_BI*MR4 zOwC(uf_j&QCE@brV;-0P42uSV0+X{rkWi4K1O@~+ACX+~i=Puz-xn3hxp$%<^zWtW zangGV`pdJWQOvH8oYyeMH)|fk}ve_ilD*Hq(I0pjGQ!-km!L zC4=h+MW{nb5+51O2x&;}8q(QPp>^9u^Y! zYuX^Hx6=?y{4|sLqnP^h?`FC8?jcl12L0-^!Sovy$F42&D~J8`Or9u9m^L?`ABuC3@VX6OKxBumMHK`u%I zWU<+@<}g|uN<}5hb{V{MTAJJR7^tPL6qy2?+kCCX%U7@N0xW`SZMc?uh3R|#^Enew zCDZFvp>jMDqZ9N8nJWi>EdBb5ss>Ky_0^&B9KCJITops>3m$7Y;c;=N4IvPsMoT{v z#F};Q-t3Q8K_2MB=!Z-4UO1#b=%l^;X6fq#CbIuUEHhL6L zs1ncK5{aSX+P=PoZtv76j^tfzg0nax3mezoA>&oVbPse3tDd{6Y=SSHOH9SETY*DS z_!nI?+MQjL6?awvNjp0`zL$!n#pdP_8HES5v27w>RT%|aa%$^BoSPPYqa)Ml{n5^) z366u?e)#v<{#7W#Zg3JR1LQnsK@`E|0x$P0j3Jkomy6hvG^Djg6uAc_pelMRx5n8dQ{J{P7+q8aW>T zLukEDk*nP)Ec`Y%2QC!fSpt)5WPtU?~7V)^9?RZcg$iK?u1SpcHoLI zxhD5{IM#?}y~Nl=&fCqow(fT&4>2sQziYB^^!I(oM4syMb+z;$NOg5|79qmKFoYyA z^PTWUMMFiwg&wsNCyFs%2?Y)Ux7Q{NWg#PB*C9|>Sg^GS-)r@#U@oNyKUqH>wzJps z-t1$UE8?x24i|D#vTM*m>0SmuX%vAM3@{TF3SHkLWso}N;RWGx&5mE56yjA{9Ua*0x(!=gn7G=S1vhI0#=D^?d@tup-e zmrbdC2gFMV77i8qH63u0@4Ew2m*lk#i)#?pYG_EhghMsCud>l|>U+4(2FX@;kvCBX zVTOuh2)SyX5BS4An^is>zf$xVBbF+EeXj00VIk}ru-;A0T~K_%XjbIogPEIfVi^9>hkDGh=4)S zqn8EciuSH0ZgXf8Ec)|}RD+@Xj!~Qat4LgA@ z%|XHgsh1BXRoJY|C*xMQeoXHYoinXtmB3=*x(d&M*5H6U`JwLOm$>-ch0O~t55NQn z&N1$g2!WyE*tYF1OXIbVPHl<&^OhHO5W4Ki8>hQQ&U}kKoj;@QDpr5HK;%*ayx4Hn z%LxeFzJK2zkG*c)I=lu{I@2&pMw9mzM#gT~72vA75^W-GHOP3N`^7|#1_Ca1C?hAQ zh|%4A^Qb$VH?9dz`ipLTWz1Tj;Ck;Q1XtXkA@o>++9D0SQp=>_}EnF_2elAcU;@)5Y^BS1mJ;~#RT%9fPZHf#GqrS zTW~0>E&;}XSzxcJPFAe&iCYy?Y*LnVmi{l;bMm{{(n7nwUwUtALaQsD zBHP@+;VtgDF1(NNWp*2{ObqTg41`+0=IHC3@Q|h&UZAR~!82a#5OS!9QiOzdqn(~# zkh5W5JJe_r*V!A&YpfEXJ!W1nSIA?&F6@4lN~VjpKV%5Xd+@`F*t#loCovM`S{*J z3yNWXLv;bWNZ-D^vN7S?ktcjD0ZtL&Fa6P}r^dL(Nxxe`x`ED^4WXX+DBKHTNbgMb z|LrQX-phCOA1wgUVTLV_8SB8t3Q;s;E(Jv}+E6u3N|4PP+Co{-kiUsvyB`&h840E1 zUZ79lP|&(Dta0bKrjcB&%&;Tz_4;CAjRC35xl%h>6=3s^!&0S!wCVzP!C+7p*YO@}JHxx(ZDL8KwHSMQ$na!b!5&t2)dpjUns* zSi_>o!5x2m_sxS+)(e?_hR2^(09g}r7>zvBdNOmuqIF{UL7gE!B@yt%Fr@+B$ z`ar*so=94h1BX0qs$(yTVDtoS3`ioqcI^56;TFK`f*k!edSCPBYin)u&Z48|&so^0DMr8kR2ouYS#&sIumn_Ny3>P;;EPItOQWN+_t zQ}?ab(K`mo*pF(`KhzBrGrV;lNf;*AW1MR}+W!h1#^0cx7M%>-fK0&xEoZQNx3;S_ zL-P*<2`S!f9YWa8T^t)(TZn|bg)j0tm;A{vKA3?}k|?=k^1{D@E>{-wyV(-8HY zorRa?=Jqw=h%*GaaU&t3Bthr?*cg+zC`o{Pr3386+a(Mv=HlZSN%zspD!&I(u>Bh^ z(PhvSF_**A^E(W*L3_FI%dCj?2ipr1*AcD(EIg@pE=(OTf=5vDoWS zg>?c|%y%aTc2jK~1;{u7arV3ej7`b}EBd%Lo7gF|F&4VdK~Be|09NgzTu8 z9g>ALMYR&!oegK&1>pieOhjQ!zJMf@=ki_=u}jzgXqhph)%yY(Xe|$W*ZhrE}gKs431A9~)|V zabL(p!$iK6Z_>iL(%A0wx_p<7*NSfW?Rc;Lnv5=w=e|~)h6@8AGk_U{0LME{=bl{~ z0|}NZfATB(8@R0yGdI`%z@A38D1<1H)x1Pe5wZE`8wt^=PcGo4HJ>E#X}tqk-K1Kn zJ$K^OgRK}>I5kBK z`h0h~0r~d5A$!NC2NKd{GTByYW;F{AYX8|1RbsV8iG!Z`u|_$edb*sAXpNuZ`~l=K zp!;wfWyKy1(GN(zlLfSrzs=8IIdZq$(|T{((L0?P%BR^#n4liX35h9DEIH+CgdO(} zZ?ja(I{c{c;T=icpKx7TM+_AywW`!MnXq5m0+-7&9S^@3Hgl$|ymP_*gj8)iu)a+`pCaYSJrv3*#wgQ&W4*ed1Yk!pFQ(HlLD%7yOwPZ zT9ro)377Fqwe=*qlkYTvy2AUDus(<0jPUA4J1o)WS!7tMt|bl)SEkqnujF~{VXh4e z^*!5hrMtYmzk|fb&yOl7#(-RWuik|+OC#TbYZm?=ZHJWXr3y95sOG8|=-21JH5Lx( z6}dF7R9om1gPiE5lG78PMC)tTjN3czd9LsPm%SAM1b|Ex3NK$@U;J_ZK33efSv?EsS2>+cc;PPQ2-r9N$M24haC9z*AD(#R#WNrKBI}e zsQQK2FSU}vi{t90Ih=~VVx+po&$UR?)&>7f=GPbNON>chsPZz%4-0C&DVkRp!J;>4 za%z~!Vt8DB;^fP(oqRGd5A<|&z-n$y;RME~q>Pk18=YRNWx*XG7E${hx8TdGGy7yubaHp8NVHmH9?=8>A+?Skp1=}-=>@C%uBtRR)C=D%}_09UxbSBMZ z4t0qlefDBR*^lWv5Bu;ZvR-bQs(Cy= zSW7JIG#1vGSlE|0f-uwti45AR>rVeD?l0vSM-U~&U%&2LLEQ4j$DdrABf9ObGy}dR zBjL>L()s%=KcP*AIk%3D(dwhk9;8N8(^@B?^dd<)_wdyZ?`#SHbFwi~5IzxkV;a6@ z9z*xmk&c}GZql!`_-$%E39uH-e~|eN-oOwOuzJY55zk265?lcWRN^al65@HTUBZEf zcMmdSj9wJlz553~1@#76n{cY3?s2@MZu$Xb?7E}a2Yl72L$b5^p4Qs8Tj+m1GS?1` zNXthO(ZO9ry+1q!mC)^v=IdR?3!}x~^PE2}o}%17ILJ=IOElIQk37Fwe`~Hbg|g`} zI>BLw9KV-F?(7q4cFGpR7ltWzT%p^*{*BJ-QOy~oAolirBtjXA_@(5s@fgRA@j27m zherw@79+X?`d-%vS#y}m1B&L))t5}|Ui^E<4^Q+wY1~)w;mSp8i8zXnHz~0dN*y)~ z+Ym6!>e8f&X|$aUXh4ya-Ju`eY+L==22Jamu%JjkX!Xn2c0mbt!STW)KXau*szsy> z1WPF>UtF-)`HrT#LaS=SWMelcBAn|Q`CGE(Vr~yZDUL^m5qsiui23+Q zrf^07+1FsIDAa`he_ODGALKOPV5fw%1L20kC?o7EVO34<%7hRM?b$6P3!-26{fexr z=Rt@ejYrY|b?w#kde3o$57g0u+ibY|puUHE=;zypCcGdCxSY#--@bT}iey#eqg96* zM>Qq&n*D~`@BVF~J zGYl7~^_kj<(RGzamE<}kM6KW6Xsqvid^pb3_n5ERCBEh+3gx|NB@&W<+Q?KF>iNPg z_6=Dd{^GB5o94Q$a~WkVQbUv;xZdq-Z9%OF>Za`bj1rp7GH0KZZ)9MM5#=-LX0D`; z`)$NU@u;uqs(oj(L~P?D-b zu>#GBd!130dQ$hk$t2}|Y`xSU?K3u5(Pb; z*Tu9f&AQnM{3lI(an$tw5-Qw3 z-cuW2T}jQzP=VSAUN#^r)y`8!#yz7@gC-ZQU80J6^)u_hRvrg%Tz(o#YR)ZNUY0s^ zA_C+c*NH_2Q10<+Y;dp&dl0nGyVnRDNC=s>Y*~Uao(%vt%qYN>MhHV3s(M>)(cFHA z$OnfTxMXo76d(Hi>lYmLEsQpossmKp#>zVJmc51h0g@xI8j-QFNA4XB z`6;a@mN5Bre~oc&o?*$p6af%umbrRc!;RDa-bpUC-JG4Oai+A}HGGu=9;la@z}se) z&dbetSOcB0Yc1l5qNPgC$A!fco?oE9RFxlPk}fm!-GO=)UyukY$QCj5Rs$RxM7b?W z!=Y0ZR15LE=XW$wD+rqJucq52RmfKPFQ{@;K2oMHjpk@GCmRM}D1mzfrDI=Klwg-j* z1rA6!FPDKc!wbL~!TD(9$}<3pSYLkx0U&GF_%{=I%6$c}0309!L<3(TJA!uuVUC@U ztEu0Uuf2nOFJ0paUDp>QzGCHUI>vtJY8AZU&#S2T;?Wt+>CiLzPd3fx8N6n^c>j0f zC%*eXeq!ukH7Y~Mh9=*=!+eCDGBSyE+(qxDu$2)C3Cmp#I5lZH{ovm zh*+q%Y@#Gwu7cp{TRr|-V|d=wBqS#0+`g@cVPIG|xuT;H%!ccW9DsX3sW1C}i4+BSip2L|59k6VadiIK~_v5`ElgqZ8N>DTAlFVI$Sv9g{*B99SD1+6<` zuyE&)U*NDIy+yeokL>v)DDjzJu=fxX5kW<0UFo<6K17(nUI@lKa90C#5W}TB;^MWU zq9@AnCamwU>ve(&RDYq7SF%4Eu6J-oLB)IG`OVhN9(8jVCx-?y31#Kc^w7x~Onqxb z|J*UI)6*r8qQQ{cQk?n?gVX~-hb&{{l|+UDgHF6Z9UUF?&_6(bF~H9u^~-K8^4~9U zYsOH3uh46QpaEEzG@9Qrh2+Jn{5QsQAZ($XusQcD2Us^jdEp_B)cHRzWT%4V69Uw= zE3Cx-!JP!OR1^ghI0p=)?#D+3g#7d{pjve|Hj-)~O%Igk7rD2cbS=?7MKFW*UJ;Rp zPn1VoeO#EA?R}}~~PZNeebR4t5MI?^Y!87Q+X{W;`?0sNV$hIm_G?+w2 zM?mQ z0juo~hQ;E?wdddcJhx&i(EMcgt`7Sf9g@$=I8`2zNct@6N7j*63UYEL0JOeDDUXW} z7KTM^LB$7xa zuIB{`r)7)s-lN^?rwjI+<&vWmcc1&F34B9~lqS)5nK0CewxQUls8l2gM?A`H{Yd(u4`#mvxH6L^dJ3Bj(AoH6tk%a^^hCV3FYy+8$i~#bHefWDMk`te?d58rg?FAkl z9>8#5A2HH6YwLkX0ll;j)6;b@Zx~U-E4o!tA)~CE-u715_Lr<9CD3cjJ1$lC1( zVJfa+&|~n`gEeJu>@6!4xp0|&1rQaJZ=D_2k+{R+akr!97rys6im~bK9Nce<8(E1z z@*bQ^1JQ&IFusSOM-)N|9Ju7Avv@{4hm}H|W~$aH?o?AF*`oMSjMMuGpqT-<@ov#| zq?q2+t2cgU^r+?H(vz?#Zyz09CLCl3?@9^_LrGC`?_OrQ&I~m<8TyUfx6-dSZXO)m zt)i0QOZ$N)Dkhz)x8;eU$c96610hX!NfmEpsY&;wGPja^Mnl$9L2uA@Y13EmD!{!n ze7BfHL`5M}oIw%*+}w%yT6E1r!^1!=L_O23B!0mD0QXo@ZEY=?*#^AUly=NO&&!MS zjT&Q$+}G29X{6hf1Nl4*x3=HzPtX;$-~IdN&vN)Coa85EdIfs-7_Ay9e2Dm)`-Fae zZgv*9`&YE7(b3a8Uo^B)&*~l?zV1ug?S@6b@IQZXXWswI*HE=FRW*W|0iY@Xltl#5 zkD8IJJS|9T;do9YwC71AMs@0DDbPhx%ggXVjIXYiZNlisYR|BdK)!_<`8EpQ_ne))R+K1zwp%kEw z6Y-%2IoEMVNAGH@*5ZNob|>&7XZF~X6j0!6byMJ3(Ls-sSAPD3&~#96vC;A(#TWf< zPTLZH%veC8x9j+md!?n1A3r7>&jF--|2CVbJK4R;`0WOQ5#SdC2a5W^_iy&yx@#h> zphOB58Naesh+w=R<|zj?3%scH(W8&ANgvS(9fSh3VRhL9XHazz6(absOE`mPo+@!k zBK51NFd28H#FLpk?&`V$KWq>P=$R-iDgt6ja8~L#c|h^LprnEe&sG)|dpkQ!?3GxI z1E2-Z3N$;xX=iiiZ~2|*9o|okaq`IrwUdQKL{fIxp{4l(o*C0p(3?84dOEdXSSwk2 z#}4PKM4N0_JhGsU;*ZF!iR%dcm}WmxE@v>643l|8_Pr$b#9)eZFKDm-izG7|Z{B;Q-A!@mjkqjAT7Z)3v&$gfA zCP~NEG4JWTZaTK8F_!uRPmI%z;tgj2(GJIac77h!s2Kh(y1?;wp*765@%OuyyUg|? zj z$uY|rgdXhp$YxkbnV-hcT72lxA%{YO><)_umn330K?D^C+)XJjexpkdrJ)F1be)`r zsyw#w*h9N&TY?WheA4U~b~2h4xx+AtN_$mQQ1F0_jf#{Mw)IKm17bh6!5dXqSC@td zao~zP@)ucI*|92*40}T%mXV<$%*;V1D4?S9s-q(hDlEd^tys->!n*D`56wYp$gA;qTy}gx%N%RN6SjnG6sSK9k3H%-$@*_s zzCFh~`XjUa0-yxkg)q-MoU8Ob8>hMs>FE_07GiAc7M^#2ISyQOo^XEylZdggu@fgw z;6n*>I0ywYGBWBW_-&T4DF+4yfN&Wg1#Ld+F8g$a*ZI&w&~RLB-E65Slm%!STg3bZ zxPnc%8;pH*DO;VG*}JelYYN0Xe)=7iGs))le%iuTKUHgCH%{4E{?5A=75R7tJUk(c z$XK9(*#;lzRXBR{gn3q5EhxEH4!wV}{mW8!tD~Ue@RT6S9eEk0)xN<&jJxiFAf~aA z8`GOkZY3M#|Fv@w6=eDO;75xPn!+>8Dfg#j%n8{_o-aFVKe^KfxH^5Ues*sB`RIb? zv-xPnT7{3MqLDevLXswMBGuH4@S%4Xn6M`6sI|AH34%)mTrD+sYI4<|`owmzSw3K% zue0k|+qqA{6F+^c3|>t|XMMUW8c>@sQSDQrRo0~hW z>@}K@l9HdBYi4S?pNN{$sKU@}e_)dUb~*~ zQko@lAAB&v7+s7IC%N~pI(iNJpqZYj_tC+-oc?o5OB z0khV4kv$pKkUITV#UKZ<|H6RO_Zs6okR}}g3bK%^SB>y<(Q?YiFex#Sl_$HTWDbob zVetT_+xPq)u=LRSvH{|)I7TKoc$)KEY(CZ@Ir_`C*NWichwCFr)nB4cj7r za$o5XyUm$#3?A?Z=qQ8XbfTCNDu#ulGv1X!m+!4gE_x-iAE~*5z=UcAwOOKr_nTD} zg_#Vq)MZJJZBdGfjGXi)rV7Q)%;y#xT4%;2t(Bas%F82K-+)OP?trN-vpNJZ&u-){ zZ!(l+>im@@`mFdV-y*RcDil^RGXSt}KVl!I2?k82`{BSl3HBVtE*1;})!fTK#`y(6 zR9HL`&gTsC+bnwBSUMY?d=9icpR=y#yXpD+sWX>vOt4qg${lMkJ$9(I=iGGQt%F>{ zI@|%t2dBr;7^AD+esSA5q3Z{Abgr_6lt`x>zV}MusLBIn*S^lvJA6K!>K~VI2#}~( z@#WlCB+*DpIH0rVp4xWk$VP{TmI~pEI?`mt?$c7>b6F8u!SL4v_W|4 zdV702Jd{M7iRi<*Z^MaX^d!$BE7~ICl29i(^5P}kDNj%Cu)e-NpPx>f(JBsLsi(IW zy*c@Z#bP5OU^tkxN8xBAsl)nsNA|lTU#$yVJO0rEKxC7>;a)S^?HF!5;`qm6pZAoeAmBRSt$)Zxr zx}eoPJS{CP+06$g+->1M9Tq<_G9v5x+4C2&ScZ#lKYpxne8#1vVPF;-SsUPE&wf

pFZNg&`@_)=<~FyleDq0|GFet;xq5 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

KY$hAOWG&cZ(WJD(%WFQK(3G%* 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

scalability

@@ -599,7 +625,7 @@ of these jobs are then posted back on the main thread's io_service.

A disk job is essentially one of:

  1. write this block to disk, i.e. a write job. For the most part this is just a matter of sticking the block in the disk cache, but if we've run out of cache space or completed a whole piece, we'll also flush blocks to disk. This is typically very fast, since the OS just sticks these buffers in its write cache which will be flushed at a later time, presumably when the drive head will pass the place on the platter where the blocks go.
  2. -
  3. read this block from disk. The first thing that happens is we look in the cache to see if the block is already in RAM. If it is, we'll return immediately with this block. If it's a cache miss, we'll have to hit the disk. Here we decide to defer this job. We find the physical offset on the drive for this block and insert the job in an ordere queue, sorted by the physical location. At a later time, once we don't have any more non-read jobs left in the queue, we pick one read job out of the ordered queue and service it. The order we pick jobs out of the queue is according to an elevator cursor moving up and down along the ordered queue of read jobs. If we have enough space in the cache we'll read read_cache_line_size number of blocks and stick those in the cache. This defaults to 32 blocks.
  4. +
  5. read this block from disk. The first thing that happens is we look in the cache to see if the block is already in RAM. If it is, we'll return immediately with this block. If it's a cache miss, we'll have to hit the disk. Here we decide to defer this job. We find the physical offset on the drive for this block and insert the job in an ordered queue, sorted by the physical location. At a later time, once we don't have any more non-read jobs left in the queue, we pick one read job out of the ordered queue and service it. The order we pick jobs out of the queue is according to an elevator cursor moving up and down along the ordered queue of read jobs. If we have enough space in the cache we'll read read_cache_line_size number of blocks and stick those in the cache. This defaults to 32 blocks. If the system supports asynchronous I/O (Windows, Linux, Mac OS X, BSD, Solars for instance), jobs will be issued immediately to the OS. This especially increases read throughput, since the OS has a much greater flexibility to reorder the read jobs.

Other disk job consist of operations that needs to be synchronized with the disk I/O, like renaming files, closing files, flushing the cache, updating the settings etc. These are relatively rare though.

diff --git a/docs/tuning.rst b/docs/tuning.rst index de648d5ea..d5696d7e4 100644 --- a/docs/tuning.rst +++ b/docs/tuning.rst @@ -3,7 +3,7 @@ libtorrent manual ================= :Author: Arvid Norberg, arvid@rasterbar.com -:Version: 1.0.0 +:Version: 1.1.0 .. contents:: Table of contents :depth: 2 @@ -285,6 +285,25 @@ the same pieces, and on the other hand assume that they won't request the same p and drop them when the first peer requests it. To enable volatile read cache, set ``session_settings::volatile_read_cache`` to true. +SSD as level 2 cache +-------------------- + +It is possible to introduce a second level of cache, below the RAM disk cache. This is done +by setting ``session_settings::mmap_cache`` to a file path pointing to the SSD drive, and +increasing the ``session_settings::cache_size`` to the number of 16 kiB blocks would fit +on the drive (or less). + +This will allocate disk buffers (for reading and writing) from a memory region that has +been mapped to the specified file. If the drive this file lives on is not significantly +faster than the destination drive, performance will be degraded. The point is to take +advantage primarily of the fast read speed from SSD drives and use it to extend the read +cache, improving seed performance. + +Which parts of the cache that actually live in RAM is determined by the operating system. + +Note that when using this feature, any block which ends up being pulled from the mmapped +file will be considered a cache hit. + uTP-TCP mixed mode ------------------ @@ -344,6 +363,16 @@ torrent limits To seed thousands of torrents, you need to increase the ``session_settings::active_limit`` and ``session_settings::active_seeds``. +SHA-1 hashing +------------- + +When downloading at very high rates, it is possible to have the CPU be the bottleneck +for passing every downloaded byte through SHA-1. In order to enable calculating SHA-1 +hashes in parallel, on multi-core systems, set ``session_settings::hashing_threads`` +to the number of threads libtorrent should start to do SHA-1 hashing. This defaults +to 1, and only if that thread is close to saturating one core does it make sense to +increase the number of threads. + scalability =========== @@ -569,7 +598,7 @@ A disk job is essentially one of: 1. write this block to disk, i.e. a write job. For the most part this is just a matter of sticking the block in the disk cache, but if we've run out of cache space or completed a whole piece, we'll also flush blocks to disk. This is typically very fast, since the OS just sticks these buffers in its write cache which will be flushed at a later time, presumably when the drive head will pass the place on the platter where the blocks go. -2. read this block from disk. The first thing that happens is we look in the cache to see if the block is already in RAM. If it is, we'll return immediately with this block. If it's a cache miss, we'll have to hit the disk. Here we decide to defer this job. We find the physical offset on the drive for this block and insert the job in an ordere queue, sorted by the physical location. At a later time, once we don't have any more non-read jobs left in the queue, we pick one read job out of the ordered queue and service it. The order we pick jobs out of the queue is according to an elevator cursor moving up and down along the ordered queue of read jobs. If we have enough space in the cache we'll read read_cache_line_size number of blocks and stick those in the cache. This defaults to 32 blocks. +2. read this block from disk. The first thing that happens is we look in the cache to see if the block is already in RAM. If it is, we'll return immediately with this block. If it's a cache miss, we'll have to hit the disk. Here we decide to defer this job. We find the physical offset on the drive for this block and insert the job in an ordered queue, sorted by the physical location. At a later time, once we don't have any more non-read jobs left in the queue, we pick one read job out of the ordered queue and service it. The order we pick jobs out of the queue is according to an elevator cursor moving up and down along the ordered queue of read jobs. If we have enough space in the cache we'll read read_cache_line_size number of blocks and stick those in the cache. This defaults to 32 blocks. If the system supports asynchronous I/O (Windows, Linux, Mac OS X, BSD, Solars for instance), jobs will be issued immediately to the OS. This especially increases read throughput, since the OS has a much greater flexibility to reorder the read jobs. Other disk job consist of operations that needs to be synchronized with the disk I/O, like renaming files, closing files, flushing the cache, updating the settings etc. These are relatively rare though. diff --git a/docs/utp.html b/docs/utp.html index 2e226a8d6..211baad23 100644 --- a/docs/utp.html +++ b/docs/utp.html @@ -49,7 +49,7 @@ Author: Arvid Norberg, arvid@rasterbar.com Version: -1.0.0 +1.1.0
diff --git a/docs/utp.rst b/docs/utp.rst index 8dd4a9833..8cfab56ce 100644 --- a/docs/utp.rst +++ b/docs/utp.rst @@ -3,7 +3,7 @@ libtorrent manual ================= :Author: Arvid Norberg, arvid@rasterbar.com -:Version: 1.0.0 +:Version: 1.1.0 .. contents:: Table of contents :depth: 2 diff --git a/docs/write_disk_buffers.png b/docs/write_disk_buffers.png index c3f4637ba0955b86ebc33238fbedf744209d47fe..d8790c859310fd5cb352160f3e5990d997fa07c1 100644 GIT binary patch literal 14209 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 N*?UUJ+&fQR{(nU@tkVDh literal 50619 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 z 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®z5Bf$!M+ck?0f(UDi+`jeftm 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 diff --git a/examples/Jamfile b/examples/Jamfile index 87f20e05a..257f29921 100644 --- a/examples/Jamfile +++ b/examples/Jamfile @@ -19,12 +19,16 @@ project client_test exe client_test : client_test.cpp ; exe simple_client : simple_client.cpp ; +exe stats_counters : stats_counters.cpp ; exe dump_torrent : dump_torrent.cpp ; exe make_torrent : make_torrent.cpp ; exe connection_tester : connection_tester.cpp ; exe rss_reader : rss_reader.cpp ; exe upnp_test : upnp_test.cpp ; +explicit stage_client_test ; +explicit stage_connection_tester ; + install stage_client_test : client_test : . ; install stage_connection_tester : connection_tester : . ; diff --git a/examples/Makefile.am b/examples/Makefile.am index 09134cc09..b08fe5b4e 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -17,6 +17,9 @@ EXTRA_DIST = Jamfile client_test_SOURCES = client_test.cpp #client_test_LDADD = $(top_builddir)/src/libtorrent-rasterbar.la +stats_counters_SOURCES = stats_counters.cpp +#stats_counters_LDADD = $(top_builddir)/src/libtorrent-rasterbar.la + dump_torrent_SOURCES = dump_torrent.cpp #dump_torrent_LDADD = $(top_builddir)/src/libtorrent-rasterbar.la diff --git a/examples/client_test.cpp b/examples/client_test.cpp index 05ddf6395..19e5f54c9 100644 --- a/examples/client_test.cpp +++ b/examples/client_test.cpp @@ -45,7 +45,6 @@ POSSIBILITY OF SUCH DAMAGE. #pragma warning(pop) #endif -#include "libtorrent/extensions/metadata_transfer.hpp" #include "libtorrent/extensions/ut_metadata.hpp" #include "libtorrent/extensions/ut_pex.hpp" #include "libtorrent/extensions/smart_ban.hpp" @@ -59,10 +58,164 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/magnet_uri.hpp" #include "libtorrent/bitfield.hpp" #include "libtorrent/peer_info.hpp" +#include "libtorrent/lazy_entry.hpp" +#include "libtorrent/escape_string.hpp" // for convert_path_to_posix +#include "libtorrent/add_torrent_params.hpp" #include "libtorrent/time.hpp" #include "libtorrent/create_torrent.hpp" using boost::bind; +using libtorrent::total_milliseconds; + +void terminal_size(int* terminal_width, int* terminal_height) +{ +#ifdef _WIN32 + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO coninfo; + if (GetConsoleScreenBufferInfo(out, &coninfo)) + { + *terminal_width = coninfo.dwSize.X; + *terminal_height = coninfo.srWindow.Bottom - coninfo.srWindow.Top; +#else + int tty = open("/dev/tty", O_RDONLY); + winsize size; + int ret = ioctl(tty, TIOCGWINSZ, (char*)&size); + close(tty); + if (ret == 0) + { + *terminal_width = size.ws_col; + *terminal_height = size.ws_row; +#endif + + if (*terminal_width < 64) + *terminal_width = 64; + if (*terminal_height < 25) + *terminal_height = 25; + } + else + { + *terminal_width = 190; + *terminal_height = 100; + } +} + +#ifdef WIN32 +void apply_ansi_code(int* attributes, bool* reverse, int code) +{ + const static int color_table[8] = + { + 0, // black + FOREGROUND_RED, // red + FOREGROUND_GREEN, // green + FOREGROUND_RED | FOREGROUND_GREEN, // yellow + FOREGROUND_BLUE, // blue + FOREGROUND_RED | FOREGROUND_BLUE, // magenta + FOREGROUND_BLUE | FOREGROUND_GREEN, // cyan + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE // white + }; + + enum + { + foreground_mask = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, + background_mask = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE + }; + + const static int fg_mask[2] = {foreground_mask, background_mask}; + const static int bg_mask[2] = {background_mask, foreground_mask}; + const static int fg_shift[2] = { 0, 4}; + const static int bg_shift[2] = { 4, 0}; + + if (code == 0) + { + // reset + *attributes = color_table[7]; + *reverse = false; + } + else if (code == 7) + { + if (*reverse) return; + *reverse = true; + int fg_col = *attributes & foreground_mask; + int bg_col = (*attributes & background_mask) >> 4; + *attributes &= ~(foreground_mask + background_mask); + *attributes |= fg_col << 4; + *attributes |= bg_col; + } + else if (code >= 30 && code <= 37) + { + // foreground color + *attributes &= ~fg_mask[*reverse]; + *attributes |= color_table[code - 30] << fg_shift[*reverse]; + } + else if (code >= 40 && code <= 47) + { + // foreground color + *attributes &= ~bg_mask[*reverse]; + *attributes |= color_table[code - 40] << bg_shift[*reverse]; + } +} +#endif + +void print_with_ansi_colors(char const* str) +{ +#ifdef WIN32 + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + + // first, clear the console and move the cursor + // to the top left corner + CONSOLE_SCREEN_BUFFER_INFO si; + GetConsoleScreenBufferInfo(out, &si); + COORD c = {0, 0}; + DWORD n; + FillConsoleOutputCharacter(out, ' ', si.dwSize.X * si.dwSize.Y, c, &n); + FillConsoleOutputAttribute(out, 0x7, si.dwSize.X * si.dwSize.Y, c, &n); + SetConsoleCursorPosition(out, c); + + char* buf = (char*)str; + + int current_attributes = 7; + bool reverse = false; + SetConsoleTextAttribute(out, current_attributes); + + char* start = buf; + DWORD written; + while (*buf != 0) + { + if (*buf == '\033' && buf[1] == '[') + { + *buf = 0; + WriteFile(out, start, buf - start, &written, NULL); + buf += 2; // skip escape and '[' + start = buf; + one_more: + while (*buf != 'm' && *buf != ';' && *buf != 0) ++buf; + if (*buf == 0) break; + int code = atoi(start); + apply_ansi_code(¤t_attributes, &reverse, code); + if (*buf == ';') + { + ++buf; + start = buf; + goto one_more; + } + SetConsoleTextAttribute(out, current_attributes); + ++buf; // skip 'm' + start = buf; + } + else + { + ++buf; + } + } + WriteFile(out, start, buf - start, &written, NULL); + +#else + // first, clear the console and move the cursor + // to the top left corner + puts("\033[2J\033[0;0H"); + puts(str); +#endif +} #ifdef _WIN32 @@ -87,17 +240,6 @@ bool sleep_and_input(int* c, int sleep) return false; }; -void clear_home() -{ - CONSOLE_SCREEN_BUFFER_INFO si; - HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE); - GetConsoleScreenBufferInfo(h, &si); - COORD c = {0, 0}; - DWORD n; - FillConsoleOutputCharacter(h, ' ', si.dwSize.X * si.dwSize.Y, c, &n); - SetConsoleCursorPosition(h, c); -} - #else #include @@ -106,8 +248,7 @@ void clear_home() #include #include #include - -#define ANSI_TERMINAL_COLORS +#include struct set_keypress { @@ -160,11 +301,6 @@ retry: return false; } -void clear_home() -{ - puts("\033[2J\033[0;0H"); -} - #endif bool print_trackers = false; @@ -185,6 +321,7 @@ bool print_block = false; bool print_peer_rate = false; bool print_fails = false; bool print_send_bufs = true; +bool print_disk_stats = false; // the number of times we've asked to save resume data // without having received a response (successful or failure) @@ -196,21 +333,21 @@ int load_file(std::string const& filename, std::vector& v, libtorrent::err FILE* f = fopen(filename.c_str(), "rb"); if (f == NULL) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); return -1; } int r = fseek(f, 0, SEEK_END); if (r != 0) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); fclose(f); return -1; } long s = ftell(f); if (s < 0) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); fclose(f); return -1; } @@ -224,7 +361,7 @@ int load_file(std::string const& filename, std::vector& v, libtorrent::err r = fseek(f, 0, SEEK_SET); if (r != 0) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); fclose(f); return -1; } @@ -239,7 +376,7 @@ int load_file(std::string const& filename, std::vector& v, libtorrent::err r = fread(&v[0], 1, v.size(), f); if (r < 0) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); fclose(f); return -1; } @@ -286,6 +423,7 @@ enum { torrents_queued, torrents_stopped, torrents_checking, + torrents_loaded, torrents_feeds, torrents_max @@ -301,7 +439,10 @@ struct torrent_entry }; // maps filenames to torrent_handles -typedef std::multimap handles_t; +typedef std::map handles_t; +typedef std::map files_t; + +files_t hash_to_filename; using libtorrent::torrent_status; @@ -335,12 +476,16 @@ bool show_torrent(libtorrent::torrent_status const& st, int torrent_filter, int* ++counters[torrents_stopped]; } - if (st.state == torrent_status::checking_files - || st.state == torrent_status::queued_for_checking) + if (st.state == torrent_status::checking_files) { ++counters[torrents_checking]; } + if (st.is_loaded) + { + ++counters[torrents_loaded]; + } + switch (torrent_filter) { case torrents_all: return true; @@ -355,8 +500,8 @@ bool show_torrent(libtorrent::torrent_status const& st, int torrent_filter, int* || st.state == torrent_status::finished); case torrents_queued: return st.paused && st.auto_managed; case torrents_stopped: return st.paused && !st.auto_managed; - case torrents_checking: return st.state == torrent_status::checking_files - || st.state == torrent_status::queued_for_checking; + case torrents_checking: return st.state == torrent_status::checking_files; + case torrents_loaded: return st.is_loaded; case torrents_feeds: return false; } return true; @@ -406,7 +551,6 @@ void update_filtered_torrents(boost::unordered_set& all_handles char const* esc(char const* code) { -#ifdef ANSI_TERMINAL_COLORS // this is a silly optimization // to avoid copying of strings enum { num_strings = 200 }; @@ -423,9 +567,28 @@ char const* esc(char const* code) ret[i++] = 'm'; ret[i++] = 0; return ret; -#else - return ""; -#endif +} + +enum color_code +{ + col_none = -1, + col_black = 0, + col_red = 1, + col_green = 2, + col_yellow = 3, + col_blue = 4, + col_magenta = 5, + col_cyan = 6, + col_white = 7, +}; + +std::string color(std::string const& s, color_code c) +{ + if (c == col_none) return s; + + char buf[1024]; + snprintf(buf, sizeof(buf), "\x1b[3%dm%s\x1b[39m", c, s.c_str()); + return buf; } std::string to_string(int v, int width) @@ -482,20 +645,7 @@ std::string add_suffix(float val, char const* suffix = 0) std::string const& piece_bar(libtorrent::bitfield const& p, int width) { -#ifdef ANSI_TERMINAL_COLORS - static const char* lookup[] = - { - // black, blue, cyan, white - "40", "44", "46", "47" - }; - - const int table_size = sizeof(lookup) / sizeof(lookup[0]); -#else - static const char char_lookup[] = - { ' ', '.', ':', '-', '+', '*', '#'}; - - const int table_size = sizeof(char_lookup) / sizeof(char_lookup[0]); -#endif + const int table_size = 18; double piece_per_char = p.size() / double(width); static std::string bar; @@ -519,31 +669,48 @@ std::string const& piece_bar(libtorrent::bitfield const& p, int width) for (int k = int(piece); k < end; ++k, ++num_pieces) if (p[k]) ++num_have; int color = int(std::ceil(num_have / float(num_pieces) * (table_size - 1))); -#ifdef ANSI_TERMINAL_COLORS - bar += esc(lookup[color]); + char buf[10]; + snprintf(buf, 10, "48;5;%d", 232 + color); + bar += esc(buf); bar += " "; -#else - bar += char_lookup[color]; -#endif } -#ifdef ANSI_TERMINAL_COLORS bar += esc("0"); -#endif bar += "]"; return bar; } -std::string const& progress_bar(int progress, int width, char const* code = "33") +std::string const& progress_bar(int progress, int width, color_code c = col_green + , char fill = '#', char bg = '-', std::string caption = "") { static std::string bar; bar.clear(); bar.reserve(width + 10); int progress_chars = (progress * width + 500) / 1000; - bar = esc(code); - std::fill_n(std::back_inserter(bar), progress_chars, '#'); - std::fill_n(std::back_inserter(bar), width - progress_chars, '-'); - bar += esc("0"); + + if (caption.empty()) + { + char code[10]; + snprintf(code, sizeof(code), "\x1b[3%dm", c); + bar = code; + std::fill_n(std::back_inserter(bar), progress_chars, fill); + std::fill_n(std::back_inserter(bar), width - progress_chars, bg); + bar += esc("39"); + } + else + { + // foreground color (depends a bit on background color) + color_code tc = col_black; + if (c == col_black || c == col_blue) + tc = col_white; + + caption.resize(width, ' '); + + char str[256]; + snprintf(str, sizeof(str), "\x1b[4%d;3%dm%s\x1b[48;5;238m\x1b[37m%s\x1b[49;39m" + , c, tc, caption.substr(0, progress_chars).c_str(), caption.substr(progress_chars).c_str()); + bar = str; + } return bar; } @@ -560,15 +727,15 @@ int peer_index(libtorrent::tcp::endpoint addr, std::vector const& peers) { using namespace libtorrent; - if (print_ip) out += "IP "; + if (print_ip) out += "IP "; #ifndef TORRENT_DISABLE_GEO_IP if (print_as) out += "AS "; #endif - out += "down (total | peak ) up (total | peak ) sent-req tmo bsy rcv flags source "; + out += "progress down (total | peak ) up (total | peak ) sent-req tmo bsy rcv flags dn up source "; if (print_fails) out += "fail hshf "; - if (print_send_bufs) out += "rq sndb quota rcvb q-bytes "; + if (print_send_bufs) out += "rq sndb rcvb q-bytes "; if (print_timers) out += "inactive wait timeout q-time "; - out += "disk rtt "; + out += " v disk ^ rtt "; if (print_block) out += "block-progress "; #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES out += "country "; @@ -585,11 +752,10 @@ void print_peer_info(std::string& out, std::vector const& if (print_ip) { - snprintf(str, sizeof(str), "%-30s %-22s", (::print_endpoint(i->ip) + + snprintf(str, sizeof(str), "%-30s ", (::print_endpoint(i->ip) + (i->flags & peer_info::utp_socket ? " [uTP]" : "") + (i->flags & peer_info::i2p_socket ? " [i2p]" : "") - ).c_str() - , ::print_endpoint(i->local_endpoint).c_str()); + ).c_str()); out += str; } @@ -608,8 +774,11 @@ void print_peer_info(std::string& out, std::vector const& , i->target_dl_queue_length); temp[7] = 0; + char peer_progress[10]; + snprintf(peer_progress, sizeof(peer_progress), "%.1f%%", i->progress_ppm / 10000.f); snprintf(str, sizeof(str) - , "%s%s (%s|%s) %s%s (%s|%s) %s%7s %4d%4d%4d %c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c %c%c%c%c%c%c " + , "%s %s%s (%s|%s) %s%s (%s|%s) %s%7s %4d%4d%4d %s%s%s%s%s%s%s%s%s%s%s%s%s %s%s%s %s%s%s %s%s%s%s%s%s " + , progress_bar(i->progress_ppm / 1000, 15, col_green, '#', '-', peer_progress).c_str() , esc("32"), add_suffix(i->down_speed, "/s").c_str() , add_suffix(i->total_download).c_str(), add_suffix(i->download_rate_peak, "/s").c_str() , esc("31"), add_suffix(i->up_speed, "/s").c_str(), add_suffix(i->total_upload).c_str() @@ -620,38 +789,33 @@ void print_peer_info(std::string& out, std::vector const& , i->busy_requests , i->upload_queue_length - , (i->flags & peer_info::interesting)?'I':'.' - , (i->flags & peer_info::choked)?'C':'.' - , (i->flags & peer_info::remote_interested)?'i':'.' - , (i->flags & peer_info::remote_choked)?'c':'.' - , (i->flags & peer_info::supports_extensions)?'e':'.' - , (i->flags & peer_info::local_connection)?'l':'r' - , (i->flags & peer_info::seed)?'s':'.' - , (i->flags & peer_info::on_parole)?'p':'.' - , (i->flags & peer_info::optimistic_unchoke)?'O':'.' - , (i->read_state == peer_info::bw_limit)?'r': - (i->read_state == peer_info::bw_network)?'R': - (i->read_state == peer_info::bw_disk)?'D':'.' - , (i->write_state == peer_info::bw_limit)?'w': - (i->write_state == peer_info::bw_network)?'W': - (i->write_state == peer_info::bw_disk)?'D':'.' - , (i->flags & peer_info::snubbed)?'S':'.' - , (i->flags & peer_info::upload_only)?'U':'D' - , (i->flags & peer_info::endgame_mode)?'-':'.' -#ifndef TORRENT_DISABLE_ENCRYPTION - , (i->flags & peer_info::rc4_encrypted)?'E': - (i->flags & peer_info::plaintext_encrypted)?'e':'.' -#else - , '.' -#endif - , (i->flags & peer_info::holepunched)?'h':'.' + , color("I", (i->flags & peer_info::interesting)?col_white:col_blue).c_str() + , color("C", (i->flags & peer_info::choked)?col_white:col_blue).c_str() + , color("i", (i->flags & peer_info::remote_interested)?col_white:col_blue).c_str() + , color("c", (i->flags & peer_info::remote_choked)?col_white:col_blue).c_str() + , color("x", (i->flags & peer_info::supports_extensions)?col_white:col_blue).c_str() + , color("o", (i->flags & peer_info::local_connection)?col_white:col_blue).c_str() + , color("p", (i->flags & peer_info::on_parole)?col_white:col_blue).c_str() + , color("O", (i->flags & peer_info::optimistic_unchoke)?col_white:col_blue).c_str() + , color("S", (i->flags & peer_info::snubbed)?col_white:col_blue).c_str() + , color("U", (i->flags & peer_info::upload_only)?col_white:col_blue).c_str() + , color("e", (i->flags & peer_info::endgame_mode)?col_white:col_blue).c_str() + , color("E", (i->flags & peer_info::rc4_encrypted)?col_white:(i->flags & peer_info::plaintext_encrypted)?col_cyan:col_blue).c_str() + , color("h", (i->flags & peer_info::holepunched)?col_white:col_blue).c_str() - , (i->source & peer_info::tracker)?'T':'_' - , (i->source & peer_info::pex)?'P':'_' - , (i->source & peer_info::dht)?'D':'_' - , (i->source & peer_info::lsd)?'L':'_' - , (i->source & peer_info::resume_data)?'R':'_' - , (i->source & peer_info::incoming)?'I':'_'); + , color("d", (i->read_state & peer_info::bw_disk)?col_white:col_blue).c_str() + , color("l", (i->read_state & peer_info::bw_limit)?col_white:col_blue).c_str() + , color("n", (i->read_state & peer_info::bw_network)?col_white:col_blue).c_str() + , color("d", (i->write_state & peer_info::bw_disk)?col_white:col_blue).c_str() + , color("l", (i->write_state & peer_info::bw_limit)?col_white:col_blue).c_str() + , color("n", (i->write_state & peer_info::bw_network)?col_white:col_blue).c_str() + + , color("t", (i->source & peer_info::tracker)?col_white:col_blue).c_str() + , color("p", (i->source & peer_info::pex)?col_white:col_blue).c_str() + , color("d", (i->source & peer_info::dht)?col_white:col_blue).c_str() + , color("l", (i->source & peer_info::lsd)?col_white:col_blue).c_str() + , color("r", (i->source & peer_info::resume_data)?col_white:col_blue).c_str() + , color("i", (i->source & peer_info::incoming)?col_white:col_blue).c_str()); out += str; if (print_fails) @@ -662,23 +826,24 @@ void print_peer_info(std::string& out, std::vector const& } if (print_send_bufs) { - snprintf(str, sizeof(str), "%2d %6d (%s) %5d %6d (%s) %6d " - , i->requests_in_buffer, i->used_send_buffer, add_suffix(i->send_buffer_size).c_str() - , i->send_quota, i->used_receive_buffer, add_suffix(i->receive_buffer_size).c_str() - , i->queue_bytes); + snprintf(str, sizeof(str), "%2d %6d %6d%5dkB " + , i->requests_in_buffer, i->used_send_buffer + , i->used_receive_buffer + , i->queue_bytes / 1000); out += str; } if (print_timers) { snprintf(str, sizeof(str), "%8d %4d %7d %6d " - , total_seconds(i->last_active) - , total_seconds(i->last_request) + , int(total_seconds(i->last_active)) + , int(total_seconds(i->last_request)) , i->request_timeout - , total_seconds(i->download_queue_time)); + , int(total_seconds(i->download_queue_time))); out += str; } - snprintf(str, sizeof(str), "%s %4d " + snprintf(str, sizeof(str), "%s|%s %4d " , add_suffix(i->pending_disk_bytes).c_str() + , add_suffix(i->pending_disk_read_bytes).c_str() , i->rtt); out += str; @@ -686,8 +851,10 @@ void print_peer_info(std::string& out, std::vector const& { if (i->downloading_piece_index >= 0) { + char buf[50]; + snprintf(buf, sizeof(buf), "%d:%d", i->downloading_piece_index, i->downloading_block_index); out += progress_bar( - i->downloading_progress * 1000 / i->downloading_total, 14); + i->downloading_progress * 1000 / i->downloading_total, 14, col_green, '-', '#', buf); } else { @@ -754,7 +921,6 @@ int torrent_upload_limit = 0; int torrent_download_limit = 0; std::string monitor_dir; std::string bind_to_interface = ""; -std::string outgoing_interface = ""; int poll_interval = 5; int max_connections_per_torrent = 50; bool seed_mode = false; @@ -762,6 +928,28 @@ bool seed_mode = false; bool share_mode = false; bool disable_storage = false; +int loop_limit = 0; + +void signal_handler(int signo) +{ + // make the main loop terminate + loop_limit = 1; +} + +void load_torrent(libtorrent::sha1_hash const& ih, std::vector& buf, libtorrent::error_code& ec) +{ + files_t::iterator i = hash_to_filename.find(ih); + if (i == hash_to_filename.end()) + { + // for magnet links and torrents downloaded via + // URL, the metadata is saved in the resume file + // TODO: pick up metadata from the resume file + ec.assign(boost::system::errc::no_such_file_or_directory, boost::system::generic_category()); + return; + } + load_file(i->second.c_str(), buf, ec); +} + // if non-empty, a peer that will be added to all torrents std::string peer; @@ -774,7 +962,7 @@ using boost::bind; void add_torrent(libtorrent::session& ses , handles_t& files , std::set& non_files - , std::string const& torrent + , std::string torrent , int allocation_mode , std::string const& save_path , bool monitored_dir @@ -782,17 +970,9 @@ void add_torrent(libtorrent::session& ses , int torrent_download_limit) { using namespace libtorrent; + static int counter = 0; - boost::intrusive_ptr t; - error_code ec; - t = new torrent_info(torrent.c_str(), ec); - if (ec) - { - fprintf(stderr, "%s: %s\n", torrent.c_str(), ec.message().c_str()); - return; - } - - printf("%s\n", t->name().c_str()); + printf("[%d] %s\n", counter++, torrent.c_str()); add_torrent_params p; if (seed_mode) p.flags |= add_torrent_params::flag_seed_mode; @@ -801,11 +981,18 @@ void add_torrent(libtorrent::session& ses lazy_entry resume_data; // TODO: implement combine_path in here, since it's internal to libtorrent - std::string filename = combine_path(save_path, combine_path(".resume", to_hex(t->info_hash().to_string()) + ".resume")); + std::string filename = combine_path(save_path, combine_path(".resume" + , libtorrent::filename(torrent) + ".resume")); + error_code ec; load_file(filename.c_str(), p.resume_data, ec); - p.ti = t; +#ifdef TORRENT_WINDOWS + convert_path_to_posix(torrent); + p.url = "file:///" + escape_path(torrent.c_str(), torrent.size()); +#else + p.url = "file://" + escape_path(torrent.c_str(), torrent.size()); +#endif p.save_path = save_path; p.storage_mode = (storage_mode_t)allocation_mode; p.flags |= add_torrent_params::flag_paused; @@ -813,6 +1000,7 @@ void add_torrent(libtorrent::session& ses p.flags |= add_torrent_params::flag_auto_managed; p.userdata = (void*)strdup(torrent.c_str()); ses.async_add_torrent(p); + files.insert(std::pair(torrent, torrent_handle())); } void scan_dir(std::string const& dir_path @@ -909,7 +1097,6 @@ void print_alert(libtorrent::alert const* a, std::string& str) { using namespace libtorrent; -#ifdef ANSI_TERMINAL_COLORS if (a->category() & alert::error_notification) { str += esc("31"); @@ -918,14 +1105,11 @@ void print_alert(libtorrent::alert const* a, std::string& str) { str += esc("33"); } -#endif str += "["; str += timestamp(); str += "] "; str += a->message(); -#ifdef ANSI_TERMINAL_COLORS str += esc("0"); -#endif if (g_log_file) fprintf(g_log_file, "[%s] %s\n", timestamp(), a->message().c_str()); @@ -953,18 +1137,25 @@ bool handle_alert(libtorrent::session& ses, libtorrent::alert* a , handles_t& files, std::set& non_files , int* counters, boost::unordered_set& all_handles , std::vector& filtered_handles - , bool& need_resort) + , bool& need_resort, std::vector& stats_counters) { using namespace libtorrent; + if (session_stats_alert* s = alert_cast(a)) + { + stats_counters.swap(s->values); + return true; + } + #ifdef TORRENT_USE_OPENSSL if (torrent_need_cert_alert* p = alert_cast(a)) { torrent_handle h = p->handle; error_code ec; file_status st; - std::string cert = combine_path("certificates", to_hex(h.info_hash().to_string())) + ".pem"; - std::string priv = combine_path("certificates", to_hex(h.info_hash().to_string())) + "_key.pem"; + std::string base_name = combine_path("certificates", to_hex(h.info_hash().to_string())); + std::string cert = base_name + ".pem"; + std::string priv = base_name + "_key.pem"; stat_file(cert, &st, ec); if (ec) { @@ -991,8 +1182,6 @@ bool handle_alert(libtorrent::session& ses, libtorrent::alert* a } #endif - boost::intrusive_ptr ti; - if (metadata_received_alert* p = alert_cast(a)) { // if we have a monitor dir, save the .torrent file we just received in it @@ -1000,16 +1189,18 @@ bool handle_alert(libtorrent::session& ses, libtorrent::alert* a // to keep the scan dir logic in sync so it's not removed, or added twice torrent_handle h = p->handle; if (h.is_valid()) { - if (!ti) ti = h.torrent_file(); + boost::shared_ptr ti = h.torrent_file(); create_torrent ct(*ti); entry te = ct.generate(); std::vector buffer; bencode(std::back_inserter(buffer), te); - std::string filename = ti->name() + "." + to_hex(ti->info_hash().to_string()) + ".torrent"; + sha1_hash hash = ti->info_hash(); + std::string filename = ti->name() + "." + to_hex(hash.to_string()) + ".torrent"; filename = combine_path(monitor_dir, filename); save_file(filename, buffer); files.insert(std::pair(filename, h)); + hash_to_filename.insert(std::make_pair(hash, filename)); non_files.erase(h); } } @@ -1031,7 +1222,7 @@ bool handle_alert(libtorrent::session& ses, libtorrent::alert* a torrent_handle h = p->handle; if (!filename.empty()) - files.insert(std::pair(filename, h)); + files[filename] = h; else non_files.insert(h); @@ -1039,7 +1230,6 @@ bool handle_alert(libtorrent::session& ses, libtorrent::alert* a h.set_max_uploads(-1); h.set_upload_limit(torrent_upload_limit); h.set_download_limit(torrent_download_limit); - h.use_interface(outgoing_interface.c_str()); #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES h.resolve_countries(true); #endif @@ -1059,13 +1249,20 @@ bool handle_alert(libtorrent::session& ses, libtorrent::alert* a } } - boost::unordered_set::iterator j - = all_handles.insert(h.status()).first; - if (show_torrent(*j, torrent_filter, counters)) + sha1_hash info_hash; + if (p->params.ti) { - filtered_handles.push_back(&*j); - need_resort = true; + info_hash = p->params.ti->info_hash(); } + else if (!p->params.info_hash.is_all_zeros()) + { + info_hash = p->params.info_hash; + } + else + { + info_hash = h.info_hash(); + } + hash_to_filename.insert(std::make_pair(info_hash, filename)); } } else if (torrent_finished_alert* p = alert_cast(a)) @@ -1089,7 +1286,8 @@ bool handle_alert(libtorrent::session& ses, libtorrent::alert* a std::vector out; bencode(std::back_inserter(out), *p->resume_data); torrent_status st = h.status(torrent_handle::query_save_path); - save_file(combine_path(st.save_path, combine_path(".resume", to_hex(st.info_hash.to_string()) + ".resume")), out); + save_file(combine_path(st.save_path, combine_path(".resume", libtorrent::filename( + hash_to_filename[st.info_hash]) + ".resume")), out); if (h.is_valid() && non_files.find(h) == non_files.end() && std::find_if(files.begin(), files.end() @@ -1123,9 +1321,16 @@ bool handle_alert(libtorrent::session& ses, libtorrent::alert* a i != p->status.end(); ++i) { boost::unordered_set::iterator j = all_handles.find(*i); - // don't add new entries here, that's done in the handler - // for add_torrent_alert - if (j == all_handles.end()) continue; + // add new entries here + if (j == all_handles.end()) + { + j = all_handles.insert(*i).first; + if (show_torrent(*j, torrent_filter, counters)) + { + filtered_handles.push_back(&*j); + need_resort = true; + } + } if (j->state != i->state || j->paused != i->paused || j->auto_managed != i->auto_managed) @@ -1143,6 +1348,7 @@ bool handle_alert(libtorrent::session& ses, libtorrent::alert* a void print_piece(libtorrent::partial_piece_info* pp , libtorrent::cached_piece_info* cs , std::vector const& peers + , torrent_status const* ts , std::string& out) { using namespace libtorrent; @@ -1171,8 +1377,8 @@ void print_piece(libtorrent::partial_piece_info* pp } else { -#ifdef ANSI_TERMINAL_COLORS - if (cs && cs->blocks[j]) color = esc("36;7"); + if (cs && cs->blocks[j] && pp->blocks[j].state != block_info::finished) + color = esc("36;7"); else if (pp->blocks[j].bytes_progress > 0 && pp->blocks[j].state == block_info::requested) { @@ -1184,13 +1390,6 @@ void print_piece(libtorrent::partial_piece_info* pp else if (pp->blocks[j].state == block_info::writing) color = esc("36;7"); else if (pp->blocks[j].state == block_info::requested) color = esc("0"); else { color = esc("0"); chr = ' '; } -#else - if (cs && cs->blocks[j]) chr = 'c'; - else if (pp->blocks[j].state == block_info::finished) chr = '#'; - else if (pp->blocks[j].state == block_info::writing) chr = '+'; - else if (pp->blocks[j].state == block_info::requested) chr = '-'; - else chr = ' '; -#endif } if (last_color == 0 || strcmp(last_color, color) != 0) snprintf(str, sizeof(str), "%s%c", color, chr); @@ -1199,14 +1398,14 @@ void print_piece(libtorrent::partial_piece_info* pp out += str; } -#ifdef ANSI_TERMINAL_COLORS out += esc("0"); -#endif - char const* piece_state[4] = {"", " slow", " medium", " fast"}; - snprintf(str, sizeof(str), "] %3d cache age: %-.1f %s\n" + + char const* cache_kind_str[] = {"read", "write", "read-volatile"}; + snprintf(str, sizeof(str), "] %3d cache age: %-5.1f state: %s%s\n" , cs ? cs->next_to_hash : 0 , cs ? (total_milliseconds(time_now() - cs->last_use) / 1000.f) : 0.f - , pp ? piece_state[pp->piece_state] : ""); + , cs ? cache_kind_str[cs->kind] : "N/A" + , ts && ts->pieces.size() ? (ts->pieces[piece] ? " have" : " dont-have") : ""); out += str; } @@ -1214,6 +1413,21 @@ static char const* state_str[] = {"checking (q)", "checking", "dl metadata" , "downloading", "finished", "seeding", "allocating", "checking (r)"}; +std::string torrent_state(torrent_status const& s) +{ + if (!s.error.empty()) return s.error; + std::string ret; + if (s.paused && !s.auto_managed) ret += "paused"; + else if (s.paused && s.auto_managed) ret += "queued"; + else if (s.upload_mode) ret += "upload mode"; + else ret += state_str[s.state]; + if (!s.paused && !s.auto_managed) ret += " [F]"; + char buf[10]; + snprintf(buf, sizeof(buf), " (%.1f%%)", s.progress_ppm / 10000.f); + ret += buf; + return ret; +} + int main(int argc, char* argv[]) { if (argc == 1) @@ -1233,6 +1447,7 @@ int main(int argc, char* argv[]) " previous command line options, so be sure to specify this first\n" " -G Add torrents in seed-mode (i.e. assume all pieces\n" " are present and check hashes on-demand)\n" + " -E specify how many hashing threads to use\n" "\n BITTORRENT OPTIONS\n" " -c sets the max number of connections\n" " -T sets the max number of connections per torrent\n" @@ -1249,6 +1464,7 @@ int main(int argc, char* argv[]) " -B sets the peer timeout\n" " -Q enables share mode. Share mode attempts to maximize\n" " share ratio rather than downloading\n" + " -K enable piece suggestions of read cache\n" " -r connect to specified peer\n" #ifndef TORRENT_DISABLE_ENCRYPTION " -e force encrypted bittorrent connections\n" @@ -1272,6 +1488,8 @@ int main(int argc, char* argv[]) " -Y Rate limit local peers\n" " -y Disable TCP connections (disable outgoing TCP and reject\n" " incoming TCP connections)\n" + " -J Disable uTP connections (disable outgoing uTP and reject\n" + " incoming uTP connections)\n" " -b sets IP of the interface to bind the\n" " listen socket to\n" " -I sets the IP of the interface to bind\n" @@ -1284,9 +1502,9 @@ int main(int argc, char* argv[]) " -a sets the allocation mode. [sparse|allocate]\n" " -R number of blocks per read cache line\n" " -C sets the max cache size. Specified in 16kB blocks\n" - " -O Disallow disk job reordering\n" " -j disable disk read-ahead\n" " -z disable piece hash checks (used for benchmarking)\n" + " -Z mmap the disk cache to the specified file, should be an SSD\n" " -0 disable disk I/O, read garbage and don't flush to disk\n" "\n\n" "TORRENT is a path to a .torrent file\n" @@ -1300,15 +1518,21 @@ int main(int argc, char* argv[]) } using namespace libtorrent; - session_settings settings; + namespace lt = libtorrent; + + settings_pack settings; + settings.set_int(settings_pack::active_loaded_limit, 20); + + std::vector stats_counters; + std::vector metrics = session_stats_metrics(); + stats_counters.resize(metrics.size(), 0); + + const int queued_bytes_idx = find_metric_idx(metrics, "queued_write_bytes"); proxy_settings ps; - int refresh_delay = 1000; + int refresh_delay = 500; bool start_dht = true; - bool start_upnp = true; - bool start_lsd = true; - int loop_limit = 0; std::deque events; @@ -1322,20 +1546,23 @@ int main(int argc, char* argv[]) std::vector filtered_handles; handles_t files; + // torrents that were not added via the monitor dir std::set non_files; int counters[torrents_max]; memset(counters, 0, sizeof(counters)); - session ses(fingerprint("LT", LIBTORRENT_VERSION_MAJOR, LIBTORRENT_VERSION_MINOR, 0, 0) - , session::add_default_plugins + libtorrent::session ses(fingerprint("LT", LIBTORRENT_VERSION_MAJOR, LIBTORRENT_VERSION_MINOR, 0, 0) + , lt::session::add_default_plugins | lt::session::start_default_features , alert::all_categories & ~(alert::dht_notification + alert::progress_notification + alert::debug_notification + alert::stats_notification)); + ses.set_load_function(&load_torrent); + std::vector in; error_code ec; if (load_file(".ses_state", in, ec) == 0) @@ -1379,6 +1606,7 @@ int main(int argc, char* argv[]) p.flags |= add_torrent_params::flag_paused; p.flags &= ~add_torrent_params::flag_duplicate_is_error; p.flags |= add_torrent_params::flag_auto_managed; + p.flags |= add_torrent_params::flag_pinned; magnet_links.push_back(p); continue; } @@ -1393,18 +1621,20 @@ int main(int argc, char* argv[]) switch (argv[i][1]) { case 'f': g_log_file = fopen(arg, "w+"); break; - case 'o': settings.half_open_limit = atoi(arg); break; - case 'h': settings.allow_multiple_connections_per_ip = true; --i; break; + case 'o': settings.set_int(settings_pack::half_open_limit, atoi(arg)); break; + case 'h': settings.set_bool(settings_pack::allow_multiple_connections_per_ip, true); --i; break; case 'p': listen_port = atoi(arg); break; - case 'k': settings = high_performance_seed(); --i; break; - case 'j': settings.use_disk_read_ahead = false; --i; break; - case 'z': settings.disable_hash_checks = true; --i; break; - case 'B': settings.peer_timeout = atoi(arg); break; - case 'n': settings.announce_to_all_tiers = true; --i; break; + case 'k': high_performance_seed(settings); --i; break; + case 'j': settings.set_bool(settings_pack::use_disk_read_ahead, false); --i; break; + case 'z': settings.set_bool(settings_pack::disable_hash_checks, true); --i; break; + case 'K': settings.set_int(settings_pack::suggest_mode, settings_pack::suggest_read_cache); --i; break; + case 'B': settings.set_int(settings_pack::peer_timeout, atoi(arg)); break; + case 'n': settings.set_bool(settings_pack::announce_to_all_tiers, true); --i; break; case 'G': seed_mode = true; --i; break; - case 'd': settings.download_rate_limit = atoi(arg) * 1000; break; - case 'u': settings.upload_rate_limit = atoi(arg) * 1000; break; - case 'S': settings.unchoke_slots_limit = atoi(arg); break; + case 'E': settings.set_int(settings_pack::hashing_threads, atoi(arg)); break; + case 'd': settings.set_int(settings_pack::download_rate_limit, atoi(arg) * 1000); break; + case 'u': settings.set_int(settings_pack::upload_rate_limit, atoi(arg) * 1000); break; + case 'S': settings.set_int(settings_pack::unchoke_slots_limit, atoi(arg)); break; case 'a': if (strcmp(arg, "allocate") == 0) allocation_mode = storage_mode_allocate; else if (strcmp(arg, "full") == 0) allocation_mode = storage_mode_allocate; @@ -1416,28 +1646,29 @@ int main(int argc, char* argv[]) case 'm': monitor_dir = arg; break; case 'Q': share_mode = true; --i; break; case 'b': bind_to_interface = arg; break; - case 'w': settings.urlseed_wait_retry = atoi(arg); break; + case 'w': settings.set_int(settings_pack::urlseed_wait_retry, atoi(arg)); break; case 't': poll_interval = atoi(arg); break; case 'F': refresh_delay = atoi(arg); break; - case 'H': start_dht = false; --i; break; - case 'l': settings.listen_queue_size = atoi(arg); break; + case 'H': + start_dht = false; + settings.set_bool(settings_pack::enable_dht, false); + --i; + break; + case 'l': settings.set_int(settings_pack::listen_queue_size, atoi(arg)); break; #ifndef TORRENT_DISABLE_ENCRYPTION case 'e': { - pe_settings s; - - s.out_enc_policy = libtorrent::pe_settings::forced; - s.in_enc_policy = libtorrent::pe_settings::forced; - s.allowed_enc_level = pe_settings::rc4; - s.prefer_rc4 = true; - ses.set_pe_settings(s); + settings.set_int(settings_pack::out_enc_policy, settings_pack::pe_forced); + settings.set_int(settings_pack::in_enc_policy, settings_pack::pe_forced); + settings.set_int(settings_pack::allowed_enc_level, settings_pack::pe_rc4); + settings.set_bool(settings_pack::prefer_rc4, true); --i; break; } #endif case 'W': - settings.max_peerlist_size = atoi(arg); - settings.max_paused_peerlist_size = atoi(arg) / 2; + settings.set_int(settings_pack::max_peerlist_size, atoi(arg)); + settings.set_int(settings_pack::max_paused_peerlist_size, atoi(arg) / 2); break; case 'x': { @@ -1459,7 +1690,7 @@ int main(int argc, char* argv[]) } } break; - case 'c': settings.connections_limit = atoi(arg); break; + case 'c': settings.set_int(settings_pack::connections_limit, atoi(arg)); break; case 'T': max_connections_per_torrent = atoi(arg); break; #if TORRENT_USE_I2P case 'i': @@ -1473,15 +1704,23 @@ int main(int argc, char* argv[]) } #endif // TORRENT_USE_I2P case 'C': - settings.cache_size = atoi(arg); - settings.use_read_cache = settings.cache_size > 0; - settings.cache_buffer_chunk_size = settings.cache_size / 100; + settings.set_int(settings_pack::cache_size, atoi(arg)); + settings.set_bool(settings_pack::use_read_cache, atoi(arg) > 0); + settings.set_int(settings_pack::cache_buffer_chunk_size, atoi(arg) / 100); + break; + case 'A': settings.set_int(settings_pack::allowed_fast_set_size, atoi(arg)); break; + case 'R': settings.set_int(settings_pack::read_cache_line_size, atoi(arg)); break; + case 'M': settings.set_int(settings_pack::mixed_mode_algorithm, settings_pack::prefer_tcp); --i; break; + case 'y': + settings.set_bool(settings_pack::enable_outgoing_tcp, false); + settings.set_bool(settings_pack::enable_incoming_tcp, false); + --i; + break; + case 'J': + settings.set_bool(settings_pack::enable_outgoing_utp, false); + settings.set_bool(settings_pack::enable_incoming_utp, false); + --i; break; - case 'A': settings.allowed_fast_set_size = atoi(arg); break; - case 'R': settings.read_cache_line_size = atoi(arg); break; - case 'O': settings.allow_reordered_disk_operations = false; --i; break; - case 'M': settings.mixed_mode_algorithm = session_settings::prefer_tcp; --i; break; - case 'y': settings.enable_outgoing_tcp = false; settings.enable_incoming_tcp = false; --i; break; case 'r': peer = arg; break; case 'P': { @@ -1516,16 +1755,37 @@ int main(int argc, char* argv[]) ps.type = proxy_settings::socks5_pw; } break; - case 'I': outgoing_interface = arg; break; - case 'N': start_upnp = false; --i; break; - case 'X': start_lsd = false; --i; break; - case 'Y': settings.ignore_limits_on_local_network = false; --i; break; - case 'v': settings.active_downloads = atoi(arg); - settings.active_limit = (std::max)(atoi(arg) * 2, settings.active_limit); + case 'I': settings.set_str(settings_pack::outgoing_interfaces, arg); break; + case 'N': + settings.set_bool(settings_pack::enable_upnp, false); + settings.set_bool(settings_pack::enable_natpmp, false); + --i; + break; + case 'Y': + { + --i; + ip_filter pcf; + // 1 is the global peer class. This should be done properly in the future + pcf.add_rule(address_v4::from_string("0.0.0.0") + , address_v4::from_string("255.255.255.255"), 1); +#if TORRENT_USE_IPV6 + pcf.add_rule(address_v6::from_string("::") + , address_v6::from_string("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), 1); +#endif + ses.set_peer_class_filter(pcf); + break; + } + case 'X': settings.set_bool(settings_pack::enable_lsd, false); --i; break; + case 'Z': + settings.set_str(settings_pack::mmap_cache, arg); + settings.set_bool(settings_pack::contiguous_recv_buffer, false); + break; + case 'v': settings.set_int(settings_pack::active_downloads, atoi(arg)); + settings.set_int(settings_pack::active_limit, atoi(arg) * 2); break; case '^': - settings.active_seeds = atoi(arg); - settings.active_limit = (std::max)(atoi(arg) * 2, settings.active_limit); + settings.set_int(settings_pack::active_seeds, atoi(arg)); + settings.set_int(settings_pack::active_limit, atoi(arg) * 2); break; case 'q': loop_limit = atoi(arg); break; case '0': disable_storage = true; --i; @@ -1539,25 +1799,10 @@ int main(int argc, char* argv[]) if (ec) fprintf(stderr, "failed to create resume file directory: %s\n", ec.message().c_str()); - if (start_lsd) - ses.start_lsd(); - - if (start_upnp) - { - ses.start_upnp(); - ses.start_natpmp(); - } - ses.set_proxy(ps); - ses.listen_on(std::make_pair(listen_port, listen_port) - , ec, bind_to_interface.c_str()); - if (ec) - { - fprintf(stderr, "failed to listen%s%s on ports %d-%d: %s\n" - , bind_to_interface.empty() ? "" : " on ", bind_to_interface.c_str() - , listen_port, listen_port+1, ec.message().c_str()); - } + if (bind_to_interface.empty()) bind_to_interface = "0.0.0.0"; + settings.set_str(settings_pack::listen_interfaces, bind_to_interface + ":" + to_string(listen_port).elems); #ifndef TORRENT_DISABLE_DHT dht_settings dht; @@ -1566,7 +1811,7 @@ int main(int argc, char* argv[]) if (start_dht) { - settings.use_dht_as_fallback = false; + settings.set_bool(settings_pack::use_dht_as_fallback, false); ses.add_dht_router(std::make_pair( std::string("router.bittorrent.com"), 6881)); @@ -1574,17 +1819,16 @@ int main(int argc, char* argv[]) std::string("router.utorrent.com"), 6881)); ses.add_dht_router(std::make_pair( std::string("router.bitcomet.com"), 6881)); - - ses.start_dht(); } #endif - settings.user_agent = "client_test/" LIBTORRENT_VERSION; - settings.choking_algorithm = session_settings::auto_expand_choker; - settings.disk_cache_algorithm = session_settings::avoid_readback; - settings.volatile_read_cache = false; + settings.set_str(settings_pack::user_agent, "client_test/" LIBTORRENT_VERSION); + settings.set_int(settings_pack::choking_algorithm, settings_pack::auto_expand_choker); + settings.set_bool(settings_pack::volatile_read_cache, false); + settings.set_int(settings_pack::disk_io_write_mode, settings_pack::disable_os_cache); + settings.set_int(settings_pack::disk_io_read_mode, settings_pack::disable_os_cache); - ses.set_settings(settings); + ses.apply_settings(settings); for (std::vector::iterator i = magnet_links.begin() , end(magnet_links.end()); i != end; ++i) @@ -1639,10 +1883,15 @@ int main(int argc, char* argv[]) int tick = 0; +#ifndef WIN32 + signal(SIGTERM, signal_handler); + signal(SIGINT, signal_handler); +#endif while (loop_limit > 1 || loop_limit == 0) { ++tick; ses.post_torrent_updates(); + ses.post_session_stats(); if (active_torrent >= int(filtered_handles.size())) active_torrent = filtered_handles.size() - 1; if (active_torrent >= 0) { @@ -1663,32 +1912,47 @@ int main(int argc, char* argv[]) std::sort(filtered_handles.begin(), filtered_handles.end(), &compare_torrent); - if (loop_limit > 1) --loop_limit; + if (loop_limit > 1) + { + // in test mode, don't quit until we're seeding + if (loop_limit > 2 || active_torrent == -1 || filtered_handles[active_torrent]->is_seeding) + --loop_limit; + } int c = 0; if (sleep_and_input(&c, refresh_delay)) { + +#ifdef WIN32 +#define ESCAPE_SEQ 224 +#define LEFT_ARROW 75 +#define RIGHT_ARROW 77 +#define UP_ARROW 72 +#define DOWN_ARROW 80 +#else +#define ESCAPE_SEQ 27 +#define LEFT_ARROW 68 +#define RIGHT_ARROW 67 +#define UP_ARROW 65 +#define DOWN_ARROW 66 +#endif + + if (c == EOF) { break; } do { - if (c == EOF) { break; } - if (c == 27) + if (c == ESCAPE_SEQ) { // escape code, read another character -#ifdef _WIN32 - c = _getch(); +#ifdef WIN32 + int c = _getch(); #else int c = getc(stdin); -#endif if (c == EOF) { break; } if (c != '[') continue; -#ifdef _WIN32 - c = _getch(); -#else c = getc(stdin); #endif if (c == EOF) break; - - if (c == 68) + if (c == LEFT_ARROW) { // arrow left if (torrent_filter > 0) @@ -1697,7 +1961,7 @@ int main(int argc, char* argv[]) update_filtered_torrents(all_handles, filtered_handles, counters); } } - else if (c == 67) + else if (c == RIGHT_ARROW) { // arrow right if (torrent_filter < torrents_max - 1) @@ -1706,13 +1970,13 @@ int main(int argc, char* argv[]) update_filtered_torrents(all_handles, filtered_handles, counters); } } - else if (c == 65) + else if (c == UP_ARROW) { // arrow up --active_torrent; if (active_torrent < 0) active_torrent = 0; } - else if (c == 66) + else if (c == DOWN_ARROW) { // arrow down ++active_torrent; @@ -1787,7 +2051,7 @@ int main(int argc, char* argv[]) files.erase(i); } if (st.handle.is_valid()) - ses.remove_torrent(st.handle, session::delete_files); + ses.remove_torrent(st.handle, lt::session::delete_files); } } } @@ -1797,6 +2061,11 @@ int main(int argc, char* argv[]) get_active_torrent(filtered_handles).handle.force_recheck(); } + if (c == 'x') + { + print_disk_stats = !print_disk_stats; + } + if (c == 'r' && !filtered_handles.empty()) { get_active_torrent(filtered_handles).handle.force_reannounce(); @@ -1898,8 +2167,7 @@ int main(int argc, char* argv[]) } if (c == 'h') { - clear_home(); - puts( + print_with_ansi_colors( "HELP SCREEN (press any key to dismiss)\n\n" "CLIENT OPTIONS\n" "[q] quit client [m] add magnet link\n" @@ -1932,38 +2200,14 @@ int main(int argc, char* argv[]) int tmp; while (sleep_and_input(&tmp, 500) == false); } - } - while (sleep_and_input(&c, 0)); - + } while (sleep_and_input(&c, 0)); if (c == 'q') break; } int terminal_width = 80; int terminal_height = 50; -#ifndef _WIN32 - { - int tty = open("/dev/tty", O_RDONLY); - winsize size; - int ret = ioctl(tty, TIOCGWINSZ, (char*)&size); - if (ret == 0) - { - terminal_width = size.ws_col; - terminal_height = size.ws_row; - - if (terminal_width < 64) - terminal_width = 64; - if (terminal_height < 25) - terminal_height = 25; - } - else - { - terminal_width = 190; - terminal_height = 100; - } - close(tty); - } -#endif + terminal_size(&terminal_width, &terminal_height); int max_lines = terminal_height - 15; @@ -1978,7 +2222,7 @@ int main(int argc, char* argv[]) TORRENT_TRY { if (!::handle_alert(ses, *i, files, non_files, counters - , all_handles, filtered_handles, need_resort)) + , all_handles, filtered_handles, need_resort, stats_counters)) { // if we didn't handle the alert, print it to the log std::string event_string; @@ -2004,9 +2248,8 @@ int main(int argc, char* argv[]) if (loop_limit > 1 && sess_stat.num_peers == 0 && tick > 30) break; std::string out; - out = "[h] show key mappings\n"; - char const* filter_names[] = { "all", "downloading", "non-paused", "seeding", "queued", "stopped", "checking", "RSS"}; + char const* filter_names[] = { "all", "downloading", "non-paused", "seeding", "queued", "stopped", "checking", "loaded", "RSS"}; for (int i = 0; i < int(sizeof(filter_names)/sizeof(filter_names[0])); ++i) { char filter[200]; @@ -2052,6 +2295,11 @@ int main(int argc, char* argv[]) start_offset = active_torrent - max_lines + lines_printed + 1; if (active_torrent < start_offset) start_offset = active_torrent; + // print title bar for torrent list + snprintf(str, sizeof(str), " %-3s %-50s %-35s %-17s %-17s %-11s %-6s %-6s %-4s\n" + , "#", "Name", "Progress", "Download", "Upload", "Peers (D:S)", "Down", "Up", "Flags"); + out += str; + for (std::vector::iterator i = filtered_handles.begin(); i != filtered_handles.end(); ++torrent_index) { @@ -2077,101 +2325,67 @@ int main(int argc, char* argv[]) ++i; } -#ifdef ANSI_TERMINAL_COLORS - char const* term = "\x1b[0m"; -#else - char const* term = ""; -#endif + // the active torrent is highligted in the list + // this inverses the forground and background colors + char const* selection = ""; if (active_torrent == torrent_index) { -#ifdef ANSI_TERMINAL_COLORS - term = "\x1b[0m\x1b[7m"; - out += esc("7"); -#endif - out += "*"; - } - else - { - out += " "; + selection = "\x1b[1m\x1b[44m"; + out += selection; } - int queue_pos = s.queue_position; - if (queue_pos == -1) out += "- "; + char queue_pos[16]; + if (s.queue_position == -1) + strcpy(queue_pos, "-"); else - { - snprintf(str, sizeof(str), "%-3d", queue_pos); - out += str; - } - - if (s.paused) out += esc("34"); - else out += esc("37"); + snprintf(queue_pos, sizeof(queue_pos), "%d", s.queue_position); std::string name = s.name; - if (name.size() > 40) name.resize(40); - snprintf(str, sizeof(str), "%-40s %s ", name.c_str(), term); - out += str; + if (name.size() > 50) name.resize(50); - if (!s.error.empty()) - { - out += esc("31"); - out += "error "; - out += s.error; - out += esc("0"); - out += "\n"; - ++lines_printed; - continue; - } +// int seeds = 0; +// int downloaders = 0; - int seeds = 0; - int downloaders = 0; +// if (s.num_complete >= 0) seeds = s.num_complete; +// else seeds = s.list_seeds; - if (s.num_complete >= 0) seeds = s.num_complete; - else seeds = s.list_seeds; +// if (s.num_incomplete >= 0) downloaders = s.num_incomplete; +// else downloaders = s.list_peers - s.list_seeds; - if (s.num_incomplete >= 0) downloaders = s.num_incomplete; - else downloaders = s.list_peers - s.list_seeds; - - snprintf(str, sizeof(str), "%s%-13s down: (%s%s%s) up: %s%s%s (%s%s%s) swarm: %4d:%4d" - " bw queue: (%d|%d) all-time (Rx: %s%s%s Tx: %s%s%s) seed rank: %x %c%s\n" - , (!s.paused && !s.auto_managed)?"[F] ":"" - , (s.paused && !s.auto_managed)?"paused": - (s.paused && s.auto_managed)?"queued": - state_str[s.state] - , esc("32"), add_suffix(s.total_download).c_str(), term - , esc("31"), add_suffix(s.upload_rate, "/s").c_str(), term - , esc("31"), add_suffix(s.total_upload).c_str(), term - , downloaders, seeds - , s.up_bandwidth_queue, s.down_bandwidth_queue - , esc("32"), add_suffix(s.all_time_download).c_str(), term - , esc("31"), add_suffix(s.all_time_upload).c_str(), term - , s.seed_rank, s.need_save_resume?'S':' ', esc("0")); - out += str; - ++lines_printed; - - if (torrent_index != active_torrent && s.state == torrent_status::seeding) continue; - char const* progress_bar_color = "33"; // yellow - if (s.state == torrent_status::downloading_metadata) - { - progress_bar_color = "35"; // magenta - } + color_code progress_bar_color = col_yellow; + if (!s.error.empty()) progress_bar_color = col_red; + else if (s.paused) progress_bar_color = col_blue; + else if (s.state == torrent_status::downloading_metadata) + progress_bar_color = col_magenta; else if (s.current_tracker.empty()) - { - progress_bar_color = "31"; // red - } + progress_bar_color = col_red; else if (sess_stat.has_incoming_connections) - { - progress_bar_color = "32"; // green - } + progress_bar_color = col_green; - snprintf(str, sizeof(str), " %-10s: %s%-11" PRId64 "%s Bytes %6.2f%% %s\n" - , s.sequential_download?"sequential":"progress" - , esc("32"), s.total_done, esc("0") - , s.progress_ppm / 10000.f - , progress_bar(s.progress_ppm / 1000, terminal_width - 43, progress_bar_color).c_str()); + snprintf(str, sizeof(str), "%c%-3s %-50s %s%s %s (%s) %s (%s) %5d:%-5d" + " %s %s %c%s\n" + , s.is_loaded ? 'L' : ' ' + , queue_pos + , name.c_str() + , progress_bar(s.progress_ppm / 1000, 35, progress_bar_color, '-', '#', torrent_state(s)).c_str() + , selection + , color(add_suffix(s.download_rate, "/s"), col_green).c_str() + , color(add_suffix(s.total_download), col_green).c_str() + , color(add_suffix(s.upload_rate, "/s"), col_red).c_str() + , color(add_suffix(s.total_upload), col_red).c_str() + , s.num_peers - s.num_seeds, s.num_seeds + , color(add_suffix(s.all_time_download), col_green).c_str() + , color(add_suffix(s.all_time_upload), col_red).c_str() + , s.need_save_resume?'S':' ', esc("0")); out += str; ++lines_printed; - if (print_piece_bar && (s.state != torrent_status::seeding || s.seed_mode)) + // if this is the selected torrent, restore the background color + if (active_torrent == torrent_index) + out += esc("0"); + + // don't print the piece bar if we don't have any piece, or if we have all + if (print_piece_bar && s.num_pieces != 0 && s.progress_ppm != 1000000) { out += " "; out += piece_bar(s.pieces, terminal_width - 7); @@ -2185,34 +2399,40 @@ int main(int argc, char* argv[]) ++lines_printed; } } +/* + if (torrent_index != active_torrent) continue; - if (s.state != torrent_status::queued_for_checking && s.state != torrent_status::checking_files) - { - boost::posix_time::time_duration t = s.next_announce; - snprintf(str, sizeof(str) - , " peers: %s%d%s (%s%d%s) seeds: %s%d%s distributed copies: %s%4.2f%s " - "sparse regions: %d download: %s%s%s next announce: %s%02d:%02d:%02d%s " - "tracker: %s%s%s\n" - , esc("37"), s.num_peers, esc("0") - , esc("37"), s.connect_candidates, esc("0") - , esc("37"), s.num_seeds, esc("0") - , esc("37"), s.distributed_copies, esc("0") - , s.sparse_regions - , esc("32"), add_suffix(s.download_rate, "/s").c_str(), esc("0") - , esc("37"), int(t.hours()), int(t.minutes()), int(t.seconds()), esc("0") - , esc("36"), s.current_tracker.c_str(), esc("0")); - out += str; - ++lines_printed; - } + boost::posix_time::time_duration t = s.next_announce; + snprintf(str, sizeof(str) + , " peers: %s%d%s (%s%d%s) seeds: %s%d%s distributed copies: %s%4.2f%s " + "sparse regions: %d download: %s%s%s next announce: %s%02d:%02d:%02d%s " + "tracker: %s%s%s\n" + , esc("37"), s.num_peers, esc("0") + , esc("37"), s.connect_candidates, esc("0") + , esc("37"), s.num_seeds, esc("0") + , esc("37"), s.distributed_copies, esc("0") + , s.sparse_regions + , esc("32"), add_suffix(s.download_rate, "/s").c_str(), esc("0") + , esc("37"), int(t.hours()), int(t.minutes()), int(t.seconds()), esc("0") + , esc("36"), s.current_tracker.c_str(), esc("0")); + out += str; + ++lines_printed; +*/ } - cache_status cs = ses.get_cache_status(); + int cache_flags = print_downloads ? 0 : lt::session::disk_cache_no_pieces; + torrent_handle h; + if (!filtered_handles.empty()) h = get_active_torrent(filtered_handles).handle; + + cache_status cs; + ses.get_cache_info(&cs, h, cache_flags); + if (cs.blocks_read < 1) cs.blocks_read = 1; if (cs.blocks_written < 1) cs.blocks_written = 1; - snprintf(str, sizeof(str), "==== conns: %d down: %s%s%s (%s%s%s) up: %s%s%s (%s%s%s) " + snprintf(str, sizeof(str), "==== conns: %d (%d) down: %s%s%s (%s%s%s) up: %s%s%s (%s%s%s) " "tcp/ip: %s%s%s %s%s%s DHT: %s%s%s %s%s%s tracker: %s%s%s %s%s%s ====\n" - , sess_stat.num_peers + , sess_stat.num_peers, sess_stat.num_dead_peers , esc("32"), add_suffix(sess_stat.download_rate, "/s").c_str(), esc("0") , esc("32"), add_suffix(sess_stat.total_download).c_str(), esc("0") , esc("31"), add_suffix(sess_stat.upload_rate, "/s").c_str(), esc("0") @@ -2226,8 +2446,8 @@ int main(int argc, char* argv[]) out += str; snprintf(str, sizeof(str), "==== waste: %s fail: %s unchoked: %d / %d " - "bw queues: %8d (%d) | %8d (%d) disk queues: %d | %d cache: w: %" PRId64 "%% r: %" PRId64 "%% " - "size: %s (%s) / %s dq: %" PRId64 " ===\n" + "bw queues: %8d (%d) | %8d (%d) disk queues: %d | %d cache: w: %d%% r: %d%% " + "size: w: %s r: %s total: %s ===\n" , add_suffix(sess_stat.total_redundant_bytes).c_str() , add_suffix(sess_stat.total_failed_bytes).c_str() , sess_stat.num_unchoked, sess_stat.allowed_upload_slots @@ -2237,12 +2457,11 @@ int main(int argc, char* argv[]) , sess_stat.down_bandwidth_queue , sess_stat.disk_write_queue , sess_stat.disk_read_queue - , (cs.blocks_written - cs.writes) * 100 / cs.blocks_written - , cs.blocks_read_hit * 100 / cs.blocks_read - , add_suffix(boost::int64_t(cs.cache_size) * 16 * 1024).c_str() + , int((cs.blocks_written - cs.writes) * 100 / cs.blocks_written) + , int(cs.blocks_read_hit * 100 / cs.blocks_read) + , add_suffix(boost::int64_t(cs.write_cache_size) * 16 * 1024).c_str() , add_suffix(boost::int64_t(cs.read_cache_size) * 16 * 1024).c_str() - , add_suffix(boost::int64_t(cs.total_used_buffers) * 16 * 1024).c_str() - , cs.queued_bytes); + , add_suffix(boost::int64_t(cs.total_used_buffers) * 16 * 1024).c_str()); out += str; snprintf(str, sizeof(str), "==== optimistic unchoke: %d unchoke counter: %d peerlist: %d ====\n" @@ -2291,6 +2510,50 @@ int main(int argc, char* argv[]) } } #endif + if (print_disk_stats) + { + snprintf(str, sizeof(str), "Disk stats:\n timing - " + " read: %6d ms | write: %6d ms | hash: %6d\n" + , cs.average_read_time / 1000, cs.average_write_time / 1000 + , cs.average_hash_time / 1000); + out += str; + + snprintf(str, sizeof(str), " jobs - queued: %4d (%4d) pending: %4d blocked: %4d " + "queued-bytes: %5" PRId64 " kB\n" + , cs.queued_jobs, cs.peak_queued, cs.pending_jobs, cs.blocked_jobs + , stats_counters[queued_bytes_idx] / 1000); + out += str; + + snprintf(str, sizeof(str), " cache - total: %4d read: %4d write: %4d pinned: %4d write-queue: %4d\n" + , cs.read_cache_size + cs.write_cache_size, cs.read_cache_size, cs.write_cache_size, cs.pinned_blocks + , int(stats_counters[queued_bytes_idx] / 0x4000)); + out += str; + + int mru_size = cs.arc_mru_size + cs.arc_mru_ghost_size; + int mfu_size = cs.arc_mfu_size + cs.arc_mfu_ghost_size; + int arc_size = mru_size + mfu_size; + + snprintf(str, sizeof(str), "LRU: (%d) %d LFU: %d (%d)\n" + , cs.arc_mru_ghost_size, cs.arc_mru_size + , cs.arc_mfu_size, cs.arc_mfu_ghost_size); + out += str; + if (arc_size > 0) + { + out += ' '; + if (mru_size > 0) + { + out += progress_bar(cs.arc_mru_ghost_size * 1000 / mru_size + , mru_size * (terminal_width-3) / arc_size, col_yellow, '-', '#'); + } + out += '|'; + if (mfu_size) + { + out += progress_bar(cs.arc_mfu_size * 1000 / mfu_size + , mfu_size * (terminal_width-3) / arc_size, col_green, '=', '-'); + } + } + out += "\n"; + } if (print_utp_stats) { @@ -2303,9 +2566,8 @@ int main(int argc, char* argv[]) torrent_status const* st = 0; if (!filtered_handles.empty()) st = &get_active_torrent(filtered_handles); - if (st && st->handle.is_valid()) + if (h.is_valid()) { - torrent_handle h = st->handle; torrent_status const& s = *st; if ((print_downloads && s.state != torrent_status::seeding) @@ -2330,7 +2592,7 @@ int main(int argc, char* argv[]) , i->tier, i->url.c_str(), i->fails, i->fail_limit, i->verified?"OK ":"- " , i->updating?"updating" :to_string(int(total_seconds(i->next_announce - now)), 8).c_str() - , i->min_announce > now ? total_seconds(i->min_announce - now) : 0 + , int(i->min_announce > now ? total_seconds(i->min_announce - now) : 0) , i->last_error ? i->last_error.message().c_str() : "" , i->message.c_str()); out += str; @@ -2339,19 +2601,16 @@ int main(int argc, char* argv[]) if (print_downloads) { - std::vector pieces; - ses.get_cache_info(h.info_hash(), pieces); - h.get_download_queue(queue); std::sort(queue.begin(), queue.end(), boost::bind(&partial_piece_info::piece_index, _1) < boost::bind(&partial_piece_info::piece_index, _2)); - std::sort(pieces.begin(), pieces.end(), boost::bind(&cached_piece_info::last_use, _1) - > boost::bind(&cached_piece_info::last_use, _2)); + std::sort(cs.pieces.begin(), cs.pieces.end(), boost::bind(&cached_piece_info::piece, _1) + > boost::bind(&cached_piece_info::piece, _2)); - for (std::vector::iterator i = pieces.begin(); - i != pieces.end(); ++i) + for (std::vector::iterator i = cs.pieces.begin(); + i != cs.pieces.end(); ++i) { partial_piece_info* pp = 0; partial_piece_info tmp; @@ -2362,7 +2621,7 @@ int main(int argc, char* argv[]) < boost::bind(&partial_piece_info::piece_index, _2)); if (ppi != queue.end() && ppi->piece_index == i->piece) pp = &*ppi; - print_piece(pp, &*i, peers, out); + print_piece(pp, &*i, peers, st, out); if (pp) queue.erase(ppi); } @@ -2370,7 +2629,7 @@ int main(int argc, char* argv[]) for (std::vector::iterator i = queue.begin() , end(queue.end()); i != end; ++i) { - print_piece(&*i, 0, peers, out); + print_piece(&*i, 0, peers, st, out); } snprintf(str, sizeof(str), "%s %s: read cache %s %s: downloading %s %s: cached %s %s: flushed\n" , esc("34;7"), esc("0") // read cache @@ -2381,30 +2640,60 @@ int main(int argc, char* argv[]) out += "___________________________________\n"; } - if (print_file_progress - && s.state != torrent_status::seeding - && s.has_metadata) + if (print_file_progress && s.has_metadata) { std::vector file_progress; h.file_progress(file_progress); - boost::intrusive_ptr ti = h.torrent_file(); + std::vector file_status; + h.file_status(file_status); + std::vector file_prio = h.file_priorities(); + std::vector::iterator f = file_status.begin(); + boost::shared_ptr ti = h.torrent_file(); for (int i = 0; i < ti->num_files(); ++i) { - bool pad_file = ti->file_at(i).pad_file; - if (!show_pad_files && pad_file) continue; - int progress = ti->file_at(i).size > 0 - ?file_progress[i] * 1000 / ti->file_at(i).size:1000; + bool pad_file = ti->files().pad_file_at(i); + if (pad_file) + { + if (show_pad_files) + { + snprintf(str, sizeof(str), "\x1b[34m%-70s %s\x1b[0m\n" + , ti->files().file_name(i).c_str() + , add_suffix(ti->files().file_size(i)).c_str()); + out += str; + } + continue; + } - char const* color = (file_progress[i] == ti->file_at(i).size) - ?"32":"33"; + int progress = ti->files().file_size(i) > 0 + ?file_progress[i] * 1000 / ti->files().file_size(i):1000; - snprintf(str, sizeof(str), "%s %s %-5.2f%% %s %s%s\n", - progress_bar(progress, 100, color).c_str() - , pad_file?esc("34"):"" - , progress / 10.f + bool complete = file_progress[i] == ti->files().file_size(i); + + std::string title = ti->files().file_name(i); + if (!complete) + { + snprintf(str, sizeof(str), " (%.1f%%)", progress / 10.f); + title += str; + } + + if (f != file_status.end() && f->file_index == i) + { + title += " [ "; + if ((f->open_mode & file::rw_mask) == file::read_write) title += "read/write "; + else if ((f->open_mode & file::rw_mask) == file::read_only) title += "read "; + else if ((f->open_mode & file::rw_mask) == file::write_only) title += "write "; + if (f->open_mode & file::random_access) title += "random_access "; + if (f->open_mode & file::lock_file) title += "locked "; + if (f->open_mode & file::sparse) title += "sparse "; + title += "]"; + ++f; + } + + snprintf(str, sizeof(str), "%s %s prio: %d\n", + progress_bar(progress, 70, complete?col_green:col_yellow, '-', '#' + , title.c_str()).c_str() , add_suffix(file_progress[i]).c_str() - , ti->files().file_name(i).c_str() - , pad_file?esc("0"):""); + , file_prio[i]); out += str; } @@ -2423,8 +2712,7 @@ int main(int argc, char* argv[]) } } - clear_home(); - puts(out.c_str()); + print_with_ansi_colors(out.c_str()); fflush(stdout); if (!monitor_dir.empty() @@ -2517,7 +2805,8 @@ int main(int argc, char* argv[]) torrent_status st = h.status(torrent_handle::query_save_path); std::vector out; bencode(std::back_inserter(out), *rd->resume_data); - save_file(combine_path(st.save_path, combine_path(".resume", to_hex(st.info_hash.to_string()) + ".resume")), out); + save_file(combine_path(st.save_path, combine_path(".resume", libtorrent::filename( + hash_to_filename[st.info_hash]) + ".resume")), out); } } diff --git a/examples/connection_tester.cpp b/examples/connection_tester.cpp index e5774d233..31f43e680 100644 --- a/examples/connection_tester.cpp +++ b/examples/connection_tester.cpp @@ -40,6 +40,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/thread.hpp" #include "libtorrent/create_torrent.hpp" #include "libtorrent/hasher.hpp" +#include "libtorrent/socket_io.hpp" #include #include #include @@ -66,6 +67,10 @@ void generate_block(boost::uint32_t* buffer, int piece, int start, int length) int local_if_counter = 0; bool local_bind = false; +// when set to true, blocks downloaded are verified to match +// the test torrents +bool verify_downloads = false; + // if this is true, one block in 1000 will be sent corrupt. // this only applies to dual and upload tests bool test_corruption = false; @@ -88,33 +93,43 @@ boost::detail::atomic_count num_suggest(0); // the number of requests made from suggested pieces boost::detail::atomic_count num_suggested_requests(0); - struct peer_conn { peer_conn(io_service& ios, int num_pieces, int blocks_pp, tcp::endpoint const& ep - , char const* ih, bool seed_) + , char const* ih, bool seed_, int churn_, bool corrupt_) : s(ios) , read_pos(0) , state(handshaking) + , choked(true) + , current_piece(-1) + , current_piece_is_allowed(false) , block(0) , blocks_per_piece(blocks_pp) , info_hash(ih) , outstanding_requests(0) , seed(seed_) , fast_extension(false) - , choked(true) , blocks_received(0) , blocks_sent(0) , num_pieces(num_pieces) , start_time(time_now_hires()) + , churn(churn_) + , corrupt(corrupt_) + , endpoint(ep) + , restarting(false) { corruption_counter = rand() % 1000; if (seed) ++num_seeds; pieces.reserve(num_pieces); + start_conn(); + } + + void start_conn() + { if (local_bind) { error_code ec; - s.open(ep.protocol(), ec); + s.open(endpoint.protocol(), ec); if (ec) { close("ERROR OPEN: %s", ec); @@ -132,7 +147,8 @@ struct peer_conn return; } } - s.async_connect(ep, boost::bind(&peer_conn::on_connect, this, _1)); + restarting = false; + s.async_connect(endpoint, boost::bind(&peer_conn::on_connect, this, _1)); } stream_socket s; @@ -151,7 +167,10 @@ struct peer_conn int state; std::vector pieces; std::vector suggested_pieces; + std::vector allowed_fast; + bool choked; int current_piece; // the piece we're currently requesting blocks from + bool current_piece_is_allowed; int block; int blocks_per_piece; char const* info_hash; @@ -159,12 +178,15 @@ struct peer_conn // if this is true, this connection is a seed bool seed; bool fast_extension; - bool choked; int blocks_received; int blocks_sent; int num_pieces; ptime start_time; ptime end_time; + int churn; + bool corrupt; + tcp::endpoint endpoint; + bool restarting; void on_connect(error_code const& ec) { @@ -273,21 +295,33 @@ struct peer_conn bool write_request() { - if (choked) return false; + // if we're choked (and there are no allowed-fast pieces left) + if (choked && allowed_fast.empty() && !current_piece_is_allowed) return false; + + // if there are no pieces left to request if (pieces.empty() && suggested_pieces.empty() && current_piece == -1) return false; if (current_piece == -1) { - if (suggested_pieces.size() > 0) + // pick a new piece + if (choked && allowed_fast.size() > 0) + { + current_piece = allowed_fast.front(); + allowed_fast.erase(allowed_fast.begin()); + current_piece_is_allowed = true; + } + else if (suggested_pieces.size() > 0) { current_piece = suggested_pieces.front(); suggested_pieces.erase(suggested_pieces.begin()); ++num_suggested_requests; + current_piece_is_allowed = false; } else if (pieces.size() > 0) { current_piece = pieces.front(); pieces.erase(pieces.begin()); + current_piece_is_allowed = false; } else { @@ -314,6 +348,7 @@ struct peer_conn { block = 0; current_piece = -1; + current_piece_is_allowed = false; } return true; } @@ -339,8 +374,9 @@ struct peer_conn if (time == 0) time = 1; float up = (boost::int64_t(blocks_sent) * 0x4000) / time / 1000.f; float down = (boost::int64_t(blocks_received) * 0x4000) / time / 1000.f; - printf("%s sent: %d received: %d duration: %d ms up: %.1fMB/s down: %.1fMB/s\n" - , tmp, blocks_sent, blocks_received, time, up, down); + error_code e; + printf("%s ep: %s sent: %d received: %d duration: %d ms up: %.1fMB/s down: %.1fMB/s\n" + , tmp, libtorrent::print_endpoint(s.local_endpoint(e)).c_str(), blocks_sent, blocks_received, time, up, down); if (seed) --num_seeds; } @@ -369,6 +405,13 @@ struct peer_conn void on_msg_length(error_code const& ec, size_t bytes_transferred) { + if ((ec == boost::asio::error::operation_aborted || ec == boost::asio::error::bad_descriptor) + && restarting) + { + start_conn(); + return; + } + if (ec) { close("ERROR RECEIVE MESSAGE PREFIX: %s", ec); @@ -378,6 +421,7 @@ struct peer_conn unsigned int length = read_uint32(ptr); if (length > sizeof(buffer)) { + fprintf(stderr, "len: %d\n", length); close("ERROR RECEIVE MESSAGE PREFIX: packet too big", error_code()); return; } @@ -387,6 +431,13 @@ struct peer_conn void on_message(error_code const& ec, size_t bytes_transferred) { + if ((ec == boost::asio::error::operation_aborted || ec == boost::asio::error::bad_descriptor) + && restarting) + { + start_conn(); + return; + } + if (ec) { close("ERROR RECEIVE MESSAGE: %s", ec); @@ -466,7 +517,7 @@ struct peer_conn } else if (msg == 7) // piece { -// if (verify_downloads) + if (verify_downloads) { int piece = read_uint32(ptr); int start = read_uint32(ptr); @@ -477,6 +528,13 @@ struct peer_conn --outstanding_requests; int piece = detail::read_int32(ptr); int start = detail::read_int32(ptr); + + if (churn && (blocks_received % churn) == 0) { + outstanding_requests = 0; + restarting = true; + s.close(); + return; + } if (int((start + bytes_transferred) / 0x4000) == blocks_per_piece) { write_have(piece); @@ -494,14 +552,49 @@ struct peer_conn ++num_suggest; } } - else if (msg == 1) // unchoke + else if (msg == 16) // reject request { - choked = false; + int piece = detail::read_int32(ptr); + int start = detail::read_int32(ptr); + int length = detail::read_int32(ptr); + + // put it back! + if (current_piece != piece) + { + if (pieces.empty() || pieces.back() != piece) + pieces.push_back(piece); + } + else + { + block = (std::min)(start / 0x4000, block); + if (block == 0) + { + pieces.push_back(current_piece); + current_piece = -1; + current_piece_is_allowed = false; + } + } + --outstanding_requests; + fprintf(stderr, "REJECT: [ piece: %d start: %d length: %d ]\n", piece, start, length); } else if (msg == 0) // choke { choked = true; } + else if (msg == 1) // unchoke + { + choked = false; + } + else if (msg == 17) // allowed_fast + { + int piece = detail::read_int32(ptr); + std::vector::iterator i = std::find(pieces.begin(), pieces.end(), piece); + if (i != pieces.end()) + { + pieces.erase(i); + allowed_fast.push_back(piece); + } + } work_download(); } } @@ -526,7 +619,7 @@ struct peer_conn { generate_block(write_buffer, piece, start, length); - if (test_corruption) + if (corrupt) { --corruption_counter; if (corruption_counter == 0) @@ -546,6 +639,11 @@ struct peer_conn vec[1] = libtorrent::asio::buffer(write_buffer, length); boost::asio::async_write(s, vec, boost::bind(&peer_conn::on_have_all_sent, this, _1, _2)); ++blocks_sent; + if (churn && (blocks_sent % churn) == 0 && seed) { + outstanding_requests = 0; + restarting = true; + s.close(); + } } void write_have(int piece) @@ -583,8 +681,9 @@ void print_usage() " -c the number of connections to make to the target\n" " -d the IP address of the target\n" " -p the port the target listens on\n" - " -t the torrent file previously generated by gen-torrent\n\n" - " -C send corrupt pieces sometimes (applies to upload and dual)\n\n" + " -t the torrent file previously generated by gen-torrent\n" + " -C send corrupt pieces sometimes (applies to upload and dual)\n" + " -r churn - number of reconnects per second\n\n" "examples:\n\n" "connection_tester gen-torrent -s 1024 -n 4 -t test.torrent\n" "connection_tester upload -c 200 -d 127.0.0.1 -p 6881 -t test.torrent\n" @@ -612,7 +711,7 @@ void hasher_thread(libtorrent::create_torrent* t, int start_piece, int end_piece } // size is in megabytes -void generate_torrent(std::vector& buf, int size, int num_files) +void generate_torrent(std::vector& buf, int size, int num_files, char const* name) { file_storage fs; // 1 MiB piece size @@ -626,7 +725,7 @@ void generate_torrent(std::vector& buf, int size, int num_files) while (s > 0) { char b[100]; - snprintf(b, sizeof(b), "t/stress_test%d", i); + snprintf(b, sizeof(b), "%s/stress_test%d", name, i); ++i; fs.add_file(b, (std::min)(s, size_type(file_size))); s -= file_size; @@ -696,6 +795,7 @@ int main(int argc, char* argv[]) int num_connections = 50; char const* destination_ip = "127.0.0.1"; int destination_port = 6881; + int churn = 0; argv += 2; argc -= 2; @@ -738,6 +838,7 @@ int main(int argc, char* argv[]) case 'c': num_connections = atoi(optarg); break; case 'p': destination_port = atoi(optarg); break; case 'd': destination_ip = optarg; break; + case 'r': churn = atoi(optarg); break; default: fprintf(stderr, "unknown option: %s\n", optname); } } @@ -745,7 +846,10 @@ int main(int argc, char* argv[]) if (strcmp(command, "gen-torrent") == 0) { std::vector tmp; - generate_torrent(tmp, size ? size : 1024, num_files ? num_files : 1); + std::string name = filename(torrent_file); + name = name.substr(0, name.find_last_of('.')); + printf("generating torrent: %s\n", name.c_str()); + generate_torrent(tmp, size ? size : 1024, num_files ? num_files : 1, name.c_str()); FILE* output = stdout; if (strcmp("-", torrent_file) != 0) @@ -861,17 +965,19 @@ int main(int argc, char* argv[]) fprintf(stderr, "ERROR LOADING .TORRENT: %s\n", ec.message().c_str()); return 1; } - - std::list conns; + + std::vector conns; + conns.reserve(num_connections); const int num_threads = 2; io_service ios[num_threads]; for (int i = 0; i < num_connections; ++i) { + bool corrupt = test_corruption && (i & 1) == 0; bool seed = false; if (test_mode == upload_test) seed = true; else if (test_mode == dual_test) seed = (i & 1); conns.push_back(new peer_conn(ios[i % num_threads], ti.num_pieces(), ti.piece_length() / 16 / 1024 - , ep, (char const*)&ti.info_hash()[0], seed)); + , ep, (char const*)&ti.info_hash()[0], seed, churn, corrupt)); libtorrent::sleep(1); ios[i % num_threads].poll_one(ec); if (ec) @@ -881,7 +987,6 @@ int main(int argc, char* argv[]) } } - thread t1(boost::bind(&io_thread, &ios[0])); thread t2(boost::bind(&io_thread, &ios[1])); @@ -893,7 +998,7 @@ int main(int argc, char* argv[]) boost::uint64_t total_sent = 0; boost::uint64_t total_received = 0; - for (std::list::iterator i = conns.begin() + for (std::vector::iterator i = conns.begin() , end(conns.end()); i != end; ++i) { peer_conn* p = *i; diff --git a/examples/dump_torrent.cpp b/examples/dump_torrent.cpp index adb3ca3da..e217587c6 100644 --- a/examples/dump_torrent.cpp +++ b/examples/dump_torrent.cpp @@ -42,21 +42,21 @@ int load_file(std::string const& filename, std::vector& v, libtorrent::err FILE* f = fopen(filename.c_str(), "rb"); if (f == NULL) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); return -1; } int r = fseek(f, 0, SEEK_END); if (r != 0) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); fclose(f); return -1; } long s = ftell(f); if (s < 0) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); fclose(f); return -1; } @@ -70,7 +70,7 @@ int load_file(std::string const& filename, std::vector& v, libtorrent::err r = fseek(f, 0, SEEK_SET); if (r != 0) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); fclose(f); return -1; } @@ -85,7 +85,7 @@ int load_file(std::string const& filename, std::vector& v, libtorrent::err r = fread(&v[0], 1, v.size(), f); if (r < 0) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); fclose(f); return -1; } diff --git a/examples/make_torrent.cpp b/examples/make_torrent.cpp index 3236d51a0..383b480c2 100644 --- a/examples/make_torrent.cpp +++ b/examples/make_torrent.cpp @@ -50,21 +50,21 @@ int load_file(std::string const& filename, std::vector& v, libtorrent::err FILE* f = fopen(filename.c_str(), "rb"); if (f == NULL) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); return -1; } int r = fseek(f, 0, SEEK_END); if (r != 0) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); fclose(f); return -1; } long s = ftell(f); if (s < 0) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); fclose(f); return -1; } @@ -78,7 +78,7 @@ int load_file(std::string const& filename, std::vector& v, libtorrent::err r = fseek(f, 0, SEEK_SET); if (r != 0) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); fclose(f); return -1; } @@ -93,7 +93,7 @@ int load_file(std::string const& filename, std::vector& v, libtorrent::err r = fread(&v[0], 1, v.size(), f); if (r < 0) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); fclose(f); return -1; } diff --git a/examples/rss_reader.cpp b/examples/rss_reader.cpp index 26db6b7d6..a4b758666 100644 --- a/examples/rss_reader.cpp +++ b/examples/rss_reader.cpp @@ -38,6 +38,7 @@ POSSIBILITY OF SUCH DAMAGE. #include using namespace libtorrent; +namespace lt = libtorrent; int load_file(std::string const& filename, std::vector& v, libtorrent::error_code& ec, int limit = 8000000) { @@ -45,21 +46,21 @@ int load_file(std::string const& filename, std::vector& v, libtorrent::err FILE* f = fopen(filename.c_str(), "rb"); if (f == NULL) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); return -1; } int r = fseek(f, 0, SEEK_END); if (r != 0) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); fclose(f); return -1; } long s = ftell(f); if (s < 0) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); fclose(f); return -1; } @@ -73,7 +74,7 @@ int load_file(std::string const& filename, std::vector& v, libtorrent::err r = fseek(f, 0, SEEK_SET); if (r != 0) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); fclose(f); return -1; } @@ -88,7 +89,7 @@ int load_file(std::string const& filename, std::vector& v, libtorrent::err r = fread(&v[0], 1, v.size(), f); if (r < 0) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); fclose(f); return -1; } @@ -163,13 +164,13 @@ int main(int argc, char* argv[]) return 0; } - session ses; + lt::session ses; - session_settings sett; - sett.active_downloads = 2; - sett.active_seeds = 1; - sett.active_limit = 3; - ses.set_settings(sett); + settings_pack pack; + pack.set_int(settings_pack::active_downloads, 2); + pack.set_int(settings_pack::active_seeds, 1); + pack.set_int(settings_pack::active_limit, 3); + ses.apply_settings(pack); std::vector in; error_code ec; diff --git a/examples/run_benchmarks.py b/examples/run_benchmarks.py index b8122ef41..65e972b69 100644 --- a/examples/run_benchmarks.py +++ b/examples/run_benchmarks.py @@ -6,6 +6,8 @@ import shlex import time import subprocess import random +import signal +import hashlib # this is a disk I/O benchmark script. It runs menchmarks # over different filesystems, different cache sizes and @@ -29,43 +31,51 @@ import random # variables to test. All these are run on the first # entry in the filesystem list. -cache_sizes = [0, 32768, 393216] -peers = [200, 1000, 2000] -builds = ['syncio'] +cache_sizes = [0, 32768, 400000] +peers = [200, 500, 1000] +builds = ['rtorrent', 'utorrent', 'aio', 'syncio'] # the drives are assumed to be mounted under ./ # or have symbolic links to them. -#filesystem = ['ext4', 'ext3', 'reiser', 'xfs'] -filesystem = ['ext3'] +filesystem = ['xfs', 'ext4', 'ext3', 'reiser'] +default_fs = filesystem[0] # the number of peers for the filesystem test. The # idea is to stress test the filesystem by using a lot # of peers, since each peer essentially is a separate # read location on the platter -filesystem_peers = 200 +default_peers = peers[1] # the amount of cache for the filesystem test -# 6 GiB of cache -filesystem_cache = 393216 +# 5.5 GiB of cache +default_cache = cache_sizes[-1] # the number of seconds to run each test. It's important that # this is shorter than what it takes to finish downloading # the test torrent, since then the average rate will not # be representative of the peak anymore # this has to be long enough to download a full copy -# of the test torrent -test_duration = 1000 +# of the test torrent. It's also important for the +# test to be long enough that the warming up of the +# disk cache is not a significant part of the test, +# since download rates will be extremely high while downloading +# into RAM +test_duration = 200 # 700 # make sure the environment is properly set up -if resource.getrlimit(resource.RLIMIT_NOFILE)[0] < 4000: - print 'please set ulimit -n to at least 4000' - sys.exit(1) +try: + if os.name == 'posix': + resource.setrlimit(resource.RLIMIT_NOFILE, (4000, 5000)) +except: + if resource.getrlimit(resource.RLIMIT_NOFILE)[0] < 4000: + print 'please set ulimit -n to at least 4000' + sys.exit(1) def build_stage_dirs(): ret = [] - for i in builds: + for i in builds[2:3]: ret.append('stage_%s' % i) return ret @@ -88,29 +98,81 @@ for i in filesystem: # make sure we have a test torrent if not os.path.exists('test.torrent'): print 'generating test torrent' - os.system('./stage_%s/connection_tester gen-torrent test.torrent' % builds[0]) + # generate a 100 GB torrent, to make sure it won't all fit in physical RAM + os.system('./stage_aio/connection_tester gen-torrent 10000 test.torrent') + +if not os.path.exists('test2.torrent'): + print 'generating test torrent 2' + # generate a 6 GB torrent, to make sure it will fit in physical RAM + os.system('./stage_aio/connection_tester gen-torrent 6000 test2.torrent') # use a new port for each test to make sure they keep working # this port is incremented for each test run -port = 10000 + random.randint(0, 5000) +port = 10000 + random.randint(0, 40000) + +def clear_caches(): + if 'linux' in sys.platform: + os.system('sync') + open('/proc/sys/vm/drop_caches', 'w').write('3') + elif 'darwin' in sys.platform: + os.system('purge') def build_commandline(config, port): + num_peers = config['num-peers'] - no_disk_reorder = ''; - if config['allow-disk-reorder'] == False: - no_disk_reorder = '-O' - no_read_ahead = '' - if config['read-ahead'] == False: - no_read_ahead = '-j' - allocation_mode = config['allocation-mode'] + torrent_path = config['torrent'] - #TODO: take config['coalesce'] into account - - global test_duration + if config['build'] == 'utorrent': + try: os.mkdir('utorrent_session') + except: pass + cfg = open('utorrent_session/settings.dat', 'w+') - return './stage_%s/client_test -k -z -N -h -H -M -B %d -l %d -S %d -T %d -c %d -C %d -s "%s" %s %s -q %d -p %d -f session_stats/alerts_log.txt -a %s test.torrent' \ - % (config['build'], test_duration, num_peers, num_peers, num_peers, num_peers, config['cache-size'], config['save-path'] \ - , no_disk_reorder, no_read_ahead, test_duration, port, config['allocation-mode']) + cfg.write('d') + cfg.write('20:ul_slots_per_torrenti%de' % num_peers) + cfg.write('17:conns_per_torrenti%de' % num_peers) + cfg.write('14:conns_globallyi%de' % num_peers) + cfg.write('9:bind_porti%de' % port) + cfg.write('19:dir_active_download%d:%s' % (len(config['save-path']), config['save-path'])) + cfg.write('19:diskio.sparse_filesi1e') + cfg.write('14:cache.overridei1e') + cfg.write('19:cache.override_sizei%de' % int(config['cache-size'] * 16 / 1024)) + cfg.write('17:dir_autoload_flagi1e') + cfg.write('12:dir_autoload8:autoload') + cfg.write('11:logger_maski4294967295e') + cfg.write('1:vi0e') + cfg.write('12:webui.enablei1e') + cfg.write('19:webui.enable_listeni1e') + cfg.write('14:webui.hashword20:' + hashlib.sha1('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaadmin').digest()) + cfg.write('10:webui.porti8080e') + cfg.write('10:webui.salt32:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + cfg.write('14:webui.username5:admin') + cfg.write('e') + cfg.close() + try: os.mkdir('utorrent_session/autoload') + except: pass + try: shutil.copy(torrent_path, 'utorrent_session/autoload/') + except: pass + return './utorrent-server-v3_0/utserver -logfile session_stats/alerts_log.txt -settingspath utorrent_session' + + if config['build'] == 'rtorrent': + if os.path.exists('rtorrent_session'): + add_command = '' + else: + try: os.mkdir('rtorrent_session') + except: pass + # it seems rtorrent may delete the original torrent when it's being added + try: shutil.copy(torrent_path, 'rtorrent_session/') + except: pass + add_command = '-O load_start_verbose=rtorrent_session/%s ' % torrent_path + + return 'rtorrent -d %s -n -p %d-%d -O max_peers=%d -O max_uploads=%d %s -s rtorrent_session -O max_memory_usage=128000000000' \ + % (config['save-path'], port, port, num_peers, num_peers, add_command) + + disable_disk = '' + if config['disable-disk']: disable_disk = '-0' + return './stage_%s/client_test -k -N -H -M -B %d -l %d -S %d -T %d -c %d -C %d -s "%s" -p %d -E %d %s -f session_stats/alerts_log.txt %s' \ + % (config['build'], test_duration, num_peers, num_peers, num_peers, num_peers, config['cache-size'], config['save-path'], port, \ + config['hash-threads'], disable_disk, torrent_path) def delete_files(files): for i in files: @@ -119,29 +181,78 @@ def delete_files(files): try: shutil.rmtree(i) except: try: - if os.exists(i): print 'failed to delete %s' % i + if os.path.exists(i): print 'failed to delete %s' % i except: pass -def build_test_config(fs, num_peers, cache_size, readahead=True, reorder=True, preallocate=False, coalesce=True, test='upload', build='aio'): - config = {'test': test, 'save-path': os.path.join('./', fs), 'num-peers': num_peers, 'allow-disk-reorder': reorder, 'cache-size': cache_size, 'read-ahead': readahead} - if preallocate: config['allocation-mode'] = 'allocate' - else: config['allocation-mode'] = 'sparse' - if coalesce: config['coalesce'] = True - else: config['coalesce'] = False - config['build'] = build +# typically the schedulers available are 'noop', 'deadline' and 'cfq' +def build_test_config(fs=default_fs, num_peers=default_peers, cache_size=default_cache, \ + test='upload', build='aio', profile='', hash_threads=1, torrent='test.torrent', \ + disable_disk = False): + config = {'test': test, 'save-path': os.path.join('./', fs), 'num-peers': num_peers, \ + 'cache-size': cache_size, 'build': build, 'profile':profile, \ + 'hash-threads': hash_threads, 'torrent': torrent, 'disable-disk': disable_disk } return config +def prefix_len(text, prefix): + for i in xrange(1, len(prefix)): + if (not text.startswith(prefix[0:i])): return i-1 + return len(prefix) + +def device_name(path): + mount = subprocess.Popen('mount', stdout=subprocess.PIPE) + + max_match_len = 0 + match_device = '' + path = os.path.abspath(path) + + for mp in mount.stdout.readlines(): + c = mp.split(' ') + device = c[0] + mountpoint = c[2] + prefix = prefix_len(path, mountpoint) + if prefix > max_match_len: + max_match_len = prefix + match_device = device + + device = match_device + device = device.split('/')[-1][0:3] + print 'device for path: %s -> %s' % (path, device) + return device + def build_target_folder(config): - reorder = 'reorder' - if config['allow-disk-reorder'] == False: reorder = 'no-reorder' - readahead = 'readahead' - if config['read-ahead'] == False: readahead = 'no-readahead' - coalesce = 'coalesce' - if config['coalesce'] == False: coalesce = 'no-coalesce' test = 'seed' if config['test'] == 'upload': test = 'download' + elif config['test'] == 'dual': test = 'dual' - return 'results_%s_%s_%d_%d_%s_%s_%s_%s_%s' % (config['build'], test, config['num-peers'], config['cache-size'], os.path.split(config['save-path'])[1], reorder, readahead, config['allocation-mode'], coalesce) + if 'linux' in sys.platform: + io_scheduler = open('/sys/block/%s/queue/scheduler' % device_name(config['save-path'])).read().split('[')[1].split(']')[0] + else: + io_scheduler = sys.platform + + no_disk = '' + if config['disable-disk']: no_disk = '_no-disk' + + return 'results_%s_%s_%d_%d_%s_%s_h%d%s' % (config['build'], test, config['num-peers'], \ + config['cache-size'], os.path.split(config['save-path'])[1], io_scheduler, \ + config['hash-threads'], no_disk) + +def find_library(name): + paths = ['/usr/lib64/', '/usr/local/lib64/', '/usr/lib/', '/usr/local/lib/'] + + for p in paths: + try: + if os.path.exists(p + name): return p + name + except: pass + return name + +def find_binary(names): + paths = ['/usr/bin/', '/usr/local/bin/'] + for n in names: + for p in paths: + try: + if os.path.exists(p + n): return p + n + except: pass + return names[0] def run_test(config): @@ -150,12 +261,18 @@ def run_test(config): print 'results already exists, skipping test (%s)' % target_folder return + print '\n\n*********************************' + print '* RUNNING TEST *' + print '*********************************\n\n' + print '%s %s' % (config['build'], config['test']) + # make sure any previous test file is removed # don't clean up unless we're running a download-test, so that we leave the test file # complete for a seed test. - if config['test'] == 'upload': + delete_files(['utorrent_session/settings.dat', 'utorrent_session/settings.dat.old', 'asserts.log']) + if config['test'] == 'upload' or config['test'] == 'dual': print 'deleting files' - delete_files([os.path.join(config['save-path'], 'stress_test_file'), '.ses_state', os.path.join(config['save-path'], '.resume'), '.dht_state', 'session_stats']) + delete_files([os.path.join(config['save-path'], 'stress_test_file'), '.ses_state', os.path.join(config['save-path'], '.resume'), 'utorrent_session', '.dht_state', 'session_stats', 'rtorrent_session']) try: os.mkdir('session_stats') except: pass @@ -163,6 +280,11 @@ def run_test(config): # save off the command line for reference global port cmdline = build_commandline(config, port) + binary = cmdline.split(' ')[0] + environment = None + if config['profile'] == 'tcmalloc': environment = {'LD_PRELOAD':find_library('libprofiler.so.0'), 'CPUPROFILE': 'session_stats/cpu_profile.prof'} + if config['profile'] == 'memory': environment = {'LD_PRELOAD':find_library('libprofiler.so.0'), 'HEAPPROFILE': 'session_stats/heap_profile.prof'} + if config['profile'] == 'perf': cmdline = 'perf timechart record --call-graph --output=session_stats/perf_profile.prof ' + cmdline f = open('session_stats/cmdline.txt', 'w+') f.write(cmdline) f.close() @@ -171,80 +293,145 @@ def run_test(config): print >>f, config f.close() - print '\n\n*********************************' - print '* RUNNING TEST *' - print '*********************************\n\n' + print 'clearing disk cache' + clear_caches() + print 'OK' client_output = open('session_stats/client.output', 'w+') + client_error = open('session_stats/client.error', 'w+') print 'launching: %s' % cmdline - client = subprocess.Popen(shlex.split(cmdline), stdout=client_output, stdin=subprocess.PIPE) + client = subprocess.Popen(shlex.split(cmdline), stdout=client_output, stdin=subprocess.PIPE, stderr=client_error, env=environment) + print 'OK' # enable disk stats printing - print >>client.stdin, 'x', - # when allocating storage, we have to wait for it to complete before we can connect - time.sleep(1) - cmdline = './stage_%s/connection_tester %s %d 127.0.0.1 %d test.torrent' % (config['build'], config['test'], config['num-peers'], port) + if config['build'] != 'rtorrent' and config['build'] != 'utorrent': + print >>client.stdin, 'x', + time.sleep(4) + cmdline = './stage_aio/connection_tester %s %d 127.0.0.1 %d %s' % (config['test'], config['num-peers'], port, config['torrent']) print 'launching: %s' % cmdline tester_output = open('session_stats/tester.output', 'w+') tester = subprocess.Popen(shlex.split(cmdline), stdout=tester_output) + print 'OK' - tester.wait() + time.sleep(2) + + print '\n' + i = 0 + while True: + time.sleep(1) + tester.poll() + if tester.returncode != None: + print 'tester terminated' + break + client.poll() + if client.returncode != None: + print 'client terminated' + break + print '\r%d / %d' % (i, test_duration), + sys.stdout.flush() + i += 1 + if config['test'] != 'upload' and config['test'] != 'dual' and i >= test_duration: break + print '\n' + + if client.returncode == None: + try: + print 'killing client' + client.send_signal(signal.SIGINT) + except: + pass + + time.sleep(10) client.wait() + tester.wait() tester_output.close() client_output.close() - if tester.returncode != 0: sys.exit(tester.returncode) - if client.returncode != 0: sys.exit(client.returncode) + terminate = False + if tester.returncode != 0: + print 'tester returned %d' % tester.returncode + terminate = True + if client.returncode != 0: + print 'client returned %d' % client.returncode + terminate = True + + try: shutil.copy('asserts.log', 'session_stats/') + except: pass + + try: shutil.move('libtorrent_logs0', 'session_stats/') + except: pass + try: shutil.move('libtorrent_logs%s' % port, 'session_stats/') + except: pass # run fragmentation test print 'analyzing fragmentation' - os.system('./stage_%s/fragmentation_test test.torrent %s' % (config['build'], config['save-path'])) - shutil.copy('fragmentation.log', 'session_stats/') - shutil.copy('fragmentation.png', 'session_stats/') + os.system('./stage_aio/fragmentation_test test.torrent %s' % (config['save-path'])) + try: shutil.copy('fragmentation.log', 'session_stats/') + except: pass + shutil.copy('fragmentation.gnuplot', 'session_stats/') + try: shutil.copy('file_access.log', 'session_stats/') + except: pass + + os.system('filefrag %s >session_stats/filefrag.out' % config['save-path']) + os.system('filefrag -v %s >session_stats/filefrag_verbose.out' % config['save-path']) os.chdir('session_stats') # parse session stats print 'parsing session log' os.system('python ../../parse_session_stats.py *.0000.log') + os.system('../stage_aio/parse_access_log file_access.log %s' % (os.path.join('..', config['save-path'], 'stress_test_file'))) os.chdir('..') + if config['profile'] == 'tcmalloc': + print 'analyzing CPU profile [%s]' % binary + os.system('%s --pdf %s session_stats/cpu_profile.prof >session_stats/cpu_profile.pdf' % (find_binary(['google-pprof', 'pprof']), binary)) + if config['profile'] == 'memory': + for i in xrange(1, 300): + profile = 'session_stats/heap_profile.prof.%04d.heap' % i + try: os.stat(profile) + except: break + print 'analyzing heap profile [%s] %d' % (binary, i) + os.system('%s --pdf %s %s >session_stats/heap_profile_%d.pdf' % (find_binary(['google-pprof', 'pprof']), binary, profile, i)) + if config['profile'] == 'perf': + print 'analyzing CPU profile [%s]' % binary + os.system('perf timechart --input=session_stats/perf_profile.prof --output=session_stats/profile_timechart.svg') + os.system('perf report --input=session_stats/perf_profile.prof --threads --show-nr-samples --vmlinux vmlinuz-2.6.38-8-generic.bzip >session_stats/profile.txt') + # move the results into its final place print 'saving results' os.rename('session_stats', build_target_folder(config)) - # clean up - # don't clean up unless we ran a seed-test, so that we leave the test file - # complete for the seed test. i.e. we don't clean up if we ran a download test -# if config['test'] == 'download': -# print 'cleaning up' -# delete_files([os.path.join(config['save-path'], 'stress_test_file'), '.ses_state', os.path.join(config['save-path'], '.resume'), '.dht_state']) - port += 1 -#config = build_test_config('ext4', filesystem_peers, filesystem_cache, True, True, False) -#run_test(config) -#sys.exit(0) + if terminate: sys.exit(1) -for fs in filesystem: -# for preallocate in [True, False]: - rdahead = True - reorder = True - preallocate = False - for b in builds: - for test in ['upload', 'download']: - config = build_test_config(fs, filesystem_peers, filesystem_cache, rdahead, reorder, preallocate, test=test, build=b) - run_test(config) +for h in range(0, 7): + config = build_test_config(num_peers=30, build='aio', test='upload', torrent='test.torrent', hash_threads=h, disable_disk=True) + run_test(config) +sys.exit(0) + +for b in ['aio', 'syncio']: + for test in ['dual', 'upload', 'download']: + config = build_test_config(build=b, test=test) + run_test(config) +sys.exit(0) + +for b in builds: + for test in ['upload', 'download']: + config = build_test_config(build=b, test=test) + run_test(config) + +for p in peers: + for test in ['upload', 'download']: + config = build_test_config(num_peers=p, test=test) + run_test(config) for c in cache_sizes: - for p in peers: -# for rdahead in [True, False]: - rdahead = False -# for reorder in [True, False]: - reorder = True -# for preallocate in [True, False]: - preallocate = False - for b in builds: - for test in ['upload', 'download']: - config = build_test_config(filesystem[0], p, c, rdahead, reorder, preallocate, test=test, build=b) - run_test(config) + for test in ['upload', 'download']: + config = build_test_config(cache_size=c, test=test) + run_test(config) + +for fs in filesystem: + for test in ['upload', 'download']: + config = build_test_config(fs=fs, test=test) + run_test(config) diff --git a/examples/simple_client.cpp b/examples/simple_client.cpp index d430d0240..d6ef181dc 100644 --- a/examples/simple_client.cpp +++ b/examples/simple_client.cpp @@ -31,6 +31,7 @@ POSSIBILITY OF SUCH DAMAGE. */ #include +#include #include "libtorrent/entry.hpp" #include "libtorrent/bencode.hpp" #include "libtorrent/session.hpp" @@ -38,6 +39,7 @@ POSSIBILITY OF SUCH DAMAGE. int main(int argc, char* argv[]) { using namespace libtorrent; + namespace lt = libtorrent; if (argc != 2) { @@ -46,9 +48,10 @@ int main(int argc, char* argv[]) return 1; } - session s; + settings_pack sett; + sett.set_str(settings_pack::listen_interfaces, "0.0.0.0:6881"); + lt::session s(sett); error_code ec; - s.listen_on(std::make_pair(6881, 6889), ec); if (ec) { fprintf(stderr, "failed to open listen socket: %s\n", ec.message().c_str()); @@ -56,7 +59,7 @@ int main(int argc, char* argv[]) } add_torrent_params p; p.save_path = "./"; - p.ti = new torrent_info(argv[1], ec); + p.ti = boost::make_shared(std::string(argv[1]), boost::ref(ec), 0); if (ec) { fprintf(stderr, "%s\n", ec.message().c_str()); diff --git a/examples/stats_counters.cpp b/examples/stats_counters.cpp new file mode 100644 index 000000000..d42941ccf --- /dev/null +++ b/examples/stats_counters.cpp @@ -0,0 +1,49 @@ +/* + +Copyright (c) 2008, 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. + +*/ + +#include +#include + +using namespace libtorrent; + +int main() +{ + std::vector m = session_stats_metrics(); + for (int i = 0; i < int(m.size()); ++i) + { + printf("%s: %s (%d)\n" + , m[i].type == stats_metric::type_counter ? "CNTR" : "GAUG" + , m[i].name, m[i].value_index); + } + return 0; +} + diff --git a/examples/upnp_test.cpp b/examples/upnp_test.cpp index bc7843b44..5da3bfb4c 100644 --- a/examples/upnp_test.cpp +++ b/examples/upnp_test.cpp @@ -63,6 +63,7 @@ void print_alert(libtorrent::alert const* a) int main(int argc, char* argv[]) { using namespace libtorrent; + namespace lt = libtorrent; if (argc != 1) { @@ -70,16 +71,19 @@ int main(int argc, char* argv[]) return 1; } - session s; - s.set_alert_mask(alert::port_mapping_notification); + settings_pack p; + p.set_int(settings_pack::alert_mask, alert::port_mapping_notification); + lt::session s(p); for (;;) { alert const* a = s.wait_for_alert(seconds(5)); if (a == 0) { - s.stop_upnp(); - s.stop_natpmp(); + settings_pack p; + p.set_bool(settings_pack::enable_upnp, false); + p.set_bool(settings_pack::enable_natpmp, false); + s.apply_settings(p); break; } std::auto_ptr holder = s.pop_alert(); diff --git a/gather_todo.py b/gather_todo.py new file mode 100644 index 000000000..4129cbbb9 --- /dev/null +++ b/gather_todo.py @@ -0,0 +1,53 @@ +import glob +import os + +paths = ['src/*.cpp', 'src/kademlia/*.cpp', 'include/libtorrent/*.hpp', 'include/libtorrent/kademlia/*.hpp', 'include/libtorrent/aux_/*.hpp', 'include/libtorrent/extensions/*.hpp'] + +os.system('ctags %s' % ' '.join(paths)) + +files = [] + +for p in paths: + files.extend(glob.glob(p)) + +output = open('todo.rst', 'w+') + +for f in files: + h = open(f) + + state = '' + line_no = 0 + + for line in h: + line_no += 1 + line = line.strip() + if 'TODO:' in line and line.startswith('//'): + line = line.split('TODO:')[1] + state = 'todo' + headline = '%s:%d' % (f.replace('_', '\\_'), line_no) + print >>output, '\n' + headline + print >>output, ('-' * len(headline)) + '\n' + print >>output, line.strip() + continue + + if state == '': continue + + if state == 'todo': + if line.strip().startswith('//'): + print >>output, line[2:].strip() + else: + state = 'context' + print >>output, '\n::\n' + print >>output, '\t%s' % line + continue + + if state == 'context': + print >>output, '\t%s' % line + state = '' + + h.close() + +output.close() + +os.system('rst2html-2.6.py todo.rst >todo.html') + diff --git a/include/libtorrent/Makefile.am b/include/libtorrent/Makefile.am index 1dd7f91cd..6750f5897 100644 --- a/include/libtorrent/Makefile.am +++ b/include/libtorrent/Makefile.am @@ -9,33 +9,44 @@ nobase_include_HEADERS = \ add_torrent_params.hpp \ alert.hpp \ alert_manager.hpp \ + alert_observer.hpp \ alert_dispatcher.hpp \ alert_types.hpp \ alloca.hpp \ allocator.hpp \ assert.hpp \ + atomic.hpp \ bandwidth_limit.hpp \ bandwidth_manager.hpp \ bandwidth_socket.hpp \ bandwidth_queue_entry.hpp \ bencode.hpp \ bitfield.hpp \ + block_cache.hpp \ bloom_filter.hpp \ broadcast_socket.hpp \ bt_peer_connection.hpp \ buffer.hpp \ build_config.hpp \ + byteswap.hpp \ chained_buffer.hpp \ config.hpp \ + connection_interface.hpp \ connection_queue.hpp \ ConvertUTF.h \ copy_ptr.hpp \ + cpuid.hpp \ + crc32c.hpp \ create_torrent.hpp \ deadline_timer.hpp \ debug.hpp \ disk_buffer_holder.hpp \ disk_buffer_pool.hpp \ + disk_interface.hpp \ + disk_io_job.hpp \ disk_io_thread.hpp \ + disk_observer.hpp \ + disk_job_pool.hpp \ entry.hpp \ enum_net.hpp \ error.hpp \ @@ -64,30 +75,43 @@ nobase_include_HEADERS = \ ip_filter.hpp \ ip_voter.hpp \ lazy_entry.hpp \ + link.hpp \ + linked_list.hpp \ lsd.hpp \ magnet_uri.hpp \ max.hpp \ natpmp.hpp \ + network_thread_pool.hpp \ packet_buffer.hpp \ parse_url.hpp \ + part_file.hpp \ pe_crypto.hpp \ + performance_counters.hpp \ peer_connection.hpp \ + peer_connection_interface.hpp \ peer.hpp \ + peer_class.hpp \ + peer_class_set.hpp \ + peer_class_type_filter.hpp \ peer_id.hpp \ peer_info.hpp \ peer_request.hpp \ piece_block_progress.hpp \ piece_picker.hpp \ + platform_util.hpp \ policy.hpp \ proxy_base.hpp \ ptime.hpp \ puff.hpp \ random.hpp \ + resolver.hpp \ + resolver_interface.hpp \ rss.hpp \ session.hpp \ session_settings.hpp \ session_status.hpp \ settings.hpp \ + settings_pack.hpp \ sha1_hash.hpp \ size_type.hpp \ sliding_average.hpp \ @@ -98,23 +122,30 @@ nobase_include_HEADERS = \ socks5_stream.hpp \ ssl_stream.hpp \ stat.hpp \ + stat_cache.hpp \ storage.hpp \ storage_defs.hpp \ + tailqueue.hpp \ string_util.hpp \ thread.hpp \ + thread_pool.hpp \ time.hpp \ timestamp_history.hpp \ torrent_handle.hpp \ torrent.hpp \ torrent_info.hpp \ + torrent_peer.hpp \ + torrent_peer_allocator.hpp \ tracker_manager.hpp \ udp_socket.hpp \ udp_tracker_connection.hpp \ + uncork_interface.hpp \ union_endpoint.hpp \ upnp.hpp \ utp_socket_manager.hpp \ utp_stream.hpp \ utf8.hpp \ + vector_utils.hpp \ version.hpp \ web_connection_base.hpp \ web_peer_connection.hpp \ @@ -126,6 +157,7 @@ nobase_include_HEADERS = \ tommath_superclass.h \ \ aux_/session_impl.hpp \ + aux_/session_settings.hpp\ \ extensions/logger.hpp \ extensions/lt_trackers.hpp \ @@ -136,6 +168,7 @@ nobase_include_HEADERS = \ \ kademlia/dht_tracker.hpp \ kademlia/dht_observer.hpp \ + kademlia/dos_blocker.hpp \ kademlia/find_data.hpp \ kademlia/logging.hpp \ kademlia/msg.hpp \ diff --git a/include/libtorrent/add_torrent_params.hpp b/include/libtorrent/add_torrent_params.hpp index 1d3a9027d..f435a894b 100644 --- a/include/libtorrent/add_torrent_params.hpp +++ b/include/libtorrent/add_torrent_params.hpp @@ -35,7 +35,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include -#include +#include #include "libtorrent/storage_defs.hpp" #include "libtorrent/peer_id.hpp" // sha1_hash @@ -249,20 +249,26 @@ namespace libtorrent // case the save_path specified in add_torrent_params is always used. flag_use_resume_save_path = 0x1000, + // indicates that this torrent should never be unloaded from RAM, even + // if unloading torrents are allowed in general. Setting this makes + // the torrent exempt from loading/unloading management. + flag_pinned = 0x2000, + // internal - default_flags = flag_update_subscribe | flag_auto_managed | flag_paused | flag_apply_ip_filter + default_flags = flag_pinned | flag_update_subscribe + | flag_auto_managed | flag_paused | flag_apply_ip_filter + #ifndef TORRENT_NO_DEPRECATE , flag_ignore_flags = 0x80000000 #endif }; - // filled in by the constructor and should be left untouched. It - // is used for forward binary compatibility. + // filled in by the constructor and should be left untouched. It is used + // for forward binary compatibility. int version; - // torrent_info object with the torrent to add. Unless the url or // info_hash is set, this is required to be initiazlied. - boost::intrusive_ptr ti; + boost::shared_ptr ti; #ifndef TORRENT_NO_DEPRECATE char const* tracker_url; @@ -350,8 +356,8 @@ namespace libtorrent // items which has UUIDs specified. std::string uuid; - // should point to the URL of the RSS feed this torrent comes from, - // if it comes from an RSS feed. + // should point to the URL of the RSS feed this torrent comes from, if it + // comes from an RSS feed. std::string source_feed_url; // flags controlling aspects of this torrent and how it's added. See diff --git a/include/libtorrent/alert.hpp b/include/libtorrent/alert.hpp index 34bbaecc4..3592fb821 100644 --- a/include/libtorrent/alert.hpp +++ b/include/libtorrent/alert.hpp @@ -36,6 +36,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #ifdef _MSC_VER #pragma warning(push, 1) @@ -71,7 +72,7 @@ POSSIBILITY OF SUCH DAMAGE. #pragma warning(pop) #endif -#include "libtorrent/ptime.hpp" +#include "libtorrent/time.hpp" #include "libtorrent/config.hpp" #ifndef TORRENT_NO_DEPRECATE @@ -84,6 +85,12 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_MAX_ALERT_TYPES 15 #endif +#ifndef TORRENT_NO_DEPRECATE +#ifndef BOOST_NO_TYPEID +#include +#endif +#endif + namespace libtorrent { // The ``alert`` class is the base class that specific messages are derived from. diff --git a/include/libtorrent/alert_dispatcher.hpp b/include/libtorrent/alert_dispatcher.hpp index 62b732173..52c4e0db6 100644 --- a/include/libtorrent/alert_dispatcher.hpp +++ b/include/libtorrent/alert_dispatcher.hpp @@ -37,7 +37,7 @@ namespace libtorrent { class alert; - struct alert_dispatcher + struct TORRENT_EXPORT alert_dispatcher { // return true if the alert was swallowed (i.e. // ownership was taken over). In this case, the diff --git a/include/libtorrent/alert_observer.hpp b/include/libtorrent/alert_observer.hpp new file mode 100644 index 000000000..2da86da6e --- /dev/null +++ b/include/libtorrent/alert_observer.hpp @@ -0,0 +1,58 @@ +/* + +Copyright (c) 2012-2013, 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_ALERT_OBSERVER_HPP_INCLUDED +#define TORRENT_ALERT_OBSERVER_HPP_INCLUDED + +#include + +namespace libtorrent +{ + +class alert; + +struct alert_observer +{ + friend struct alert_handler; + + alert_observer(): num_types(0), flags(0) {} + virtual void handle_alert(alert const* a) = 0; +private: + boost::uint8_t types[64]; + int num_types; + int flags; +}; + +}; + +#endif // TORRENT_ALERT_OBSERVER_HPP_INCLUDED + diff --git a/include/libtorrent/alert_types.hpp b/include/libtorrent/alert_types.hpp index 5262d349a..d808f4613 100644 --- a/include/libtorrent/alert_types.hpp +++ b/include/libtorrent/alert_types.hpp @@ -43,17 +43,13 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/stat.hpp" #include "libtorrent/rss.hpp" // for feed_handle -// lines reserved for future includes -// the type-ids of the alert types -// are derived from the line on which -// they are declared - - - - namespace libtorrent { + // maps an operation id (from peer_error_alert and peer_disconnected_alert) + // to its name. See peer_connection for the constants + TORRENT_EXPORT char const* operation_name(int op); + // user defined alerts should use IDs greater than this const static int user_alert_id = 10000; @@ -62,17 +58,16 @@ namespace libtorrent struct TORRENT_EXPORT torrent_alert: alert { // internal - torrent_alert(torrent_handle const& h) - : handle(h) - {} + torrent_alert(torrent_handle const& h); // internal - const static int alert_type = 1; + const static int alert_type = 0; virtual std::string message() const; // The torrent_handle pointing to the torrent this // alert is associated with. torrent_handle handle; + std::string name; }; // The peer alert is a base class for alerts that refer to a specific peer. It includes all @@ -87,7 +82,7 @@ namespace libtorrent , pid(pi) {} - const static int alert_type = 2; + const static int alert_type = 1; const static int static_category = alert::peer_notification; virtual int category() const { return static_category; } virtual std::string message() const; @@ -111,7 +106,7 @@ namespace libtorrent , url(u) {} - const static int alert_type = 3; + const static int alert_type = 2; const static int static_category = alert::tracker_notification; virtual int category() const { return static_category; } virtual std::string message() const; @@ -120,8 +115,8 @@ namespace libtorrent std::string url; }; -#define TORRENT_DEFINE_ALERT(name) \ - const static int alert_type = __LINE__; \ +#define TORRENT_DEFINE_ALERT(name, seq) \ + const static int alert_type = seq; \ virtual int type() const { return alert_type; } \ virtual std::auto_ptr clone() const \ { return std::auto_ptr(new name(*this)); } \ @@ -139,7 +134,7 @@ namespace libtorrent : torrent_alert(h) {} - TORRENT_DEFINE_ALERT(torrent_added_alert); + TORRENT_DEFINE_ALERT(torrent_added_alert, 3); const static int static_category = alert::status_notification; virtual std::string message() const; }; @@ -164,9 +159,10 @@ namespace libtorrent , info_hash(ih) {} - TORRENT_DEFINE_ALERT(torrent_removed_alert); + TORRENT_DEFINE_ALERT(torrent_removed_alert, 4); const static int static_category = alert::status_notification; virtual std::string message() const; + virtual bool discardable() const { return false; } sha1_hash info_hash; }; @@ -195,7 +191,7 @@ namespace libtorrent , size(0) {} - TORRENT_DEFINE_ALERT(read_piece_alert); + TORRENT_DEFINE_ALERT(read_piece_alert, 5); const static int static_category = alert::storage_notification; virtual std::string message() const; @@ -218,7 +214,7 @@ namespace libtorrent , index(idx) {} - TORRENT_DEFINE_ALERT(file_completed_alert); + TORRENT_DEFINE_ALERT(file_completed_alert, 6); const static int static_category = alert::progress_notification; virtual std::string message() const; @@ -240,7 +236,7 @@ namespace libtorrent , index(idx) {} - TORRENT_DEFINE_ALERT(file_renamed_alert); + TORRENT_DEFINE_ALERT(file_renamed_alert, 7); const static int static_category = alert::storage_notification; virtual std::string message() const; @@ -266,7 +262,7 @@ namespace libtorrent , error(ec) {} - TORRENT_DEFINE_ALERT(file_rename_failed_alert); + TORRENT_DEFINE_ALERT(file_rename_failed_alert, 8); const static int static_category = alert::storage_notification; @@ -287,7 +283,7 @@ namespace libtorrent { // This warning means that the number of bytes queued to be written to disk - // exceeds the max disk byte queue setting (``session_settings::max_queued_disk_bytes``). + // exceeds the max disk byte queue setting (``settings_pack::max_queued_disk_bytes``). // This might restrict the download rate, by not queuing up enough write jobs // to the disk I/O thread. When this alert is posted, peer connections are // temporarily stopped from downloading, until the queued disk bytes have fallen @@ -296,9 +292,9 @@ namespace libtorrent outstanding_disk_buffer_limit_reached, // This is posted when libtorrent would like to send more requests to a peer, - // but it's limited by ``session_settings::max_out_request_queue``. The queue length + // but it's limited by ``settings_pack::max_out_request_queue``. The queue length // libtorrent is trying to achieve is determined by the download rate and the - // assumed round-trip-time (``session_settings::request_queue_time``). The assumed + // assumed round-trip-time (``settings_pack::request_queue_time``). The assumed // rount-trip-time is not limited to just the network RTT, but also the remote disk // access time and message handling time. It defaults to 3 seconds. The target number // of outstanding requests is set to fill the bandwidth-delay product (assumed RTT @@ -350,10 +346,11 @@ namespace libtorrent // queue. Either lower ``max_queued_disk_bytes`` or increase ``cache_size``. too_high_disk_queue_limit, + aio_limit_reached, bittyrant_with_no_uplimit, // This is generated if outgoing peer connections are failing because of *address in use* - // errors, indicating that ``session_settings::outgoing_ports`` is set and is too small of + // errors, indicating that ``settings_pack::outgoing_ports`` is set and is too small of // a range. Consider not using the ``outgoing_ports`` setting at all, or widen the range to // include more ports. too_few_outgoing_ports, @@ -370,7 +367,7 @@ namespace libtorrent , warning_code(w) {} - TORRENT_DEFINE_ALERT(performance_alert); + TORRENT_DEFINE_ALERT(performance_alert, 9); const static int static_category = alert::performance_warning; @@ -391,7 +388,7 @@ namespace libtorrent , prev_state(prev_st) {} - TORRENT_DEFINE_ALERT(state_changed_alert); + TORRENT_DEFINE_ALERT(state_changed_alert, 10); const static int static_category = alert::status_notification; @@ -430,7 +427,7 @@ namespace libtorrent TORRENT_ASSERT(!url.empty()); } - TORRENT_DEFINE_ALERT(tracker_error_alert); + TORRENT_DEFINE_ALERT(tracker_error_alert, 11); const static int static_category = alert::tracker_notification | alert::error_notification; virtual std::string message() const; @@ -454,7 +451,7 @@ namespace libtorrent , msg(m) { TORRENT_ASSERT(!url.empty()); } - TORRENT_DEFINE_ALERT(tracker_warning_alert); + TORRENT_DEFINE_ALERT(tracker_warning_alert, 12); const static int static_category = alert::tracker_notification | alert::error_notification; virtual std::string message() const; @@ -476,7 +473,7 @@ namespace libtorrent , complete(comp) { TORRENT_ASSERT(!url.empty()); } - TORRENT_DEFINE_ALERT(scrape_reply_alert); + TORRENT_DEFINE_ALERT(scrape_reply_alert, 13); virtual std::string message() const; @@ -506,7 +503,7 @@ namespace libtorrent , msg(m) { TORRENT_ASSERT(!url.empty()); } - TORRENT_DEFINE_ALERT(scrape_failed_alert); + TORRENT_DEFINE_ALERT(scrape_failed_alert, 14); const static int static_category = alert::tracker_notification | alert::error_notification; virtual std::string message() const; @@ -528,7 +525,7 @@ namespace libtorrent , num_peers(np) { TORRENT_ASSERT(!url.empty()); } - TORRENT_DEFINE_ALERT(tracker_reply_alert); + TORRENT_DEFINE_ALERT(tracker_reply_alert, 15); virtual std::string message() const; @@ -551,7 +548,7 @@ namespace libtorrent , num_peers(np) {} - TORRENT_DEFINE_ALERT(dht_reply_alert); + TORRENT_DEFINE_ALERT(dht_reply_alert, 16); virtual std::string message() const; @@ -570,7 +567,7 @@ namespace libtorrent , event(e) { TORRENT_ASSERT(!url.empty()); } - TORRENT_DEFINE_ALERT(tracker_announce_alert); + TORRENT_DEFINE_ALERT(tracker_announce_alert, 17); virtual std::string message() const; @@ -595,7 +592,7 @@ namespace libtorrent , piece_index(index) { TORRENT_ASSERT(index >= 0);} - TORRENT_DEFINE_ALERT(hash_failed_alert); + TORRENT_DEFINE_ALERT(hash_failed_alert, 18); const static int static_category = alert::status_notification; virtual std::string message() const; @@ -613,7 +610,7 @@ namespace libtorrent : peer_alert(h, ep, peer_id) {} - TORRENT_DEFINE_ALERT(peer_ban_alert); + TORRENT_DEFINE_ALERT(peer_ban_alert, 19); virtual std::string message() const; }; @@ -628,7 +625,7 @@ namespace libtorrent : peer_alert(h, ep, peer_id) {} - TORRENT_DEFINE_ALERT(peer_unsnubbed_alert); + TORRENT_DEFINE_ALERT(peer_unsnubbed_alert, 20); virtual std::string message() const; }; @@ -643,7 +640,7 @@ namespace libtorrent : peer_alert(h, ep, peer_id) {} - TORRENT_DEFINE_ALERT(peer_snubbed_alert); + TORRENT_DEFINE_ALERT(peer_snubbed_alert, 21); virtual std::string message() const; }; @@ -654,8 +651,9 @@ namespace libtorrent { // internal peer_error_alert(torrent_handle const& h, tcp::endpoint const& ep - , peer_id const& peer_id, error_code const& e) + , peer_id const& peer_id, int op, error_code const& e) : peer_alert(h, ep, peer_id) + , operation(op) , error(e) { #ifndef TORRENT_NO_DEPRECATE @@ -663,13 +661,14 @@ namespace libtorrent #endif } - TORRENT_DEFINE_ALERT(peer_error_alert); + TORRENT_DEFINE_ALERT(peer_error_alert, 22); const static int static_category = alert::peer_notification; - virtual std::string message() const - { - return peer_alert::message() + " peer error: " + convert_from_native(error.message()); - } + virtual std::string message() const; + + // a NULL-terminated string of the low-level operation that failed, or NULL if + // there was no low level disk operation. + int operation; // tells you what error caused this alert. error_code error; @@ -689,7 +688,7 @@ namespace libtorrent , socket_type(type) {} - TORRENT_DEFINE_ALERT(peer_connect_alert); + TORRENT_DEFINE_ALERT(peer_connect_alert, 23); const static int static_category = alert::debug_notification; virtual std::string message() const; @@ -703,8 +702,9 @@ namespace libtorrent { // internal peer_disconnected_alert(torrent_handle const& h, tcp::endpoint const& ep - , peer_id const& peer_id, error_code const& e) + , peer_id const& peer_id, int op, error_code const& e) : peer_alert(h, ep, peer_id) + , operation(op) , error(e) { #ifndef TORRENT_NO_DEPRECATE @@ -712,11 +712,15 @@ namespace libtorrent #endif } - TORRENT_DEFINE_ALERT(peer_disconnected_alert); + TORRENT_DEFINE_ALERT(peer_disconnected_alert, 24); const static int static_category = alert::debug_notification; virtual std::string message() const; + // a NULL-terminated string of the low-level operation that failed, or NULL if + // there was no low level disk operation. + int operation; + // tells you what error caused peer to disconnect. error_code error; @@ -737,7 +741,7 @@ namespace libtorrent , request(r) {} - TORRENT_DEFINE_ALERT(invalid_request_alert); + TORRENT_DEFINE_ALERT(invalid_request_alert, 25); virtual std::string message() const; @@ -755,7 +759,7 @@ namespace libtorrent : torrent_alert(h) {} - TORRENT_DEFINE_ALERT(torrent_finished_alert); + TORRENT_DEFINE_ALERT(torrent_finished_alert, 26); const static int static_category = alert::status_notification; virtual std::string message() const @@ -775,7 +779,7 @@ namespace libtorrent , piece_index(piece_num) { TORRENT_ASSERT(piece_index >= 0);} - TORRENT_DEFINE_ALERT(piece_finished_alert); + TORRENT_DEFINE_ALERT(piece_finished_alert, 27); const static int static_category = alert::progress_notification; virtual std::string message() const; @@ -795,7 +799,7 @@ namespace libtorrent , piece_index(piece_num) { TORRENT_ASSERT(block_index >= 0 && piece_index >= 0);} - TORRENT_DEFINE_ALERT(request_dropped_alert); + TORRENT_DEFINE_ALERT(request_dropped_alert, 28); const static int static_category = alert::progress_notification | alert::peer_notification; @@ -816,7 +820,7 @@ namespace libtorrent , piece_index(piece_num) { TORRENT_ASSERT(block_index >= 0 && piece_index >= 0);} - TORRENT_DEFINE_ALERT(block_timeout_alert); + TORRENT_DEFINE_ALERT(block_timeout_alert, 29); const static int static_category = alert::progress_notification | alert::peer_notification; @@ -837,7 +841,7 @@ namespace libtorrent , piece_index(piece_num) { TORRENT_ASSERT(block_index >= 0 && piece_index >= 0);} - TORRENT_DEFINE_ALERT(block_finished_alert); + TORRENT_DEFINE_ALERT(block_finished_alert, 30); const static int static_category = alert::progress_notification; virtual std::string message() const; @@ -858,7 +862,7 @@ namespace libtorrent , piece_index(piece_num) { TORRENT_ASSERT(block_index >= 0 && piece_index >= 0); } - TORRENT_DEFINE_ALERT(block_downloading_alert); + TORRENT_DEFINE_ALERT(block_downloading_alert, 31); const static int static_category = alert::progress_notification; virtual std::string message() const; @@ -880,7 +884,7 @@ namespace libtorrent , piece_index(piece_num) { TORRENT_ASSERT(block_index >= 0 && piece_index >= 0);} - TORRENT_DEFINE_ALERT(unwanted_block_alert); + TORRENT_DEFINE_ALERT(unwanted_block_alert, 32); virtual std::string message() const; @@ -900,7 +904,7 @@ namespace libtorrent , path(p) {} - TORRENT_DEFINE_ALERT(storage_moved_alert); + TORRENT_DEFINE_ALERT(storage_moved_alert, 33); const static int static_category = alert::storage_notification; virtual std::string message() const @@ -917,21 +921,35 @@ namespace libtorrent struct TORRENT_EXPORT storage_moved_failed_alert: torrent_alert { // internal - storage_moved_failed_alert(torrent_handle const& h, error_code const& e) + storage_moved_failed_alert(torrent_handle const& h + , error_code const& e + , std::string const& file + , char const* op) : torrent_alert(h) , error(e) + , file(file) + , operation(op) {} - TORRENT_DEFINE_ALERT(storage_moved_failed_alert); + TORRENT_DEFINE_ALERT(storage_moved_failed_alert, 34); const static int static_category = alert::storage_notification; virtual std::string message() const { - return torrent_alert::message() + " storage move failed: " + return torrent_alert::message() + " storage move failed. " + + (operation?operation:"") + " (" + file + "): " + convert_from_native(error.message()); } error_code error; + + // If the error happened for a speific file, ``file`` is its path. If the error + // happened in a specific disk operation. + std::string file; + + // a NULL terminated string + // naming which one, otherwise it's NULL. + char const* operation; }; // This alert is generated when a request to delete the files of a torrent complete. @@ -950,7 +968,7 @@ namespace libtorrent : torrent_alert(h) { info_hash = ih; } - TORRENT_DEFINE_ALERT(torrent_deleted_alert); + TORRENT_DEFINE_ALERT(torrent_deleted_alert, 35); const static int static_category = alert::storage_notification; virtual std::string message() const @@ -975,7 +993,7 @@ namespace libtorrent #endif } - TORRENT_DEFINE_ALERT(torrent_delete_failed_alert); + TORRENT_DEFINE_ALERT(torrent_delete_failed_alert, 36); const static int static_category = alert::storage_notification | alert::error_notification; @@ -1008,7 +1026,7 @@ namespace libtorrent , resume_data(rd) {} - TORRENT_DEFINE_ALERT(save_resume_data_alert); + TORRENT_DEFINE_ALERT(save_resume_data_alert, 37); const static int static_category = alert::storage_notification; virtual std::string message() const @@ -1034,7 +1052,7 @@ namespace libtorrent #endif } - TORRENT_DEFINE_ALERT(save_resume_data_failed_alert); + TORRENT_DEFINE_ALERT(save_resume_data_failed_alert, 38); const static int static_category = alert::storage_notification | alert::error_notification; @@ -1062,7 +1080,7 @@ namespace libtorrent : torrent_alert(h) {} - TORRENT_DEFINE_ALERT(torrent_paused_alert); + TORRENT_DEFINE_ALERT(torrent_paused_alert, 39); const static int static_category = alert::status_notification; virtual std::string message() const @@ -1077,7 +1095,7 @@ namespace libtorrent torrent_resumed_alert(torrent_handle const& h) : torrent_alert(h) {} - TORRENT_DEFINE_ALERT(torrent_resumed_alert); + TORRENT_DEFINE_ALERT(torrent_resumed_alert, 40); const static int static_category = alert::status_notification; virtual std::string message() const @@ -1093,7 +1111,7 @@ namespace libtorrent : torrent_alert(h) {} - TORRENT_DEFINE_ALERT(torrent_checked_alert); + TORRENT_DEFINE_ALERT(torrent_checked_alert, 41); const static int static_category = alert::status_notification; virtual std::string message() const @@ -1121,7 +1139,7 @@ namespace libtorrent , msg(m) {} - TORRENT_DEFINE_ALERT(url_seed_alert); + TORRENT_DEFINE_ALERT(url_seed_alert, 42); const static int static_category = alert::peer_notification | alert::error_notification; virtual std::string message() const @@ -1143,27 +1161,30 @@ namespace libtorrent { // internal file_error_alert( - std::string const& f - , torrent_handle const& h - , error_code const& e) + error_code const& ec + , std::string const& file + , char const* op + , torrent_handle const& h) : torrent_alert(h) - , file(f) - , error(e) + , file(file) + , error(ec) + , operation(op) { #ifndef TORRENT_NO_DEPRECATE msg = convert_from_native(error.message()); #endif } - TORRENT_DEFINE_ALERT(file_error_alert); + TORRENT_DEFINE_ALERT(file_error_alert, 43); const static int static_category = alert::status_notification | alert::error_notification | alert::storage_notification; virtual std::string message() const { - return torrent_alert::message() + " file (" + file + ") error: " - + convert_from_native(error.message()); + return torrent_alert::message() + " " + + (operation?operation:"") + " (" + file + + ") error: " + convert_from_native(error.message()); } // the path to the file that was accessed when the error occurred. @@ -1171,6 +1192,7 @@ namespace libtorrent // the error code describing the error. error_code error; + char const* operation; #ifndef TORRENT_NO_DEPRECATE std::string msg; @@ -1184,18 +1206,19 @@ namespace libtorrent struct TORRENT_EXPORT metadata_failed_alert: torrent_alert { // internal - metadata_failed_alert(const torrent_handle& h, error_code e) + metadata_failed_alert(const torrent_handle& h, error_code const& ec) : torrent_alert(h) - , error(e) + , error(ec) {} - TORRENT_DEFINE_ALERT(metadata_failed_alert); + TORRENT_DEFINE_ALERT(metadata_failed_alert, 44); const static int static_category = alert::error_notification; virtual std::string message() const - { return torrent_alert::message() + " invalid metadata received"; } + { return torrent_alert::message() + " invalid metadata received: " + error.message(); } - // the error that occurred + // indicates what failed when parsing the metadata. This error is + // what's returned from lazy_bdecode(). error_code error; }; @@ -1211,7 +1234,7 @@ namespace libtorrent // // torrent_handle h = alert->handle(); // if (h.is_valid()) { - // boost::intrusive_ptr ti = h.torrent_file(); + // boost::shared_ptr ti = h.torrent_file(); // create_torrent ct(*ti); // entry te = ct.generate(); // std::vector buffer; @@ -1231,7 +1254,7 @@ namespace libtorrent : torrent_alert(h) {} - TORRENT_DEFINE_ALERT(metadata_received_alert); + TORRENT_DEFINE_ALERT(metadata_received_alert, 45); const static int static_category = alert::status_notification; virtual std::string message() const @@ -1251,7 +1274,7 @@ namespace libtorrent , error(ec) {} - TORRENT_DEFINE_ALERT(udp_error_alert); + TORRENT_DEFINE_ALERT(udp_error_alert, 46); const static int static_category = alert::error_notification; virtual std::string message() const @@ -1278,7 +1301,7 @@ namespace libtorrent : external_address(ip) {} - TORRENT_DEFINE_ALERT(external_ip_alert); + TORRENT_DEFINE_ALERT(external_ip_alert, 47); const static int static_category = alert::status_notification; virtual std::string message() const @@ -1306,24 +1329,24 @@ namespace libtorrent // internal listen_failed_alert( - tcp::endpoint const& ep + std::string iface , int op , error_code const& ec , socket_type_t t) - : endpoint(ep) + : interface(iface) , error(ec) , operation(op) , sock_type(t) {} - TORRENT_DEFINE_ALERT(listen_failed_alert); + TORRENT_DEFINE_ALERT(listen_failed_alert, 48); const static int static_category = alert::status_notification | alert::error_notification; virtual std::string message() const; virtual bool discardable() const { return false; } - // the endpoint libtorrent attempted to listen on - tcp::endpoint endpoint; + // the interface libtorrent attempted to listen on + std::string interface; // the error the system returned error_code error; @@ -1353,7 +1376,7 @@ namespace libtorrent , sock_type(t) {} - TORRENT_DEFINE_ALERT(listen_succeeded_alert); + TORRENT_DEFINE_ALERT(listen_succeeded_alert, 49); const static int static_category = alert::status_notification; virtual std::string message() const; @@ -1384,7 +1407,7 @@ namespace libtorrent #endif } - TORRENT_DEFINE_ALERT(portmap_error_alert); + TORRENT_DEFINE_ALERT(portmap_error_alert, 50); const static int static_category = alert::port_mapping_notification | alert::error_notification; @@ -1415,7 +1438,7 @@ namespace libtorrent : mapping(i), external_port(port), map_type(t) {} - TORRENT_DEFINE_ALERT(portmap_alert); + TORRENT_DEFINE_ALERT(portmap_alert, 51); const static int static_category = alert::port_mapping_notification; virtual std::string message() const; @@ -1442,7 +1465,7 @@ namespace libtorrent : map_type(t), msg(m) {} - TORRENT_DEFINE_ALERT(portmap_log_alert); + TORRENT_DEFINE_ALERT(portmap_log_alert, 52); const static int static_category = alert::port_mapping_notification; virtual std::string message() const; @@ -1458,24 +1481,39 @@ namespace libtorrent { // internal fastresume_rejected_alert(torrent_handle const& h - , error_code const& e) + , error_code const& ec + , std::string const& file + , char const* op) : torrent_alert(h) - , error(e) + , error(ec) + , file(file) + , operation(op) { #ifndef TORRENT_NO_DEPRECATE msg = convert_from_native(error.message()); #endif } - TORRENT_DEFINE_ALERT(fastresume_rejected_alert); + TORRENT_DEFINE_ALERT(fastresume_rejected_alert, 53); const static int static_category = alert::status_notification | alert::error_notification; virtual std::string message() const - { return torrent_alert::message() + " fast resume rejected: " + convert_from_native(error.message()); } + { + return torrent_alert::message() + " fast resume rejected. " + + (operation?operation:"") + "(" + file + "): " + convert_from_native(error.message()); + } error_code error; + // If the error happend to a specific file, ``file`` is the path to it. If the error happened + // in a disk operation. + std::string file; + + // a NULL-terminated string of the name of that operation. + // ``operation`` is NULL otherwise. + char const* operation; + #ifndef TORRENT_NO_DEPRECATE std::string msg; #endif @@ -1499,7 +1537,7 @@ namespace libtorrent , reason(r) {} - TORRENT_DEFINE_ALERT(peer_blocked_alert); + TORRENT_DEFINE_ALERT(peer_blocked_alert, 54); const static int static_category = alert::ip_block_notification; virtual std::string message() const; @@ -1514,7 +1552,8 @@ namespace libtorrent i2p_mixed, privileged_ports, utp_disabled, - tcp_disabled + tcp_disabled, + invalid_local_interface }; int reason; @@ -1532,7 +1571,7 @@ namespace libtorrent , info_hash(ih) {} - TORRENT_DEFINE_ALERT(dht_announce_alert); + TORRENT_DEFINE_ALERT(dht_announce_alert, 55); const static int static_category = alert::dht_notification; virtual std::string message() const; @@ -1551,7 +1590,7 @@ namespace libtorrent : info_hash(ih) {} - TORRENT_DEFINE_ALERT(dht_get_peers_alert); + TORRENT_DEFINE_ALERT(dht_get_peers_alert, 56); const static int static_category = alert::dht_notification; virtual std::string message() const; @@ -1568,7 +1607,7 @@ namespace libtorrent stats_alert(torrent_handle const& h, int interval , stat const& s); - TORRENT_DEFINE_ALERT(stats_alert); + TORRENT_DEFINE_ALERT(stats_alert, 57); const static int static_category = alert::stats_notification; virtual std::string message() const; @@ -1579,14 +1618,12 @@ namespace libtorrent upload_protocol, download_payload, download_protocol, -#ifndef TORRENT_DISABLE_FULL_STATS upload_ip_protocol, upload_dht_protocol, upload_tracker_protocol, download_ip_protocol, download_dht_protocol, download_tracker_protocol, -#endif num_channels }; @@ -1611,7 +1648,7 @@ namespace libtorrent // internal cache_flushed_alert(torrent_handle const& h); - TORRENT_DEFINE_ALERT(cache_flushed_alert); + TORRENT_DEFINE_ALERT(cache_flushed_alert, 58); const static int static_category = alert::storage_notification; }; @@ -1630,7 +1667,7 @@ namespace libtorrent , str(s) {} - TORRENT_DEFINE_ALERT(anonymous_mode_alert); + TORRENT_DEFINE_ALERT(anonymous_mode_alert, 59); const static int static_category = alert::error_notification; virtual std::string message() const; @@ -1658,7 +1695,7 @@ namespace libtorrent : peer_alert(h, i, peer_id(0)) {} - TORRENT_DEFINE_ALERT(lsd_peer_alert); + TORRENT_DEFINE_ALERT(lsd_peer_alert, 60); const static int static_category = alert::peer_notification; virtual std::string message() const; @@ -1677,7 +1714,7 @@ namespace libtorrent , trackerid(id) {} - TORRENT_DEFINE_ALERT(trackerid_alert); + TORRENT_DEFINE_ALERT(trackerid_alert, 61); const static int static_category = alert::status_notification; virtual std::string message() const; @@ -1692,7 +1729,7 @@ namespace libtorrent // internal dht_bootstrap_alert() {} - TORRENT_DEFINE_ALERT(dht_bootstrap_alert); + TORRENT_DEFINE_ALERT(dht_bootstrap_alert, 62); const static int static_category = alert::dht_notification; virtual std::string message() const; @@ -1710,7 +1747,7 @@ namespace libtorrent : handle(h), url(u), state(s), error(ec) {} - TORRENT_DEFINE_ALERT(rss_alert); + TORRENT_DEFINE_ALERT(rss_alert, 63); const static int static_category = alert::rss_notification; virtual std::string message() const; @@ -1750,18 +1787,22 @@ namespace libtorrent { // internal torrent_error_alert(torrent_handle const& h - , error_code const& e) + , error_code const& e, std::string const& f) : torrent_alert(h) , error(e) + , error_file(f) {} - TORRENT_DEFINE_ALERT(torrent_error_alert); + TORRENT_DEFINE_ALERT(torrent_error_alert, 64); const static int static_category = alert::error_notification | alert::status_notification; virtual std::string message() const; // specifies which error the torrent encountered. error_code error; + + // the filename (or object) the error occurred on. + std::string error_file; }; // This is always posted for SSL torrents. This is a reminder to the client that @@ -1775,7 +1816,7 @@ namespace libtorrent : torrent_alert(h) {} - TORRENT_DEFINE_ALERT(torrent_need_cert_alert); + TORRENT_DEFINE_ALERT(torrent_need_cert_alert, 65); const static int static_category = alert::status_notification; virtual std::string message() const; @@ -1798,7 +1839,7 @@ namespace libtorrent , ip(i) {} - TORRENT_DEFINE_ALERT(incoming_connection_alert); + TORRENT_DEFINE_ALERT(incoming_connection_alert, 66); const static int static_category = alert::peer_notification; virtual std::string message() const; @@ -1836,7 +1877,7 @@ namespace libtorrent , error(ec) {} - TORRENT_DEFINE_ALERT(add_torrent_alert); + TORRENT_DEFINE_ALERT(add_torrent_alert, 67); const static int static_category = alert::status_notification; virtual std::string message() const; @@ -1856,7 +1897,7 @@ namespace libtorrent // it's not subject to filtering, since it's only manually posted anyway. struct TORRENT_EXPORT state_update_alert : alert { - TORRENT_DEFINE_ALERT(state_update_alert); + TORRENT_DEFINE_ALERT(state_update_alert, 68); const static int static_category = alert::status_notification; virtual std::string message() const; @@ -1868,6 +1909,49 @@ namespace libtorrent // by the torrent_handle or hashed by it, for efficient updates. std::vector status; }; + + struct TORRENT_EXPORT mmap_cache_alert : alert + { + mmap_cache_alert(error_code const& ec): error(ec) {} + TORRENT_DEFINE_ALERT(mmap_cache_alert, 69); + + const static int static_category = alert::error_notification; + virtual std::string message() const; + + error_code error; + }; + + // The session_stats_alert is posted when the user requests session statistics by + // calling post_session_stats() on the session object. Its category is + // ``status_notification``, but it is not subject to filtering, since it's only + // manually posted anyway. + struct TORRENT_EXPORT session_stats_alert : alert + { + session_stats_alert() {} + TORRENT_DEFINE_ALERT(session_stats_alert, 70); + + const static int static_category = alert::stats_notification; + virtual std::string message() const; + virtual bool discardable() const { return false; } + + // the number of microseconds since the session was + // started. It represent the time when the snapshot of values was taken. When + // the network thread is under heavy load, the latency between calling + // post_session_stats() and receiving this alert may be significant, and + // the timestamp may help provide higher accuracy in measurements. + boost::uint64_t timestamp; + + // An array are a mix of *counters* and *gauges*, which + // meanings can be queries via the session_stats_metrics() function on the session. + // The mapping from a specific metric to an index into this array is constant for a + // specific version of libtorrent, but may differ for other versions. The intended + // usage is to request the mapping, i.e. call session_stats_metrics(), once + // on startup, and then use that mapping to interpret these values throughout + // the process' runtime. + // + // For more information, see the session-statistics_ section. + std::vector values; + }; // When a torrent changes its info-hash, this alert is posted. This only happens in very // specific cases. For instance, when a torrent is downloaded from a URL, the true info @@ -1884,7 +1968,7 @@ namespace libtorrent , new_ih(new_hash) {} - TORRENT_DEFINE_ALERT(torrent_update_alert); + TORRENT_DEFINE_ALERT(torrent_update_alert, 71); const static int static_category = alert::status_notification; virtual std::string message() const; @@ -1908,7 +1992,7 @@ namespace libtorrent , item(item) {} - TORRENT_DEFINE_ALERT(rss_item_alert); + TORRENT_DEFINE_ALERT(rss_item_alert, 72); const static int static_category = alert::rss_notification; virtual std::string message() const; @@ -1925,7 +2009,7 @@ namespace libtorrent dht_error_alert(int op, error_code const& ec) : error(ec), operation(op_t(op)) {} - TORRENT_DEFINE_ALERT(dht_error_alert); + TORRENT_DEFINE_ALERT(dht_error_alert, 73); const static int static_category = alert::error_notification | alert::dht_notification; @@ -1951,7 +2035,7 @@ namespace libtorrent dht_immutable_item_alert(sha1_hash const& t, entry const& i) : target(t), item(i) {} - TORRENT_DEFINE_ALERT(dht_immutable_item_alert); + TORRENT_DEFINE_ALERT(dht_immutable_item_alert, 74); const static int static_category = alert::error_notification | alert::dht_notification; @@ -1977,7 +2061,7 @@ namespace libtorrent , entry const& i) : key(k), signature(sig), seq(sequence), salt(s), item(i) {} - TORRENT_DEFINE_ALERT(dht_mutable_item_alert); + TORRENT_DEFINE_ALERT(dht_mutable_item_alert, 75); const static int static_category = alert::error_notification | alert::dht_notification; @@ -2025,7 +2109,7 @@ namespace libtorrent , seq(sequence_number) {} - TORRENT_DEFINE_ALERT(dht_put_alert); + TORRENT_DEFINE_ALERT(dht_put_alert, 76); const static int static_category = alert::dht_notification; virtual std::string message() const; @@ -2047,7 +2131,7 @@ namespace libtorrent { i2p_alert(error_code const& ec) : error(ec) {} - TORRENT_DEFINE_ALERT(i2p_alert); + TORRENT_DEFINE_ALERT(i2p_alert, 77); const static int static_category = alert::error_notification; virtual std::string message() const; @@ -2058,7 +2142,9 @@ namespace libtorrent #undef TORRENT_DEFINE_ALERT + enum { num_alert_types = 74 }; } #endif + diff --git a/include/libtorrent/allocator.hpp b/include/libtorrent/allocator.hpp index a49e60483..4cf9e937d 100644 --- a/include/libtorrent/allocator.hpp +++ b/include/libtorrent/allocator.hpp @@ -48,6 +48,9 @@ namespace libtorrent static char* malloc(const size_type bytes); static void free(char* block); +#ifdef TORRENT_DEBUG_BUFFERS + static bool in_use(char const* block); +#endif }; struct TORRENT_EXTRA_EXPORT aligned_holder diff --git a/include/libtorrent/assert.hpp b/include/libtorrent/assert.hpp index 5e0c2ca2e..88148e0d4 100644 --- a/include/libtorrent/assert.hpp +++ b/include/libtorrent/assert.hpp @@ -46,25 +46,27 @@ TORRENT_EXPORT void print_backtrace(char* out, int len, int max_depth = 0); extern char const* libtorrent_assert_log; #endif -#if (defined __linux__ || defined __MACH__) && defined __GNUC__ && !TORRENT_USE_SYSTEM_ASSERT +#if !TORRENT_USE_SYSTEM_ASSERT #if TORRENT_USE_IOSTREAM #include #endif +TORRENT_EXPORT void assert_print(char const* fmt, ...); + TORRENT_EXPORT void assert_fail(const char* expr, int line, char const* file , char const* function, char const* val, int kind = 0); #define TORRENT_ASSERT_PRECOND(x) \ - do { if (x) {} else assert_fail(#x, __LINE__, __FILE__, __PRETTY_FUNCTION__, "", 1); } while (false) + do { if (x) {} else assert_fail(#x, __LINE__, __FILE__, TORRENT_FUNCTION, 0, 1); } while (false) #define TORRENT_ASSERT(x) \ - do { if (x) {} else assert_fail(#x, __LINE__, __FILE__, __PRETTY_FUNCTION__, "", 0); } while (false) + do { if (x) {} else assert_fail(#x, __LINE__, __FILE__, TORRENT_FUNCTION, 0, 0); } while (false) #if TORRENT_USE_IOSTREAM #define TORRENT_ASSERT_VAL(x, y) \ do { if (x) {} else { std::stringstream __s__; __s__ << #y ": " << y; \ - assert_fail(#x, __LINE__, __FILE__, __PRETTY_FUNCTION__, __s__.str().c_str(), 0); } } while (false) + assert_fail(#x, __LINE__, __FILE__, TORRENT_FUNCTION, __s__.str().c_str(), 0); } } while (false) #else #define TORRENT_ASSERT_VAL(x, y) TORRENT_ASSERT(x) #endif diff --git a/include/libtorrent/atomic.hpp b/include/libtorrent/atomic.hpp new file mode 100644 index 000000000..745faf876 --- /dev/null +++ b/include/libtorrent/atomic.hpp @@ -0,0 +1,131 @@ +/* + +Copyright (c) 2012-2013, 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_ATOMIC_HPP_INCLUDED +#define TORRENT_ATOMIC_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if TORRENT_USE_OSATOMIC +#include +#endif + +#if TORRENT_USE_INTERLOCKED_ATOMIC +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#endif + +#if TORRENT_USE_BEOS_ATOMIC +#include +#endif + +#if TORRENT_USE_SOLARIS_ATOMIC +#include +#endif + +namespace libtorrent +{ + struct atomic_count + { + atomic_count() : m_value(0) {} + atomic_count(int v) : m_value(v) {} + +#if TORRENT_USE_INTERLOCKED_ATOMIC + typedef LONG value_type; +#elif TORRENT_USE_SOLARIS_ATOMIC + typedef unsigned int value_type; +#else + typedef int value_type; +#endif + +#if TORRENT_USE_OSATOMIC + operator value_type() const { return OSAtomicAdd32(0, const_cast(&m_value)); } + value_type operator-=(int v) { return OSAtomicAdd32Barrier(-v, &m_value); } + value_type operator+=(int v) { return OSAtomicAdd32Barrier(v, &m_value); } + // pre inc/dec operators + value_type operator++() { return OSAtomicAdd32Barrier(1, &m_value); } + value_type operator--() { return OSAtomicAdd32Barrier(-1, &m_value); } + // post inc/dec operators + value_type operator++(int) { return OSAtomicAdd32Barrier(1, &m_value)-1; } + value_type operator--(int) { return OSAtomicAdd32Barrier(-1, &m_value)+1; } +#elif TORRENT_USE_GCC_ATOMIC + operator value_type() const { return __sync_fetch_and_add(const_cast(&m_value), 0); } + value_type operator-=(value_type v) { return __sync_sub_and_fetch(&m_value, v); } + value_type operator+=(value_type v) { return __sync_add_and_fetch(&m_value, v); } + // pre inc/dec operators + value_type operator++() { return __sync_add_and_fetch(&m_value, 1); } + value_type operator--() { return __sync_add_and_fetch(&m_value, -1); } + // post inc/dec operators + value_type operator++(int) { return __sync_fetch_and_add(&m_value, 1); } + value_type operator--(int) { return __sync_fetch_and_add(&m_value, -1); } +#elif TORRENT_USE_INTERLOCKED_ATOMIC + operator value_type() const { return InterlockedExchangeAdd(const_cast(&m_value), 0); } + value_type operator-=(value_type v) { return InterlockedExchangeAdd(&m_value, -v); } + value_type operator+=(value_type v) { return InterlockedExchangeAdd(&m_value, v); } + // pre inc/dec operators + value_type operator++() { return InterlockedIncrement(&m_value); } + value_type operator--() { return InterlockedDecrement(&m_value); } + // post inc/dec operators + value_type operator++(int) { return InterlockedIncrement(&m_value) - 1; } + value_type operator--(int) { return InterlockedDecrement(&m_value) + 1; } +#elif TORRENT_USE_SOLARIS_ATOMIC + operator value_type() const { return atomic_add_32_nv(const_cast(&m_value), 0); } + value_type operator-=(value_type v) { return atomic_add_32(&m_value, -v); } + value_type operator+=(value_type v) { return atomic_add_32(&m_value, v); } + // pre inc/dec operators + value_type operator++() { return atomic_add_32_nv(&m_value, 1); } + value_type operator--() { return atomic_add_32_nv(&m_value, -1); } + // post inc/dec operators + value_type operator++(int) { return atomic_add_32_nv(&m_value, 1) - 1; } + value_type operator--(int) { return atomic_add_32_nv(&m_value, -1) + 1; } +#elif TORRENT_USE_BEOS_ATOMIC + operator value_type() const { return atomic_add(const_cast(&m_value), 0); } + value_type operator-=(value_type v) { return atomic_add(&m_value, -v) - v; } + value_type operator+=(value_type v) { return atomic_add(&m_value, v) + v; } + // pre inc/dec operators + value_type operator++() { return atomic_add(&m_value, 1) + 1; } + value_type operator--() { return atomic_add(&m_value, -1) - 1; } + // post inc/dec operators + value_type operator++(int) { return atomic_add(&m_value, 1); } + value_type operator--(int) { return atomic_add(&m_value, -1); } +#else +#error "don't know which atomic operations to use" +#endif + private: + volatile value_type m_value; + }; +} + +#endif + diff --git a/include/libtorrent/aux_/session_impl.hpp b/include/libtorrent/aux_/session_impl.hpp index 18dab8b9b..7e542d153 100644 --- a/include/libtorrent/aux_/session_impl.hpp +++ b/include/libtorrent/aux_/session_impl.hpp @@ -39,6 +39,15 @@ POSSIBILITY OF SUCH DAMAGE. #include #include // for va_start, va_end +#include "libtorrent/config.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/aux_/session_interface.hpp" +#include "libtorrent/uncork_interface.hpp" +#include "libtorrent/linked_list.hpp" +#include "libtorrent/torrent_peer.hpp" +#include "libtorrent/torrent_peer_allocator.hpp" +#include "libtorrent/performance_counters.hpp" // for counters + #ifndef TORRENT_DISABLE_GEO_IP #ifdef WITH_SHIPPED_GEOIP_H #include "libtorrent/GeoIP.h" @@ -51,12 +60,15 @@ POSSIBILITY OF SUCH DAMAGE. #pragma warning(push, 1) #endif -#include +#if TORRENT_HAS_BOOST_UNORDERED +#include +#endif #ifdef _MSC_VER #pragma warning(pop) #endif +#include "libtorrent/session.hpp" // for user_load_function_t #include "libtorrent/ip_voter.hpp" #include "libtorrent/torrent_handle.hpp" #include "libtorrent/entry.hpp" @@ -79,7 +91,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/udp_socket.hpp" #include "libtorrent/assert.hpp" #include "libtorrent/thread.hpp" -#include "libtorrent/policy.hpp" // for policy::peer #include "libtorrent/alert_manager.hpp" // for alert_manager #include "libtorrent/deadline_timer.hpp" #include "libtorrent/socket_io.hpp" // for print_address @@ -87,8 +98,13 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/utp_socket_manager.hpp" #include "libtorrent/bloom_filter.hpp" #include "libtorrent/rss.hpp" +#include "libtorrent/peer_class.hpp" +#include "libtorrent/disk_io_job.hpp" // block_cache_reference +#include "libtorrent/network_thread_pool.hpp" +#include "libtorrent/peer_class_type_filter.hpp" #include "libtorrent/alert_dispatcher.hpp" #include "libtorrent/kademlia/dht_observer.hpp" +#include "libtorrent/resolver.hpp" #if TORRENT_COMPLETE_TYPES_REQUIRED #include "libtorrent/peer_connection.hpp" @@ -115,6 +131,7 @@ namespace libtorrent struct fingerprint; class torrent; class alert; + struct cache_info; namespace dht { @@ -151,6 +168,7 @@ namespace libtorrent namespace aux { struct session_impl; + struct session_settings; #if defined TORRENT_STATS && !defined __MACH__ struct vm_statistics_data_t @@ -167,8 +185,8 @@ namespace libtorrent struct thread_cpu_usage { - ptime user_time; - ptime system_time; + time_duration user_time; + time_duration system_time; }; #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING @@ -187,11 +205,14 @@ namespace libtorrent // this is the link between the main thread and the // thread started to run the main downloader loop struct TORRENT_EXTRA_EXPORT session_impl - : alert_dispatcher + : session_interface + , alert_dispatcher , dht::dht_observer , boost::noncopyable , initialize_timer , udp_socket_observer + , uncork_interface + , single_threaded { #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING // this needs to be destructed last, since other components may log @@ -201,49 +222,69 @@ namespace libtorrent #endif // the size of each allocation that is chained in the send buffer - enum { send_buffer_size = 128 }; + enum { send_buffer_size_impl = 128 }; #ifdef TORRENT_DEBUG friend class ::libtorrent::peer_connection; #endif friend struct checker_impl; friend class invariant_access; - typedef std::set > connection_map; + typedef std::set > connection_map; +#if TORRENT_HAS_BOOST_UNORDERED + typedef boost::unordered_map > torrent_map; +#else typedef std::map > torrent_map; +#endif - session_impl( - std::pair listen_port_range - , fingerprint const& cl_fprint - , char const* listen_interface - , boost::uint32_t alert_mask); + session_impl(fingerprint const& cl_fprint); virtual ~session_impl(); - void update_dht_announce_interval(); + void init(); - void start_session(); + void start_session(settings_pack const& pack); #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING void set_log_path(std::string const& p) { m_logpath = p; } #endif + void set_load_function(user_load_function_t fun) + { m_user_load_torrent = fun; } + + void queue_async_resume_data(boost::shared_ptr const& t); + void done_async_resume(); + void async_resume_dispatched(bool all); + + void init_peer_class_filter(bool unlimited_local); + #ifndef TORRENT_DISABLE_EXTENSIONS void add_extension(boost::function( torrent*, void*)> ext); void add_ses_extension(boost::shared_ptr ext); #endif #if TORRENT_USE_ASSERTS - bool has_peer(peer_connection const* p) const - { - TORRENT_ASSERT(is_network_thread()); - return std::find_if(m_connections.begin(), m_connections.end() - , boost::bind(&boost::intrusive_ptr::get, _1) == p) - != m_connections.end(); - } + bool has_peer(peer_connection const* p) const; + bool any_torrent_has_peer(peer_connection const* p) const; + bool is_single_thread() const { return single_threaded::is_single_thread(); } + bool is_posting_torrent_updates() const { return m_posting_torrent_updates; } // this is set while the session is building the // torrent status update message bool m_posting_torrent_updates; #endif + void main_thread(); - void open_listen_port(int flags, error_code& ec); + void open_listen_port(); + void maybe_open_listen_port(); + + torrent_peer_allocator_interface* get_peer_allocator() { return &m_peer_allocator; } + + io_service& get_io_service() { return m_io_service; } + resolver_interface& get_resolver() { return m_host_resolver; } + + std::vector& torrent_list(int i) + { + TORRENT_ASSERT(i >= 0); + TORRENT_ASSERT(i < session_interface::num_torrent_lists); + return m_torrent_lists[i]; + } // prioritize this torrent to be allocated some connection // attempts, because this torrent needs more peers. @@ -265,25 +306,6 @@ namespace libtorrent void incoming_connection(boost::shared_ptr const& s); -#if TORRENT_USE_ASSERTS - bool is_network_thread() const - { -#if defined BOOST_HAS_PTHREADS - if (m_network_thread == 0) return true; - return m_network_thread == pthread_self(); -#endif - return true; - } - bool is_not_network_thread() const - { -#if defined BOOST_HAS_PTHREADS - if (m_network_thread == 0) return true; - return m_network_thread != pthread_self(); -#endif - return true; - } -#endif - feed_handle add_feed(feed_settings const& feed); void remove_feed(feed_handle h); void get_feeds(std::vector* f) const; @@ -291,15 +313,33 @@ namespace libtorrent boost::weak_ptr find_torrent(sha1_hash const& info_hash) const; boost::weak_ptr find_torrent(std::string const& uuid) const; boost::weak_ptr find_disconnect_candidate_torrent() const; + int num_torrents() const { return m_torrents.size(); } + + void insert_torrent(sha1_hash const& ih, boost::shared_ptr const& t + , std::string uuid); + void insert_uuid_torrent(std::string uuid, boost::shared_ptr const& t) + { m_uuids.insert(std::make_pair(uuid, t)); } + boost::shared_ptr delay_load_torrent(sha1_hash const& info_hash + , peer_connection* pc); + void set_queue_position(torrent* t, int p); peer_id const& get_peer_id() const { return m_peer_id; } - void close_connection(peer_connection const* p, error_code const& ec); + void close_connection(peer_connection* p, error_code const& ec + , bool cancel_in_cq); - void set_settings(session_settings const& s); +#ifndef TORRENT_NO_DEPRECATE + void set_settings(libtorrent::session_settings const& s); + libtorrent::session_settings deprecated_settings() const; +#endif + + void apply_settings_pack(settings_pack* pack); session_settings const& settings() const { return m_settings; } #ifndef TORRENT_DISABLE_DHT + dht::dht_tracker* dht() { return m_dht.get(); } + bool announce_dht() const { return !m_listen_sockets.empty(); } + void add_dht_node_name(std::pair const& node); void add_dht_node(udp::endpoint n); void add_dht_router(std::pair const& node); @@ -308,6 +348,7 @@ namespace libtorrent void start_dht(); void stop_dht(); void start_dht(entry const& startup_state); + bool has_dht() const; // this is called for torrents when they are started // it will prioritize them for announcing to @@ -335,14 +376,16 @@ namespace libtorrent #endif void on_dht_announce(error_code const& e); void on_dht_router_name_lookup(error_code const& e - , tcp::resolver::iterator host); + , std::vector
const& addresses, int port); #endif void maybe_update_udp_mapping(int nat, int local_port, int external_port); #ifndef TORRENT_DISABLE_ENCRYPTION - void set_pe_settings(pe_settings const& settings); - pe_settings const& get_pe_settings() const { return m_pe_settings; } + torrent const* find_encrypted_torrent( + sha1_hash const& info_hash, sha1_hash const& xor_mask); + + void add_obfuscated_hash(sha1_hash const& obfuscated, boost::weak_ptr const& t); #endif void on_port_map_log(char const* msg, int map_transport); @@ -364,17 +407,48 @@ namespace libtorrent ip_filter const& get_ip_filter() const; void set_port_filter(port_filter const& f); + port_filter const& get_port_filter() const; + + + // TODO: move the login info into the tracker_request object + void queue_tracker_request(tracker_request& req + , std::string login, boost::weak_ptr c + , boost::uint32_t key); + + // ==== peer class operations ==== + + // implements session_interface + void set_peer_classes(peer_class_set* s, address const& a, int st); + peer_class_pool const& peer_classes() const { return m_classes; } + peer_class_pool& peer_classes() { return m_classes; } + bool ignore_unchoke_slots_set(peer_class_set const& set) const; + int copy_pertinent_channels(peer_class_set const& set + , int channel, bandwidth_channel** dst, int max); + int use_quota_overhead(peer_class_set& set, int amount_down, int amount_up); + bool use_quota_overhead(bandwidth_channel* ch, int channel, int amount); + + int create_peer_class(char const* name); + void delete_peer_class(int cid); + void set_peer_class_filter(ip_filter const& f); + ip_filter const& get_peer_class_filter() const; + + void set_peer_class_type_filter(peer_class_type_filter f); + peer_class_type_filter get_peer_class_type_filter(); + + peer_class_info get_peer_class(int cid); + void set_peer_class(int cid, peer_class_info const& pci); - void listen_on( - std::pair const& port_range - , error_code& ec - , const char* net_interface = 0 - , int flags = 0); bool is_listening() const; +#ifndef TORRENT_DISABLE_EXTENSIONS + void add_extensions_to_torrent( + boost::shared_ptr const& torrent_ptr, void* userdata); +#endif + torrent_handle add_torrent(add_torrent_params const&, error_code& ec); torrent_handle add_torrent_impl(add_torrent_params const&, error_code& ec); void async_add_torrent(add_torrent_params* params); + void on_async_load_torrent(disk_io_job const* j); void remove_torrent(torrent_handle const& h, int options); void remove_torrent_impl(boost::shared_ptr tptr, int options); @@ -385,13 +459,10 @@ namespace libtorrent void refresh_torrent_status(std::vector* ret , boost::uint32_t flags) const; void post_torrent_updates(); + void post_session_stats(); std::vector get_torrents() const; - void queue_check_torrent(boost::shared_ptr const& t); - void dequeue_check_torrent(boost::shared_ptr const& t); - - void set_alert_mask(boost::uint32_t m); size_t set_alert_queue_size_limit(size_t queue_size_limit_); std::auto_ptr pop_alert(); void pop_alerts(std::deque* alerts); @@ -417,15 +488,34 @@ namespace libtorrent int max_connections() const; int max_uploads() const; int max_half_open_connections() const; - #endif + bool half_open_done(int ticket) + { return m_half_open.done(ticket); } + + bandwidth_manager* get_bandwidth_manager(int channel); + + int upload_rate_limit(peer_class_t c) const; + int download_rate_limit(peer_class_t c) const; + void set_upload_rate_limit(peer_class_t c, int limit); + void set_download_rate_limit(peer_class_t c, int limit); + + void set_rate_limit(peer_class_t c, int channel, int limit); + int rate_limit(peer_class_t c, int channel) const; + + bool preemptive_unchoke() const; int num_uploads() const { return m_num_unchoked; } int num_connections() const { return m_connections.size(); } + int peak_up_rate() const { return m_peak_up_rate; } + void unchoke_peer(peer_connection& c); void choke_peer(peer_connection& c); + void trigger_unchoke() + { m_unchoke_time_scaler = 0; } + void trigger_optimistic_unchoke() + { m_optimistic_unchoke_time_scaler = 0; } session_status status() const; void set_peer_id(peer_id const& id); @@ -434,6 +524,9 @@ namespace libtorrent boost::uint16_t listen_port() const; boost::uint16_t ssl_listen_port() const; + alert_manager& alerts() { return m_alerts; } + disk_interface& disk_thread() { return m_disk_thread; } + void abort(); torrent_handle find_torrent_handle(sha1_hash const& info_hash); @@ -443,32 +536,21 @@ namespace libtorrent void save_state(entry* e, boost::uint32_t flags) const; void load_state(lazy_entry const* e); - void set_proxy(proxy_settings const& s); - proxy_settings const& proxy() const { return m_proxy; } + bool has_connection(peer_connection* p) const; + void insert_peer(boost::shared_ptr const& c); -#ifndef TORRENT_NO_DEPRECATE - void set_peer_proxy(proxy_settings const& s) { set_proxy(s); } - void set_web_seed_proxy(proxy_settings const& s) { set_proxy(s); } - void set_tracker_proxy(proxy_settings const& s) { set_proxy(s); } - proxy_settings const& peer_proxy() const { return proxy(); } - proxy_settings const& web_seed_proxy() const { return proxy(); } - proxy_settings const& tracker_proxy() const { return proxy(); } - -#ifndef TORRENT_DISABLE_DHT - void set_dht_proxy(proxy_settings const& s) { set_proxy(s); } - proxy_settings const& dht_proxy() const { return proxy(); } -#endif -#endif // TORRENT_NO_DEPRECATE + proxy_settings proxy() const; #ifndef TORRENT_DISABLE_DHT bool is_dht_running() const { return (m_dht.get() != NULL); } + int external_udp_port() const { return m_external_udp_port; } #endif #if TORRENT_USE_I2P - void set_i2p_proxy(proxy_settings const& s); + char const* i2p_session() const { return m_i2p_conn.session_id(); } + proxy_settings i2p_proxy() const; + void on_i2p_open(error_code const& ec); - proxy_settings const& i2p_proxy() const - { return m_i2p_conn.proxy(); } void open_new_incoming_i2p_connection(); void on_i2p_accept(boost::shared_ptr const& s , error_code const& e); @@ -505,34 +587,59 @@ namespace libtorrent , int local_port); void delete_port_mapping(int handle); - int next_port(); + int next_port() const; void add_redundant_bytes(size_type b, int reason) { TORRENT_ASSERT(b > 0); - m_total_redundant_bytes += b; m_redundant_bytes[reason] += b; + m_stats_counters.inc_stats_counter(counters::recv_redundant_bytes, b); } void add_failed_bytes(size_type b) { TORRENT_ASSERT(b > 0); - m_total_failed_bytes += b; + m_stats_counters.inc_stats_counter(counters::recv_failed_bytes, b); } + // load the specified torrent, also + // pick the least recently used torrent and unload it, unless + // t is the least recently used, then the next least recently + // used is picked + // returns true if the torrent was loaded successfully + bool load_torrent(torrent* t); + + // bump t to the top of the list of least recently used. i.e. + // make it the most recently used. This is done every time + // an action is performed that required the torrent to be + // loaded, indicating activity + void bump_torrent(torrent* t, bool back = true); + + // evict torrents until there's space for one new torrent, + void evict_torrents_except(torrent* ignore); + void evict_torrent(torrent* t); + + void deferred_submit_jobs(); + char* allocate_buffer(); + torrent_peer* allocate_peer_entry(int type); + void free_peer_entry(torrent_peer* p); + void free_buffer(char* buf); + int send_buffer_size() const { return send_buffer_size_impl; } - char* allocate_disk_buffer(char const* category); + // implements buffer_allocator_interface void free_disk_buffer(char* buf); - - enum - { - source_dht = 1, - source_peer = 2, - source_tracker = 4, - source_router = 8 - }; + char* allocate_disk_buffer(char const* category); + char* allocate_disk_buffer(bool& exceeded + , boost::shared_ptr o + , char const* category); + char* async_allocate_disk_buffer(char const* category + , boost::function const& handler); + void reclaim_block(block_cache_reference ref); + + bool exceeded_cache_use() const + { return m_disk_thread.exceeded_cache_use(); } // implements dht_observer virtual void set_external_address(address const& ip @@ -540,121 +647,99 @@ namespace libtorrent void set_external_address(address const& ip , int source_type, address const& source); - - external_ip const& external_address() const; - - bool can_write_to_disk() const - { return m_disk_thread.can_write(); } + virtual external_ip const& external_address() const; // used when posting synchronous function // calls to session_impl and torrent objects mutable libtorrent::mutex mut; mutable libtorrent::condition_variable cond; - void inc_disk_queue(int channel) - { - TORRENT_ASSERT(channel >= 0 && channel < 2); - ++m_disk_queues[channel]; - } + // cork a peer and schedule a delayed uncork + // does nothing if the peer is already corked + void cork_burst(peer_connection* p); - void dec_disk_queue(int channel) - { - TORRENT_ASSERT(channel >= 0 && channel < 2); - TORRENT_ASSERT(m_disk_queues[channel] > 0); - --m_disk_queues[channel]; - } + // uncork all peers added to the delayed uncork queue + // implements uncork_interface + void do_delayed_uncork(); - void inc_active_downloading() { ++m_num_active_downloading; } - void dec_active_downloading() - { - TORRENT_ASSERT(m_num_active_downloading > 0); - --m_num_active_downloading; - } - void inc_active_finished() { ++m_num_active_finished; } - void dec_active_finished() - { - TORRENT_ASSERT(m_num_active_finished > 0); - --m_num_active_finished; - } + void post_socket_job(socket_job& j); -#if TORRENT_USE_ASSERTS - bool in_state_updates(boost::shared_ptr t) - { - return std::find_if(m_state_updates.begin(), m_state_updates.end() - , boost::bind(&boost::weak_ptr::lock, _1) == t) != m_state_updates.end(); - } -#endif + // implements session_interface + virtual tcp::endpoint bind_outgoing_socket(socket_type& s, address + const& remote_address, error_code& ec) const; + virtual bool verify_bound_address(address const& addr, bool utp + , error_code& ec); - void add_to_update_queue(boost::weak_ptr t) - { - TORRENT_ASSERT(std::find_if(m_state_updates.begin(), m_state_updates.end() - , boost::bind(&boost::weak_ptr::lock, _1) == t.lock()) == m_state_updates.end()); - m_state_updates.push_back(t); - } + bool has_lsd() const { return m_lsd.get(); } + + std::vector& block_info_storage() { return m_block_info_storage; } + + connection_queue& half_open() { return m_half_open; } + libtorrent::utp_socket_manager* utp_socket_manager() { return &m_utp_socket_manager; } + void inc_boost_connections() { ++m_boost_connections; } // private: + std::vector m_torrent_lists[num_torrent_lists]; + + peer_class_pool m_classes; + +// private: + + void submit_disk_jobs(); + // implements alert_dispatcher virtual bool post_alert(alert* a); - void update_connections_limit(); - void update_unchoke_limit(); + void update_proxy(); + void update_i2p_bridge(); + void update_peer_tos(); + void update_user_agent(); + void update_choking_algorithm(); + void update_connection_speed(); + void update_queued_disk_bytes(); + void update_alert_queue_size(); + void update_dht_upload_rate_limit(); + void update_disk_threads(); + void update_network_threads(); + void update_cache_buffer_chunk_size(); + void update_report_web_seed_downloads(); void trigger_auto_manage(); - void on_trigger_auto_manage(); - void update_rate_settings(); + void update_outgoing_interfaces(); + void update_listen_interfaces(); + void update_privileged_ports(); + + void update_upnp(); + void update_natpmp(); + void update_lsd(); + void update_dht(); + + void on_trigger_auto_manage(); + + void update_socket_buffer_size(); + void update_dht_announce_interval(); + void update_anonymous_mode(); + void update_force_proxy(); + void update_half_open(); + void update_download_rate(); + void update_upload_rate(); + void update_connections_limit(); +#ifndef TORRENT_NO_DEPRECATE + void update_local_download_rate(); + void update_local_upload_rate(); + void update_rate_limit_utp(); + void update_ignore_rate_limits_on_local_network(); +#endif + void update_alert_mask(); - void update_disk_thread_settings(); void on_lsd_peer(tcp::endpoint peer, sha1_hash const& ih); void setup_socket_buffers(socket_type& s); // the settings for the client - session_settings m_settings; + aux::session_settings m_settings; - // this is a shared pool where policy_peer objects - // are allocated. It's a pool since we're likely - // to have tens of thousands of peers, and a pool - // saves significant overhead -#ifdef TORRENT_STATS - struct logging_allocator - { - typedef std::size_t size_type; - typedef std::ptrdiff_t difference_type; - - static char* malloc(const size_type bytes) - { - allocated_bytes += bytes; - ++allocations; - return (char*)::malloc(bytes); - } - - static void free(char* const block) - { - --allocations; - return ::free(block); - } - - static int allocations; - static int allocated_bytes; - }; - boost::object_pool< - policy::ipv4_peer, logging_allocator> m_ipv4_peer_pool; -#if TORRENT_USE_IPV6 - boost::object_pool< - policy::ipv6_peer, logging_allocator> m_ipv6_peer_pool; -#endif -#if TORRENT_USE_I2P - boost::object_pool< - policy::i2p_peer, logging_allocator> m_i2p_peer_pool; -#endif -#else - boost::object_pool m_ipv4_peer_pool; -#if TORRENT_USE_IPV6 - boost::object_pool m_ipv6_peer_pool; -#endif -#if TORRENT_USE_I2P - boost::object_pool m_i2p_peer_pool; -#endif -#endif + // this is a pool allocator for torrent_peer objects + torrent_peer_allocator m_peer_allocator; // this vector is used to store the block_info // objects pointed to by partial_piece_info returned @@ -667,14 +752,6 @@ namespace libtorrent boost::pool<> m_send_buffers; #endif - // the file pool that all storages in this session's - // torrents uses. It sets a limit on the number of - // open files by this session. - // file pool must be destructed after the torrents - // since they will still have references to it - // when they are destructed. - file_pool m_files; - // this is where all active sockets are stored. // the selector can sleep while there's no activity on // them @@ -699,6 +776,10 @@ namespace libtorrent // constructed after it. disk_io_thread m_disk_thread; + // a thread pool used for async_write_some calls, + // to distribute its cost to multiple threads + std::vector > m_net_thread_pool; + // this is a list of half-open tcp connections // (only outgoing connections) // this has to be one of the last @@ -712,51 +793,66 @@ namespace libtorrent bandwidth_manager m_download_rate; bandwidth_manager m_upload_rate; - // the global rate limiter bandwidth channels - bandwidth_channel m_download_channel; - bandwidth_channel m_upload_channel; - - // bandwidth channels for local peers when - // rate limits are ignored. They are only - // throttled by these global rate limiters - // and they don't have a rate limit set by - // default - bandwidth_channel m_local_download_channel; - bandwidth_channel m_local_upload_channel; + // the peer class that all peers belong to by default + peer_class_t m_global_class; + // the peer class all TCP peers belong to by default // all tcp peer connections are subject to these // bandwidth limits. Local peers are excempted // from this limit. The purpose is to be able to // throttle TCP that passes over the internet // bottleneck (i.e. modem) to avoid starving out // uTP connections. - bandwidth_channel m_tcp_download_channel; - bandwidth_channel m_tcp_upload_channel; + peer_class_t m_tcp_peer_class; - bandwidth_channel* m_bandwidth_channel[2]; - - // the number of peer connections that are waiting - // for the disk. one for each channel. - // upload_channel means waiting to read from disk - // and download_channel is waiting to write to disk - int m_disk_queues[2]; + // peer class for local peers + peer_class_t m_local_peer_class; tracker_manager m_tracker_manager; torrent_map m_torrents; + +#ifndef TORRENT_DISABLE_ENCRYPTION + // this maps obfuscated hashes to torrents. It's only + // used when encryption is enabled + torrent_map m_obfuscated_torrents; +#endif + + // this is an LRU for torrents. It's used to determine + // which torrents should be loaded into RAM and which ones + // shouldn't. Each torrent that's loaded is part of this + // list. + linked_list m_torrent_lru; + std::map > m_uuids; - // counters of how many of the active (non-paused) torrents - // are finished and downloading. This is used to weigh the - // priority of downloading and finished torrents when connecting - // more peers. - int m_num_active_downloading; - int m_num_active_finished; + // when saving resume data for many torrents, torrents are + // queued up in this list in order to not have too many of them + // outstanding at any given time, since the resume data may use + // a lot of memory. + std::list > m_save_resume_queue; + + // the number of save resume data disk jobs that are currently + // outstanding + int m_num_save_resume; + + // the number of resume data job that are complete and are waiting + // to be reaped in the alert queue + int m_num_queued_resume; + + // peer connections are put here when disconnected to avoid + // race conditions with the disk thread. It's important that + // peer connections are destructed from the network thread, + // once a peer is disconnected, it's put in this list and + // every second their refcount is checked, and if it's 1, + // they are deleted (from the network thread) + std::vector > m_undead_peers; + + // keep the io_service alive until we have posted the job + // to clear the undead peers + boost::optional m_work; typedef std::list > check_queue_t; - // this has all torrents that wants to be checked in it - check_queue_t m_queued_for_checking; - // this maps sockets to their peer_connection // object. It is the complete list of all connected // peers. @@ -768,13 +864,13 @@ namespace libtorrent // they would linger and stall or hang session shutdown std::set > m_incoming_sockets; - // peer connections are put here when disconnected to avoid - // race conditions with the disk thread. It's important that - // peer connections are destructed from the network thread, - // once a peer is disconnected, it's put in this list and - // every second their refcount is checked, and if it's 1, - // they are deleted (from the network thread) - std::vector > m_undead_peers; + // maps IP ranges to bitfields representing peer class IDs + // to assign peers matching a specific IP range based on its + // remote endpoint + ip_filter m_peer_class_filter; + + // maps socket types to peer classes + peer_class_type_filter m_peer_class_type_filter; // filters incoming connections ip_filter m_ip_filter; @@ -785,6 +881,12 @@ namespace libtorrent // the peer id that is generated at the start of the session peer_id m_peer_id; + // this is the highest queue position of any torrent + // in this session. queue positions are packed (i.e. there + // are no gaps). If there are no torrents with queue positions + // this is -1. + int m_max_queue_pos; + // the key is an id that is used to identify the // client with the tracker only. It is randomized // at startup @@ -795,13 +897,25 @@ namespace libtorrent // is incremented by one int m_listen_port_retries; - // the ip-address of the interface - // we are supposed to listen on. - // if the ip is set to zero, it means - // that we should let the os decide which - // interface to listen on + // the addresses or device names of the interfaces we are supposed to + // listen on. if empty, it means that we should let the os decide + // which interface to listen on + std::vector > m_listen_interfaces; + + // keep this around until everything uses the list of interfaces + // instead. tcp::endpoint m_listen_interface; + // the network interfaces outgoing connections are opened through. If + // there is more then one, they are used in a round-robin fasion + // each element is a device name or IP address (in string form) and + // a port number. The port determins which port to bind the listen + // socket to, and the device or IP determines which network adapter + // to be used. If no adapter with the specified name exists, the listen + // socket fails. + // TODO: should this be renamed m_outgoing_interfaces? + std::vector m_net_interfaces; + // if we're listening on an IPv6 interface // this is one of the non local IPv6 interfaces // on this machine @@ -818,6 +932,7 @@ namespace libtorrent #endif #ifdef TORRENT_USE_OPENSSL + boost::asio::ssl::context* ssl_ctx() { return &m_ssl_ctx; } void ssl_handshake(error_code const& ec, boost::shared_ptr s); #endif @@ -826,13 +941,13 @@ namespace libtorrent boost::shared_ptr m_socks_listen_socket; boost::uint16_t m_socks_listen_port; + // round-robin index into m_net_interfaces + mutable boost::uint8_t m_interface_index; + void open_new_incoming_socks_connection(); - void setup_listener(listen_socket_t* s, tcp::endpoint ep, int& retries - , bool v6_only, int flags, error_code& ec); - - // the proxy used for bittorrent - proxy_settings m_proxy; + void setup_listener(listen_socket_t* s, std::string const& device + , bool ipv4, int port, int& retries, int flags, error_code& ec); #ifndef TORRENT_DISABLE_DHT entry m_dht_state; @@ -882,22 +997,35 @@ namespace libtorrent // using an explicit read read cache. int m_cache_rotation_timer; + // the index of the torrent that we'll + // refresh the next time + int m_next_suggest_torrent; + + // this is a counter of the number of seconds until + // the next time the suggest pieces are refreshed + int m_suggest_timer; + // statistics gathered from all torrents. stat m_stat; + // implements session_interface + virtual void sent_bytes(int bytes_payload, int bytes_protocol); + virtual void received_bytes(int bytes_payload, int bytes_protocol); + virtual void trancieve_ip_packet(int bytes, bool ipv6); + virtual void sent_syn(bool ipv6); + virtual void received_synack(bool ipv6); + int m_peak_up_rate; int m_peak_down_rate; - void on_disk_queue(); void on_tick(error_code const& e); - void try_connect_more_peers(int num_downloads, int num_downloads_peers); + void try_connect_more_peers(); void auto_manage_torrents(std::vector& list - , int& dht_limit, int& tracker_limit, int& lsd_limit - , int& hard_limit, int type_limit); + , int& checking_limit, int& dht_limit, int& tracker_limit + , int& lsd_limit, int& hard_limit, int type_limit); void recalculate_auto_managed_torrents(); - void recalculate_unchoke_slots(int congested_torrents - , int uncongested_torrents); + void recalculate_unchoke_slots(); void recalculate_optimistic_unchoke_slots(); ptime m_created; @@ -905,9 +1033,6 @@ namespace libtorrent ptime m_last_tick; ptime m_last_second_tick; - // used to limit how often disk warnings are generated - ptime m_last_disk_performance_warning; - ptime m_last_disk_queue_performance_warning; // the last time we went through the peers // to decide which ones to choke/unchoke @@ -922,7 +1047,7 @@ namespace libtorrent // when outgoing_ports is configured, this is the // port we'll bind the next outgoing socket to - int m_next_port; + mutable int m_next_port; #ifndef TORRENT_DISABLE_DHT boost::intrusive_ptr m_dht; @@ -954,17 +1079,13 @@ namespace libtorrent rate_limited_udp_socket m_udp_socket; - utp_socket_manager m_utp_socket_manager; + libtorrent::utp_socket_manager m_utp_socket_manager; // the number of torrent connection boosts // connections that have been made this second // this is deducted from the connect speed int m_boost_connections; -#ifndef TORRENT_DISABLE_ENCRYPTION - pe_settings m_pe_settings; -#endif - boost::intrusive_ptr m_natpmp; boost::intrusive_ptr m_upnp; boost::intrusive_ptr m_lsd; @@ -1013,21 +1134,27 @@ namespace libtorrent // by Local service discovery deadline_timer m_lsd_announce_timer; - tcp::resolver m_host_resolver; + resolver m_host_resolver; // the index of the torrent that will be offered to // connect to a peer next time on_tick is called. - // This implements a round robin. - torrent_map::iterator m_next_connect_torrent; + // This implements a round robin peer connections among + // torrents that want more peers. The index is into + // m_torrent_lists[torrent_want_peers_downloading] + // (which is a list of torrent pointers with all + // torrents that want peers and are downloading) + int m_next_downloading_connect_torrent; + int m_next_finished_connect_torrent; // this is the number of attempts of connecting to - // peers we have given to the torrent pointed to - // by m_next_connect_torrent. Once this reaches - // the number of connection attempts this particular - // torrent should have, the counter is reset and - // m_next_connect_torrent takes a step forward - // to give the next torrent its connection attempts. - int m_current_connect_attempts; + // peers we have given to downloading torrents. + // when this gets high enough, we try to connect + // a peer from a finished torrent + int m_download_connect_attempts; + + // index into m_torrent_lists[torrent_want_scrape] referring + // to the next torrent to auto-scrape + int m_next_scrape_torrent; // this is the round-robin cursor for peers that // get to download again after the disk has been @@ -1037,10 +1164,11 @@ namespace libtorrent void check_invariant() const; #endif -#ifdef TORRENT_DISK_STATS +#ifdef TORRENT_BUFFER_STATS void log_buffer_usage(); // used to log send buffer usage statistics std::ofstream m_buffer_usage_logger; + std::ofstream& buffer_usage_logger() { return m_buffer_usage_logger; } // the number of send buffers that are allocated int m_buffer_allocations; #endif @@ -1048,12 +1176,20 @@ namespace libtorrent #ifdef TORRENT_REQUEST_LOGGING // used to log all requests from peers FILE* m_request_log; + FILE* get_request_log() { return m_request_log; } #endif + boost::uint64_t inc_stats_counter(int c, int value = 1) + { return m_stats_counters.inc_stats_counter(c, value); } + + counters& stats_counters() { return m_stats_counters; } + + void received_buffer(int size); + void sent_buffer(int size); + #ifdef TORRENT_STATS void rotate_stats_log(); void print_log_line(int tick_interval_ms, ptime now); - void reset_stat_counters(); void enable_stats_logging(bool s); bool m_stats_logging_enabled; @@ -1067,84 +1203,16 @@ namespace libtorrent // rotated every hour and the sequence number is // incremented by one int m_log_seq; - // the number of peers that were disconnected this - // tick due to protocol error - int m_error_peers; - int m_disconnected_peers; - int m_eof_peers; - int m_connreset_peers; - int m_connrefused_peers; - int m_connaborted_peers; - int m_perm_peers; - int m_buffer_peers; - int m_unreachable_peers; - int m_broken_pipe_peers; - int m_addrinuse_peers; - int m_no_access_peers; - int m_invalid_arg_peers; - int m_aborted_peers; - int m_piece_requests; - int m_max_piece_requests; - int m_invalid_piece_requests; - int m_choked_piece_requests; - int m_cancelled_piece_requests; - int m_piece_rejects; - - int m_error_incoming_peers; - int m_error_outgoing_peers; - int m_error_rc4_peers; - int m_error_encrypted_peers; - int m_error_tcp_peers; - int m_error_utp_peers; - // the number of times the piece picker fell through - // to the end-game mode - int m_end_game_piece_picker_blocks; - int m_piece_picker_blocks; - int m_piece_picks; - int m_reject_piece_picks; - int m_unchoke_piece_picks; - int m_incoming_redundant_piece_picks; - int m_incoming_piece_picks; - int m_end_game_piece_picks; - int m_snubbed_piece_picks; - int m_connect_timeouts; - int m_uninteresting_peers; - int m_timeout_peers; - int m_no_memory_peers; - int m_too_many_peers; - int m_transport_timeout_peers; cache_status m_last_cache_status; size_type m_last_failed; size_type m_last_redundant; size_type m_last_uploaded; size_type m_last_downloaded; - int m_connection_attempts; - int m_num_banned_peers; - int m_banned_for_hash_failure; vm_statistics_data_t m_last_vm_stat; thread_cpu_usage m_network_thread_cpu_usage; sliding_average<20> m_read_ops; sliding_average<20> m_write_ops;; - enum - { - on_read_counter, - on_write_counter, - on_tick_counter, - on_lsd_counter, - on_lsd_peer_counter, - on_udp_counter, - on_accept_counter, - on_disk_queue_counter, - on_disk_read_counter, - on_disk_write_counter, - max_messages - }; - int m_num_messages[max_messages]; - // 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, - // 16384, 32768, 65536, 131072, 262144, 524288, 1048576 - int m_send_buffer_sizes[18]; - int m_recv_buffer_sizes[18]; #endif // each second tick the timer takes a little @@ -1157,15 +1225,16 @@ namespace libtorrent // accumulated error boost::uint16_t m_tick_residual; - // the number of torrents that have apply_ip_filter - // set to false. This is typically 0 - int m_non_filtered_torrents; - #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING - boost::shared_ptr create_log(std::string const& name + virtual boost::shared_ptr create_log(std::string const& name , int instance, bool append = true); - void session_log(char const* fmt, ...) const; + virtual void session_log(char const* fmt, ...) const; + virtual void session_vlog(char const* fmt, va_list& va) const; + +#if defined TORRENT_VERBOSE_LOGGING + void log_all_torrents(peer_connection* p); +#endif // this list of tracker loggers serves as tracker_callbacks when // shutting down. This list is just here to keep them alive during @@ -1176,10 +1245,13 @@ namespace libtorrent { return m_logpath; } std::string m_logpath; + FILE* m_request_logger; +#endif private: -#endif + counters m_stats_counters; + #ifdef TORRENT_UPNP_LOGGING std::ofstream m_upnp_log; #endif @@ -1192,6 +1264,10 @@ namespace libtorrent ses_extension_list_t m_ses_extensions; #endif + // if this function is set, it indicates that torrents are allowed + // to be unloaded. If it isn't, torrents will never be unloaded + user_load_function_t m_user_load_torrent; + #ifndef TORRENT_DISABLE_GEO_IP GeoIP* m_asnum_db; GeoIP* m_country_db; @@ -1199,14 +1275,14 @@ namespace libtorrent // maps AS number to the peak download rate // we've seen from it. Entries are never removed // from this map. Pointers to its elements - // are kept in the policy::peer structures. + // are kept in the torrent_peer structures. std::map m_as_peak; #endif - // total redundant and failed bytes - size_type m_total_failed_bytes; - size_type m_total_redundant_bytes; - + // this is true whenever we have posted a deferred-disk job + // it means we don't need to post another one + bool m_deferred_submit_disk_jobs; + // this is set to true when a torrent auto-manage // event is triggered, and reset whenever the message // is delivered and the auto-manage is executed. @@ -1228,20 +1304,19 @@ namespace libtorrent // is true if the session is paused bool m_paused; - // is false by default and set to true when - // the first incoming connection is established - // this is used to know if the client is behind - // NAT or not. - bool m_incoming_connection; // redundant bytes per category size_type m_redundant_bytes[7]; std::vector > m_feeds; - // this is the set of (subscribed) torrents that have changed - // their states since the last time the user requested updates. - std::vector > m_state_updates; + // this is a list of peer connections who have been + // corked (i.e. their network socket) and needs to be + // uncorked at the end of the burst of events. This is + // here to coalesce the effects of bursts of events + // into fewer network writes, saving CPU and possibly + // ending up sending larger network packets + std::vector m_delayed_uncorks; // the main working thread boost::scoped_ptr m_thread; @@ -1254,7 +1329,7 @@ namespace libtorrent #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING struct tracker_logger : request_callback { - tracker_logger(session_impl& ses); + tracker_logger(session_interface& ses); void tracker_warning(tracker_request const& req , std::string const& str); void tracker_response(tracker_request const& @@ -1274,7 +1349,7 @@ namespace libtorrent , int response_code, error_code const& ec, const std::string& str , int retry_interval); void debug_log(const char* fmt, ...) const; - session_impl& m_ses; + session_interface& m_ses; }; #endif diff --git a/include/libtorrent/aux_/session_interface.hpp b/include/libtorrent/aux_/session_interface.hpp new file mode 100644 index 000000000..0d69d6e51 --- /dev/null +++ b/include/libtorrent/aux_/session_interface.hpp @@ -0,0 +1,346 @@ +/* + +Copyright (c) 2012, 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_SESSION_INTERFACE_HPP_INCLUDED +#define TORRENT_SESSION_INTERFACE_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/peer_id.hpp" +#include +#include + +#include "libtorrent/address.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/disk_buffer_holder.hpp" + +#ifndef TORRENT_DISABLE_DHT +#include "libtorrent/socket.hpp" +#endif + +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING +#include +#endif + +#ifdef TORRENT_USE_OPENSSL +#include +#endif + +#include "libtorrent/socket.hpp" // for tcp::endpoint + +namespace libtorrent +{ + class peer_connection; + class torrent; + struct proxy_settings; + struct socket_job; +#ifndef TORRENT_NO_DEPRECATE + struct pe_settings; +#endif + struct peer_class_set; + struct bandwidth_channel; + struct bandwidth_manager; + struct peer_class_pool; + struct disk_observer; +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + struct logger; +#endif + struct torrent_peer; + class alert_manager; + struct disk_interface; + struct tracker_request; + struct request_callback; + class connection_queue; + struct utp_socket_manager; + struct socket_type; + struct block_info; + struct external_ip; + struct torrent_handle; + struct ip_filter; + class port_filter; + struct settings_pack; + struct torrent_peer_allocator_interface; + struct counters; + struct resolver_interface; + +#ifndef TORRENT_DISABLE_DHT + namespace dht + { + struct dht_tracker; + } +#endif +} + +namespace libtorrent { namespace aux +{ + // TOOD: make this interface a lot smaller + struct session_interface + : buffer_allocator_interface + { + // TODO: 2 the IP voting mechanism should be factored out + // to its own class, not part of the session + enum + { + source_dht = 1, + source_peer = 2, + source_tracker = 4, + source_router = 8 + }; + + virtual void set_external_address(address const& ip + , int source_type, address const& source) = 0; + virtual external_ip const& external_address() const = 0; + + virtual disk_interface& disk_thread() = 0; + + virtual alert_manager& alerts() = 0; + + virtual torrent_peer_allocator_interface* get_peer_allocator() = 0; + virtual io_service& get_io_service() = 0; + virtual resolver_interface& get_resolver() = 0; + + virtual bool has_connection(peer_connection* p) const = 0; + virtual void insert_peer(boost::shared_ptr const& c) = 0; + + virtual void add_redundant_bytes(size_type b, int reason) = 0; + virtual void add_failed_bytes(size_type b) = 0; + + virtual void queue_async_resume_data(boost::shared_ptr const& t) = 0; + virtual void done_async_resume() = 0; + virtual void evict_torrent(torrent* t) = 0; + + virtual void remove_torrent(torrent_handle const& h, int options = 0) = 0; + virtual void remove_torrent_impl(boost::shared_ptr tptr, int options) = 0; + + // ip and port filter + virtual ip_filter const& get_ip_filter() const = 0; + virtual port_filter const& get_port_filter() const = 0; + + virtual boost::int64_t session_time() const = 0; + + virtual bool is_paused() const = 0; + virtual bool is_aborted() const = 0; + virtual int num_uploads() const = 0; + virtual bool preemptive_unchoke() const = 0; + virtual void unchoke_peer(peer_connection& c) = 0; + virtual void choke_peer(peer_connection& c) = 0; + virtual void trigger_optimistic_unchoke() = 0; + virtual void trigger_unchoke() = 0; + + virtual boost::weak_ptr find_torrent(sha1_hash const& info_hash) const = 0; + virtual boost::weak_ptr find_disconnect_candidate_torrent() const = 0; + virtual boost::shared_ptr delay_load_torrent(sha1_hash const& info_hash + , peer_connection* pc) = 0; + virtual void insert_torrent(sha1_hash const& ih, boost::shared_ptr const& t + , std::string uuid) = 0; + virtual void insert_uuid_torrent(std::string uuid, boost::shared_ptr const& t) = 0; + virtual void set_queue_position(torrent* t, int p) = 0; + virtual int num_torrents() const = 0; + + virtual peer_id const& get_peer_id() const = 0; + + // cork a peer and schedule a delayed uncork + // does nothing if the peer is already corked + virtual void cork_burst(peer_connection* p) = 0; + + virtual void close_connection(peer_connection* p, error_code const& ec, bool cancel_with_cq) = 0; + virtual int num_connections() const = 0; + + virtual char* allocate_buffer() = 0; + virtual void free_buffer(char* buf) = 0; + virtual int send_buffer_size() const = 0; + + virtual void deferred_submit_jobs() = 0; + + virtual boost::uint16_t listen_port() const = 0; + virtual boost::uint16_t ssl_listen_port() const = 0; + + // used to (potentially) issue socket write calls onto multiple threads + virtual void post_socket_job(socket_job& j) = 0; + + // load the specified torrent. also evict one torrent, except + // for the one specified, if we are at the limit of loaded torrents + virtual bool load_torrent(torrent* t) = 0; + + // bump the specified torrent to make it the most recently used one + // in the torrent LRU (i.e. the least likely to get unloaded) + virtual void bump_torrent(torrent* t, bool back = true) = 0; + + // ask for which interface and port to bind outgoing peer connections on + virtual tcp::endpoint bind_outgoing_socket(socket_type& s, address const& + remote_address, error_code& ec) const = 0; + virtual bool verify_bound_address(address const& addr, bool utp + , error_code& ec) = 0; + + // TODO: it would be nice to not have this be part of session_interface + virtual proxy_settings proxy() const = 0; + +#if TORRENT_USE_I2P + virtual proxy_settings i2p_proxy() const = 0; + virtual char const* i2p_session() const = 0; +#endif + + virtual void prioritize_connections(boost::weak_ptr t) = 0; + + virtual tcp::endpoint get_ipv6_interface() const = 0; + virtual tcp::endpoint get_ipv4_interface() const = 0; + + virtual void trigger_auto_manage() = 0; + + virtual void apply_settings_pack(settings_pack* pack) = 0; + virtual session_settings const& settings() const = 0; + + virtual void queue_tracker_request(tracker_request& req + , std::string login, boost::weak_ptr c + , boost::uint32_t key) = 0; + + // peer-classes + virtual void set_peer_classes(peer_class_set* s, address const& a, int st) = 0; + virtual peer_class_pool const& peer_classes() const = 0; + virtual peer_class_pool& peer_classes() = 0; + virtual bool ignore_unchoke_slots_set(peer_class_set const& set) const = 0; + virtual int copy_pertinent_channels(peer_class_set const& set + , int channel, bandwidth_channel** dst, int max) = 0; + virtual int use_quota_overhead(peer_class_set& set, int amount_down, int amount_up) = 0; + + virtual bandwidth_manager* get_bandwidth_manager(int channel) = 0; + + virtual void sent_bytes(int bytes_payload, int bytes_protocol) = 0; + virtual void received_bytes(int bytes_payload, int bytes_protocol) = 0; + virtual void trancieve_ip_packet(int bytes, bool ipv6) = 0; + virtual void sent_syn(bool ipv6) = 0; + virtual void received_synack(bool ipv6) = 0; + + // half-open + virtual bool half_open_done(int ticket) = 0; + + virtual int peak_up_rate() const = 0; + + enum torrent_list_index + { + // this is the set of (subscribed) torrents that have changed + // their states since the last time the user requested updates. + torrent_state_updates, + + // all torrents that want to be ticked every second + torrent_want_tick, + + // all torrents that want more peers and are still downloading + // these typically have higher priority when connecting peers + torrent_want_peers_download, + + // all torrents that want more peers and are finished downloading + torrent_want_peers_finished, + + // torrents that want auto-scrape (only paused auto-managed ones) + torrent_want_scrape, + + // all torrents that have resume data to save +// torrent_want_save_resume, + + num_torrent_lists, + }; + + virtual std::vector& torrent_list(int i) = 0; + + virtual bool has_lsd() const = 0; + virtual void announce_lsd(sha1_hash const& ih, int port, bool broadcast = false) = 0; + virtual connection_queue& half_open() = 0; + virtual libtorrent::utp_socket_manager* utp_socket_manager() = 0; + virtual void inc_boost_connections() = 0; + virtual void setup_socket_buffers(socket_type& s) = 0; + virtual std::vector& block_info_storage() = 0; + +#ifdef TORRENT_USE_OPENSSL + virtual boost::asio::ssl::context* ssl_ctx() = 0 ; +#endif + +#ifndef TORRENT_DISABLE_ENCRYPTION + virtual torrent const* find_encrypted_torrent( + sha1_hash const& info_hash, sha1_hash const& xor_mask) = 0; + virtual void add_obfuscated_hash(sha1_hash const& obfuscated + , boost::weak_ptr const& t) = 0; +#endif + +#ifndef TORRENT_DISABLE_DHT + virtual bool announce_dht() const = 0; + virtual void add_dht_node(udp::endpoint n) = 0; + virtual bool has_dht() const = 0; + virtual int external_udp_port() const = 0; + virtual dht::dht_tracker* dht() = 0; + virtual void prioritize_dht(boost::weak_ptr t) = 0; +#endif + +#ifndef TORRENT_DISABLE_GEO_IP + virtual bool has_asnum_db() const = 0; + virtual bool has_country_db() const = 0; + virtual char const* country_for_ip(address const& a) = 0; + virtual std::string as_name_for_ip(address const& a) = 0; + virtual int as_for_ip(address const& a) = 0; + virtual std::pair* lookup_as(int as) = 0; +#endif + +#if TORRENT_USE_ASSERTS + virtual bool is_single_thread() const = 0; + virtual bool has_peer(peer_connection const* p) const = 0; + virtual bool any_torrent_has_peer(peer_connection const* p) const = 0; + virtual bool is_posting_torrent_updates() const = 0; +#endif + +#ifdef TORRENT_REQUEST_LOGGING + virtual FILE* get_request_log() = 0; +#endif + +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + virtual boost::shared_ptr create_log(std::string const& name + , int instance, bool append = true) = 0; + virtual void session_log(char const* fmt, ...) const = 0; + virtual void session_vlog(char const* fmt, va_list& va) const = 0; + virtual std::string get_log_path() const = 0; +#if defined TORRENT_VERBOSE_LOGGING + virtual void log_all_torrents(peer_connection* p) = 0; +#endif +#endif + +#ifdef TORRENT_BUFFER_STATS + virtual void log_buffer_usage() = 0; + virtual std::ofstream& buffer_usage_logger() = 0; +#endif + + virtual boost::uint64_t inc_stats_counter(int c, int value = 1) = 0; + virtual counters& stats_counters() = 0; + virtual void received_buffer(int size) = 0; + virtual void sent_buffer(int size) = 0; + }; +}} + +#endif + diff --git a/include/libtorrent/aux_/session_settings.hpp b/include/libtorrent/aux_/session_settings.hpp new file mode 100644 index 000000000..b1da71f65 --- /dev/null +++ b/include/libtorrent/aux_/session_settings.hpp @@ -0,0 +1,82 @@ +/* + +Copyright (c) 2012, 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_AUX_SESSION_SETTINGS_HPP_INCLUDED +#define TORRENT_AUX_SESSION_SETTINGS_HPP_INCLUDED + +#include "libtorrent/version.hpp" +#include "libtorrent/config.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/assert.hpp" + +#include + +namespace libtorrent { namespace aux +{ + +#define SET(type) \ + TORRENT_ASSERT((name & settings_pack::type_mask) == settings_pack:: type ## _type_base); \ + if ((name & settings_pack::type_mask) != settings_pack:: type ## _type_base) return; \ + m_ ## type ## s[name - settings_pack:: type ## _type_base] = value + +#define GET(type, default_val) \ + TORRENT_ASSERT((name & settings_pack::type_mask) == settings_pack:: type ## _type_base); \ + if ((name & settings_pack::type_mask) != settings_pack:: type ## _type_base) return default_val; \ + return m_ ## type ## s[name - settings_pack:: type ## _type_base] + + struct session_settings + { + friend void libtorrent::save_settings_to_dict( + aux::session_settings const& s, entry::dictionary_type& sett); + + void set_str(int name, std::string const& value) { SET(string); } + std::string const& get_str(int name) const { GET(string, m_strings[0]); } + void set_int(int name, int value) { SET(int); } + int get_int(int name) const { GET(int, 0); } + void set_bool(int name, bool value) { SET(bool); } + bool get_bool(int name) const { GET(bool, false); } + + session_settings() { initialize_default_settings(*this); } + + private: + std::string m_strings[settings_pack::num_string_settings]; + int m_ints[settings_pack::num_int_settings]; + bool m_bools[settings_pack::num_bool_settings]; + }; + +#undef GET +#undef SET + +} } + +#endif + diff --git a/include/libtorrent/bandwidth_manager.hpp b/include/libtorrent/bandwidth_manager.hpp index 975ccca8f..87da4f839 100644 --- a/include/libtorrent/bandwidth_manager.hpp +++ b/include/libtorrent/bandwidth_manager.hpp @@ -33,7 +33,7 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_BANDWIDTH_MANAGER_HPP_INCLUDED #define TORRENT_BANDWIDTH_MANAGER_HPP_INCLUDED -#include +#include #ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT #include @@ -47,10 +47,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/bandwidth_queue_entry.hpp" #include "libtorrent/thread.hpp" #include "libtorrent/bandwidth_socket.hpp" -#include "libtorrent/ptime.hpp" - -using boost::intrusive_ptr; - +#include "libtorrent/time.hpp" namespace libtorrent { @@ -76,13 +73,8 @@ struct TORRENT_EXTRA_EXPORT bandwidth_manager // this is used by web seeds // returns the number of bytes to assign to the peer, or 0 // if the peer's 'assign_bandwidth' callback will be called later - int request_bandwidth(intrusive_ptr const& peer - , int blk, int priority - , bandwidth_channel* chan1 = 0 - , bandwidth_channel* chan2 = 0 - , bandwidth_channel* chan3 = 0 - , bandwidth_channel* chan4 = 0 - , bandwidth_channel* chan5 = 0); + int request_bandwidth(boost::shared_ptr const& peer + , int blk, int priority, bandwidth_channel** chan, int num_channels); #if TORRENT_USE_INVARIANT_CHECKS void check_invariant() const; diff --git a/include/libtorrent/bandwidth_queue_entry.hpp b/include/libtorrent/bandwidth_queue_entry.hpp index 641e1ebc4..ba9d5476c 100644 --- a/include/libtorrent/bandwidth_queue_entry.hpp +++ b/include/libtorrent/bandwidth_queue_entry.hpp @@ -33,7 +33,7 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_BANDWIDTH_QUEUE_ENTRY_HPP_INCLUDED #define TORRENT_BANDWIDTH_QUEUE_ENTRY_HPP_INCLUDED -#include +#include #include "libtorrent/bandwidth_limit.hpp" #include "libtorrent/bandwidth_socket.hpp" @@ -41,10 +41,10 @@ namespace libtorrent { struct TORRENT_EXTRA_EXPORT bw_request { - bw_request(boost::intrusive_ptr const& pe + bw_request(boost::shared_ptr const& pe , int blk, int prio); - boost::intrusive_ptr peer; + boost::shared_ptr peer; // 1 is normal prio int priority; // the number of bytes assigned to this request so far @@ -62,8 +62,8 @@ struct TORRENT_EXTRA_EXPORT bw_request // from the most limiting one int assign_bandwidth(); - enum { max_bandwidth_channels = 5 }; - // we don't actually support more than 5 channels per peer + enum { max_bandwidth_channels = 10 }; + // we don't actually support more than 10 channels per peer bandwidth_channel* channel[max_bandwidth_channels]; }; diff --git a/include/libtorrent/bandwidth_socket.hpp b/include/libtorrent/bandwidth_socket.hpp index 486f8fac9..111427110 100644 --- a/include/libtorrent/bandwidth_socket.hpp +++ b/include/libtorrent/bandwidth_socket.hpp @@ -34,12 +34,9 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_BANDWIDTH_SOCKET_HPP_INCLUDED #define TORRENT_BANDWIDTH_SOCKET_HPP_INCLUDED -#include "libtorrent/intrusive_ptr_base.hpp" - namespace libtorrent { struct bandwidth_socket - : public intrusive_ptr_base { virtual void assign_bandwidth(int channel, int amount) = 0; virtual bool is_disconnecting() const = 0; diff --git a/include/libtorrent/bitfield.hpp b/include/libtorrent/bitfield.hpp index eff90b715..a9ea3766a 100644 --- a/include/libtorrent/bitfield.hpp +++ b/include/libtorrent/bitfield.hpp @@ -35,11 +35,18 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/assert.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/byteswap.hpp" +#include "libtorrent/cpuid.hpp" + #include // for memset and memcpy #include // for malloc, free and realloc #include // uint32_t #include // for min() +#ifdef _MSC_VER +#include +#endif + namespace libtorrent { // The bitfiled type stores any number of bits as a bitfield @@ -54,95 +61,102 @@ namespace libtorrent // The constructor taking a pointer ``b`` and ``bits`` copies a bitfield // from the specified buffer, and ``bits`` number of bits (rounded up to // the nearest byte boundry). - bitfield(): m_bytes(0), m_size(0), m_own(false) {} - bitfield(int bits): m_bytes(0), m_size(0), m_own(false) + bitfield(): m_buf(NULL) {} + bitfield(int bits): m_buf(NULL) { resize(bits); } - bitfield(int bits, bool val): m_bytes(0), m_size(0), m_own(false) + bitfield(int bits, bool val): m_buf(NULL) { resize(bits, val); } - bitfield(char const* b, int bits): m_bytes(0), m_size(0), m_own(false) + bitfield(char const* b, int bits): m_buf(NULL) { assign(b, bits); } - bitfield(bitfield const& rhs): m_bytes(0), m_size(0), m_own(false) + bitfield(bitfield const& rhs): m_buf(NULL) { assign(rhs.bytes(), rhs.size()); } #if __cplusplus > 199711L - bitfield(bitfield&& rhs): m_bytes(rhs.m_bytes), m_size(rhs.m_size), m_own(rhs.m_own) - { rhs.m_bytes = NULL; } + bitfield(bitfield&& rhs): m_buf(rhs.m_buf) + { rhs.m_buf = NULL; } #endif - // assigns a bitfield pointed to ``b`` of ``bits`` number of bits, without - // taking ownership of the buffer. This is a way to avoid copying data and - // yet provide a raw buffer to functions that may operate on the bitfield - // type. It is the user's responsibility to make sure the passed-in buffer's - // life time exceeds all uses of the bitfield. - void borrow_bytes(char* b, int bits) - { - dealloc(); - m_bytes = (unsigned char*)b; - m_size = bits; - m_own = false; - } - // hidden ~bitfield() { dealloc(); } // copy bitfield from buffer ``b`` of ``bits`` number of bits, rounded up to // the nearest byte boundary. void assign(char const* b, int bits) - { resize(bits); std::memcpy(m_bytes, b, (bits + 7) / 8); clear_trailing_bits(); } + { + resize(bits); + if (bits > 0) + { + std::memcpy(m_buf, b, ((bits + 7) / 8)); + clear_trailing_bits(); + } + } // query bit at ``index``. Returns true if bit is 1, otherwise false. bool operator[](int index) const { return get_bit(index); } + bool get_bit(int index) const { TORRENT_ASSERT(index >= 0); - TORRENT_ASSERT(index < m_size); - return (m_bytes[index / 8] & (0x80 >> (index & 7))) != 0; + TORRENT_ASSERT(index < size()); + return (m_buf[index / 32] & htonl((0x80000000 >> (index & 31)))) != 0; } // set bit at ``index`` to 0 (clear_bit) or 1 (set_bit). void clear_bit(int index) { TORRENT_ASSERT(index >= 0); - TORRENT_ASSERT(index < m_size); - m_bytes[index / 8] &= ~(0x80 >> (index & 7)); + TORRENT_ASSERT(index < size()); + m_buf[index / 32] &= htonl(~(0x80000000 >> (index & 31))); } void set_bit(int index) { TORRENT_ASSERT(index >= 0); - TORRENT_ASSERT(index < m_size); - m_bytes[index / 8] |= (0x80 >> (index & 7)); + TORRENT_ASSERT(index < size()); + m_buf[index / 32] |= htonl((0x80000000 >> (index & 31))); } // returns true if all bits in the bitfield are set bool all_set() const { - const int num_words = m_size / 32; - const int num_bytes = m_size / 8; - boost::uint32_t* bits = (boost::uint32_t*)m_bytes; - for (int i = 0; i < num_words; ++i) + const int words = size() / 32; + for (int i = 0; i < words; ++i) { - if (bits[i] != 0xffffffff) return false; + if (m_buf[i] != 0xffffffff) return false; } - - for (int i = num_words * 4; i < num_bytes; ++i) - { - if (m_bytes[i] != 0xff) return false; - } - int rest = m_size - num_bytes * 8; - boost::uint8_t mask = (0xff << (8-rest)) & 0xff; - if (rest > 0 && (m_bytes[num_bytes] & mask) != mask) + int rest = size() & 31; + boost::uint32_t mask = htonl(0xffffffff << (32-rest)); + if (rest > 0 && (m_buf[words] & mask) != mask) return false; return true; } + bool none_set() const + { + const int words = num_words(); + for (int i = 0; i < words; ++i) + { + if (m_buf[i] != 0) return false; + } + return true; + } + // returns the size of the bitfield in bits. - std::size_t size() const { return m_size; } + int size() const + { + return m_buf == NULL ? 0 : m_buf[-1]; + } + + int num_words() const + { + return (size() + 31) / 32; + } // returns true if the bitfield has zero size. - bool empty() const { return m_size == 0; } + bool empty() const { return m_buf == NULL ? true : m_buf[-1] == 0; } // returns a pointer to the internal buffer of the bitfield. - char const* bytes() const { return (char*)m_bytes; } + // TODO: rename to data() ? + char const* bytes() const { return (char const*)m_buf; } // copy operator bitfield& operator=(bitfield const& rhs) @@ -154,27 +168,43 @@ namespace libtorrent // count the number of bits in the bitfield that are set to 1. int count() const { - // 0000, 0001, 0010, 0011, 0100, 0101, 0110, 0111, - // 1000, 1001, 1010, 1011, 1100, 1101, 1110, 1111 - const static char num_bits[] = - { - 0, 1, 1, 2, 1, 2, 2, 3, - 1, 2, 2, 3, 2, 3, 3, 4 - }; - int ret = 0; - const int num_bytes = m_size / 8; - for (int i = 0; i < num_bytes; ++i) + const int words = num_words(); +#if TORRENT_HAS_SSE + unsigned int cpui[4]; + cpuid(cpui, 1); + if (cpui[2] & (1 << 23)) { - ret += num_bits[m_bytes[i] & 0xf] + num_bits[m_bytes[i] >> 4]; + for (int i = 0; i < words; ++i) + { +#ifdef __GNUC__ + ret += __builtin_popcount(m_buf[i]); +#else + ret += _mm_popcnt_u32(m_buf[i]); +#endif + } + + return ret; + } +#endif // TORRENT_HAS_SSE + + for (int i = 0; i < words; ++i) + { + boost::uint32_t v = m_buf[i]; + // from: + // http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel + static const int S[] = {1, 2, 4, 8, 16}; // Magic Binary Numbers + static const int B[] = {0x55555555, 0x33333333, 0x0F0F0F0F, 0x00FF00FF, 0x0000FFFF}; + + boost::uint32_t c = v - ((v >> 1) & B[0]); + c = ((c >> S[1]) & B[1]) + (c & B[1]); + c = ((c >> S[2]) + c) & B[2]; + c = ((c >> S[3]) + c) & B[3]; + c = ((c >> S[4]) + c) & B[4]; + ret += c; } - int rest = m_size - num_bytes * 8; - for (int i = 0; i < rest; ++i) - { - ret += (m_bytes[num_bytes] >> (7-i)) & 1; - } - TORRENT_ASSERT(ret <= m_size); + TORRENT_ASSERT(ret <= size()); TORRENT_ASSERT(ret >= 0); return ret; } @@ -189,7 +219,7 @@ namespace libtorrent typedef bool& reference; typedef std::forward_iterator_tag iterator_category; - bool operator*() { return (*byte & bit) != 0; } + bool operator*() { return (*buf & htonl(bit)) != 0; } const_iterator& operator++() { inc(); return *this; } const_iterator operator++(int) { const_iterator ret(*this); inc(); return ret; } @@ -197,21 +227,21 @@ namespace libtorrent const_iterator operator--(int) { const_iterator ret(*this); dec(); return ret; } - const_iterator(): byte(0), bit(0x80) {} + const_iterator(): buf(0), bit(0x80000000) {} bool operator==(const_iterator const& rhs) const - { return byte == rhs.byte && bit == rhs.bit; } + { return buf == rhs.buf && bit == rhs.bit; } bool operator!=(const_iterator const& rhs) const - { return byte != rhs.byte || bit != rhs.bit; } + { return buf != rhs.buf || bit != rhs.bit; } private: void inc() { - TORRENT_ASSERT(byte); + TORRENT_ASSERT(buf); if (bit == 0x01) { - bit = 0x80; - ++byte; + bit = 0x80000000; + ++buf; } else { @@ -220,103 +250,115 @@ namespace libtorrent } void dec() { - TORRENT_ASSERT(byte); - if (bit == 0x80) + TORRENT_ASSERT(buf); + if (bit == 0x80000000) { bit = 0x01; - --byte; + --buf; } else { bit <<= 1; } } - const_iterator(unsigned char const* ptr, int offset) - : byte(ptr), bit(0x80 >> offset) {} - unsigned char const* byte; - int bit; + const_iterator(boost::uint32_t const* ptr, int offset) + : buf(ptr), bit(0x80000000 >> offset) {} + boost::uint32_t const* buf; + boost::uint32_t bit; }; - const_iterator begin() const { return const_iterator(m_bytes, 0); } - const_iterator end() const { return const_iterator(m_bytes + m_size / 8, m_size & 7); } + const_iterator begin() const { return const_iterator(m_buf, 0); } + const_iterator end() const { return const_iterator( + m_buf + num_words() - (((size() & 31) == 0) ? 0 : 1), size() & 31); } // set the size of the bitfield to ``bits`` length. If the bitfield is extended, // the new bits are initialized to ``val``. void resize(int bits, bool val) { - int s = m_size; - int b = m_size & 7; + if (bits == size()) return; + + int s = size(); + int b = size() & 31; resize(bits); - if (s >= m_size) return; - int old_size_bytes = (s + 7) / 8; - int new_size_bytes = (m_size + 7) / 8; + if (s >= size()) return; + int old_size_words = (s + 31) / 32; + int new_size_words = num_words(); if (val) { - if (old_size_bytes && b) m_bytes[old_size_bytes - 1] |= (0xff >> b); - if (old_size_bytes < new_size_bytes) - std::memset(m_bytes + old_size_bytes, 0xff, new_size_bytes - old_size_bytes); + if (old_size_words && b) m_buf[old_size_words - 1] |= htonl((0xffffffff >> b)); + if (old_size_words < new_size_words) + std::memset(m_buf + old_size_words, 0xff, (new_size_words - old_size_words) * 4); clear_trailing_bits(); } else { - if (old_size_bytes < new_size_bytes) - std::memset(m_bytes + old_size_bytes, 0x00, new_size_bytes - old_size_bytes); + if (old_size_words < new_size_words) + std::memset(m_buf + old_size_words, 0x00, (new_size_words - old_size_words) * 4); } + TORRENT_ASSERT(size() == bits); } + void resize(int bits) { + if (bits == size()) return; + TORRENT_ASSERT(bits >= 0); - const int b = (bits + 7) / 8; - if (m_bytes) + // +1 because the first word is the size (in bits) + const int b = (bits + 31) / 32; + if (m_buf) { - if (m_own) - { - m_bytes = (unsigned char*)std::realloc(m_bytes, b); - m_own = true; - } - else if (bits > m_size) - { - unsigned char* tmp = (unsigned char*)std::malloc(b); - std::memcpy(tmp, m_bytes, (std::min)(int(m_size + 7)/ 8, b)); - m_bytes = tmp; - m_own = true; - } + m_buf = (boost::uint32_t*)std::realloc(m_buf-1, (b+1) * 4); + m_buf = m_buf + 1; + m_buf[-1] = bits; } else if (bits > 0) { - m_bytes = (unsigned char*)std::malloc(b); - m_own = true; + m_buf = (boost::uint32_t*)std::malloc((b+1) * 4); + m_buf = m_buf + 1; + m_buf[-1] = bits; + } + else if (m_buf != NULL) + { + std::free(m_buf-1); + m_buf = NULL; } - m_size = bits; clear_trailing_bits(); + TORRENT_ASSERT(size() == bits); } // set all bits in the bitfield to 1 (set_all) or 0 (clear_all). void set_all() { - std::memset(m_bytes, 0xff, (m_size + 7) / 8); + std::memset(m_buf, 0xff, num_words() * 4); clear_trailing_bits(); } void clear_all() { - std::memset(m_bytes, 0x00, (m_size + 7) / 8); + std::memset(m_buf, 0x00, num_words() * 4); } // make the bitfield empty, of zero size. - void clear() { dealloc(); m_size = 0; } + void clear() { dealloc(); } private: void clear_trailing_bits() { // clear the tail bits in the last byte - if (m_size & 7) m_bytes[(m_size + 7) / 8 - 1] &= 0xff << (8 - (m_size & 7)); + if (size() & 31) m_buf[num_words() - 1] &= htonl(0xffffffff << (32 - (size() & 31))); } - void dealloc() { if (m_own) std::free(m_bytes); m_bytes = 0; } - unsigned char* m_bytes; - int m_size:31; // in bits - bool m_own:1; + void dealloc() + { + if (m_buf) std::free(m_buf-1); + m_buf = NULL; + } + + // the first element is not part of the bitfield, it's the + // number of bits. For this purpose, the m_buf actually points + // the element 1, not 0. To access the size (in bits), access + // m_buf[-1] + boost::uint32_t* m_buf; }; } diff --git a/include/libtorrent/block_cache.hpp b/include/libtorrent/block_cache.hpp new file mode 100644 index 000000000..f97cfa070 --- /dev/null +++ b/include/libtorrent/block_cache.hpp @@ -0,0 +1,535 @@ +/* + +Copyright (c) 2010-2013, 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_BLOCK_CACHE +#define TORRENT_BLOCK_CACHE + +#include +#include +#include +#include +#include +#include + +#include "libtorrent/time.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/io_service_fwd.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/sliding_average.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/tailqueue.hpp" +#include "libtorrent/linked_list.hpp" +#include "libtorrent/disk_buffer_pool.hpp" +#include "libtorrent/file.hpp" // for iovec_t + +#if TORRENT_USE_ASSERTS +#include "libtorrent/disk_io_job.hpp" +#endif + +namespace libtorrent +{ + struct disk_io_job; + class piece_manager; + struct disk_buffer_pool; + struct cache_status; + struct block_cache_reference; + struct counters; + namespace aux { struct session_settings; } + struct alert_dispatcher; +#if TORRENT_USE_ASSERTS + class file_storage; +#endif + +#if TORRENT_USE_ASSERTS + struct piece_log_t + { + piece_log_t(int j, int b= -1): job(j), block(b) {} + int job; + int block; + + // these are "jobs" thar cause piece_refcount + // to be incremented + enum artificial_jobs + { + flushing = disk_io_job::num_job_ids, // 20 + flush_expired, + try_flush_write_blocks, + try_flush_write_blocks2, + flush_range, + clear_outstanding_jobs, + set_outstanding_jobs, + + last_job + }; + + static char const* job_names[7]; + }; + + char const* job_name(int j); + + void print_piece_log(std::vector const& piece_log); + +#endif + + struct TORRENT_EXTRA_EXPORT partial_hash + { + partial_hash(): offset(0) {} + // the number of bytes in the piece that has been hashed + int offset; + // the sha-1 context + hasher h; + }; + + struct cached_block_entry + { + cached_block_entry() + : buf(0) + , refcount(0) + , dirty(false) + , hitcount(0) + , pending(false) + { +#if TORRENT_USE_ASSERTS + hashing_count = 0; + reading_count = 0; + flushing_count = 0; +#endif + } + + char* buf; + + enum { max_refcount = (1 << 15) -1 }; + + // the number of references to this buffer. These references + // might be in outstanding asyncronous requests or in peer + // connection send buffers. We can't free the buffer until + // all references are gone and refcount reaches 0. The buf + // pointer in this struct doesn't count as a reference and + // is always the last to be cleared + boost::uint16_t refcount:15; + + // if this is true, this block needs to be written to + // disk before it's freed. Typically all blocks in a piece + // would either be dirty (write coalesce cache) or not dirty + // (read-ahead cache). Once blocks are written to disk, the + // dirty flag is cleared and effectively turns the block + // into a read cache block + boost::uint16_t dirty:1; + + // the number of times this block has been copied out of + // the cache, serving a request. + boost::uint16_t hitcount:15; + + // pending means that this buffer has not yet been filled in + // with valid data. There's an outstanding read job for this. + // If the dirty flag is set, it means there's an outstanding + // write job to write this block. + boost::uint16_t pending:1; + +#if TORRENT_USE_ASSERTS + // this many of the references are held by hashing operations + int hashing_count; + // this block is being used in this many peer's send buffers currently + int reading_count; + // the number of references held by flushing operations + int flushing_count; +#endif + }; + + // list_node is here to be able to link this cache entry + // into one of the LRU lists + struct TORRENT_EXTRA_EXPORT cached_piece_entry : list_node + { + cached_piece_entry(); + ~cached_piece_entry(); + + bool ok_to_evict(bool ignore_hash = false) const + { + return refcount == 0 + && piece_refcount == 0 + && num_blocks == 0 + && !hashing + && read_jobs.size() == 0 + && (ignore_hash || !hash || hash->offset == 0); + } + + // storage this piece belongs to + boost::shared_ptr storage; + + // write jobs hanging off of this piece + tailqueue jobs; + + // read jobs waiting for the read job currently outstanding + // on this piece to complete. These are executed at that point. + tailqueue read_jobs; + + int get_piece() const { return piece; } + void* get_storage() const { return storage.get(); } + + bool operator==(cached_piece_entry const& rhs) const + { return storage.get() == rhs.storage.get() && piece == rhs.piece; } + + // if this is set, we'll be calculating the hash + // for this piece. This member stores the interim + // state while we're calulcating the hash. + partial_hash* hash; + + // set to a unique identifier of a peer that last + // requested from this piece. + void* last_requester; + + // the pointers to the block data. If this is a ghost + // cache entry, there won't be any data here + // TODO: 3 could this be a scoped_array instead? does cached_piece_entry really need to be copyable? + // cached_piece_entry does need to be copyable since it's part of a container, but it's possible + // it could be a raw pointer or boost::unique_ptr perhaps + boost::shared_array blocks; + + // the last time a block was written to this piece + // plus the minimum amount of time the block is guaranteed + // to stay in the cache + //TODO: make this 32 bits and to count seconds since the block cache was created + ptime expire; + + boost::uint64_t piece:22; + + // the number of dirty blocks in this piece + boost::uint64_t num_dirty:14; + + // the number of blocks in the cache for this piece + boost::uint64_t num_blocks:14; + + // the total number of blocks in this piece (and the number + // of elements in the blocks array) + boost::uint64_t blocks_in_piece:14; + + // ---- 64 bit boundary ---- + + // while we have an outstanding async hash operation + // working on this piece, 'hashing' is set to 1 + // When the operation returns, this is set to 0. + boost::uint32_t hashing:1; + + // if we've completed at least one hash job on this + // piece, and returned it. This is set to one + boost::uint32_t hashing_done:1; + + // if this is true, whenever refcount hits 0, + // this piece should be deleted + boost::uint32_t marked_for_deletion:1; + + // this is set to true once we flush blocks past + // the hash cursor. Once this happens, there's + // no point in keeping cache blocks around for + // it in avoid_readback mode + boost::uint32_t need_readback:1; + + // indicates which LRU list this piece is chained into + enum cache_state_t + { + // this is the LRU list for pieces with dirty blocks + write_lru, + + // this is the LRU list for volatile pieces. i.e. + // pieces with very low cache priority. These are + // always the first ones to be evicted. + volatile_read_lru, + + // this is the LRU list for read blocks that have + // been requested once + read_lru1, + + // the is the LRU list for read blocks that have + // been requested once recently, but then was evicted. + // if these are requested again, they will be moved + // to list 2, the frequently requested pieces + read_lru1_ghost, + + // this is the LRU of frequently used pieces. Any + // piece that has been requested by a second peer + // while pulled in to list 1 by a different peer + // is moved into this list + read_lru2, + + // this is the LRU of frequently used pieces but + // that has been recently evicted. If a piece in + // this list is requested, it's moved back into list 2. + read_lru2_ghost, + num_lrus + }; + + boost::uint32_t cache_state:3; + + // this is the number of threads that are currently holding + // a reference to this piece. A piece may not be removed from + // the cache while this is > 0 + boost::uint32_t piece_refcount:7; + + // if this is set to one, it means there is an outstanding + // flush_hashed job for this piece, and there's no need to + // issue another one. + boost::uint32_t outstanding_flush:1; + + // as long as there is a read operation outstanding on this + // piece, this is set to 1. Otherwise 0. + // the purpose is to make sure not two threads are reading + // the same blocks at the same time. If a new read job is + // added when this is 1, that new job should be hung on the + // read job queue (read_jobs). + boost::uint32_t outstanding_read:1; + + // the number of blocks that have >= 1 refcount + boost::uint32_t pinned:16; + + // ---- 32 bit boundary --- + + // the sum of all refcounts in all blocks + boost::uint32_t refcount; + +#if TORRENT_USE_ASSERTS + // the number of times this piece has finished hashing + int hash_passes; + + // this is a debug facility to keep a log + // of which operations have been run on this piece + std::vector piece_log; + + bool in_storage; + bool in_use; +#endif + }; + + inline std::size_t hash_value(cached_piece_entry const& p) + { + return std::size_t(p.storage.get()) + p.piece; + } + + struct TORRENT_EXTRA_EXPORT block_cache : disk_buffer_pool + { + block_cache(int block_size, io_service& ios + , boost::function const& trigger_trim + , alert_dispatcher* alert_disp); + + private: + + typedef boost::unordered_set cache_t; + + public: + + typedef cache_t::iterator iterator; + typedef cache_t::const_iterator const_iterator; + + // returns the number of blocks this job would cause to be read in + int pad_job(disk_io_job const* j, int blocks_in_piece + , int read_ahead) const; + + int allocate_iovec(file::iovec_t* iov, int iov_len); + void free_iovec(file::iovec_t* iov, int iov_len); + + void reclaim_block(block_cache_reference const& ref); + + // returns a range of all pieces. This migh be a very + // long list, use carefully + std::pair all_pieces() const; + int num_pieces() const { return m_pieces.size(); } + + list_iterator write_lru_pieces() const + { return m_lru[cached_piece_entry::write_lru].iterate(); } + + int num_write_lru_pieces() const { return m_lru[cached_piece_entry::write_lru].size(); } + + // mark this piece for deletion. If there are no outstanding + // requests to this piece, it's removed immediately, and the + // passed in iterator will be invalidated + void mark_for_deletion(cached_piece_entry* p); + + // similar to mark_for_deletion, except for actually marking the + // piece for deletion. If the piece was actually deleted, + // the function returns true + bool evict_piece(cached_piece_entry* p, tailqueue& jobs); + + // if this piece is in L1 or L2 proper, move it to + // its respective ghost list + void move_to_ghost(cached_piece_entry* p); + + // returns the number of bytes read on success (cache hit) + // -1 on cache miss + int try_read(disk_io_job* j, bool count_stats = true, bool expect_no_fail = false); + + // called when we're reading and we found the piece we're + // reading from in the hash table (not necessarily that we + // hit the block we needed) + void cache_hit(cached_piece_entry* p, void* requester, bool volatile_read); + + // free block from piece entry + void free_block(cached_piece_entry* pe, int block); + + // erase a piece (typically from the ghost list). Reclaim all + // its blocks and unlink it and free it. + void erase_piece(cached_piece_entry* p); + + // bump the piece 'p' to the back of the LRU list it's + // in (back == MRU) + // this is only used for the write cache + void bump_lru(cached_piece_entry* p); + + // move p into the correct lru queue + void update_cache_state(cached_piece_entry* p); + + // if the piece is marked for deletion and has a refcount + // of 0, this function will post any sync jobs and + // delete the piece from the cache + bool maybe_free_piece(cached_piece_entry* p); + + // either returns the piece in the cache, or allocates + // a new empty piece and returns it. + // cache_state is one of cache_state_t enum + cached_piece_entry* allocate_piece(disk_io_job const* j, int cache_state); + + // looks for this piece in the cache. If it's there, returns a pointer + // to it, otherwise 0. + cached_piece_entry* find_piece(block_cache_reference const& ref); + cached_piece_entry* find_piece(disk_io_job const* j); + cached_piece_entry* find_piece(piece_manager* st, int piece); + + // clear free all buffers marked as dirty with + // refcount of 0. + void abort_dirty(cached_piece_entry* p); + + // used to convert dirty blocks into non-dirty ones + // i.e. from being part of the write cache to being part + // of the read cache. it's used when flushing blocks to disk + void blocks_flushed(cached_piece_entry* pe, int const* flushed, int num_flushed); + + // adds a block to the cache, marks it as dirty and + // associates the job with it. When the block is + // flushed, the callback is posted + cached_piece_entry* add_dirty_block(disk_io_job* j); + + enum { blocks_inc_refcount = 1 }; + void insert_blocks(cached_piece_entry* pe, int block, file::iovec_t *iov + , int iov_len, disk_io_job* j, int flags = 0); + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const; +#endif + + // try to remove num number of read cache blocks from the cache + // pick the least recently used ones first + // return the number of blocks that was requested to be evicted + // that couldn't be + int try_evict_blocks(int num, cached_piece_entry* ignore = 0); + + // if there are any dirty blocks + void clear(tailqueue& jobs); + + void update_stats_counters(counters& c) const; + void get_stats(cache_status* ret) const; + void set_settings(aux::session_settings const& sett); + + enum reason_t { ref_hashing = 0, ref_reading = 1, ref_flushing = 2 }; + bool inc_block_refcount(cached_piece_entry* pe, int block, int reason); + void dec_block_refcount(cached_piece_entry* pe, int block, int reason); + + int pinned_blocks() const { return m_pinned_blocks; } + +#if TORRENT_USE_ASSERTS + void mark_deleted(file_storage const& fs); +#endif + + private: + + // returns number of bytes read on success, -1 on cache miss + // (just because the piece is in the cache, doesn't mean all + // the blocks are there) + int copy_from_piece(cached_piece_entry* p, disk_io_job* j, bool expect_no_fail = false); + + void free_piece(cached_piece_entry* p); + int drain_piece_bufs(cached_piece_entry& p, std::vector& buf); + + // block container + cache_t m_pieces; + + // linked list of all elements in m_pieces, in usage order + // the most recently used are in the tail. iterating from head + // to tail gives the least recently used entries first + // the read-list is for read blocks and the write-list is for + // dirty blocks that needs flushing before being evicted + // [0] = write-LRU + // [1] = read-LRU1 + // [2] = read-LRU1-ghost + // [3] = read-LRU2 + // [4] = read-LRU2-ghost + linked_list m_lru[cached_piece_entry::num_lrus]; + + // this is used to determine whether to evict blocks from + // L1 or L2. + enum cache_op_t + { + cache_miss, + ghost_hit_lru1, + ghost_hit_lru2 + }; + int m_last_cache_op; + + // the number of pieces to keep in the ARC ghost lists + // this is determined by being a fraction of the cache size + int m_ghost_size; + + // the number of blocks in the cache + // that are in the read cache + boost::uint32_t m_read_cache_size; + // the number of blocks in the cache + // that are in the write cache + boost::uint32_t m_write_cache_size; + + // the number of blocks that are currently sitting + // in peer's send buffers. If two peers are sending + // the same block, it counts as 2, even though there're + // no buffer duplication + boost::uint32_t m_send_buffer_blocks; + + boost::uint32_t m_blocks_read_hit; + + // the number of blocks with a refcount > 0, i.e. + // they may not be evicted + int m_pinned_blocks; + +#if TORRENT_USE_ASSERTS + std::vector > m_deleted_storages; +#endif + }; + +} + +#endif // TORRENT_BLOCK_CACHE + diff --git a/include/libtorrent/bt_peer_connection.hpp b/include/libtorrent/bt_peer_connection.hpp index e8b7b618e..7e216d97f 100644 --- a/include/libtorrent/bt_peer_connection.hpp +++ b/include/libtorrent/bt_peer_connection.hpp @@ -86,13 +86,15 @@ namespace libtorrent // The peer_conenction should handshake and verify that the // other end has the correct id bt_peer_connection( - aux::session_impl& ses + aux::session_interface& ses + , aux::session_settings const& sett + , buffer_allocator_interface& allocator + , disk_interface& disk_thread , boost::shared_ptr s , tcp::endpoint const& remote - , policy::peer* peerinfo + , torrent_peer* peerinfo , peer_id const& pid - , boost::weak_ptr t = boost::weak_ptr() - , bool outgoing = false); + , boost::weak_ptr t = boost::weak_ptr()); void start(); @@ -225,8 +227,9 @@ namespace libtorrent void write_cancel(peer_request const& r); void write_bitfield(); void write_have(int index); + void write_dont_have(int index); void write_piece(peer_request const& r, disk_buffer_holder& buffer); - void write_handshake(); + void write_handshake(bool plain_handshake = false); #ifndef TORRENT_DISABLE_EXTENSIONS void write_extensions(); void write_upload_only(); @@ -299,22 +302,22 @@ public: // 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 - virtual void append_const_send_buffer(char const* buffer, int size); + virtual void append_const_send_buffer(char const* buffer, int size + , chained_buffer::free_buffer_fun destructor = &nop + , void* userdata = NULL, block_cache_reference ref + = block_cache_reference()); + virtual void send_buffer(char const* begin, int size, int flags = 0 , void (*fun)(char*, int, void*) = 0, void* userdata = 0); - template - void bt_append_send_buffer(char* buffer, int size, Destructor const& destructor) - { -#ifndef TORRENT_DISABLE_ENCRYPTION - if (m_rc4_encrypted) - m_enc_handler->encrypt(buffer, size); -#endif - peer_connection::append_send_buffer(buffer, size, destructor, true); - } + + virtual void append_send_buffer(char* buffer, int size + , chained_buffer::free_buffer_fun destructor = &nop + , void* userdata = NULL, block_cache_reference ref + = block_cache_reference(), bool encrypted = false); private: - enum state + enum state_t { #ifndef TORRENT_DISABLE_ENCRYPTION read_pe_dhkey = 0, @@ -345,7 +348,7 @@ private: }; #endif - // state of on_receive + // state of on_receive. one of the enums in state_t boost::uint8_t m_state; // this is set to true if the handshake from @@ -355,15 +358,15 @@ private: bool m_supports_dht_port:1; bool m_supports_fast:1; -#if TORRENT_USE_ASSERTS - // this is set to true when the client's - // bitfield is sent to this peer + // this is set to true when we send the bitfield message. + // for magnet links we can't do that right away, + // since we don't know how many pieces there are in + // the torrent. bool m_sent_bitfield:1; - bool m_in_constructor:1; - + // true if we're done sending the bittorrent handshake, + // and can send bittorrent messages bool m_sent_handshake:1; -#endif #ifndef TORRENT_DISABLE_ENCRYPTION // this is set to true after the encryption method has been @@ -375,19 +378,8 @@ private: bool m_rc4_encrypted:1; #endif -#ifndef TORRENT_DISABLE_EXTENSIONS - // the message ID for upload only message - // 0 if not supported - boost::uint8_t m_upload_only_id; - - // the message ID for holepunch messages - boost::uint8_t m_holepunch_id; -#endif - std::string m_client_version; - static const message_handler m_message_handler[num_supported_messages]; - // the peer ID we advertise for ourself peer_id m_our_peer_id; @@ -408,13 +400,8 @@ private: int start; int length; }; - static bool range_below_zero(const range& r) - { return r.start < 0; } - std::vector m_payloads; - // we have suggested these pieces to the peer - // don't suggest it again - bitfield m_sent_suggested_pieces; + std::vector m_payloads; #ifndef TORRENT_DISABLE_ENCRYPTION // initialized during write_pe1_2_dhkey, and destroyed on @@ -436,13 +423,24 @@ private: // the sync hash (hash("req1",secret)). Destroyed after the // sync step. boost::scoped_ptr m_sync_hash; +#endif // #ifndef TORRENT_DISABLE_ENCRYPTION + static const message_handler m_message_handler[num_supported_messages]; + +#ifndef TORRENT_DISABLE_ENCRYPTION // used to disconnect peer if sync points are not found within // the maximum number of bytes int m_sync_bytes_read; -#endif // #ifndef TORRENT_DISABLE_ENCRYPTION +#endif #ifndef TORRENT_DISABLE_EXTENSIONS + // the message ID for upload only message + // 0 if not supported + boost::uint8_t m_upload_only_id; + + // the message ID for holepunch messages + boost::uint8_t m_holepunch_id; + // the message ID for don't-have message boost::uint8_t m_dont_have_id; @@ -450,10 +448,13 @@ private: // 0 if not supported boost::uint8_t m_share_mode_id; - // the reserved bits received from the other peer - // in the bittorrent handshake char m_reserved_bits[8]; #endif + +#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS + bool m_in_constructor; +#endif + }; } diff --git a/include/libtorrent/buffer.hpp b/include/libtorrent/buffer.hpp index 85b354afd..ea6369f95 100644 --- a/include/libtorrent/buffer.hpp +++ b/include/libtorrent/buffer.hpp @@ -36,6 +36,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/invariant_check.hpp" #include "libtorrent/assert.hpp" #include // malloc/free/realloc +#include namespace libtorrent { @@ -98,16 +99,16 @@ public: buffer(std::size_t n = 0) : m_begin(0) - , m_end(0) - , m_last(0) + , m_size(0) + , m_capacity(0) { if (n) resize(n); } buffer(buffer const& b) : m_begin(0) - , m_end(0) - , m_last(0) + , m_size(0) + , m_capacity(0) { if (b.size() == 0) return; resize(b.size()); @@ -115,8 +116,14 @@ public: } #if __cplusplus > 199711L - buffer(buffer&& b): m_begin(b.m_begin), m_end(b.m_end), m_last(b.m_last) - { b.m_begin = b.m_end = b.m_last = NULL; } + buffer(buffer&& b) + : m_begin(b.m_begin) + , m_size(b.m_size) + , m_capacity(b.m_capacity) + { + b.m_begin = NULL; + b.m_size = b.m_capacity = 0; + } #endif buffer& operator=(buffer const& b) @@ -133,19 +140,21 @@ public: std::free(m_begin); } - buffer::interval data() { return interval(m_begin, m_end); } - buffer::const_interval data() const { return const_interval(m_begin, m_end); } + buffer::interval data() + { return interval(m_begin, m_begin + m_size); } + buffer::const_interval data() const + { return const_interval(m_begin, m_begin + m_size); } void resize(std::size_t n) { reserve(n); - m_end = m_begin + n; + m_size = n; } void insert(char* point, char const* first, char const* last) { std::size_t p = point - m_begin; - if (point == m_end) + if (point == m_begin + m_size) { resize(size() + last - first); std::memcpy(m_begin + p, first, last - first); @@ -159,52 +168,51 @@ public: void erase(char* b, char* e) { - TORRENT_ASSERT(e <= m_end); + TORRENT_ASSERT(e <= m_begin + m_size); TORRENT_ASSERT(b >= m_begin); TORRENT_ASSERT(b <= e); - if (e == m_end) + if (e == m_begin + m_size) { resize(b - m_begin); return; } - std::memmove(b, e, m_end - e); - m_end = b + (m_end - e); + std::memmove(b, e, m_begin + m_size - e); + TORRENT_ASSERT(e - b <= m_size); + m_size -= e - b; } - void clear() { m_end = m_begin; } - std::size_t size() const { return m_end - m_begin; } - std::size_t capacity() const { return m_last - m_begin; } + void clear() { m_size = 0; } + std::size_t size() const { return m_size; } + std::size_t capacity() const { return m_capacity; } void reserve(std::size_t n) { if (n <= capacity()) return; TORRENT_ASSERT(n > 0); - std::size_t s = size(); m_begin = (char*)std::realloc(m_begin, n); - m_end = m_begin + s; - m_last = m_begin + n; + m_capacity = n; } - bool empty() const { return m_begin == m_end; } + bool empty() const { return m_size == 0; } char& operator[](std::size_t i) { TORRENT_ASSERT(i < size()); return m_begin[i]; } char const& operator[](std::size_t i) const { TORRENT_ASSERT(i < size()); return m_begin[i]; } char* begin() { return m_begin; } char const* begin() const { return m_begin; } - char* end() { return m_end; } - char const* end() const { return m_end; } + char* end() { return m_begin + m_size; } + char const* end() const { return m_begin + m_size; } void swap(buffer& b) { using std::swap; swap(m_begin, b.m_begin); - swap(m_end, b.m_end); - swap(m_last, b.m_last); + swap(m_size, b.m_size); + swap(m_capacity, b.m_capacity); } private: - char* m_begin; // first - char* m_end; // one passed end of size - char* m_last; // one passed end of allocation + char* m_begin; + boost::uint32_t m_size; + boost::uint32_t m_capacity; }; diff --git a/include/libtorrent/byteswap.hpp b/include/libtorrent/byteswap.hpp new file mode 100644 index 000000000..6a6b1eb9f --- /dev/null +++ b/include/libtorrent/byteswap.hpp @@ -0,0 +1,50 @@ +/* + +Copyright (c) 2013, 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_BYTESWAP_HPP_INCLUDED +#define TORRENT_BYTESWAP_HPP_INCLUDED + +// this header makes sure htonl(), nothl(), htons() and ntohs() +// are available + +#include "libtorrent/config.hpp" + +#ifdef TORRENT_WINDOWS +#include +#else +// posix header +// for ntohl and htonl +#include +#endif + +#endif // TORRENT_BYTESWAP_HPP_INCLUDED + diff --git a/include/libtorrent/chained_buffer.hpp b/include/libtorrent/chained_buffer.hpp index 7fd5bf437..e508c4e69 100644 --- a/include/libtorrent/chained_buffer.hpp +++ b/include/libtorrent/chained_buffer.hpp @@ -35,37 +35,47 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/config.hpp" -#include #include #if BOOST_VERSION < 103500 #include #else #include #endif -#include +#include +#include #include // for memcpy +#include "libtorrent/disk_io_job.hpp" // for block_cache_reference +#include "libtorrent/debug.hpp" + namespace libtorrent { #if BOOST_VERSION >= 103500 namespace asio = boost::asio; #endif - struct TORRENT_EXTRA_EXPORT chained_buffer + struct TORRENT_EXTRA_EXPORT chained_buffer : private single_threaded { chained_buffer(): m_bytes(0), m_capacity(0) { + thread_started(); #if TORRENT_USE_ASSERTS m_destructed = false; #endif } + // destructs/frees the buffer (1st arg) with + // 2nd argument as userdata + typedef void (*free_buffer_fun)(char*, void*, block_cache_reference ref); + struct buffer_t { - boost::function free; // destructs the buffer + free_buffer_fun free_fun; + void* userdata; char* buf; // the first byte of the buffer char* start; // the first byte to send/receive in the buffer int size; // the total size of the buffer int used_size; // this is the number of bytes to send/receive + block_cache_reference ref; }; bool empty() const { return m_bytes == 0; } @@ -75,7 +85,8 @@ namespace libtorrent void pop_front(int bytes_to_pop); void append_buffer(char* buffer, int s, int used_size - , boost::function const& destructor); + , free_buffer_fun destructor, void* userdata + , block_cache_reference ref = block_cache_reference()); // returns the number of bytes available at the // end of the last chained buffer. @@ -91,7 +102,9 @@ namespace libtorrent // enough room, returns 0 char* allocate_appendix(int s); - std::list const& build_iovec(int to_send); + std::vector const& build_iovec(int to_send); + + void clear(); ~chained_buffer(); @@ -99,11 +112,7 @@ namespace libtorrent // this is the list of all the buffers we want to // send - std::list m_vec; - - // this is the vector of buffers used when - // invoking the async write call - std::list m_tmp_vec; + std::deque m_vec; // this is the number of bytes in the send buf. // this will always be equal to the sum of the @@ -114,6 +123,10 @@ namespace libtorrent // including unused space int m_capacity; + // this is the vector of buffers used when + // invoking the async write call + std::vector m_tmp_vec; + #if TORRENT_USE_ASSERTS bool m_destructed; #endif diff --git a/include/libtorrent/config.hpp b/include/libtorrent/config.hpp index 00cd32b22..ec84fd927 100644 --- a/include/libtorrent/config.hpp +++ b/include/libtorrent/config.hpp @@ -33,6 +33,8 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_CONFIG_HPP_INCLUDED #define TORRENT_CONFIG_HPP_INCLUDED +#define _FILE_OFFSET_BITS 64 + #if !defined _MSC_VER || _MSC_VER >= 1600 #ifndef __STDC_LIMIT_MACROS #define __STDC_LIMIT_MACROS 1 @@ -43,11 +45,16 @@ POSSIBILITY OF SUCH DAMAGE. #endif #include +#include #include #include #include // for snprintf #include // for IOV_MAX +#ifdef __linux__ +#include // for LINUX_VERSION_CODE and KERNEL_VERSION +#endif // __linux + #if defined TORRENT_DEBUG_BUFFERS && !defined TORRENT_DISABLE_POOL_ALLOCATOR #error TORRENT_DEBUG_BUFFERS only works if you also disable pool allocators with TORRENT_DISABLE_POOL_ALLOCATOR #endif @@ -169,8 +176,6 @@ POSSIBILITY OF SUCH DAMAGE. #if defined __AMIGA__ || defined __amigaos__ || defined __AROS__ #define TORRENT_AMIGA #define TORRENT_USE_MLOCK 0 -#define TORRENT_USE_WRITEV 0 -#define TORRENT_USE_READV 0 #define TORRENT_USE_IPV6 0 #define TORRENT_USE_BOOST_THREAD 0 #define TORRENT_USE_IOSTREAM 0 @@ -190,17 +195,20 @@ POSSIBILITY OF SUCH DAMAGE. // we don't need iconv on mac, because // the locale is always utf-8 #if defined __APPLE__ -#ifndef TORRENT_USE_ICONV -#define TORRENT_USE_ICONV 0 -#define TORRENT_USE_LOCALE 0 -#define TORRENT_CLOSE_MAY_BLOCK 1 +# define TORRENT_USE_OSATOMIC 1 +# ifndef TORRENT_USE_ICONV +# define TORRENT_USE_ICONV 0 +# define TORRENT_USE_LOCALE 0 +# endif #include +#define TORRENT_USE_PURGABLE_CONTROL 1 + #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 -#ifdef TORRENT_USE_OPENSSL -#define TORRENT_USE_COMMONCRYPTO 1 -#endif // TORRENT_USE_OPENSSL +# ifdef TORRENT_USE_OPENSSL +# define TORRENT_USE_COMMONCRYPTO 1 +# endif // TORRENT_USE_OPENSSL #endif // MAC_OS_X_VERSION_MIN_REQUIRED // execinfo.h is available in the MacOS X 10.5 SDK. @@ -208,16 +216,18 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_USE_EXECINFO 1 #endif -#endif // __APPLE__ - -#else +#else // __APPLE__ // FreeBSD has a reasonable iconv signature // unless we're on glibc #ifndef __GLIBC__ # define TORRENT_ICONV_ARG (const char**) #endif -#endif +#endif // __APPLE__ + +#define TORRENT_HAVE_MMAP 1 + #define TORRENT_HAS_FALLOCATE 0 + #define TORRENT_USE_IFADDRS 1 #define TORRENT_USE_SYSCTL 1 #define TORRENT_USE_IFCONF 1 @@ -226,6 +236,16 @@ POSSIBILITY OF SUCH DAMAGE. // ==== LINUX === #elif defined __linux__ #define TORRENT_LINUX + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30) +# define TORRENT_USE_PREADV 1 +# define TORRENT_USE_PREAD 0 +#else +# define TORRENT_USE_PREADV 0 +# define TORRENT_USE_PREAD 1 +#endif + +#define TORRENT_HAVE_MMAP 1 #define TORRENT_USE_NETLINK 1 #define TORRENT_USE_IFCONF 1 #define TORRENT_HAS_SALEN 0 @@ -237,10 +257,12 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_USE_ICONV 0 #define TORRENT_USE_IFADDRS 0 #define TORRENT_USE_MEMALIGN 1 -#else +#define TORRENT_HAVE_FDATASYNC 0 +#else // ANDROID #define TORRENT_USE_IFADDRS 1 #define TORRENT_USE_POSIX_MEMALIGN 1 -#endif +#define TORRENT_HAVE_FDATASYNC 1 +#endif // ANDROID #if __amd64__ || __i386__ #define TORRENT_USE_EXECINFO 1 @@ -259,28 +281,36 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_USE_GETADAPTERSADDRESSES 1 #define TORRENT_HAS_SALEN 0 #define TORRENT_USE_GETIPFORWARDTABLE 1 +#define TORRENT_USE_INTERLOCKED_ATOMIC 1 #ifndef TORRENT_USE_UNC_PATHS # define TORRENT_USE_UNC_PATHS 1 #endif +// these are emulated on windows +#define TORRENT_USE_PREADV 1 +#define TORRENT_USE_PWRITEV 1 // ==== WINDOWS === #elif defined WIN32 #define TORRENT_WINDOWS #ifndef TORRENT_USE_GETIPFORWARDTABLE -#define TORRENT_USE_GETIPFORWARDTABLE 1 +# define TORRENT_USE_GETIPFORWARDTABLE 1 #endif #define TORRENT_USE_GETADAPTERSADDRESSES 1 #define TORRENT_HAS_SALEN 0 // windows has its own functions to convert #ifndef TORRENT_USE_ICONV -#define TORRENT_USE_ICONV 0 -#define TORRENT_USE_LOCALE 1 +# define TORRENT_USE_ICONV 0 +# define TORRENT_USE_LOCALE 1 #endif #define TORRENT_USE_RLIMIT 0 #define TORRENT_HAS_FALLOCATE 0 +#define TORRENT_USE_INTERLOCKED_ATOMIC 1 #ifndef TORRENT_USE_UNC_PATHS -#define TORRENT_USE_UNC_PATHS 1 +# define TORRENT_USE_UNC_PATHS 1 #endif +// these are emulated on windows +#define TORRENT_USE_PREADV 1 +#define TORRENT_USE_PWRITEV 1 // ==== SOLARIS === #elif defined sun || defined __sun @@ -288,6 +318,9 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_COMPLETE_TYPES_REQUIRED 1 #define TORRENT_USE_IFCONF 1 #define TORRENT_HAS_SALEN 0 +#define TORRENT_HAS_SEM_RELTIMEDWAIT 1 +#define TORRENT_HAVE_MMAP 1 +#define TORRENT_USE_SOLARIS_ATOMIC 1 // ==== BEOS === #elif defined __BEOS__ || defined __HAIKU__ @@ -295,6 +328,7 @@ POSSIBILITY OF SUCH DAMAGE. #include // B_PATH_NAME_LENGTH #define TORRENT_HAS_FALLOCATE 0 #define TORRENT_USE_MLOCK 0 +#define TORRENT_USE_BEOS_ATOMIC 1 #ifndef TORRENT_USE_ICONV #define TORRENT_USE_ICONV 0 #endif @@ -322,6 +356,16 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_BSD #endif +#if defined __GNUC__ && !(defined TORRENT_USE_OSATOMIC \ + || defined TORRENT_USE_INTERLOCKED_ATOMIC \ + || defined TORRENT_USE_BEOS_ATOMIC \ + || defined TORRENT_USE_SOLARIS_ATOMIC) +// atomic operations in GCC were introduced in 4.1.1 +# if (__GNUC__ >= 4 && __GNUC_MINOR__ >= 1 && __GNUC_PATCHLEVEL__ >= 1) || __GNUC__ > 4 +# define TORRENT_USE_GCC_ATOMIC 1 +# endif +#endif + // on windows, NAME_MAX refers to Unicode characters // on linux it refers to bytes (utf-8 encoded) // TODO: Make this count Unicode characters instead of bytes on windows @@ -386,7 +430,23 @@ int snprintf(char* buf, int len, char const* fmt, ...) #define TORRENT_ICONV_ARG (char**) #endif -// libiconv presence, not implemented yet +#ifndef TORRENT_USE_INTERLOCKED_ATOMIC +#define TORRENT_USE_INTERLOCKED_ATOMIC 0 +#endif + +#ifndef TORRENT_USE_GCC_ATOMIC +#define TORRENT_USE_GCC_ATOMIC 0 +#endif + +#ifndef TORRENT_USE_OSATOMIC +#define TORRENT_USE_OSATOMIC 0 +#endif + +#ifndef TORRENT_USE_BEOS_ATOMIC +#define TORRENT_USE_BEOS_ATOMIC 0 +#endif + +// libiconv presence detection is not implemented yet #ifndef TORRENT_USE_ICONV #define TORRENT_USE_ICONV 1 #endif @@ -415,16 +475,12 @@ int snprintf(char* buf, int len, char const* fmt, ...) #define TORRENT_USE_GETIPFORWARDTABLE 0 #endif -#ifndef TORRENT_USE_LOCALE -#define TORRENT_USE_LOCALE 0 +#ifndef TORRENT_HAS_SEM_RELTIMEDWAIT +#define TORRENT_HAS_SEM_RELTIMEDWAIT 0 #endif -// set this to true if close() may block on your system -// Mac OS X does this if the file being closed is not fully -// allocated on disk yet for instance. When defined, the disk -// I/O subsytem will use a separate thread for closing files -#ifndef TORRENT_CLOSE_MAY_BLOCK -#define TORRENT_CLOSE_MAY_BLOCK 0 +#ifndef TORRENT_USE_LOCALE +#define TORRENT_USE_LOCALE 0 #endif #ifndef TORRENT_BROKEN_UNIONS @@ -459,10 +515,18 @@ int snprintf(char* buf, int len, char const* fmt, ...) #define TORRENT_DEPRECATED #endif +#ifndef TORRENT_HAVE_MMAP +#define TORRENT_HAVE_MMAP 0 +#endif + #ifndef TORRENT_COMPLETE_TYPES_REQUIRED #define TORRENT_COMPLETE_TYPES_REQUIRED 0 #endif +#ifndef TORRENT_USE_FDATASYNC +#define TORRENT_USE_FDATASYNC 0 +#endif + #ifndef TORRENT_USE_UNC_PATHS #define TORRENT_USE_UNC_PATHS 0 #endif @@ -483,12 +547,14 @@ int snprintf(char* buf, int len, char const* fmt, ...) #define TORRENT_USE_MLOCK 1 #endif -#ifndef TORRENT_USE_WRITEV -#define TORRENT_USE_WRITEV 1 +// if preadv() exists, we assume pwritev() does as well +#ifndef TORRENT_USE_PREADV +#define TORRENT_USE_PREADV 0 #endif -#ifndef TORRENT_USE_READV -#define TORRENT_USE_READV 1 +// if pread() exists, we assume pwrite() does as well +#ifndef TORRENT_USE_PREAD +#define TORRENT_USE_PREAD 1 #endif #ifndef TORRENT_NO_FPU @@ -513,6 +579,14 @@ int snprintf(char* buf, int len, char const* fmt, ...) #define TORRENT_USE_I2P 1 #endif +#ifndef TORRENT_HAS_BOOST_UNORDERED +#define TORRENT_HAS_BOOST_UNORDERED 1 +#endif + +#ifndef TORRENT_USE_PURGABLE_CONTROL +#define TORRENT_USE_PURGABLE_CONTROL 0 +#endif + #if !defined TORRENT_IOV_MAX #ifdef IOV_MAX #define TORRENT_IOV_MAX IOV_MAX @@ -549,6 +623,13 @@ int snprintf(char* buf, int len, char const* fmt, ...) #define TORRENT_UNION union #endif +#if defined __GNUC__ +#define TORRENT_FUNCTION __PRETTY_FUNCTION__ +#else +#define TORRENT_FUNCTION __FUNCTION__ +#endif + + // determine what timer implementation we can use // if one is already defined, don't pick one // autmatically. This lets the user control this @@ -605,6 +686,20 @@ int snprintf(char* buf, int len, char const* fmt, ...) #define TORRENT_DECLARE_DUMMY(x, y) #endif // BOOST_NO_EXCEPTIONS +// SSE is x86 / amd64 specific. On top of that, we only +// know how to access it on msvc and gcc (and gcc compatibles). +// GCC requires the user to enable SSE support in order for +// the program to have access to the intrinsics, this is +// indicated by the __SSE4_1__ macro +#if (defined _M_AMD64 || defined _M_IX86 || defined _M_X64 \ + || defined __amd64__ || defined __i386 || defined __i386__ \ + || defined __x86_64__ || defined __x86_64) \ + && (defined __GNUC__ || defined _MSC_VER) +#define TORRENT_HAS_SSE 1 +#else +#define TORRENT_HAS_SSE 0 +#endif + #endif // TORRENT_CONFIG_HPP_INCLUDED diff --git a/include/libtorrent/connection_interface.hpp b/include/libtorrent/connection_interface.hpp new file mode 100644 index 000000000..c5e142277 --- /dev/null +++ b/include/libtorrent/connection_interface.hpp @@ -0,0 +1,55 @@ +/* + +Copyright (c) 2012-2013, 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_CONNECTION_INTERFACE_HPP +#define TORRENT_CONNECTION_INTERFACE_HPP + +namespace libtorrent +{ + struct connection_interface + { + // called when the connection may be initiated + // this is when the timeout countdown starts + virtual void on_allow_connect(int ticket) = 0; + + // called if done() hasn't been called within the timeout + // or if the connection queue aborts. This means there + // are 3 different interleaves of these function calls: + // 1. on_connect + // 2. on_connect, on_timeout + // 3. on_timeout + virtual void on_connect_timeout() = 0; + }; +} + +#endif + diff --git a/include/libtorrent/connection_queue.hpp b/include/libtorrent/connection_queue.hpp index 73be10d7a..22fc68afd 100644 --- a/include/libtorrent/connection_queue.hpp +++ b/include/libtorrent/connection_queue.hpp @@ -30,12 +30,11 @@ POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TORRENT_CONNECTION_QUEUE -#define TORRENT_CONNECTION_QUEUE +#ifndef TORRENT_CONNECTION_QUEUE_HPP +#define TORRENT_CONNECTION_QUEUE_HPP -#include -#include -#include +#include +#include #include #include "libtorrent/io_service.hpp" #include "libtorrent/error_code.hpp" @@ -46,11 +45,16 @@ POSSIBILITY OF SUCH DAMAGE. #endif #include "libtorrent/thread.hpp" +#include "libtorrent/debug.hpp" namespace libtorrent { -class TORRENT_EXTRA_EXPORT connection_queue : public boost::noncopyable +struct connection_interface; + +class TORRENT_EXTRA_EXPORT connection_queue + : public boost::noncopyable + , single_threaded { public: connection_queue(io_service& ios); @@ -59,25 +63,24 @@ public: // number of queued up connections int free_slots() const; - void enqueue(boost::function const& on_connect - , boost::function const& on_timeout + void enqueue(connection_interface* conn , time_duration timeout, int priority = 0); + bool cancel(connection_interface* conn); bool done(int ticket); void limit(int limit); int limit() const; void close(); int size() const { return m_queue.size(); } - int num_connecting() const { return m_num_connecting; } + int num_connecting() const { return int(m_connecting.size()); } #if defined TORRENT_ASIO_DEBUGGING float next_timeout() const { return total_milliseconds(m_timer.expires_at() - time_now_hires()) / 1000.f; } float max_timeout() const { ptime max_timeout = min_time(); - for (std::list::const_iterator i = m_queue.begin() - , end(m_queue.end()); i != end; ++i) + for (std::map::const_iterator i = m_connecting.begin() + , end(m_connecting.end()); i != end; ++i) { - if (!i->connecting) continue; - if (i->expires > max_timeout) max_timeout = i->expires; + if (i->second.expires > max_timeout) max_timeout = i->second.expires; } if (max_timeout == min_time()) return 0.f; return total_milliseconds(max_timeout - time_now_hires()) / 1000.f; @@ -90,52 +93,39 @@ public: private: - typedef mutex mutex_t; - - void try_connect(mutex_t::scoped_lock& l); + void try_connect(); void on_timeout(error_code const& e); void on_try_connect(); - struct entry + struct queue_entry { - entry() - : expires(max_time()) - , ticket(0) - , connecting(false) - , priority(0) - {} - // called when the connection is initiated - // this is when the timeout countdown starts - boost::function on_connect; - // called if done hasn't been called within the timeout - // or if the connection queue aborts. This means there - // are 3 different interleaves of these function calls: - // 1. on_connect - // 2. on_connect, on_timeout - // 3. on_timeout - boost::function on_timeout; - ptime expires; + queue_entry(): conn(0), priority(0) {} + connection_interface* conn; time_duration timeout; boost::int32_t ticket; bool connecting; boost::uint8_t priority; }; + struct connect_entry + { + connect_entry(): conn(0), expires(max_time()), priority(0) {} + connection_interface* conn; + ptime expires; + int priority; + }; - std::list m_queue; + std::vector m_queue; + std::map m_connecting; // the next ticket id a connection will be given int m_next_ticket; - int m_num_connecting; int m_half_open_limit; - bool m_abort; // the number of outstanding timers int m_num_timers; deadline_timer m_timer; - mutable mutex_t m_mutex; - #ifdef TORRENT_DEBUG bool m_in_timeout_function; #endif diff --git a/include/libtorrent/cpuid.hpp b/include/libtorrent/cpuid.hpp new file mode 100644 index 000000000..768170c1b --- /dev/null +++ b/include/libtorrent/cpuid.hpp @@ -0,0 +1,63 @@ +/* + +Copyright (c) 2014, 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_CPUID_HPP_INCLUDED +#define TORRENT_CPUID_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include + +#if defined _MSC_VER && TORRENT_HAS_SSE +#include +#include +#endif + +namespace libtorrent +{ + inline void cpuid(unsigned int info[4], int type) + { +#if TORRENT_HAS_SSE && defined _MSC_VER + __cpuid((int*)info, type); + +#elif TORRENT_HAS_SSE && defined __GNUC__ + asm volatile + ("cpuid" : "=a" (info[0]), "=b" (info[1]), "=c" (info[2]), "=d" (info[3]) + : "a" (type), "c" (0)); +#else + // for non-x86 and non-amd64, just return zeroes + std::memset(info, 0, sizeof(info)); +#endif + } +} + +#endif // TORRENT_CPUID_HPP_INCLUDED + diff --git a/include/libtorrent/crc32c.hpp b/include/libtorrent/crc32c.hpp new file mode 100644 index 000000000..7b786b64a --- /dev/null +++ b/include/libtorrent/crc32c.hpp @@ -0,0 +1,48 @@ +/* + +Copyright (c) 2014, 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_CRC32C_HPP_INCLUDE +#define TORRENT_CRC32C_HPP_INCLUDE + +#include + +namespace libtorrent +{ + + // this is the crc32c (Castagnoli) polynomial + boost::uint32_t crc32c_32(boost::uint32_t v); + boost::uint32_t crc32c(boost::uint64_t const* v, int num_words); +} + +#endif + + diff --git a/include/libtorrent/create_torrent.hpp b/include/libtorrent/create_torrent.hpp index e1983b28a..9eedc5958 100644 --- a/include/libtorrent/create_torrent.hpp +++ b/include/libtorrent/create_torrent.hpp @@ -417,7 +417,7 @@ namespace libtorrent // The overloads that don't take an ``error_code&`` may throw an exception in case of a // file error, the other overloads sets the error code to reflect the error, if any. TORRENT_EXPORT void set_piece_hashes(create_torrent& t, std::string const& p - , boost::function f, error_code& ec); + , boost::function const& f, error_code& ec); inline void set_piece_hashes(create_torrent& t, std::string const& p, error_code& ec) { set_piece_hashes(t, p, detail::nop, ec); @@ -465,8 +465,13 @@ namespace libtorrent , filename(utf8), detail::default_pred, flags); } - void TORRENT_EXPORT set_piece_hashes(create_torrent& t, std::wstring const& p - , boost::function const& f, error_code& ec); + inline void set_piece_hashes(create_torrent& t, std::wstring const& p, boost::function f + , error_code& ec) + { + std::string utf8; + wchar_utf8(p, utf8); + set_piece_hashes(t, utf8, f, ec); + } #ifndef BOOST_NO_EXCEPTIONS template diff --git a/include/libtorrent/deadline_timer.hpp b/include/libtorrent/deadline_timer.hpp index 71c52c796..354cc854c 100644 --- a/include/libtorrent/deadline_timer.hpp +++ b/include/libtorrent/deadline_timer.hpp @@ -33,70 +33,12 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_DEADLINE_TIMER_HPP_INCLUDED #define TORRENT_DEADLINE_TIMER_HPP_INCLUDED -#ifdef __OBJC__ -#define Protocol Protocol_ -#endif - -#if __GNUC__ < 3 -// in GCC 2.95 templates seems to have all symbols -// resolved as they are parsed, so the time_traits -// template actually needs the definitions it uses, -// even though it's never instantiated -#include -#else -#include -#endif - -#if BOOST_VERSION < 103500 -#include -#else -#include -#endif - -#ifdef __OBJC__ -#undef Protocol -#endif - -#include "libtorrent/time.hpp" - -// asio time_traits -#if !TORRENT_USE_BOOST_DATE_TIME -#if BOOST_VERSION >= 103500 -namespace boost { -#endif -namespace asio -{ - template<> - struct time_traits - { - typedef libtorrent::ptime time_type; - typedef libtorrent::time_duration duration_type; - static time_type now() - { return time_type(libtorrent::time_now_hires()); } - static time_type add(time_type t, duration_type d) - { return time_type(t.time + d.diff);} - static duration_type subtract(time_type t1, time_type t2) - { return duration_type(t1 - t2); } - static bool less_than(time_type t1, time_type t2) - { return t1 < t2; } - static boost::posix_time::time_duration to_posix_duration( - duration_type d) - { return boost::posix_time::microseconds(libtorrent::total_microseconds(d)); } - }; -} -#if BOOST_VERSION >= 103500 -} -#endif -#endif +#include namespace libtorrent { -#if BOOST_VERSION < 103500 - typedef ::asio::basic_deadline_timer deadline_timer; -#else - typedef boost::asio::basic_deadline_timer deadline_timer; -#endif + typedef boost::asio::high_resolution_timer deadline_timer; } #endif // TORRENT_DEADLINE_TIMER_HPP_INCLUDED diff --git a/include/libtorrent/debug.hpp b/include/libtorrent/debug.hpp index 02a2af49c..00660f844 100644 --- a/include/libtorrent/debug.hpp +++ b/include/libtorrent/debug.hpp @@ -33,13 +33,27 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_DEBUG_HPP_INCLUDED #define TORRENT_DEBUG_HPP_INCLUDED +#include "libtorrent/config.hpp" + +#if TORRENT_USE_ASSERTS && defined BOOST_HAS_PTHREADS +#include +#endif + #if defined TORRENT_ASIO_DEBUGGING #include "libtorrent/assert.hpp" #include "libtorrent/thread.hpp" +#include "libtorrent/time.hpp" #include #include +#include + +#ifdef __MACH__ +#include +#include +#include +#endif std::string demangle(char const* name); @@ -52,10 +66,27 @@ namespace libtorrent int refs; }; + // defined in session_impl.cpp extern std::map _async_ops; extern int _async_ops_nthreads; extern mutex _async_ops_mutex; + // timestamp -> operation + struct wakeup_t + { + ptime timestamp; + boost::uint64_t context_switches; + char const* operation; + }; + extern std::deque _wakeups; + + inline bool has_outstanding_async(char const* name) + { + mutex::scoped_lock l(_async_ops_mutex); + std::map::iterator i = _async_ops.find(name); + return i != _async_ops.end(); + } + inline void add_outstanding_async(char const* name) { mutex::scoped_lock l(_async_ops_mutex); @@ -64,7 +95,12 @@ namespace libtorrent { char stack_text[10000]; print_backtrace(stack_text, sizeof(stack_text), 9); - a.stack = stack_text; + + // skip the stack frame of 'add_outstanding_async' + char* ptr = strchr(stack_text, '\n'); + if (ptr != NULL) ++ptr; + else ptr = stack_text; + a.stack = ptr; } ++a.refs; } @@ -75,6 +111,19 @@ namespace libtorrent async_t& a = _async_ops[name]; TORRENT_ASSERT(a.refs > 0); --a.refs; + _wakeups.push_back(wakeup_t()); + wakeup_t& w = _wakeups.back(); + w.timestamp = time_now_hires(); +#ifdef __MACH__ + task_events_info teinfo; + mach_msg_type_number_t t_info_count = TASK_EVENTS_INFO_COUNT; + task_info(mach_task_self(), TASK_EVENTS_INFO, (task_info_t)&teinfo + , &t_info_count); + w.context_switches = teinfo.csw; +#else + w.context_switches = 0; +#endif + w.operation = name; } inline void async_inc_threads() @@ -104,7 +153,44 @@ namespace libtorrent } } +#endif // TORRENT_ASIO_DEBUGGING + +namespace libtorrent +{ +#if TORRENT_USE_ASSERTS && defined BOOST_HAS_PTHREADS + struct single_threaded + { + single_threaded(): m_single_thread(0) {} + ~single_threaded() { m_single_thread = 0; } + bool is_single_thread() const + { + if (m_single_thread == 0) + { + m_single_thread = pthread_self(); + return true; + } + return m_single_thread == pthread_self(); + } + bool is_not_thread() const + { + if (m_single_thread == 0) return true; + return m_single_thread != pthread_self(); + } + + void thread_started() + { m_single_thread = pthread_self(); } + + private: + mutable pthread_t m_single_thread; + }; +#else + struct single_threaded { + bool is_single_thread() const { return true; } + void thread_started() {} + bool is_not_thread() const {return true; } + }; #endif +} #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING @@ -123,6 +209,8 @@ namespace libtorrent { // DEBUG API + // TODO: rewrite this class to use FILE* instead and + // have a printf-like interface struct logger { #if TORRENT_USE_IOSTREAM diff --git a/include/libtorrent/disk_buffer_holder.hpp b/include/libtorrent/disk_buffer_holder.hpp index 80905cf7b..caf47a318 100644 --- a/include/libtorrent/disk_buffer_holder.hpp +++ b/include/libtorrent/disk_buffer_holder.hpp @@ -35,13 +35,27 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/config.hpp" #include "libtorrent/assert.hpp" +#include "libtorrent/disk_io_job.hpp" // for block_cache_reference #include +#include +#include namespace libtorrent { + struct disk_io_thread; + struct disk_observer; - namespace aux { struct session_impl; } - struct disk_buffer_pool; + struct buffer_allocator_interface + { + virtual char* allocate_disk_buffer(char const* category) = 0; + virtual void free_disk_buffer(char* b) = 0; + virtual void reclaim_block(block_cache_reference ref) = 0; + virtual char* allocate_disk_buffer(bool& exceeded + , boost::shared_ptr o + , char const* category) = 0; + virtual char* async_allocate_disk_buffer(char const* category + , boost::function const& handler) = 0; + }; // The disk buffer holder acts like a ``scoped_ptr`` that frees a disk buffer // when it's destructed, unless it's released. ``release`` returns the disk @@ -54,12 +68,12 @@ namespace libtorrent struct TORRENT_EXPORT disk_buffer_holder { // internal - disk_buffer_holder(aux::session_impl& ses, char* buf); + disk_buffer_holder(buffer_allocator_interface& alloc, char* buf); // construct a buffer holder that will free the held buffer // using a disk buffer pool directly (there's only one // disk_buffer_pool per session) - disk_buffer_holder(disk_buffer_pool& disk_pool, char* buf); + disk_buffer_holder(buffer_allocator_interface& alloc, disk_io_job const& j); // frees any unreleased disk buffer held by this object ~disk_buffer_holder(); @@ -76,14 +90,18 @@ namespace libtorrent // (or NULL by default). If it's already holding a // disk buffer, it will first be freed. void reset(char* buf = 0); + void reset(disk_io_job const& j); // swap pointers of two disk buffer holders. void swap(disk_buffer_holder& h) { - TORRENT_ASSERT(&h.m_disk_pool == &m_disk_pool); + TORRENT_ASSERT(&h.m_allocator == &m_allocator); std::swap(h.m_buf, m_buf); + std::swap(h.m_ref, m_ref); } + block_cache_reference ref() const { return m_ref; } + typedef char* (disk_buffer_holder::*unspecified_bool_type)(); // internal @@ -91,8 +109,9 @@ namespace libtorrent { return m_buf == 0? 0: &disk_buffer_holder::release; } private: - disk_buffer_pool& m_disk_pool; + buffer_allocator_interface& m_allocator; char* m_buf; + block_cache_reference m_ref; }; } diff --git a/include/libtorrent/disk_buffer_pool.hpp b/include/libtorrent/disk_buffer_pool.hpp index 9df167e20..9b9f4bda7 100644 --- a/include/libtorrent/disk_buffer_pool.hpp +++ b/include/libtorrent/disk_buffer_pool.hpp @@ -30,44 +30,57 @@ POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TORRENT_DISK_BUFFER_POOL -#define TORRENT_DISK_BUFFER_POOL +#ifndef TORRENT_DISK_BUFFER_POOL_HPP +#define TORRENT_DISK_BUFFER_POOL_HPP #include #include "libtorrent/config.hpp" #include "libtorrent/thread.hpp" -#include "libtorrent/session_settings.hpp" -#include "libtorrent/allocator.hpp" +#include "libtorrent/io_service_fwd.hpp" +#include +#include +#include + +#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS +#include +#endif #ifndef TORRENT_DISABLE_POOL_ALLOCATOR +#include "libtorrent/allocator.hpp" // for page_aligned_allocator #include #endif -#ifdef TORRENT_DISK_STATS -#include -#endif - -#if TORRENT_USE_ASSERTS || TORRENT_DISK_STATS +#ifdef TORRENT_BUFFER_STATS #include #endif namespace libtorrent { + namespace aux { struct session_settings; } + class alert; + struct alert_dispatcher; + struct disk_observer; + struct TORRENT_EXTRA_EXPORT disk_buffer_pool : boost::noncopyable { - disk_buffer_pool(int block_size); -#if TORRENT_USE_ASSERTS + disk_buffer_pool(int block_size, io_service& ios + , boost::function const& trigger_trim + , alert_dispatcher* alert_disp); ~disk_buffer_pool(); -#endif -#if TORRENT_USE_ASSERTS || TORRENT_DISK_STATS +#if TORRENT_USE_ASSERTS || TORRENT_BUFFER_STATS bool is_disk_buffer(char* buffer , mutex::scoped_lock& l) const; bool is_disk_buffer(char* buffer) const; #endif + // tries to allocate a disk buffer. If the cache is full, this function will + // return NULL and call the disk_observer once a buffer becomes available + char* async_allocate_buffer(char const* category, boost::function const& handler); + char* allocate_buffer(char const* category); + char* allocate_buffer(bool& exceeded, boost::shared_ptr o, char const* category); void free_buffer(char* buf); void free_multiple_buffers(char** bufvec, int numbufs); @@ -78,17 +91,29 @@ namespace libtorrent { return m_allocations; } #endif -#ifdef TORRENT_DISK_STATS - std::ofstream m_disk_access_log; -#endif - void release_memory(); - int in_use() const { return m_in_use; } + boost::uint32_t in_use() const + { + mutex::scoped_lock l(m_pool_mutex); + return m_in_use; + } + boost::uint32_t num_to_evict(int num_needed = 0); + bool exceeded_max_size() const { return m_exceeded_max_size; } + + void set_settings(aux::session_settings const& sett); + + struct handler_t + { + char* buffer; // argument to the callback + char const* category; // category of allocation + boost::function callback; + }; protected: void free_buffer_impl(char* buf, mutex::scoped_lock& l); + char* allocate_buffer_impl(mutex::scoped_lock& l, char const* category); // number of bytes per block. The BitTorrent // protocol defines the block size to 16 KiB. @@ -97,12 +122,58 @@ namespace libtorrent // number of disk buffers currently allocated int m_in_use; - session_settings m_settings; + // cache size limit + int m_max_use; + + // if we have exceeded the limit, we won't start + // allowing allocations again until we drop below + // this low watermark + int m_low_watermark; + + // if we exceed the max number of buffers, we start + // adding up callbacks to this queue. Once the number + // of buffers in use drops below the low watermark, + // we start calling these functions back + // TODO: try to remove the observers, only using the async_allocate handlers + std::vector > m_observers; + + // these handlers are executed when a new buffer is available + std::vector m_handlers; + + // callback used to tell the cache it needs to free up some blocks + boost::function m_trigger_cache_trim; + + // set to true to throttle more allocations + bool m_exceeded_max_size; + + // this is the main thread io_service. Callbacks are + // posted on this in order to have them execute in + // the main thread. + io_service& m_ios; private: + void check_buffer_level(mutex::scoped_lock& l); + mutable mutex m_pool_mutex; + int m_cache_buffer_chunk_size; + bool m_lock_disk_cache; + +#if TORRENT_HAVE_MMAP + // the file descriptor of the cache mmap file + int m_cache_fd; + // the pointer to the block of virtual address space + // making up the mmapped cache space + char* m_cache_pool; + // list of block indices that are not in use. block_index + // times 0x4000 + m_cache_pool is the address where the + // corresponding memory lives + std::vector m_free_list; +#endif + + alert_dispatcher* m_post_alert; + #ifndef TORRENT_DISABLE_POOL_ALLOCATOR // if this is true, all buffers are allocated // from m_pool. If this is false, all buffers @@ -117,29 +188,41 @@ namespace libtorrent // or once the client goes idle for a while. bool m_using_pool_allocator; + // this is the actual user setting + bool m_want_pool_allocator; + // memory pool for read and write operations // and disk cache boost::pool m_pool; #endif -#if defined TORRENT_DISK_STATS || defined TORRENT_STATS +#if defined TORRENT_BUFFER_STATS || defined TORRENT_STATS int m_allocations; #endif -#ifdef TORRENT_DISK_STATS + +#ifdef TORRENT_BUFFER_STATS public: void rename_buffer(char* buf, char const* category); - protected: boost::unordered_map m_categories; + protected: boost::unordered_map m_buf_to_category; - std::ofstream m_log; + FILE* m_log; private: #endif + + // this is specifically exempt from release_asserts + // since it's a quite costly check. Only for debug + // builds. +#if defined TORRENT_DEBUG + std::set m_buffers_in_use; +#endif #if TORRENT_USE_ASSERTS int m_magic; + bool m_settings_set; #endif }; } -#endif // TORRENT_DISK_BUFFER_POOL +#endif // TORRENT_DISK_BUFFER_POOL_HPP diff --git a/include/libtorrent/disk_interface.hpp b/include/libtorrent/disk_interface.hpp new file mode 100644 index 000000000..a8aec6420 --- /dev/null +++ b/include/libtorrent/disk_interface.hpp @@ -0,0 +1,119 @@ +/* + +Copyright (c) 2012-2013, 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_DISK_INTERFACE_HPP +#define TORRENT_DISK_INTERFACE_HPP + +#include +#include + +#include "libtorrent/lazy_entry.hpp" + +#include + +namespace libtorrent +{ + struct disk_io_job; + class piece_manager; + struct peer_request; + struct disk_observer; + struct file_pool; + struct add_torrent_params; + + struct disk_interface + { + virtual void async_read(piece_manager* storage, peer_request const& r + , boost::function const& handler, void* requester + , int flags = 0) = 0; + virtual void async_write(piece_manager* storage, peer_request const& r + , disk_buffer_holder& buffer + , boost::function const& handler + , int flags = 0) = 0; + virtual void async_hash(piece_manager* storage, int piece, int flags + , boost::function const& handler, void* requester) = 0; + virtual void async_move_storage(piece_manager* storage, std::string const& p, int flags + , boost::function const& handler) = 0; + virtual void async_release_files(piece_manager* storage + , boost::function const& handler + = boost::function()) = 0; + virtual void async_check_fastresume(piece_manager* storage + , lazy_entry const* resume_data + , boost::function const& handler) = 0; +#ifndef TORRENT_NO_DEPRECATE + virtual void async_finalize_file(piece_manager*, int file + , boost::function const& handler + = boost::function()) = 0; +#endif + virtual void async_flush_piece(piece_manager* storage, int piece + , boost::function const& handler + = boost::function()) = 0; + virtual void async_cache_piece(piece_manager* storage, int piece + , boost::function const& handler) = 0; + virtual void async_stop_torrent(piece_manager* storage + , boost::function const& handler)= 0; + virtual void async_rename_file(piece_manager* storage, int index, std::string const& name + , boost::function const& handler) = 0; + virtual void async_delete_files(piece_manager* storage + , boost::function const& handler) = 0; + virtual void async_save_resume_data(piece_manager* storage + , boost::function const& handler) = 0; + virtual void async_set_file_priority(piece_manager* storage + , std::vector const& prio + , boost::function const& handler) = 0; + virtual void async_load_torrent(add_torrent_params* params + , boost::function const& handler) = 0; + virtual void async_tick_torrent(piece_manager* storage + , boost::function const& handler) = 0; + + virtual void clear_read_cache(piece_manager* storage) = 0; + virtual void async_clear_piece(piece_manager* storage, int index + , boost::function const& handler) = 0; + virtual void clear_piece(piece_manager* storage, int index) = 0; + + virtual void update_stats_counters(counters& c) const = 0; + virtual void get_cache_info(cache_status* ret, bool no_pieces = true + , piece_manager const* storage = 0) const = 0; + + virtual file_pool& files() = 0; + +#if TORRENT_USE_ASSERTS || defined TORRENT_BUFFER_STATS + virtual bool is_disk_buffer(char* buffer) const = 0; +#endif + +#ifdef TORRENT_BUFFER_STATS + virtual void rename_buffer(char* buf, char const* category) = 0; +#endif + }; +} + +#endif + diff --git a/include/libtorrent/disk_io_job.hpp b/include/libtorrent/disk_io_job.hpp new file mode 100644 index 000000000..b0d1db037 --- /dev/null +++ b/include/libtorrent/disk_io_job.hpp @@ -0,0 +1,226 @@ +/* + +Copyright (c) 2010-2013, 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_DISK_IO_JOB_HPP +#define TORRENT_DISK_IO_JOB_HPP + +#include +#include "libtorrent/time.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/tailqueue.hpp" +#include "libtorrent/peer_id.hpp" +#include +#include +#include + +namespace libtorrent +{ + class entry; + class piece_manager; + struct cached_piece_entry; + + struct block_cache_reference + { + void* storage; + int piece; + int block; + }; + + // disk_io_jobs are allocated in a pool allocator in disk_io_thread + // they are always allocated from the network thread, posted + // (as pointers) to the disk I/O thread, and then passed back + // to the network thread for completion handling and to be freed. + // each disk_io_job can belong to one tailqueue. The job queue + // in the disk thread, is one, the jobs waiting on completion + // on a cache piece (in block_cache) is another, and a job + // waiting for a storage fence to be lowered is another. Jobs + // are never in more than one queue at a time. Only passing around + // pointers and chaining them back and forth into lists saves + // a lot of heap allocation churn of using general purpose + // containers. + struct TORRENT_EXTRA_EXPORT disk_io_job : tailqueue_node, boost::noncopyable + { + disk_io_job(); + ~disk_io_job(); + + enum action_t + { + read + , write + , hash + , move_storage + , release_files + , delete_files + , check_fastresume + , save_resume_data + , rename_file + , stop_torrent + , cache_piece +#ifndef TORRENT_NO_DEPRECATE + , finalize_file +#endif + , flush_piece + , flush_hashed + , flush_storage + , trim_cache + , file_priority + , load_torrent + , clear_piece + , tick_storage + + , num_job_ids + }; + + enum flags_t + { + sequential_access = 0x1, + + // this flag is set on a job when a read operation did + // not hit the disk, but found the data in the read cache. + cache_hit = 0x2, + + // force making a copy of the cached block, rather + // than getting a reference to the block already in + // the cache. + force_copy = 0x4, + + // this is set by the storage object when a fence is raised + // for this job. It means that this no other jobs on the same + // storage will execute in parallel with this one. It's used + // to lower the fence when the job has completed + fence = 0x8, + + // don't keep the read block in cache + volatile_read = 0x10, + + // this job is currently being performed, or it's hanging + // on a cache piece that may be flushed soon + in_progress = 0x20, + + // turns into file::coalesce_buffers in the file operation + coalesce_buffers = 0x40, + }; + + // for write jobs, returns true if its block + // is not dirty anymore + bool completed(cached_piece_entry const* pe, int block_size); + + // unique identifier for the peer when reading + void* requester; + + // for write, this points to the data to write, + // for read, the data read is returned here + // for other jobs, it may point to other job-specific types + // for move_storage and rename_file this is a string allocated + // with malloc() + // an entry* for save_resume_data + // for aiocb_complete this points to the aiocb that completed + // for get_cache_info this points to a cache_status object which + // is filled in + char* buffer; + + // the disk storage this job applies to (if applicable) + boost::shared_ptr storage; + + // this is called when operation completes + boost::function callback; + + // the error code from the file operation + // on error, this also contains the path of the + // file the disk operation failed on + storage_error error; + + union + { + // result for hash jobs + char piece_hash[20]; + + struct io_args + { + // if this is set, the read operation is required to + // release the block references once it's done sending + // the buffer. For aligned block requests (by far the + // most common) the buffers are not actually copied + // into the send buffer, but simply referenced. When this + // is set in a response to a read, the buffer needs to + // be de-referenced by sending a reclaim_block message + // back to the disk thread + block_cache_reference ref; + + // for read and write, the offset into the piece + // the read or write should start + // for hash jobs, this is the first block the hash + // job is still holding a reference to. The end of + // the range of blocks a hash jobs holds references + // to is always the last block in the piece. + boost::uint32_t offset; + + // number of bytes 'buffer' points to. Used for read & write + boost::uint16_t buffer_size; + } io; + } d; + + // arguments used for read and write + // the piece this job applies to + boost::uint32_t piece:24; + + // the type of job this is + boost::uint32_t action:8; + + enum { operation_failed = -1 }; + + // return value of operation + boost::int32_t ret; + + // flags controlling this job + boost::uint8_t flags; + +#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS + bool in_use:1; + + // set to true when the job is added to the completion queue. + // to make sure we don't add it twice + mutable bool job_posted:1; + + // set to true when the callback has been called once + // used to make sure we don't call it twice + mutable bool callback_called:1; + + // this is true when the job is blocked by a storage_fence + mutable bool blocked:1; +#endif + }; + +} + +#endif // TORRENT_DISK_IO_JOB_HPP + diff --git a/include/libtorrent/disk_io_thread.hpp b/include/libtorrent/disk_io_thread.hpp index 975f855c6..0149dad1c 100644 --- a/include/libtorrent/disk_io_thread.hpp +++ b/include/libtorrent/disk_io_thread.hpp @@ -37,34 +37,36 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/allocator.hpp" #include "libtorrent/io_service.hpp" #include "libtorrent/sliding_average.hpp" +#include "libtorrent/disk_io_job.hpp" +#include "libtorrent/disk_job_pool.hpp" +#include "libtorrent/block_cache.hpp" +#include "libtorrent/file_pool.hpp" +#include "libtorrent/disk_interface.hpp" #include -#include #include #include +#include +#include #include #include "libtorrent/config.hpp" +#ifndef TORRENT_DISABLE_POOL_ALLOCATOR +#include +#endif +#include "libtorrent/aux_/session_settings.hpp" #include "libtorrent/thread.hpp" -#include "libtorrent/disk_buffer_pool.hpp" - -#include -#include -#include -#include +#include "libtorrent/atomic.hpp" namespace libtorrent { - using boost::multi_index::multi_index_container; - using boost::multi_index::ordered_non_unique; - using boost::multi_index::ordered_unique; - using boost::multi_index::indexed_by; - using boost::multi_index::member; - using boost::multi_index::const_mem_fun; + class alert; + struct alert_dispatcher; + struct add_torrent_params; + struct counters; struct cached_piece_info { - // the piece index for this cache entry. - int piece; + piece_manager* storage; // holds one entry for each block in this piece. ``true`` represents // the data for that block being in the disk cache and ``false`` means it's not. @@ -80,99 +82,17 @@ namespace libtorrent // compare its hash against the hashes in the .torrent file. int next_to_hash; - enum kind_t { read_cache = 0, write_cache = 1 }; + // the piece index for this cache entry. + int piece; + + enum kind_t { read_cache = 0, write_cache = 1, volatile_read_cache = 2 }; // specifies if this piece is part of the read cache or the write cache. kind_t kind; + + bool need_readback; }; - struct disk_io_job - { - disk_io_job() - : buffer(0) - , buffer_size(0) - , piece(0) - , offset(0) - , max_cache_line(0) - , cache_min_time(0) - , action(read) - {} - - enum action_t - { - read - , write - , hash - , move_storage - , release_files - , delete_files - , check_fastresume - , check_files - , save_resume_data - , rename_file - , abort_thread - , clear_read_cache - , abort_torrent - , update_settings - , read_and_hash - , cache_piece - , file_priority -#ifndef TORRENT_NO_DEPRECATE - , finalize_file -#endif - }; - - - char* buffer; - - // this is called when operation completes - boost::function callback; - - boost::intrusive_ptr storage; - - boost::shared_ptr resume_data; - - // the error code from the file operation - error_code error; - - // the time when this job was issued. This is used to - // keep track of disk I/O congestion - ptime start_time; - - // used for move_storage and rename_file. On errors, this is set - // to the error message - std::string str; - - // on error, this is set to the path of the - // file the disk operation failed on - std::string error_file; - - int buffer_size; - - // arguments used for read and write - // piece is used as flags for move_storage - int piece, offset; - - // if this is > 0, it specifies the max number of blocks to read - // ahead in the read cache for this access. This is only valid - // for 'read' actions - int max_cache_line; - - // if this is > 0, it may increase the minimum time the cache - // line caused by this operation stays in the cache - int cache_min_time; - - boost::uint8_t action; - }; - - // returns true if the fundamental operation - // of the given disk job is a read operation - bool is_read_operation(disk_io_job const& j); - - // this is true if the buffer field in the disk_io_job - // points to a disk buffer - bool operation_has_buffer(disk_io_job const& j); - // this struct holds a number of statistics counters // relevant for the disk io thread and disk cache. struct TORRENT_EXPORT cache_status @@ -184,29 +104,47 @@ namespace libtorrent , blocks_read(0) , blocks_read_hit(0) , reads(0) +#ifndef TORRENT_NO_DEPRECATE , queued_bytes(0) , cache_size(0) +#endif + , write_cache_size(0) , read_cache_size(0) + , pinned_blocks(0) , total_used_buffers(0) - , average_queue_time(0) , average_read_time(0) , average_write_time(0) , average_hash_time(0) , average_job_time(0) - , average_sort_time(0) - , job_queue_length(0) , cumulative_job_time(0) , cumulative_read_time(0) , cumulative_write_time(0) , cumulative_hash_time(0) - , cumulative_sort_time(0) , total_read_back(0) , read_queue_size(0) - {} + , blocked_jobs(0) + , queued_jobs(0) + , peak_queued(0) + , pending_jobs(0) + , num_jobs(0) + , num_read_jobs(0) + , num_write_jobs(0) + , arc_mru_size(0) + , arc_mru_ghost_size(0) + , arc_mfu_size(0) + , arc_mfu_ghost_size(0) + , arc_write_size(0) + , arc_volatile_size(0) + , num_writing_threads(0) + { + memset(num_fence_jobs, 0, sizeof(num_fence_jobs)); + } + + std::vector pieces; // the total number of 16 KiB blocks written to disk // since this session was started. - size_type blocks_written; + atomic_count blocks_written; // the total number of write operations performed since this // session was started. @@ -214,11 +152,11 @@ namespace libtorrent // The ratio (``blocks_written`` - ``writes``) / ``blocks_written`` represents // the number of saved write operations per total write operations. i.e. a kind // of cache hit ratio for the write cahe. - size_type writes; + atomic_count writes; // the number of blocks that were requested from the // bittorrent engine (from peers), that were served from disk or cache. - size_type blocks_read; + atomic_count blocks_read; // the number of blocks that was just copied from the read cache // @@ -227,19 +165,27 @@ namespace libtorrent size_type blocks_read_hit; // the number of read operations used - size_type reads; + atomic_count reads; - // the number of bytes waiting, in the disk job queue, to be written - // or inserted into the disk cache +#ifndef TORRENT_NO_DEPRECATE + // the number of bytes queued for writing, including bytes + // submitted to the OS for writing, but not yet complete mutable size_type queued_bytes; // the number of 16 KiB blocks currently in the disk cache (both read and write). // This includes both read and write cache. int cache_size; +#endif + // the number of blocks in the cache used for write cache + int write_cache_size; // the number of 16KiB blocks in the read cache. int read_cache_size; + // the number of blocks with a refcount > 0, i.e. + // they may not be evicted + int pinned_blocks; + // the total number of buffers currently in use. // This includes the read/write disk cache as well as send and receive buffers // used in peer connections. @@ -247,7 +193,6 @@ namespace libtorrent // the number of microseconds an average disk I/O job // has to wait in the job queue before it get processed. - int average_queue_time; // the time read jobs takes on average to complete // (not including the time in the queue), in microseconds. This only measures @@ -265,18 +210,13 @@ namespace libtorrent // also includes checking files without valid resume data. int average_hash_time; int average_job_time; - int average_sort_time; - - // the number of jobs in the job queue. - int job_queue_length; // the number of milliseconds spent in all disk jobs, and specific ones // since the start of the session. Times are specified in milliseconds - boost::uint32_t cumulative_job_time; - boost::uint32_t cumulative_read_time; - boost::uint32_t cumulative_write_time; - boost::uint32_t cumulative_hash_time; - boost::uint32_t cumulative_sort_time; + atomic_count cumulative_job_time; + atomic_count cumulative_read_time; + atomic_count cumulative_write_time; + atomic_count cumulative_hash_time; // the number of blocks that had to be read back from disk because // they were flushed before the SHA-1 hash got to hash them. If this @@ -285,182 +225,320 @@ namespace libtorrent // number of read jobs in the disk job queue int read_queue_size; + + // number of jobs blocked because of a fence + int blocked_jobs; + + // number of jobs waiting to be issued (m_to_issue) + // average over 30 seconds + int queued_jobs; + // largest ever seen number of queued jobs + int peak_queued; + // number of jobs waiting to complete (m_pending) + // average over 30 seconds + int pending_jobs; + + // total number of disk job objects allocated right now + int num_jobs; + + // total number of disk read job objects allocated right now + int num_read_jobs; + + // total number of disk write job objects allocated right now + int num_write_jobs; + + // ARC cache stats. All of these counters are in number of pieces + // not blocks. A piece does not necessarily correspond to a certain + // number of blocks. The pieces in the ghost list never have any + // blocks in them + int arc_mru_size; + int arc_mru_ghost_size; + int arc_mfu_size; + int arc_mfu_ghost_size; + int arc_write_size; + int arc_volatile_size; + + // the number of threads currently writing to disk + int num_writing_threads; + + // counts only fence jobs that are currently blocking jobs + // not fences that are themself blocked + int num_fence_jobs[disk_io_job::num_job_ids]; }; // this is a singleton consisting of the thread and a queue // of disk io jobs - struct TORRENT_EXTRA_EXPORT disk_io_thread : disk_buffer_pool + struct TORRENT_EXTRA_EXPORT disk_io_thread + : disk_job_pool + , disk_interface + , buffer_allocator_interface { disk_io_thread(io_service& ios - , boost::function const& queue_callback - , file_pool& fp + , alert_dispatcher* alert_disp + , void* userdata , int block_size = 16 * 1024); ~disk_io_thread(); - void abort(); - void join(); + void set_settings(settings_pack* sett); + void set_num_threads(int i, bool wait = true); - // aborts read operations - void stop(boost::intrusive_ptr s); + void async_read(piece_manager* storage, peer_request const& r + , boost::function const& handler, void* requester + , int flags = 0); + void async_write(piece_manager* storage, peer_request const& r + , disk_buffer_holder& buffer + , boost::function const& handler + , int flags = 0); + void async_hash(piece_manager* storage, int piece, int flags + , boost::function const& handler, void* requester); + void async_move_storage(piece_manager* storage, std::string const& p, int flags + , boost::function const& handler); + void async_release_files(piece_manager* storage + , boost::function const& handler + = boost::function()); + void async_delete_files(piece_manager* storage + , boost::function const& handler); + void async_check_fastresume(piece_manager* storage + , lazy_entry const* resume_data + , boost::function const& handler); + void async_save_resume_data(piece_manager* storage + , boost::function const& handler); + void async_rename_file(piece_manager* storage, int index, std::string const& name + , boost::function const& handler); + void async_stop_torrent(piece_manager* storage + , boost::function const& handler); + void async_cache_piece(piece_manager* storage, int piece + , boost::function const& handler); +#ifndef TORRENT_NO_DEPRECATE + void async_finalize_file(piece_manager* storage, int file + , boost::function const& handler + = boost::function()); +#endif + void async_flush_piece(piece_manager* storage, int piece + , boost::function const& handler + = boost::function()); + void async_set_file_priority(piece_manager* storage + , std::vector const& prio + , boost::function const& handler); + void async_load_torrent(add_torrent_params* params + , boost::function const& handler); + void async_tick_torrent(piece_manager* storage + , boost::function const& handler); - // returns the disk write queue size - int add_job(disk_io_job const& j - , boost::function const& f - = boost::function()); + void clear_read_cache(piece_manager* storage); + void async_clear_piece(piece_manager* storage, int index + , boost::function const& handler); + // this is not asynchronous and requires that the piece does not + // have any pending buffers. It's meant to be used for pieces that + // were just read and hashed and failed the hash check. + // there should be no read-operations left, and all buffers should + // be discardable + void clear_piece(piece_manager* storage, int index); - // keep track of the number of bytes in the job queue - // at any given time. i.e. the sum of all buffer_size. - // this is used to slow down the download global download - // speed when the queue buffer size is too big. - size_type queue_buffer_size() const; - bool can_write() const; + // implements buffer_allocator_interface + void reclaim_block(block_cache_reference ref); + void free_disk_buffer(char* buf) { m_disk_cache.free_buffer(buf); } + char* allocate_disk_buffer(char const* category) + { + bool exceed = false; + return allocate_disk_buffer(exceed, boost::shared_ptr(), category); + } - void get_cache_info(sha1_hash const& ih - , std::vector& ret) const; + void trigger_cache_trim(); + char* allocate_disk_buffer(bool& exceeded, boost::shared_ptr o + , char const* category); + char* async_allocate_disk_buffer(char const* category, boost::function const& handler); - cache_status status() const; + bool exceeded_cache_use() const + { return m_disk_cache.exceeded_max_size(); } - void thread_fun(); + void update_stats_counters(counters& c) const; + void get_cache_info(cache_status* ret, bool no_pieces = true + , piece_manager const* storage = 0) const; + + // this submits all queued up jobs to the thread + void submit_jobs(); + + block_cache* cache() { return &m_disk_cache; } + +#if TORRENT_USE_ASSERTS || defined TORRENT_BUFFER_STATS + bool is_disk_buffer(char* buffer) const { return m_disk_cache.is_disk_buffer(buffer); } +#endif + +#ifdef TORRENT_BUFFER_STATS + void rename_buffer(char* buf, char const* category) + { m_disk_cache.rename_buffer(buf, category); } +#endif + + enum thread_type_t { + generic_thread, + hasher_thread + }; + + void thread_fun(int thread_id, thread_type_t type); + + file_pool& files() { return m_file_pool; } + + io_service& get_io_service() { return m_ios; } + + int prep_read_job_impl(disk_io_job* j, bool check_fence = true); #if TORRENT_USE_INVARIANT_CHECKS void check_invariant() const; #endif - - struct cached_block_entry - { - cached_block_entry(): buf(0) {} - // the buffer pointer (this is a disk_pool buffer) - // or 0 - char* buf; - // callback for when this block is flushed to disk - boost::function callback; - }; + void maybe_issue_queued_read_jobs(cached_piece_entry* pe, tailqueue& completed_jobs); + int do_read(disk_io_job* j, tailqueue& completed_jobs); + int do_uncached_read(disk_io_job* j); - struct cached_piece_entry - { - int piece; - // storage this piece belongs to - boost::intrusive_ptr storage; - // the pointers to the block data - boost::shared_array blocks; - // the last time a block was writting to this piece - // plus the minimum amount of time the block is guaranteed - // to stay in the cache - ptime expire; - // the number of blocks in the cache for this piece - int num_blocks; - // used to determine if this piece should be flushed - int num_contiguous_blocks; - // this is the first block that has not yet been hashed - // by the partial hasher. When minimizing read-back, this - // is used to determine if flushing a range would force us - // to read it back later when hashing - int next_block_to_hash; - - std::pair storage_piece_pair() const - { return std::pair(storage.get(), piece); } - }; + int do_write(disk_io_job* j, tailqueue& completed_jobs); + int do_uncached_write(disk_io_job* j); - typedef multi_index_container< - cached_piece_entry, indexed_by< - ordered_unique - , &cached_piece_entry::storage_piece_pair> > - , ordered_non_unique > - > - > cache_t; + int do_hash(disk_io_job* j, tailqueue& completed_jobs); + int do_uncached_hash(disk_io_job* j); - typedef cache_t::nth_index<0>::type cache_piece_index_t; - typedef cache_t::nth_index<1>::type cache_lru_index_t; + int do_move_storage(disk_io_job* j, tailqueue& completed_jobs); + int do_release_files(disk_io_job* j, tailqueue& completed_jobs); + int do_delete_files(disk_io_job* j, tailqueue& completed_jobs); + int do_check_fastresume(disk_io_job* j, tailqueue& completed_jobs); + int do_save_resume_data(disk_io_job* j, tailqueue& completed_jobs); + int do_rename_file(disk_io_job* j, tailqueue& completed_jobs); + int do_stop_torrent(disk_io_job* j, tailqueue& completed_jobs); + int do_read_and_hash(disk_io_job* j, tailqueue& completed_jobs); + int do_cache_piece(disk_io_job* j, tailqueue& completed_jobs); +#ifndef TORRENT_NO_DEPRECATE + int do_finalize_file(disk_io_job* j, tailqueue& completed_jobs); +#endif + int do_flush_piece(disk_io_job* j, tailqueue& completed_jobs); + int do_flush_hashed(disk_io_job* j, tailqueue& completed_jobs); + int do_flush_storage(disk_io_job* j, tailqueue& completed_jobs); + int do_trim_cache(disk_io_job* j, tailqueue& completed_jobs); + int do_file_priority(disk_io_job* j, tailqueue& completed_jobs); + int do_load_torrent(disk_io_job* j, tailqueue& completed_jobs); + int do_clear_piece(disk_io_job* j, tailqueue& completed_jobs); + int do_tick(disk_io_job* j, tailqueue& completed_jobs); + + void call_job_handlers(void* userdata); private: - int add_job(disk_io_job const& j - , mutex::scoped_lock& l - , boost::function const& f - = boost::function()); - - bool test_error(disk_io_job& j); - void post_callback(disk_io_job const& j, int ret); - - // cache operations - cache_piece_index_t::iterator find_cached_piece( - cache_t& cache, disk_io_job const& j - , mutex::scoped_lock& l); - bool is_cache_hit(cached_piece_entry& p - , disk_io_job const& j, mutex::scoped_lock& l); - int copy_from_piece(cached_piece_entry& p, bool& hit - , disk_io_job const& j, mutex::scoped_lock& l); - - struct ignore_t + enum return_value_t { - ignore_t(): piece(-1), storage(0) {} - ignore_t(int idx, piece_manager* st): piece(idx), storage(st) {} - int piece; - piece_manager* storage; + // the do_* functions can return this to indicate the disk + // job did not complete immediately, and shouldn't be posted yet + defer_handler = -200, + + // the job cannot be completed right now, put it back in the + // queue and try again later + retry_job = -201, }; - // write cache operations - enum options_t { dont_flush_write_blocks = 1, ignore_cache_size = 2 }; - int flush_cache_blocks(mutex::scoped_lock& l - , int blocks, ignore_t ignore = ignore_t(), int options = 0); - void flush_expired_pieces(); - int flush_contiguous_blocks(cached_piece_entry& p - , mutex::scoped_lock& l, int lower_limit = 0, bool avoid_readback = false); - int flush_range(cached_piece_entry& p, int start, int end, mutex::scoped_lock& l); - int cache_block(disk_io_job& j - , boost::function& handler - , int cache_expire - , mutex::scoped_lock& l); + void add_completed_job(disk_io_job* j); + void add_completed_jobs(tailqueue& jobs); + void add_completed_jobs_impl(tailqueue& jobs + , tailqueue& completed_jobs); - // read cache operations - int clear_oldest_read_piece(int num_blocks, ignore_t ignore - , mutex::scoped_lock& l); - int read_into_piece(cached_piece_entry& p, int start_block - , int options, int num_blocks, mutex::scoped_lock& l); - int cache_read_block(disk_io_job const& j, mutex::scoped_lock& l); - int free_piece(cached_piece_entry& p, mutex::scoped_lock& l); - int drain_piece_bufs(cached_piece_entry& p, std::vector& buf - , mutex::scoped_lock& l); + void fail_jobs(storage_error const& e, tailqueue& jobs_); + void fail_jobs_impl(storage_error const& e, tailqueue& src, tailqueue& dst); - enum cache_flags_t { - cache_only = 1 + void check_cache_level(mutex::scoped_lock& l, tailqueue& completed_jobs); + + void perform_job(disk_io_job* j, tailqueue& completed_jobs); + + // this queues up another job to be submitted + void add_job(disk_io_job* j); + void add_fence_job(piece_manager* storage, disk_io_job* j); + + // assumes l is locked (cache mutex). + // writes out the blocks [start, end) (releases the lock + // during the file operation) + int flush_range(cached_piece_entry* p, int start, int end + , int flags, tailqueue& completed_jobs, mutex::scoped_lock& l); + + // low level flush operations, used by flush_range + int build_iovec(cached_piece_entry* pe, int start, int end + , file::iovec_t* iov, int* flushing, int block_base_index = 0); + void flush_iovec(cached_piece_entry* pe, file::iovec_t const* iov, int const* flushing + , int num_blocks, storage_error& error); + void iovec_flushed(cached_piece_entry* pe + , int* flushing, int num_blocks, int block_offset + , storage_error const& error + , tailqueue& completed_jobs); + + // assumes l is locked (the cache mutex). + // assumes pe->hash to be set. + // If there are new blocks in piece 'pe' that have not been + // hashed by the partial_hash object attached to this piece, + // the piece will + void kick_hasher(cached_piece_entry* pe, mutex::scoped_lock& l); + + // flags to pass in to flush_cache() + enum flush_flags_t + { + // only flush read cache (this is cheap) + flush_read_cache = 1, + // flush read cache, and write cache + flush_write_cache = 2, + // flush read cache, delete write cache without flushing to disk + flush_delete_cache = 4, + // expect all pieces for the storage to have been + // cleared when flush_cache() returns. This is only + // used for asserts and only applies for fence jobs + flush_expect_clear = 8 }; - int try_read_from_cache(disk_io_job const& j, bool& hit, int flags = 0); - int read_piece_from_cache_and_hash(disk_io_job const& j, sha1_hash& h); - int cache_piece(disk_io_job const& j, cache_piece_index_t::iterator& p - , bool& hit, int options, mutex::scoped_lock& l); + void flush_cache(piece_manager* storage, boost::uint32_t flags, tailqueue& completed_jobs, mutex::scoped_lock& l); + void flush_expired_write_blocks(tailqueue& completed_jobs, mutex::scoped_lock& l); + void flush_piece(cached_piece_entry* pe, int flags, tailqueue& completed_jobs, mutex::scoped_lock& l); - // this mutex only protects m_jobs, m_queue_buffer_size, - // m_exceeded_write_queue and m_abort - mutable mutex m_queue_mutex; - event m_signal; - bool m_abort; - bool m_waiting_to_shutdown; - std::deque m_jobs; - size_type m_queue_buffer_size; + int try_flush_hashed(cached_piece_entry* p, int cont_blocks, tailqueue& completed_jobs, mutex::scoped_lock& l); + + void try_flush_write_blocks(int num, tailqueue& completed_jobs, mutex::scoped_lock& l); + + // used to batch reclaiming of blocks to once per cycle + void commit_reclaimed_blocks(); + + // this is a counter which is atomically incremented + // by each thread as it's started up, in order to + // assign a unique id to each thread + atomic_count m_num_threads; + + // this is a counter of how many threads are currently running. + // it's used to identify the last thread still running while + // shutting down. This last thread is responsible for cleanup + atomic_count m_num_running_threads; + + // this is the number of threads currently writing bytes + // to disk + atomic_count m_num_writing_threads; + + // the actual threads running disk jobs + std::vector > m_threads; + + aux::session_settings m_settings; + + // userdata pointer for the complete_job function, which + // is posted to the network thread when jobs complete + void* m_userdata; + + // the last time we expired write blocks from the cache + ptime m_last_cache_expiry; ptime m_last_file_check; - // this protects the piece cache and related members - mutable mutex m_piece_mutex; - // write cache - cache_t m_pieces; - - // read cache - cache_t m_read_pieces; + // LRU cache of open files + file_pool m_file_pool; - void flip_stats(ptime now); + // disk cache + mutable mutex m_cache_mutex; + block_cache m_disk_cache; + + void flip_stats(); // total number of blocks in use by both the read // and the write cache. This is not supposed to // exceed m_cache_size cache_status m_cache_stats; - // keeps average queue time for disk jobs (in microseconds) - average_accumulator m_queue_time; - // average read time for cache misses (in microseconds) average_accumulator m_read_time; @@ -473,32 +551,26 @@ namespace libtorrent // average time to serve a job (any job) in microseconds average_accumulator m_job_time; - // average time to ask for physical offset on disk - // and insert into queue - average_accumulator m_sort_time; - // the last time we reset the average time and store the // latest value in m_cache_stats ptime m_last_stats_flip; - typedef std::multimap read_jobs_t; - read_jobs_t m_sorted_read_jobs; - -#ifdef TORRENT_DISK_STATS - std::ofstream m_log; -#endif - - // the amount of physical ram in the machine - boost::uint64_t m_physical_ram; - - // if we exceeded the max queue disk write size - // this is set to true. It remains true until the - // queue is smaller than the low watermark - bool m_exceeded_write_queue; + // the total number of outstanding jobs. This is used to + // limit the number of jobs issued in parallel. It also creates + // an opportunity to sort the jobs by physical offset before + // issued to the AIO subsystem + atomic_count m_outstanding_jobs; + // this is the main thread io_service. Callbacks are + // posted on this in order to have them execute in + // the main thread. io_service& m_ios; - boost::function m_queue_callback; + // the number of jobs that have been blocked by a fence. These + // jobs are queued up in their respective storage, waiting for + // the fence to be lowered. This counter is just used to know + // when it's OK to exit the main loop of the disk thread + atomic_count m_num_blocked_jobs; // this keeps the io_service::run() call blocked from // returning. When shutting down, it's possible that @@ -512,22 +584,50 @@ namespace libtorrent // exist anymore, and crash. This prevents that. boost::optional m_work; - // reference to the file_pool which is a member of - // the session_impl object - file_pool& m_file_pool; + // used to wake up the disk IO thread when there are new + // jobs on the job queue (m_queued_jobs) + condition_variable m_job_cond; - // when completion notifications are queued, they're stuck - // in this list - std::list > m_queued_completions; + // mutex to protect the m_queued_jobs list + mutable mutex m_job_mutex; + // jobs queued for servicing + tailqueue m_queued_jobs; + + // when using more than 2 threads, this is + // used for just hashing jobs, just for threads + // dedicated to do hashing + condition_variable m_hash_job_cond; + tailqueue m_queued_hash_jobs; + + // used to rate limit disk performance warnings + ptime m_last_disk_aio_performance_warning; + + // function to be posted to the network thread to post + // an alert (used for performance warnings) + alert_dispatcher* m_post_alert; + + // jobs that are completed are put on this queue + // whenever the queue size grows from 0 to 1 + // a message is posted to the network thread, which + // will then drain the queue and execute the jobs' + // handler functions + mutex m_completed_jobs_mutex; + tailqueue m_completed_jobs; + + // these are blocks that have been returned by the main thread + // but they haven't been freed yet. This is used to batch + // reclaiming of blocks, to only need one mutex lock per cycle + std::vector m_blocks_to_reclaim; + + // when this is true, there is an outstanding message in the + // message queue that will reclaim all blocks in + // m_blocks_to_reclaim, there's no need to send another one + bool m_outstanding_reclaim_message; #if TORRENT_USE_ASSERTS int m_magic; #endif - - // thread for performing blocking disk io operations - thread m_disk_io_thread; }; - } #endif diff --git a/include/libtorrent/disk_job_pool.hpp b/include/libtorrent/disk_job_pool.hpp new file mode 100644 index 000000000..65fa85aab --- /dev/null +++ b/include/libtorrent/disk_job_pool.hpp @@ -0,0 +1,72 @@ +/* + +Copyright (c) 2010-2013, 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_DISK_JOB_POOL +#define TORRENT_DISK_JOB_POOL + +#include "libtorrent/config.hpp" +#include "libtorrent/thread.hpp" +#include + +namespace libtorrent +{ + struct disk_io_job; + + struct disk_job_pool + { + disk_job_pool(); + ~disk_job_pool(); + + disk_io_job* allocate_job(int type); + void free_job(disk_io_job* j); + void free_jobs(disk_io_job** j, int num); + + int jobs_in_use() const { return m_jobs_in_use; } + int read_jobs_in_use() const { return m_read_jobs; } + int write_jobs_in_use() const { return m_write_jobs; } + + private: + + // total number of in-use jobs + int m_jobs_in_use; + // total number of in-use read jobs + int m_read_jobs; + // total number of in-use write jobs + int m_write_jobs; + + mutex m_job_mutex; + boost::pool<> m_job_pool; + }; +} + +#endif // TORRENT_DISK_JOB_POOL + diff --git a/include/libtorrent/settings.hpp b/include/libtorrent/disk_observer.hpp similarity index 68% rename from include/libtorrent/settings.hpp rename to include/libtorrent/disk_observer.hpp index 3d58e4ba0..8ef452253 100644 --- a/include/libtorrent/settings.hpp +++ b/include/libtorrent/disk_observer.hpp @@ -1,6 +1,6 @@ /* -Copyright (c) 2010-2014, Arvid Norberg +Copyright (c) 2012-2013, Arvid Norberg All rights reserved. Redistribution and use in source and binary forms, with or without @@ -30,36 +30,20 @@ POSSIBILITY OF SUCH DAMAGE. */ -#ifndef SETTINGS_HPP_INCLUDED -#define SETTINGS_HPP_INCLUDED +#ifndef TORRENT_DISK_OBSERVER_HPP +#define TORRENT_DISK_OBSERVER_HPP #include "libtorrent/config.hpp" -#include "libtorrent/session_settings.hpp" namespace libtorrent { - struct lazy_entry; - class entry; - - // internal - enum struct_field_type_t + struct disk_observer { - std_string, character, integer, floating_point, - boolean, size_integer, time_integer, integer16 + // called when the disk cache size has dropped + // below the low watermark again and we can + // resume downloading from peers + virtual void on_disk() = 0; }; - - // this is used to map struct entries - // to names in a bencoded dictionary to - // save and load the struct - struct bencode_map_entry - { - char const* name; - int offset; // struct offset - int type; - }; - - void load_struct(lazy_entry const& e, void* s, bencode_map_entry const* m, int num); - void save_struct(entry& e, void const* s, bencode_map_entry const* m, int num, void const* def = 0); } #endif diff --git a/include/libtorrent/enum_net.hpp b/include/libtorrent/enum_net.hpp index 786621165..74327a0af 100644 --- a/include/libtorrent/enum_net.hpp +++ b/include/libtorrent/enum_net.hpp @@ -35,12 +35,18 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/config.hpp" #include +#include #include "libtorrent/io_service_fwd.hpp" #include "libtorrent/address.hpp" #include "libtorrent/error_code.hpp" +#if TORRENT_USE_IFCONF || TORRENT_USE_NETLINK || TORRENT_USE_SYSCTL +#include // for SO_BINDTODEVICE +#endif + namespace libtorrent { + struct socket_type; // the interface should not have a netmask struct ip_interface @@ -68,15 +74,106 @@ namespace libtorrent TORRENT_EXTRA_EXPORT std::vector enum_routes(io_service& ios, error_code& ec); // return (a1 & mask) == (a2 & mask) - TORRENT_EXTRA_EXPORT bool match_addr_mask(address const& a1, address const& a2, address const& mask); + TORRENT_EXTRA_EXPORT bool match_addr_mask(address const& a1 + , address const& a2, address const& mask); // returns true if the specified address is on the same // local network as us TORRENT_EXTRA_EXPORT bool in_local_network(io_service& ios, address const& addr , error_code& ec); + TORRENT_EXTRA_EXPORT bool in_local_network(std::vector const& net + , address const& addr); TORRENT_EXTRA_EXPORT address get_default_gateway(io_service& ios , error_code& ec); + +#ifdef SO_BINDTODEVICE + struct bind_to_device_opt + { + bind_to_device_opt(char const* device): m_value(device) {} + template + int level(Protocol const&) const { return SOL_SOCKET; } + template + int name(Protocol const&) const { return SO_BINDTODEVICE; } + template + const char* data(Protocol const&) const { return m_value; } + template + size_t size(Protocol const&) const { return IFNAMSIZ; } + char const* m_value; + }; +#endif + + // attempt to bind socket to the device with the specified name. For systems + // that don't support SO_BINDTODEVICE the socket will be bound to one of the + // IP addresses of the specified device. In this case it is necessary to + // verify the local endpoint of the socket once the connection is established. + // the returned address is the ip the socket was bound to (or address_v4::any() + // in case SO_BINDTODEVICE succeeded and we don't need to verify it). + template + address bind_to_device(io_service& ios, Socket& sock + , bool ipv4, char const* device_name, int port, error_code& ec) + { + boost::asio::ip::tcp::endpoint bind_ep(address_v4::any(), port); + + address ip = address::from_string(device_name, ec); + if (!ec) + { + bind_ep.address(ip); + // it appears to be an IP. Just bind to that address + sock.bind(bind_ep, ec); + return bind_ep.address(); + } + + ec.clear(); + +#ifdef SO_BINDTODEVICE + // try to use SO_BINDTODEVICE here, if that exists. If it fails, + // fall back to the mechanism we have below + sock.set_option(bind_to_device_opt(device_name), ec); + if (ec) +#endif + { + ec.clear(); + // TODO: 2 this could be done more efficiently by just looking up + // the interface with the given name, maybe even with if_nametoindex() + std::vector ifs = enum_net_interfaces(ios, ec); + if (ec) return bind_ep.address(); + + bool found = false; + + for (int i = 0; i < int(ifs.size()); ++i) + { + // we're looking for a specific interface, and its address + // (which must be of the same family as the address we're + // connecting to) + if (strcmp(ifs[i].name, device_name) != 0) continue; + if (ifs[i].interface_address.is_v4() != ipv4) + continue; + + bind_ep.address(ifs[i].interface_address); + found = true; + break; + } + + if (!found) + { + ec = error_code(boost::system::errc::no_such_device, generic_category()); + return bind_ep.address(); + } + } + sock.bind(bind_ep, ec); + return bind_ep.address(); + } + + // returns true if the given device exists + TORRENT_EXTRA_EXPORT bool has_interface(char const* name, io_service& ios + , error_code& ec); + + // returns the device name whose local address is ``addr``. If + // no such device is found, an empty string is returned. + TORRENT_EXTRA_EXPORT std::string device_for_address(address addr + , io_service& ios, error_code& ec); + } #endif diff --git a/include/libtorrent/error_code.hpp b/include/libtorrent/error_code.hpp index 50f70a2fd..25ecb681a 100644 --- a/include/libtorrent/error_code.hpp +++ b/include/libtorrent/error_code.hpp @@ -329,6 +329,9 @@ namespace libtorrent // the torrent is not an SSL torrent, and the operation requires // an SSL torrent not_an_ssl_torrent, + // peer was banned because its listen port is within a banned port + // range, as specified by the port_filter. + banned_by_port_filter, // The NAT-PMP router responded with an unsupported protocol version @@ -377,6 +380,10 @@ namespace libtorrent // and the files on disk are using compact storage. The pieces needs // to be moved to their right position pieces_need_reorder, + // this error is returned when asking to save resume data and + // specifying the flag to only save when there's anything new to save + // (torrent_handle::only_if_modified) and there wasn't anything changed. + resume_data_not_modified, @@ -483,10 +490,10 @@ namespace libtorrent #if BOOST_VERSION < 103500 typedef asio::error_code error_code; // hidden - inline asio::error::error_category get_posix_category() + inline asio::error::error_category posix_category() { return asio::error::system_category; } // hidden - inline asio::error::error_category get_system_category() + inline asio::error::error_category system_category() { return asio::error::system_category; } // hidden @@ -524,7 +531,7 @@ namespace libtorrent using boost::system::error_code; // hidden - inline boost::system::error_category const& get_system_category() + inline boost::system::error_category const& system_category() #if BOOST_VERSION < 104400 { return boost::system::get_system_category(); } #else @@ -558,6 +565,58 @@ namespace libtorrent mutable char* m_msg; }; #endif + + // used by storage to return errors + // also includes which underlying file the + // error happened on + struct TORRENT_EXPORT storage_error + { + storage_error(): file(-1), operation(0) {} + storage_error(error_code e): ec(e), file(-1), operation(0) {} + + operator bool() const { return ec.value() != 0; } + // the error that occurred + error_code ec; + + // the file the error occurred on + boost::int32_t file:24; + + // A code from file_operation_t enum, indicating what + // kind of operation failed. + boost::uint32_t operation:8; + + enum file_operation_t { + none, + stat, + mkdir, + open, + rename, + remove, + copy, + read, + write, + fallocate, + alloc_cache_piece, + partfile_move, + partfile_read, + partfile_write, + }; + + // Returns a string literal representing the file operation + // that failed. If there were no failure, it returns + // an empty string. + char const* operation_str() const + { + char const* ops[] = + { + "", "stat", "mkdir", "open", "rename", "remove", "copy" + , "read", "write", "fallocate", "allocate cache piece" + , "partfile move", "partfile read", "partfile write" + }; + return ops[operation]; + } + }; + } #endif diff --git a/include/libtorrent/escape_string.hpp b/include/libtorrent/escape_string.hpp index ffe59cd42..644fb2853 100644 --- a/include/libtorrent/escape_string.hpp +++ b/include/libtorrent/escape_string.hpp @@ -53,6 +53,9 @@ namespace libtorrent // it will be encoded TORRENT_EXTRA_EXPORT std::string maybe_url_encode(std::string const& url); + // convert a file://-URL to a proper path + TORRENT_EXTRA_EXPORT std::string resolve_file_url(std::string const& url); + // returns true if the given string (not null terminated) contains // characters that would need to be escaped if used in a URL TORRENT_EXTRA_EXPORT bool need_encoding(char const* str, int len); @@ -68,6 +71,9 @@ namespace libtorrent // replaces \ with / TORRENT_EXTRA_EXPORT void convert_path_to_posix(std::string& path); +#ifdef TORRENT_WINDOWS + TORRENT_EXTRA_EXPORT void convert_path_to_windows(std::string& path); +#endif TORRENT_EXTRA_EXPORT std::string read_until(char const*& str, char delim, char const* end); TORRENT_EXTRA_EXPORT int hex_to_int(char in); diff --git a/include/libtorrent/extensions.hpp b/include/libtorrent/extensions.hpp index c78d52d5f..8290b2475 100644 --- a/include/libtorrent/extensions.hpp +++ b/include/libtorrent/extensions.hpp @@ -179,8 +179,9 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/config.hpp" #include "libtorrent/buffer.hpp" #include "libtorrent/socket.hpp" +#include "libtorrent/sha1_hash.hpp" // for sha1_hash #include "libtorrent/error_code.hpp" -#include "libtorrent/policy.hpp" // for policy::peer +#include "libtorrent/torrent_peer.hpp" // for torrent_peer namespace libtorrent { @@ -197,6 +198,7 @@ namespace libtorrent class alert; struct torrent_plugin; class torrent; + struct add_torrent_params; struct torrent_peer; // this is the base class for a session plugin. One primary feature @@ -226,6 +228,11 @@ namespace libtorrent // posted virtual void on_alert(alert const*) {} + // return true if the add_torrent_params should be added + virtual bool on_unknown_torrent(sha1_hash const& info_hash + , peer_connection* pc, add_torrent_params& p) + { return false; } + // called once per second virtual void on_tick() {} @@ -235,7 +242,7 @@ namespace libtorrent // optimistically unchoked. // if the plugin returns true then the ordering provided will be // used and no other plugin will be allowed to change it. - virtual bool on_optimistic_unchoke(std::vector& /* peers */) + virtual bool on_optimistic_unchoke(std::vector& /* peers */) { return false; } // called when saving settings state @@ -308,6 +315,19 @@ namespace libtorrent // enum members virtual void on_state(int /*s*/) {} + // called when the torrent is unloaded from RAM + // and loaded again, respectively + // unload is called right before the torrent is + // unloaded and load is called right after it's + // loaded. i.e. the full torrent state is available + // when these callbacks are called. + virtual void on_unload() {} + virtual void on_load() {} + + // called every time policy::add_peer is called + // src is a bitmask of which sources this peer + // has been seen from. flags is a bitmask of: + enum flags_t { // this is the first time we see this peer first_time = 1, diff --git a/include/libtorrent/extensions/metadata_transfer.hpp b/include/libtorrent/extensions/metadata_transfer.hpp index 84f7aa7b7..f975f6a54 100644 --- a/include/libtorrent/extensions/metadata_transfer.hpp +++ b/include/libtorrent/extensions/metadata_transfer.hpp @@ -46,6 +46,8 @@ POSSIBILITY OF SUCH DAMAGE. #pragma warning(pop) #endif +#ifndef TORRENT_NO_DEPRECATE + namespace libtorrent { struct torrent_plugin; @@ -62,6 +64,7 @@ namespace libtorrent create_metadata_plugin(torrent*, void*) TORRENT_DEPRECATED; #endif } +#endif #endif // TORRENT_DISABLE_EXTENSIONS diff --git a/include/libtorrent/file.hpp b/include/libtorrent/file.hpp index ca7b97e56..49e5e5572 100644 --- a/include/libtorrent/file.hpp +++ b/include/libtorrent/file.hpp @@ -41,14 +41,17 @@ POSSIBILITY OF SUCH DAMAGE. #endif #include +#include #ifdef _MSC_VER #pragma warning(pop) #endif +#include "libtorrent/config.hpp" #include "libtorrent/error_code.hpp" #include "libtorrent/size_type.hpp" -#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/time.hpp" #include "libtorrent/intrusive_ptr_base.hpp" #ifdef TORRENT_WINDOWS @@ -81,8 +84,16 @@ POSSIBILITY OF SUCH DAMAGE. #endif +#include + namespace libtorrent { +#ifdef TORRENT_WINDOWS + typedef HANDLE handle_type; +#else + typedef int handle_type; +#endif + struct file_status { size_type file_size; @@ -141,6 +152,8 @@ namespace libtorrent // internal used by create_torrent.hpp TORRENT_EXPORT std::string parent_path(std::string const& f); TORRENT_EXTRA_EXPORT bool has_parent_path(std::string const& f); + TORRENT_EXTRA_EXPORT char const* filename_cstr(char const* f); + // internal used by create_torrent.hpp TORRENT_EXPORT std::string filename(std::string const& f); TORRENT_EXTRA_EXPORT std::string combine_path(std::string const& lhs @@ -182,6 +195,34 @@ namespace libtorrent bool m_done; }; + struct file; + +#if TORRENT_DEBUG_FILE_LEAKS + struct file_handle + { + file_handle(); + file_handle(file* f); + file_handle(file_handle const& fh); + ~file_handle(); + file* operator->(); + file const* operator->() const; + file& operator*(); + file const& operator*() const; + file* get(); + file const* get() const; + operator bool() const; + file_handle& reset(file* f = NULL); + + char stack[2048]; + private: + boost::intrusive_ptr m_file; + }; + + void TORRENT_EXTRA_EXPORT print_open_files(char const* event, char const* name); +#else +typedef boost::intrusive_ptr file_handle; +#endif + struct TORRENT_EXTRA_EXPORT file: boost::noncopyable, intrusive_ptr_base { // the open mode for files. Used for the file constructor or @@ -200,39 +241,43 @@ namespace libtorrent // the mask for the bits determining read or write mode rw_mask = read_only | write_only | read_write, - // indicate that the file should be opened in - // *direct io* mode, i.e. bypassing the operating - // system's disk cache, or as much as possible of it - // depending on the system. - // when a file is opened with no_buffer, - // file offsets have to be aligned to - // pos_alignment() and buffer addresses - // to buf_alignment() and read/write sizes - // to size_alignment() - no_buffer = 4, - // open the file in sparse mode (if supported by the // filesystem). - sparse = 8, + sparse = 0x4, // don't update the access timestamps on the file (if // supported by the operating system and filesystem). // this generally improves disk performance. - no_atime = 16, - + no_atime = 0x8, + // open the file for random acces. This disables read-ahead // logic - random_access = 32, + random_access = 0x10, // prevent the file from being opened by another process // while it's still being held open by this handle - lock_file = 64, + lock_file = 0x20, + + // don't put any pressure on the OS disk cache + // because of access to this file. We expect our + // files to be fairly large, and there is already + // a cache at the bittorrent block level. This + // may improve overall system performance by + // leaving running applications in the page cache + no_cache = 0x40, + + // this corresponds to Linux' O_DIRECT flag + // and may impose alignment restrictions + direct_io = 0x80, + + // this is only used for readv/writev flags + coalesce_buffers = 0x100, // when creating a file, set the hidden attribute (windows only) - attribute_hidden = 0x1000, + attribute_hidden = 0x200, // when creating a file, set the executable attribute - attribute_executable = 0x2000, + attribute_executable = 0x400, // the mask of all attribute bits attribute_mask = attribute_hidden | attribute_executable @@ -267,23 +312,10 @@ namespace libtorrent int open_mode() const { return m_open_mode; } - // when opened in unbuffered mode, this is the - // required alignment of file_offsets. i.e. - // any (file_offset & (pos_alignment()-1)) == 0 - // is a precondition to read and write operations - int pos_alignment() const; - - // when opened in unbuffered mode, this is the - // required alignment of buffer addresses - int buf_alignment() const; - - // read/write buffer sizes needs to be aligned to - // this when in unbuffered mode - int size_alignment() const; - - size_type writev(size_type file_offset, iovec_t const* bufs, int num_bufs, error_code& ec); - size_type readv(size_type file_offset, iovec_t const* bufs, int num_bufs, error_code& ec); - void hint_read(size_type file_offset, int len); + size_type writev(size_type file_offset, iovec_t const* bufs, int num_bufs + , error_code& ec, int flags = 0); + size_type readv(size_type file_offset, iovec_t const* bufs, int num_bufs + , error_code& ec, int flags = 0); size_type get_size(error_code& ec) const; @@ -291,31 +323,29 @@ namespace libtorrent // belongs to a data-region size_type sparse_end(size_type start) const; - size_type phys_offset(size_type offset); + handle_type native_handle() const { return m_file_handle; } -#ifdef TORRENT_WINDOWS - HANDLE native_handle() const { return m_file_handle; } -#else - int native_handle() const { return m_fd; } +#ifdef TORRENT_DISK_STATS + boost::uint32_t file_id() const { return m_file_id; } +#endif + +#if TORRENT_DEBUG_FILE_LEAKS + void print_info(FILE* out) const; #endif private: -#ifdef TORRENT_WINDOWS - HANDLE m_file_handle; -#if TORRENT_USE_WSTRING + handle_type m_file_handle; +#ifdef TORRENT_DISK_STATS + boost::uint32_t m_file_id; +#endif + +#if defined TORRENT_WINDOWS && TORRENT_USE_WSTRING std::wstring m_path; -#else +#elif defined TORRENT_WINDOWS std::string m_path; -#endif // TORRENT_USE_WSTRING -#else // TORRENT_WINDOWS - int m_fd; #endif // TORRENT_WINDOWS -#if defined TORRENT_WINDOWS || defined TORRENT_LINUX || defined TORRENT_DEBUG - static void init_file(); - static int m_page_size; -#endif int m_open_mode; #if defined TORRENT_WINDOWS || defined TORRENT_LINUX mutable int m_sector_size; @@ -325,6 +355,10 @@ namespace libtorrent static bool has_manage_volume_privs; #endif + +#if TORRENT_DEBUG_FILE_LEAKS + std::string m_file_path; +#endif }; } diff --git a/include/libtorrent/file_pool.hpp b/include/libtorrent/file_pool.hpp index 4062a1b6a..16a9acf98 100644 --- a/include/libtorrent/file_pool.hpp +++ b/include/libtorrent/file_pool.hpp @@ -33,24 +33,47 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_FILE_POOL_HPP #define TORRENT_FILE_POOL_HPP -#ifdef _MSC_VER -#pragma warning(push, 1) -#endif - -#include - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - #include #include "libtorrent/file.hpp" -#include "libtorrent/ptime.hpp" +#include "libtorrent/time.hpp" #include "libtorrent/thread.hpp" #include "libtorrent/file_storage.hpp" namespace libtorrent { + struct pool_file_status + { + // the index of the file this entry refers to into the ``file_storage`` + // file list of this torrent. This starts indexing at 0. + int file_index; + + // a (high precision) timestamp of when the file was last used. + ptime last_use; + + // ``open_mode`` is a bitmask of the file flags this file is currently opened with. These + // are the flags used in the ``file::open()`` function. This enum is defined as a member + // of the ``file`` class. + // + // :: + // + // enum + // { + // read_only = 0, + // write_only = 1, + // read_write = 2, + // rw_mask = 3, + // no_buffer = 4, + // sparse = 8, + // no_atime = 16, + // random_access = 32, + // lock_file = 64, + // }; + // + // Note that the read/write mode is not a bitmask. The two least significant bits are used + // to represent the read/write mode. Those bits can be masked out using the ``rw_mask`` constant. + int open_mode; + }; + // this is an internal cache of open file handles. It's primarily used by // storage_interface implementations. It provides semi weak guarantees of // not opening more file handles than specified. Given multiple threads, @@ -66,13 +89,12 @@ namespace libtorrent // return an open file handle to file at ``file_index`` in the // file_storage ``fs`` opened at save path ``p``. ``m`` is the // file open mode (see file::open_mode_t). - boost::intrusive_ptr open_file(void* st, std::string const& p + file_handle open_file(void* st, std::string const& p , int file_index, file_storage const& fs, int m, error_code& ec); - // release all files belonging to the specified storage_interface (``st``) // the overload that takes ``file_index`` releases only the file with // that index in storage ``st``. - void release(void* st); + void release(void* st = NULL); void release(void* st, int file_index); // update the allowed number of open file handles to ``size``. @@ -84,10 +106,20 @@ namespace libtorrent // internal void set_low_prio_io(bool b) { m_low_prio_io = b; } + void get_status(std::vector* files, void* st) const; + +#if TORRENT_USE_ASSERTS + bool assert_idle_files(void* st) const; + + // remember that this storage has had + // its files deleted. We may not open any + // files from it again + void mark_deleted(file_storage const& fs); +#endif private: - void remove_oldest(); + void remove_oldest(mutex::scoped_lock& l); int m_size; bool m_low_prio_io; @@ -95,7 +127,7 @@ namespace libtorrent struct lru_file_entry { lru_file_entry(): key(0), last_use(time_now()), mode(0) {} - mutable boost::intrusive_ptr file_ptr; + mutable file_handle file_ptr; void* key; ptime last_use; int mode; @@ -106,17 +138,10 @@ namespace libtorrent typedef std::map, lru_file_entry> file_set; file_set m_files; - mutex m_mutex; - -#if TORRENT_CLOSE_MAY_BLOCK - void closer_thread_fun(); - mutex m_closer_mutex; - std::vector > m_queued_for_close; - bool m_stop_thread; - - // used to close files - thread m_closer_thread; +#if TORRENT_USE_ASSERTS + std::vector > m_deleted_storages; #endif + mutable mutex m_mutex; }; } diff --git a/include/libtorrent/file_storage.hpp b/include/libtorrent/file_storage.hpp index 56014fd3e..20738cb8a 100644 --- a/include/libtorrent/file_storage.hpp +++ b/include/libtorrent/file_storage.hpp @@ -393,6 +393,7 @@ namespace libtorrent { using std::swap; swap(ti.m_files, m_files); + swap(ti.m_num_files, m_num_files); swap(ti.m_file_hashes, m_file_hashes); swap(ti.m_symlinks, m_symlinks); swap(ti.m_mtime, m_mtime); @@ -404,6 +405,13 @@ namespace libtorrent swap(ti.m_piece_length, m_piece_length); } + // deallocates most of the memory used by this + // instance, leaving it only partially usable + void unload(); + + // returns true when populated with at least one file + bool is_loaded() const { return !m_files.empty(); } + // if pad_file_limit >= 0, files larger than that limit will be padded, // default is to not add any padding (-1). The alignment specifies the // alignment files should be padded to. This defaults to the piece size @@ -470,6 +478,8 @@ namespace libtorrent flag_symlink = 8 }; + std::vector const& paths() const { return m_paths; } + // returns a bitmask of flags from file_flags_t that apply // to file at ``index``. int file_flags(int index) const; @@ -520,6 +530,13 @@ namespace libtorrent private: + // the number of bytes in a regular piece + // (i.e. not the potentially truncated last piece) + int m_piece_length; + + // the number of pieces in the torrent + int m_num_pieces; + void update_path_index(internal_file_entry& e); void reorder_file(int index, int dst); @@ -564,10 +581,10 @@ namespace libtorrent // the sum of all filesizes size_type m_total_size; - // the number of pieces in the torrent - int m_num_pieces; + // the number of files. This is used when + // the torrent is unloaded + int m_num_files; - int m_piece_length; }; } diff --git a/include/libtorrent/hasher.hpp b/include/libtorrent/hasher.hpp index 452998d70..1920a281a 100644 --- a/include/libtorrent/hasher.hpp +++ b/include/libtorrent/hasher.hpp @@ -112,14 +112,11 @@ namespace libtorrent // returns the SHA-1 digest of the buffers previously passed to // update() and the hasher constructor. sha1_hash final(); - // restore the hasher state to be as if the hasher has just been // default constructed. void reset(); -#ifdef TORRENT_USE_GCRYPT ~hasher(); -#endif private: @@ -133,6 +130,7 @@ namespace libtorrent sha_ctx m_context; #endif }; + } #endif // TORRENT_HASHER_HPP_INCLUDED diff --git a/include/libtorrent/http_connection.hpp b/include/libtorrent/http_connection.hpp index 150a2dcf4..e1c8b9a8b 100644 --- a/include/libtorrent/http_connection.hpp +++ b/include/libtorrent/http_connection.hpp @@ -41,7 +41,6 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include #include #include "libtorrent/socket.hpp" @@ -51,6 +50,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/assert.hpp" #include "libtorrent/socket_type.hpp" #include "libtorrent/session_settings.hpp" +#include "libtorrent/connection_interface.hpp" #include "libtorrent/i2p_stream.hpp" @@ -63,6 +63,7 @@ namespace libtorrent struct http_connection; class connection_queue; +struct resolver_interface; const int default_max_bottled_buffer_size = 2*1024*1024; @@ -71,16 +72,20 @@ typedef boost::function http_connect_handler; -typedef boost::function&)> http_filter_handler; +typedef boost::function&)> http_filter_handler; // when bottled, the last two arguments to the handler // will always be 0 struct TORRENT_EXTRA_EXPORT http_connection - : boost::enable_shared_from_this + : connection_interface + , boost::enable_shared_from_this , boost::noncopyable { - http_connection(io_service& ios, connection_queue& cc - , http_handler const& handler, bool bottled = true + http_connection(io_service& ios + , connection_queue& cc + , resolver_interface& resolver + , http_handler const& handler + , bool bottled = true , int max_bottled_buffer_size = default_max_bottled_buffer_size , http_connect_handler const& ch = http_connect_handler() , http_filter_handler const& fh = http_filter_handler() @@ -89,7 +94,7 @@ struct TORRENT_EXTRA_EXPORT http_connection #endif ); - ~http_connection(); + virtual ~http_connection(); void rate_limit(int limit); @@ -101,15 +106,17 @@ struct TORRENT_EXTRA_EXPORT http_connection void get(std::string const& url, time_duration timeout = seconds(30) , int prio = 0, proxy_settings const* ps = 0, int handle_redirects = 5 , std::string const& user_agent = "", address const& bind_addr = address_v4::any() + , int resolve_flags = 0 #if TORRENT_USE_I2P , i2p_connection* i2p_conn = 0 #endif ); - void start(std::string const& hostname, std::string const& port + void start(std::string const& hostname, int port , time_duration timeout, int prio = 0, proxy_settings const* ps = 0 , bool ssl = false, int handle_redirect = 5 , address const& bind_addr = address_v4::any() + , int resolve_flags = 0 #if TORRENT_USE_I2P , i2p_connection* i2p_conn = 0 #endif @@ -119,7 +126,7 @@ struct TORRENT_EXTRA_EXPORT http_connection socket_type const& socket() const { return m_sock; } - std::list const& endpoints() const { return m_endpoints; } + std::vector const& endpoints() const { return m_endpoints; } private: @@ -128,9 +135,9 @@ private: , char const* destination); #endif void on_resolve(error_code const& e - , tcp::resolver::iterator i); + , std::vector
const& addresses); void queue_connect(); - void connect(int ticket, tcp::endpoint target_address); + void on_allow_connect(int ticket); void on_connect_timeout(); void on_connect(error_code const& e); void on_write(error_code const& e); @@ -142,44 +149,63 @@ private: void callback(error_code e, char const* data = 0, int size = 0); std::vector m_recvbuffer; - socket_type m_sock; -#if TORRENT_USE_I2P - i2p_connection* m_i2p_conn; -#endif - int m_read_pos; - tcp::resolver m_resolver; - http_parser m_parser; - http_handler m_handler; - http_connect_handler m_connect_handler; - http_filter_handler m_filter_handler; - deadline_timer m_timer; - time_duration m_read_timeout; - time_duration m_completion_timeout; - ptime m_last_receive; - ptime m_start_time; - - // bottled means that the handler is called once, when - // everything is received (and buffered in memory). - // non bottled means that once the headers have been - // received, data is streamed to the handler - bool m_bottled; - // maximum size of bottled buffer - int m_max_bottled_buffer_size; - - // set to true the first time the handler is called - bool m_called; std::string m_hostname; - std::string m_port; std::string m_url; std::string m_user_agent; - std::list m_endpoints; + std::vector m_endpoints; + + // used to keep us alive when queued in the connection_queue + boost::shared_ptr m_self_reference; + + connection_queue& m_cc; + #ifdef TORRENT_USE_OPENSSL asio::ssl::context* m_ssl_ctx; bool m_own_ssl_context; #endif + socket_type m_sock; +#if TORRENT_USE_I2P + i2p_connection* m_i2p_conn; +#endif + resolver_interface& m_resolver; + + http_parser m_parser; + http_handler m_handler; + http_connect_handler m_connect_handler; + http_filter_handler m_filter_handler; + deadline_timer m_timer; + + time_duration m_read_timeout; + time_duration m_completion_timeout; + + // the timer fires every 250 millisecond as long + // as all the quota was used. + deadline_timer m_limiter_timer; + + ptime m_last_receive; + ptime m_start_time; + + // specifies whether or not the connection is + // configured to use a proxy + proxy_settings m_proxy; + + // the address to bind to. address_v4::any() + // means do not bind + address m_bind_addr; + + int m_read_pos; + + // the number of redirects to follow (in sequence) + int m_redirects; + + int m_connection_ticket; + + // maximum size of bottled buffer + int m_max_bottled_buffer_size; + // the current download limit, in bytes per second // 0 is unlimited. int m_rate_limit; @@ -187,36 +213,34 @@ private: // the number of bytes we are allowed to receive int m_download_quota; + // the priority we have in the connection queue. + // 0 is normal, 1 is high + int m_priority; + + // used for DNS lookups + int m_resolve_flags; + + boost::uint16_t m_port; + + // bottled means that the handler is called once, when + // everything is received (and buffered in memory). + // non bottled means that once the headers have been + // received, data is streamed to the handler + bool m_bottled; + + // set to true the first time the handler is called + bool m_called; + // only hand out new quota 4 times a second if the // quota is 0. If it isn't 0 wait for it to reach // 0 and continue to hand out quota at that time. bool m_limiter_timer_active; - // the timer fires every 250 millisecond as long - // as all the quota was used. - deadline_timer m_limiter_timer; - - // the number of redirects to follow (in sequence) - int m_redirects; - - int m_connection_ticket; - connection_queue& m_cc; - - // specifies whether or not the connection is - // configured to use a proxy - proxy_settings m_proxy; + bool m_queued_for_connection; // true if the connection is using ssl bool m_ssl; - // the address to bind to. address_v4::any() - // means do not bind - address m_bind_addr; - - // the priority we have in the connection queue. - // 0 is normal, 1 is high - int m_priority; - bool m_abort; }; diff --git a/include/libtorrent/http_parser.hpp b/include/libtorrent/http_parser.hpp index fe3dda77e..98c15e094 100644 --- a/include/libtorrent/http_parser.hpp +++ b/include/libtorrent/http_parser.hpp @@ -131,7 +131,6 @@ namespace libtorrent private: size_type m_recv_pos; - int m_status_code; std::string m_method; std::string m_path; std::string m_protocol; @@ -141,18 +140,8 @@ namespace libtorrent size_type m_range_start; size_type m_range_end; - enum { read_status, read_header, read_body, error_state } m_state; - std::multimap m_header; buffer::const_interval m_recv_buffer; - int m_body_start_pos; - - // this is true if the server is HTTP/1.0 or - // if it sent "connection: close" - bool m_connection_close; - bool m_chunked_encoding; - bool m_finished; - // contains offsets of the first and one-past-end of // each chunked range in the response std::vector > m_chunked_ranges; @@ -162,6 +151,8 @@ namespace libtorrent // in the chunk tail header or the next chunk header) size_type m_cur_chunk_end; + int m_status_code; + // the sum of all chunk headers read so far int m_chunk_header_size; @@ -169,6 +160,16 @@ namespace libtorrent // controls some behaviors of the parser int m_flags; + + int m_body_start_pos; + + enum { read_status, read_header, read_body, error_state } m_state; + + // this is true if the server is HTTP/1.0 or + // if it sent "connection: close" + bool m_connection_close; + bool m_chunked_encoding; + bool m_finished; }; } diff --git a/include/libtorrent/http_seed_connection.hpp b/include/libtorrent/http_seed_connection.hpp index 181309174..afdbfbd2b 100644 --- a/include/libtorrent/http_seed_connection.hpp +++ b/include/libtorrent/http_seed_connection.hpp @@ -82,10 +82,12 @@ namespace libtorrent // The peer_conenction should handshake and verify that the // other end has the correct id http_seed_connection( - aux::session_impl& ses + aux::session_interface& ses + , aux::session_settings const& sett + , buffer_allocator_interface& allocator + , disk_interface& disk_thread , boost::weak_ptr t , boost::shared_ptr s - , tcp::endpoint const& remote , web_seed_entry& web); virtual int type() const { return peer_connection::http_seed_connection; } @@ -98,7 +100,7 @@ namespace libtorrent std::string const& url() const { return m_url; } virtual void get_specific_peer_info(peer_info& p) const; - virtual void disconnect(error_code const& ec, int error = 0); + virtual void disconnect(error_code const& ec, peer_connection_interface::operation_t op, int error = 0); void write_request(peer_request const& r); diff --git a/include/libtorrent/http_stream.hpp b/include/libtorrent/http_stream.hpp index 816510221..5be779f69 100644 --- a/include/libtorrent/http_stream.hpp +++ b/include/libtorrent/http_stream.hpp @@ -77,8 +77,6 @@ public: } #endif - typedef boost::function handler_type; - template void async_connect(endpoint_type const& endpoint, Handler const& handler) { diff --git a/include/libtorrent/http_tracker_connection.hpp b/include/libtorrent/http_tracker_connection.hpp index fb672871f..b8e60f475 100644 --- a/include/libtorrent/http_tracker_connection.hpp +++ b/include/libtorrent/http_tracker_connection.hpp @@ -34,6 +34,7 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_HTTP_TRACKER_CONNECTION_HPP_INCLUDED #include +#include #ifdef _MSC_VER #pragma warning(push, 1) @@ -58,8 +59,7 @@ namespace libtorrent class entry; class http_parser; class connection_queue; - struct session_settings; - namespace aux { struct session_impl; } + namespace aux { struct session_impl; struct session_settings; } class TORRENT_EXTRA_EXPORT http_tracker_connection : public tracker_connection @@ -73,8 +73,7 @@ namespace libtorrent , tracker_manager& man , tracker_request const& req , boost::weak_ptr c - , aux::session_impl const& ses - , proxy_settings const& ps + , aux::session_impl& ses , std::string const& password = "" #if TORRENT_USE_I2P , i2p_connection* i2p_conn = 0 @@ -89,7 +88,7 @@ namespace libtorrent boost::intrusive_ptr self() { return boost::intrusive_ptr(this); } - void on_filter(http_connection& c, std::list& endpoints); + void on_filter(http_connection& c, std::vector& endpoints); void on_connect(http_connection& c); void on_response(error_code const& ec, http_parser const& parser , char const* data, int size); @@ -101,9 +100,8 @@ namespace libtorrent tracker_manager& m_man; boost::shared_ptr m_tracker_connection; - aux::session_impl const& m_ses; + aux::session_impl& m_ses; address m_tracker_ip; - proxy_settings const& m_ps; connection_queue& m_cc; io_service& m_ios; #if TORRENT_USE_I2P diff --git a/include/libtorrent/i2p_stream.hpp b/include/libtorrent/i2p_stream.hpp index e75aeec73..4f03d998a 100644 --- a/include/libtorrent/i2p_stream.hpp +++ b/include/libtorrent/i2p_stream.hpp @@ -94,8 +94,6 @@ public: void set_destination(std::string const& d) { m_dest = d; } std::string const& destination() { return m_dest; } - typedef boost::function handler_type; - template void async_connect(endpoint_type const& endpoint, Handler const& handler) { @@ -123,7 +121,6 @@ public: private: - bool handle_error(error_code const& e, boost::shared_ptr const& h); void do_connect(error_code const& e, tcp::resolver::iterator i , boost::shared_ptr h); void connected(error_code const& e, boost::shared_ptr h); @@ -161,7 +158,7 @@ public: i2p_connection(io_service& ios); ~i2p_connection(); - proxy_settings const& proxy() const { return m_sam_router; } + proxy_settings proxy() const; bool is_open() const { @@ -169,7 +166,7 @@ public: && m_sam_socket->is_open() && m_state != sam_connecting; } - void open(proxy_settings const& s, i2p_stream::handler_type const& h); + void open(std::string const& hostname, int port, i2p_stream::handler_type const& h); void close(error_code&); char const* session_id() const { return m_session_id.c_str(); } @@ -192,7 +189,8 @@ private: // to talk to i2p SAM bridge boost::shared_ptr m_sam_socket; - proxy_settings m_sam_router; + std::string m_hostname; + int m_port; // our i2p endpoint key std::string m_i2p_local_endpoint; diff --git a/include/libtorrent/intrusive_ptr_base.hpp b/include/libtorrent/intrusive_ptr_base.hpp index 7ebe98615..7aaaea328 100644 --- a/include/libtorrent/intrusive_ptr_base.hpp +++ b/include/libtorrent/intrusive_ptr_base.hpp @@ -33,14 +33,15 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_INTRUSIVE_PTR_BASE #define TORRENT_INTRUSIVE_PTR_BASE -#include #include #include #include "libtorrent/config.hpp" #include "libtorrent/assert.hpp" +#include "libtorrent/atomic.hpp" namespace libtorrent { + // TODO: 2 remove this class and transition over to using shared_ptr and make_shared instead template struct intrusive_ptr_base { @@ -75,7 +76,7 @@ namespace libtorrent private: // reference counter for intrusive_ptr - mutable boost::detail::atomic_count m_refs; + mutable atomic_count m_refs; }; } diff --git a/include/libtorrent/ip_filter.hpp b/include/libtorrent/ip_filter.hpp index d868ea7cc..23db5c500 100644 --- a/include/libtorrent/ip_filter.hpp +++ b/include/libtorrent/ip_filter.hpp @@ -42,6 +42,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include #ifdef _MSC_VER @@ -68,7 +69,7 @@ struct ip_range { Addr first; Addr last; - int flags; + boost::uint32_t flags; }; namespace detail @@ -161,8 +162,8 @@ namespace detail TORRENT_ASSERT(j != m_access_list.begin()); TORRENT_ASSERT(j != i); - int first_access = i->access; - int last_access = boost::prior(j)->access; + boost::uint32_t first_access = i->access; + boost::uint32_t last_access = boost::prior(j)->access; if (i->start != first && first_access != flags) { @@ -182,7 +183,7 @@ namespace detail // we can do this const-cast because we know that the new // start address will keep the set correctly ordered const_cast(i->start) = first; - const_cast(i->access) = flags; + const_cast(i->access) = flags; } else if (first_access != flags) { @@ -203,7 +204,7 @@ namespace detail TORRENT_ASSERT(!m_access_list.empty()); } - int access(Addr const& addr) const + boost::uint32_t access(Addr const& addr) const { TORRENT_ASSERT(!m_access_list.empty()); typename range_t::const_iterator i = m_access_list.upper_bound(addr); @@ -250,7 +251,7 @@ namespace detail Addr start; // the end of the range is implicit // and given by the next entry in the set - int access; + boost::uint32_t access; }; typedef std::set range_t; @@ -290,7 +291,7 @@ struct TORRENT_EXPORT ip_filter // // This means that in a case of overlapping ranges, the last one applied takes // precedence. - void add_rule(address first, address last, int flags); + void add_rule(address first, address last, boost::uint32_t flags); // Returns the access permissions for the given address (``addr``). The permission // can currently be 0 or ``ip_filter::blocked``. The complexity of this operation @@ -343,7 +344,7 @@ public: // set the flags for the specified port range (``first``, ``last``) to // ``flags`` overwriting any existing rule for those ports. The range // is inclusive, i.e. the port ``last`` also has the flag set on it. - void add_rule(boost::uint16_t first, boost::uint16_t last, int flags); + void add_rule(boost::uint16_t first, boost::uint16_t last, boost::uint32_t flags); // test the specified port (``port``) for whether it is blocked // or not. The returned value is the flags set for this port. diff --git a/include/libtorrent/kademlia/dht_tracker.hpp b/include/libtorrent/kademlia/dht_tracker.hpp index 691169b51..3f8a93f7f 100644 --- a/include/libtorrent/kademlia/dht_tracker.hpp +++ b/include/libtorrent/kademlia/dht_tracker.hpp @@ -46,6 +46,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/kademlia/node.hpp" #include "libtorrent/kademlia/node_id.hpp" #include "libtorrent/kademlia/traversal_algorithm.hpp" +#include "libtorrent/kademlia/dos_blocker.hpp" + #include "libtorrent/session_settings.hpp" #include "libtorrent/session_status.hpp" #include "libtorrent/udp_socket.hpp" @@ -57,6 +59,7 @@ namespace libtorrent { namespace aux { struct session_impl; } struct lazy_entry; + struct counters; } namespace libtorrent { namespace dht @@ -77,7 +80,7 @@ namespace libtorrent { namespace dht friend void intrusive_ptr_release(dht_tracker const*); dht_tracker(libtorrent::aux::session_impl& ses, rate_limited_udp_socket& sock - , dht_settings const& settings, entry const* state = 0); + , dht_settings const& settings, counters& cnt, entry const* state = 0); virtual ~dht_tracker(); void start(entry const& bootstrap @@ -131,13 +134,16 @@ namespace libtorrent { namespace dht void tick(error_code const& e); // implements udp_socket_interface + virtual bool has_quota(); virtual bool send_packet(libtorrent::entry& e, udp::endpoint const& addr , int send_flags); + counters& m_counters; node_impl m_dht; rate_limited_udp_socket& m_sock; std::vector m_send_buf; + dos_blocker m_blocker; ptime m_last_new_key; deadline_timer m_timer; @@ -155,19 +161,6 @@ namespace libtorrent { namespace dht int m_sent_bytes; int m_received_bytes; - // used to ignore abusive dht nodes - struct node_ban_entry - { - node_ban_entry(): count(0) {} - address src; - ptime limit; - int count; - }; - - enum { num_ban_nodes = 20 }; - - node_ban_entry m_ban_nodes[num_ban_nodes]; - // reference counter for intrusive_ptr mutable boost::detail::atomic_count m_refs; diff --git a/include/libtorrent/kademlia/dos_blocker.hpp b/include/libtorrent/kademlia/dos_blocker.hpp new file mode 100644 index 000000000..84449b5be --- /dev/null +++ b/include/libtorrent/kademlia/dos_blocker.hpp @@ -0,0 +1,75 @@ +/* + +Copyright (c) 2006-2013, 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_DHT_DOS_BLOCKER +#define TORRENT_DHT_DOS_BLOCKER + +#include "libtorrent/config.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/address.hpp" + +namespace libtorrent { namespace dht +{ + +// this is a class that maintains a list of abusive DHT nodes, +// blocking their access to our DHT node. + struct TORRENT_EXTRA_EXPORT dos_blocker + { + dos_blocker(); + + // called every time we receive an incoming packet. Returns + // true if we should let the packet through, and false if + // it's blocked + bool incoming(address addr, ptime now); + + private: + + // used to ignore abusive dht nodes + struct node_ban_entry + { + node_ban_entry(): count(0) {} + address src; + ptime limit; + int count; + }; + + enum { num_ban_nodes = 20 }; + + node_ban_entry m_ban_nodes[num_ban_nodes]; + + }; + +} +} + +#endif + diff --git a/include/libtorrent/kademlia/logging.hpp b/include/libtorrent/kademlia/logging.hpp index f10529a21..45206add6 100644 --- a/include/libtorrent/kademlia/logging.hpp +++ b/include/libtorrent/kademlia/logging.hpp @@ -39,7 +39,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include -#include "libtorrent/ptime.hpp" +#include "libtorrent/time.hpp" namespace libtorrent { namespace dht { diff --git a/include/libtorrent/kademlia/node.hpp b/include/libtorrent/kademlia/node.hpp index b381a5ad1..9f85e9a29 100644 --- a/include/libtorrent/kademlia/node.hpp +++ b/include/libtorrent/kademlia/node.hpp @@ -59,6 +59,7 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { class alert_manager; struct alert_dispatcher; + struct counters; } namespace libtorrent { namespace dht @@ -184,6 +185,7 @@ struct count_peers struct udp_socket_interface { + virtual bool has_quota() = 0; virtual bool send_packet(entry& e, udp::endpoint const& addr, int flags) = 0; }; @@ -196,7 +198,7 @@ typedef std::map dht_mutable_table_t; public: node_impl(alert_dispatcher* alert_disp, udp_socket_interface* sock , libtorrent::dht_settings const& settings, node_id nid, address const& external_address - , dht_observer* observer); + , dht_observer* observer, counters& cnt); virtual ~node_impl() {} @@ -276,6 +278,7 @@ public: void status(libtorrent::session_status& s); libtorrent::dht_settings const& settings() const { return m_settings; } + counters& stats_counters() const { return m_counters; } protected: @@ -285,7 +288,7 @@ protected: , char* tags) const; libtorrent::dht_settings const& m_settings; - + private: typedef libtorrent::mutex mutex_t; mutex_t m_mutex; @@ -316,6 +319,7 @@ private: alert_dispatcher* m_post_alert; udp_socket_interface* m_sock; + counters& m_counters; }; diff --git a/include/libtorrent/kademlia/observer.hpp b/include/libtorrent/kademlia/observer.hpp index 61c6d714e..2151e14dc 100644 --- a/include/libtorrent/kademlia/observer.hpp +++ b/include/libtorrent/kademlia/observer.hpp @@ -37,7 +37,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include +#include #include namespace libtorrent { @@ -51,17 +51,6 @@ struct traversal_algorithm; TORRENT_EXTRA_EXPORT void intrusive_ptr_add_ref(observer const*); TORRENT_EXTRA_EXPORT void intrusive_ptr_release(observer const*); -// intended struct layout (on 32 bit architectures) -// offset size alignment field -// 0 8 8 sent -// 8 8 4 m_refs -// 16 4 4 pool_allocator -// 20 16 4 m_addr -// 36 2 2 m_port -// 38 1 1 flags -// 39 1 1 -// 40 - struct observer : boost::noncopyable { friend TORRENT_EXTRA_EXPORT void intrusive_ptr_add_ref(observer const*); @@ -70,9 +59,9 @@ struct observer : boost::noncopyable observer(boost::intrusive_ptr const& a , udp::endpoint const& ep, node_id const& id) : m_sent() - , m_refs(0) , m_algorithm(a) , m_id(id) + , m_refs(0) , m_port(0) , m_transaction_id() , flags(0) @@ -143,9 +132,6 @@ protected: ptime m_sent; - // reference counter for intrusive_ptr - mutable boost::detail::atomic_count m_refs; - const boost::intrusive_ptr m_algorithm; node_id m_id; @@ -158,6 +144,9 @@ protected: address_v4::bytes_type v4; } m_addr; + // reference counter for intrusive_ptr + mutable boost::uint16_t m_refs; + boost::uint16_t m_port; // the transaction ID for this call diff --git a/include/libtorrent/kademlia/routing_table.hpp b/include/libtorrent/kademlia/routing_table.hpp index 025767f5d..9938eaed8 100644 --- a/include/libtorrent/kademlia/routing_table.hpp +++ b/include/libtorrent/kademlia/routing_table.hpp @@ -48,7 +48,8 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include +#include +#include namespace libtorrent { @@ -190,9 +191,6 @@ private: dht_settings const& m_settings; - // constant called k in paper - int m_bucket_size; - // (k-bucket, replacement cache) pairs // the first entry is the bucket the furthest // away from our own ID. Each time the bucket @@ -229,7 +227,11 @@ private: // table. It's used to only allow a single entry // per IP in the whole table. Currently only for // IPv4 - std::multiset m_ips; + boost::unordered_multiset m_ips; + + // constant called k in paper + int m_bucket_size; + }; } } // namespace libtorrent::dht diff --git a/include/libtorrent/kademlia/rpc_manager.hpp b/include/libtorrent/kademlia/rpc_manager.hpp index 4e277079c..c9e365de3 100644 --- a/include/libtorrent/kademlia/rpc_manager.hpp +++ b/include/libtorrent/kademlia/rpc_manager.hpp @@ -40,13 +40,19 @@ POSSIBILITY OF SUCH DAMAGE. #include #include +#if TORRENT_HAS_BOOST_UNORDERED +#include +#else +#include +#endif + #include #include #include #include #include -#include "libtorrent/ptime.hpp" +#include "libtorrent/time.hpp" namespace libtorrent { namespace aux { struct session_impl; } } @@ -108,15 +114,19 @@ private: mutable boost::pool<> m_pool_allocator; - typedef std::deque transactions_t; +#if TORRENT_HAS_BOOST_UNORDERED + typedef boost::unordered_multimap transactions_t; +#else + typedef std::multimap transactions_t; +#endif transactions_t m_transactions; udp_socket_interface* m_sock; routing_table& m_table; ptime m_timer; node_id m_our_id; - int m_allocated_observers; - bool m_destructing; + boost::uint32_t m_allocated_observers:31; + boost::uint32_t m_destructing:1; }; } } // namespace libtorrent::dht diff --git a/include/libtorrent/kademlia/traversal_algorithm.hpp b/include/libtorrent/kademlia/traversal_algorithm.hpp index 1abd13d56..f3b5095bc 100644 --- a/include/libtorrent/kademlia/traversal_algorithm.hpp +++ b/include/libtorrent/kademlia/traversal_algorithm.hpp @@ -102,6 +102,7 @@ protected: friend void intrusive_ptr_add_ref(traversal_algorithm* p) { + TORRENT_ASSERT(p->m_ref_count < 0xffff); p->m_ref_count++; } @@ -111,16 +112,19 @@ protected: delete p; } - int m_ref_count; - node_impl& m_node; - node_id const m_target; std::vector m_results; - int m_invoke_count; - int m_branch_factor; - int m_responses; - int m_timeouts; - int m_num_target_nodes; + node_id const m_target; + boost::uint16_t m_ref_count; + boost::uint16_t m_invoke_count; + boost::uint16_t m_branch_factor; + boost::uint16_t m_responses; + boost::uint16_t m_timeouts; + + // the IP addresses of the nodes in m_results + std::set m_peer4_prefixes; +// no IPv6 support yet anyway +// std::set m_peer6_prefixes; }; struct traversal_observer : observer diff --git a/include/libtorrent/lazy_entry.hpp b/include/libtorrent/lazy_entry.hpp index 2d8d24b6b..68b9eb4e9 100644 --- a/include/libtorrent/lazy_entry.hpp +++ b/include/libtorrent/lazy_entry.hpp @@ -142,8 +142,8 @@ namespace libtorrent }; // internal - lazy_entry() : m_begin(0), m_len(0), m_size(0), m_capacity(0), m_type(none_t) - { m_data.start = 0; } + lazy_entry() : m_begin(0), m_len(0), m_size(0), m_type(none_t) + { m_data.start = NULL; } // tells you which specific type this lazy entry has. // See entry_type_t. The type determines which subset of @@ -213,7 +213,6 @@ namespace libtorrent TORRENT_ASSERT(m_type == none_t); m_type = dict_t; m_size = 0; - m_capacity = 0; m_begin = begin; } @@ -268,7 +267,6 @@ namespace libtorrent TORRENT_ASSERT(m_type == none_t); m_type = list_t; m_size = 0; - m_capacity = 0; m_begin = begin; } @@ -281,7 +279,7 @@ namespace libtorrent { TORRENT_ASSERT(m_type == list_t); TORRENT_ASSERT(i < int(m_size)); - return &m_data.list[i]; + return &m_data.list[i+1]; } lazy_entry const* list_at(int i) const { return const_cast(this)->list_at(i); } @@ -322,9 +320,8 @@ namespace libtorrent // internal: releases ownership of any memory allocated void release() { - m_data.start = 0; + m_data.start = NULL; m_size = 0; - m_capacity = 0; m_type = none_t; } @@ -343,19 +340,23 @@ namespace libtorrent boost::uint32_t tmp = e.m_type; e.m_type = m_type; m_type = tmp; - tmp = e.m_capacity; - e.m_capacity = m_capacity; - m_capacity = tmp; + tmp = e.m_size; + e.m_size = m_size; + m_size = tmp; swap(m_data.start, e.m_data.start); - swap(m_size, e.m_size); swap(m_begin, e.m_begin); swap(m_len, e.m_len); } private: + int capacity() const; + union data_t { + // for the dict and list arrays, the first item is not part + // of the array. Instead its m_len member indicates the capacity + // of the allocation lazy_dict_entry* dict; lazy_entry* list; char const* start; @@ -364,14 +365,13 @@ namespace libtorrent // used for dictionaries and lists to record the range // in the original buffer they are based on char const* m_begin; + // the number of bytes this entry extends in the - // bencoded byffer + // bencoded buffer boost::uint32_t m_len; // if list or dictionary, the number of items - boost::uint32_t m_size; - // if list or dictionary, allocated number of items - boost::uint32_t m_capacity:29; + boost::uint32_t m_size:29; // element type (dict, list, int, string) boost::uint32_t m_type:3; diff --git a/include/libtorrent/link.hpp b/include/libtorrent/link.hpp new file mode 100644 index 000000000..7352927d4 --- /dev/null +++ b/include/libtorrent/link.hpp @@ -0,0 +1,77 @@ +/* + +Copyright (c) 2011-2013, 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_LINK_HPP_INCLUDED +#define TORRENT_LINK_HPP_INCLUDED + +namespace libtorrent +{ + struct link + { + link() : index(-1) {} + // this is either -1 (not in the list) + // or the index of where in the list this + // element is found + int index; + + bool in_list() const { return index >= 0; } + + void clear() { index = -1; } + + template + void unlink(std::vector& list, int link_index) + { + if (index == -1) return; + TORRENT_ASSERT(index >= 0 && index < int(list.size())); + int last = int(list.size()) - 1; + if (index < last) + { + list[last]->m_links[link_index].index = index; + list[index] = list[last]; + } + list.resize(last); + index = -1; + } + + template + void insert(std::vector& list, T* self) + { + if (index >= 0) return; + TORRENT_ASSERT(index == -1); + index = int(list.size()); + list.push_back(self); + } + }; +}; + +#endif + diff --git a/include/libtorrent/linked_list.hpp b/include/libtorrent/linked_list.hpp new file mode 100644 index 000000000..af4b2bcbe --- /dev/null +++ b/include/libtorrent/linked_list.hpp @@ -0,0 +1,151 @@ +/* + +Copyright (c) 2012-2013, 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_LINKED_LIST_HPP +#define TORRENT_LINKED_LIST_HPP + +#include "libtorrent/assert.hpp" + +namespace libtorrent +{ + struct list_node + { + list_node() : prev(0), next(0) {} + list_node* prev; + list_node* next; + }; + + struct list_iterator + { + friend struct linked_list; + list_node const* get() const { return m_current; } + list_node* get() { return m_current; } + void next() { m_current = m_current->next; } + void prev() { m_current = m_current->prev; } + + private: + list_iterator(list_node* cur) + : m_current(cur) {} + // the current element + list_node* m_current; + }; + + struct linked_list + { + linked_list(): m_first(0), m_last(0), m_size(0) {} + + list_iterator iterate() const + { return list_iterator(m_first); } + + void erase(list_node* e) + { +#ifdef TORRENT_DEBUG + list_node* tmp = m_first; + bool found = false; + while (tmp) + { + if (tmp == e) + { + found = true; + break; + } + tmp = tmp->next; + } + TORRENT_ASSERT(found); +#endif + if (e == m_first) + { + TORRENT_ASSERT(e->prev == 0); + m_first = e->next; + } + if (e == m_last) + { + TORRENT_ASSERT(e->next == 0); + m_last = e->prev; + } + if (e->prev) e->prev->next = e->next; + if (e->next) e->next->prev = e->prev; + e->next = 0; + e->prev = 0; + TORRENT_ASSERT(m_size > 0); + --m_size; + TORRENT_ASSERT(m_last == 0 || m_last->next == 0); + } + void push_front(list_node* e) + { + TORRENT_ASSERT(e->next == 0); + TORRENT_ASSERT(e->prev== 0); + TORRENT_ASSERT(m_last == 0 || m_last->next == 0); + e->prev = 0; + e->next = m_first; + if (m_first) m_first->prev = e; + else m_last = e; + m_first = e; + ++m_size; + } + void push_back(list_node* e) + { + TORRENT_ASSERT(e->next == 0); + TORRENT_ASSERT(e->prev== 0); + TORRENT_ASSERT(m_last == 0 || m_last->next == 0); + e->prev = m_last; + e->next = 0; + if (m_last) m_last->next = e; + else m_first = e; + m_last = e; + ++m_size; + } + list_node* get_all() + { + TORRENT_ASSERT(m_last == 0 || m_last->next == 0); + TORRENT_ASSERT(m_first == 0 || m_first->prev == 0); + list_node* e = m_first; + m_first = 0; + m_last = 0; + m_size = 0; + return e; + } + list_node* back() { return m_last; } + list_node* front() { return m_first; } + list_node const* back() const { return m_last; } + list_node const* front() const { return m_first; } + int size() const { return m_size; } + bool empty() const { return m_size == 0; } + private: + list_node* m_first; + list_node* m_last; + int m_size; + }; +}; + +#endif // LINKED_LIST_HPP + diff --git a/include/libtorrent/max.hpp b/include/libtorrent/max.hpp index f6c88002c..d8c726250 100644 --- a/include/libtorrent/max.hpp +++ b/include/libtorrent/max.hpp @@ -36,6 +36,9 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { + template + struct min { enum { value = v1 struct max { enum { value = v1>v2?v1:v2 }; }; diff --git a/include/libtorrent/network_thread_pool.hpp b/include/libtorrent/network_thread_pool.hpp new file mode 100644 index 000000000..88821323c --- /dev/null +++ b/include/libtorrent/network_thread_pool.hpp @@ -0,0 +1,78 @@ +/* + +Copyright (c) 2011-2013, 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_NETWORK_THREAD_POOL_HPP_INCLUDED +#define TORRENT_NETWORK_THREAD_POOL_HPP_INCLUDED + +#include "libtorrent/thread_pool.hpp" +#include +#include + +namespace libtorrent +{ + + class peer_connection; + class buffer; + + struct socket_job + { + socket_job() : vec(NULL), recv_buf(NULL), buf_size(0) {} + + enum job_type_t + { + read_job = 0, + write_job + }; + + job_type_t type; + + // used for write jobs + std::vector const* vec; + // used for read jobs + char* recv_buf; + int buf_size; + boost::array read_vec; + + boost::shared_ptr peer; + // defined in session_impl.cpp + ~socket_job(); + }; + + // defined in session_impl.cpp + struct network_thread_pool : thread_pool + { + void process_job(socket_job const& j, bool post); + }; +} + +#endif // TORRENT_NETWORK_THREAD_POOL_HPP_INCLUDED + diff --git a/include/libtorrent/part_file.hpp b/include/libtorrent/part_file.hpp new file mode 100644 index 000000000..a07697176 --- /dev/null +++ b/include/libtorrent/part_file.hpp @@ -0,0 +1,112 @@ +/* + +Copyright (c) 2012-2013, 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. + +*/ + +#include +#include +#include +#include "libtorrent/config.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/error_code.hpp" +#include "libtorrent/thread.hpp" // for mutex + +namespace libtorrent +{ + struct TORRENT_EXTRA_EXPORT part_file + { + // create a part file at 'path', that can hold 'num_pieces' pieces. + // each piece being 'piece_size' number of bytes + part_file(std::string const& path, std::string const& name, int num_pieces, int piece_size); + ~part_file(); + + int writev(file::iovec_t const* bufs, int num_bufs, int piece, int offset, error_code& ec); + int readv(file::iovec_t const* bufs, int num_bufs, int piece, int offset, error_code& ec); + + // free the slot the given piece is stored in. We no longer need to store this + // piece in the part file + void free_piece(int piece, error_code& ec); + + void move_partfile(std::string const& path, error_code& ec); + + void import_file(file& f, size_type offset, size_type size, error_code& ec); + void export_file(file& f, size_type offset, size_type size, error_code& ec); + + // flush the metadata + void flush_metadata(error_code& ec); + + private: + + void open_file(int mode, error_code& ec); + void flush_metadata_impl(error_code& ec); + + std::string m_path; + std::string m_name; + + // allocate a slot and return the slot index + int allocate_slot(int piece); + + // this mutex must be held while accessing the data + // structure. Not while reading or writing from the file though! + // it's important to support multithreading + mutex m_mutex; + + // this is a list of unallocated slots in the part file + // within the m_num_allocated range + std::vector m_free_slots; + + // this is the number of slots allocated + int m_num_allocated; + + // the max number of pieces in the torrent this part file is + // backing + int m_max_pieces; + + // number of bytes each piece contains + int m_piece_size; + + // this is the size of the part_file header, it is added + // to offsets when calculating the offset to read and write + // payload data from + int m_header_size; + + // if this is true, the metadata in memory has changed since + // we last saved or read it from disk. It means that we + // need to flush the metadata before closing the file + bool m_dirty_metadata; + + // maps a piece index to the part-file slot it is stored in + boost::unordered_map m_piece_map; + + // this is the file handle to the part file + file m_file; + }; +} + diff --git a/include/libtorrent/peer_class.hpp b/include/libtorrent/peer_class.hpp new file mode 100644 index 000000000..4b313bdea --- /dev/null +++ b/include/libtorrent/peer_class.hpp @@ -0,0 +1,145 @@ +/* + +Copyright (c) 2011-2013, 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_PEER_CLASS_HPP_INCLUDED +#define TORRENT_PEER_CLASS_HPP_INCLUDED + +#include "libtorrent/intrusive_ptr_base.hpp" +#include "libtorrent/bandwidth_limit.hpp" +#include "libtorrent/assert.hpp" + +#include +#include +#include +#include + +namespace libtorrent +{ + typedef boost::uint32_t peer_class_t; + + struct peer_class_info + { + // ``ignore_unchoke_slots`` determines whether peers should always unchoke a peer, + // regardless of the choking algorithm, or if it should honor the unchoke slot limits. + // It's used for local peers by default. If *any* of the peer classes a peer belongs to + // has this set to true, that peer will be unchoked at all times. + bool ignore_unchoke_slots; + + // adjusts the connection limit (global and per torrent) that + // applies to this peer class. By default, local peers are allowed to exceed the normal + // connection limit for instance. This is specified as a percent factor. 100 makes + // the peer class apply normally to the limit. 200 means as long as there are fewer + // connections than twice the limit, we accept this peer. This factor applies both to + // the global connection limit and the per-torrent limit. Note that if not used carefully + // one peer class can potentially completely starve out all other over time. + int connection_limit_factor; + + // not used by libtorrent. It's intended as a potentially user-facing identifier + // of this peer class. + std::string label; + + // transfer rates limits for the whole peer class. + // They are specified in bytes per second and apply to the sum of all peers that are + // members of this class. + int upload_limit; + int download_limit; + + // relative priorities used by the + // bandwidth allocator in the rate limiter. If no rate limits are in use, the priority + // is not used either. Priorities start at 1 (0 is not a valid priority) and may not + // exceed 255. + int upload_priority; + int download_priority; + }; + + struct TORRENT_EXTRA_EXPORT peer_class : intrusive_ptr_base + { + friend struct peer_class_pool; + + peer_class(std::string const& label) + : ignore_unchoke_slots(false) + , connection_limit_factor(100) + , label(label) + , references(1) + { + priority[0] = 1; + priority[1] = 1; + } + + void set_info(peer_class_info const* pci); + void get_info(peer_class_info* pci) const; + + void set_upload_limit(int limit); + void set_download_limit(int limit); + + // the bandwidth channels, upload and download + // keeps track of the current quotas + bandwidth_channel channel[2]; + + bool ignore_unchoke_slots; + int connection_limit_factor; + + // priority for bandwidth allocation + // in rate limiter. One for upload and one + // for download + int priority[2]; + + // the name of this peer class + std::string label; + + private: + int references; + + }; + + struct TORRENT_EXTRA_EXPORT peer_class_pool + { + + peer_class_t new_peer_class(std::string const& label); + void decref(peer_class_t c); + void incref(peer_class_t c); + peer_class* at(peer_class_t c); + peer_class const* at(peer_class_t c) const; + + private: + + // state for peer classes (a peer can belong to multiple classes) + // this can control + std::vector > m_peer_classes; + + // indices in m_peer_classes that are no longer used + std::vector m_free_list; + }; +} + +#endif // TORRENT_PEER_CLASS_HPP_INCLUDED + diff --git a/include/libtorrent/peer_class_set.hpp b/include/libtorrent/peer_class_set.hpp new file mode 100644 index 000000000..b45c3c59b --- /dev/null +++ b/include/libtorrent/peer_class_set.hpp @@ -0,0 +1,67 @@ +/* + +Copyright (c) 2011-2013, 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_PEER_CLASS_SET_HPP_INCLUDED +#define TORRENT_PEER_CLASS_SET_HPP_INCLUDED + +#include "libtorrent/peer_class.hpp" +#include + +namespace libtorrent { + + // this represents an object that can have many peer classes applied + // to it. Most notably, peer connections and torrents derive from this. + struct peer_class_set + { + void add_class(peer_class_pool& pool, peer_class_t c); + bool has_class(peer_class_t c) const; + void remove_class(peer_class_pool& pool, peer_class_t c); + int num_classes() const { return m_class.size(); } + peer_class_t class_at(int i) const + { + TORRENT_ASSERT(i >= 0 && i < int(m_class.size())); + return m_class[i]; + } + + private: + + // if this object belongs to any peer-class, this + // vector contains all class IDs. Each ID refers + // to a an entry in m_ses.m_peer_classes which + // holds the metadata about the class. Classes + // affect bandwidth limits among other things + std::vector m_class; + }; +} + +#endif // TORRENT_PEER_CLASS_SET_HPP_INCLUDED + diff --git a/include/libtorrent/peer_class_type_filter.hpp b/include/libtorrent/peer_class_type_filter.hpp new file mode 100644 index 000000000..672755a4a --- /dev/null +++ b/include/libtorrent/peer_class_type_filter.hpp @@ -0,0 +1,141 @@ +/* + +Copyright (c) 2012-2013, 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_PEER_CLASS_TYPE_FILTER_HPP_INCLUDED +#define TORRENT_PEER_CLASS_TYPE_FILTER_HPP_INCLUDED + +#include // for memset +#include + +namespace libtorrent +{ + + // ``peer_class_type_filter`` is a simple container for rules for adding and subtracting + // peer-classes from peers. It is applied *after* the peer class filter is applied (which + // is based on the peer's IP address). + struct peer_class_type_filter + { + peer_class_type_filter() + { + memset(m_peer_class_type_mask, 0xff, sizeof(m_peer_class_type_mask)); + memset(m_peer_class_type, 0, sizeof(m_peer_class_type)); + } + + enum socket_type_t + { + // these match the socket types from socket_type.hpp + // shifted one down + tcp_socket = 0, + utp_socket, + ssl_tcp_socket, + ssl_utp_socket, + i2p_socket, + num_socket_types + }; + + + // ``add()`` and ``remove()`` adds and removes a peer class to be added + // to new peers based on socket type. + void add(socket_type_t st, int peer_class) + { + TORRENT_ASSERT(peer_class >= 0); + TORRENT_ASSERT(peer_class < 32); + if (peer_class < 0 || peer_class > 31) return; + + TORRENT_ASSERT(st < num_socket_types && st >= 0); + if (st < 0 || st >= num_socket_types) return; + m_peer_class_type[st] |= 1 << peer_class; + } + void remove(socket_type_t st, int peer_class) + { + TORRENT_ASSERT(peer_class >= 0); + TORRENT_ASSERT(peer_class < 32); + if (peer_class < 0 || peer_class > 31) return; + + TORRENT_ASSERT(st < num_socket_types && st >= 0); + if (st < 0 || st >= num_socket_types) return; + m_peer_class_type[st] &= ~(1 << peer_class); + } + + // ``disallow()`` and ``allow()`` adds and removes a peer class to be + // removed from new peers based on socket type. + // + // The ``peer_class`` argument cannot be greater than 31. The bitmasks representing + // peer classes in the ``peer_class_type_filter`` are 32 bits. + void disallow(socket_type_t st, int peer_class) + { + TORRENT_ASSERT(peer_class >= 0); + TORRENT_ASSERT(peer_class < 32); + if (peer_class < 0 || peer_class > 31) return; + + TORRENT_ASSERT(st < num_socket_types && st >= 0); + if (st < 0 || st >= num_socket_types) return; + m_peer_class_type_mask[st] &= ~(1 << peer_class); + } + void allow(socket_type_t st, int peer_class) + { + TORRENT_ASSERT(peer_class >= 0); + TORRENT_ASSERT(peer_class < 32); + if (peer_class < 0 || peer_class > 31) return; + + TORRENT_ASSERT(st < num_socket_types && st >= 0); + if (st < 0 || st >= num_socket_types) return; + m_peer_class_type_mask[st] |= 1 << peer_class; + } + + // takes a bitmask of peer classes and returns a new bitmask of + // peer classes after the rules have been applied, based on the socket type argument + // (``st``). + boost::uint32_t apply(int st, boost::uint32_t peer_class_mask) + { + TORRENT_ASSERT(st < num_socket_types && st >= 0); + if (st < 0 || st >= num_socket_types) return peer_class_mask; + + // filter peer classes based on type + peer_class_mask &= m_peer_class_type_mask[st]; + // add peer classes based on type + peer_class_mask |= m_peer_class_type[st]; + return peer_class_mask; + } + + private: + // maps socket type to a bitmask that's used to filter out + // (mask) bits from the m_peer_class_filter. + boost::uint32_t m_peer_class_type_mask[5]; + // peer class bitfield added based on socket type + boost::uint32_t m_peer_class_type[5]; + }; + +} + +#endif + diff --git a/include/libtorrent/peer_connection.hpp b/include/libtorrent/peer_connection.hpp index 7e67daf71..09f8aa46c 100644 --- a/include/libtorrent/peer_connection.hpp +++ b/include/libtorrent/peer_connection.hpp @@ -68,9 +68,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/piece_block_progress.hpp" #include "libtorrent/config.hpp" #include "libtorrent/bandwidth_limit.hpp" -#include "libtorrent/policy.hpp" #include "libtorrent/socket_type_fwd.hpp" -#include "libtorrent/intrusive_ptr_base.hpp" #include "libtorrent/assert.hpp" #include "libtorrent/chained_buffer.hpp" #include "libtorrent/disk_buffer_holder.hpp" @@ -79,38 +77,42 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket_type_fwd.hpp" #include "libtorrent/error_code.hpp" #include "libtorrent/sliding_average.hpp" +#include "libtorrent/peer_class.hpp" +#include "libtorrent/peer_class_set.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/disk_observer.hpp" +#include "libtorrent/connection_interface.hpp" +#include "libtorrent/peer_connection_interface.hpp" +#include "libtorrent/piece_picker.hpp" // for piece_block +#include "libtorrent/socket.hpp" // for tcp::endpoint #include "libtorrent/io_service_fwd.hpp" -#ifdef TORRENT_STATS -#include "libtorrent/aux_/session_impl.hpp" -#endif - namespace libtorrent { class torrent; struct peer_info; struct disk_io_job; + struct disk_interface; + struct torrent_peer; + #ifndef TORRENT_DISABLE_EXTENSIONS struct peer_plugin; #endif - namespace detail + namespace aux { - struct session_impl; + struct session_interface; } struct pending_block { pending_block(piece_block const& b) - : block(b), skipped(0), not_wanted(false) - , timed_out(false), busy(false) {} + : block(b), not_wanted(false) + , timed_out(false), busy(false) + {} piece_block block; - // the number of times the request - // has been skipped by out of order blocks - boost::uint16_t skipped:13; - // if any of these are set to true, this block // is not allocated // in the piece picker anymore, and open for @@ -128,8 +130,9 @@ namespace libtorrent bool operator==(pending_block const& b) { - return b.skipped == skipped && b.block == block - && b.not_wanted == not_wanted && b.timed_out == timed_out; + return b.block == block + && b.not_wanted == not_wanted + && b.timed_out == timed_out; } }; @@ -141,11 +144,125 @@ namespace libtorrent { return pb.block == block; } }; + // internal + inline void nop(char*, void*, block_cache_reference) {} + + struct peer_connection_hot_members + { + peer_connection_hot_members( + boost::weak_ptr t + , aux::session_interface& ses + , aux::session_settings const& sett + , boost::shared_ptr const& sock + , bool outgoing) + : m_torrent(t) + , m_ses(ses) + , m_settings(sett) + , m_disconnecting(false) + , m_connecting(outgoing) + , m_endgame_mode(false) + , m_snubbed(false) + , m_interesting(false) + , m_choked(true) + , m_corked(false) + , m_ignore_stats(false) + , m_recv_pos(0) + , m_packet_size(0) + {} + + protected: + + // the pieces the other end have + bitfield m_have_piece; + + // this is the torrent this connection is + // associated with. If the connection is an + // incoming connection, this is set to zero + // until the info_hash is received. Then it's + // set to the torrent it belongs to. + + // TODO: make this a raw pointer (to save size in + // the first cache line) and make the constructor + // take a raw pointer. torrent objects should always + // outlive their peers + boost::weak_ptr m_torrent; + + public: + + // a back reference to the session + // the peer belongs to. + aux::session_interface& m_ses; + + // settings that apply to this peer + aux::session_settings const& m_settings; + + protected: + + // this is true if this connection has been added + // to the list of connections that will be closed. + bool m_disconnecting:1; + + // this is true until this socket has become + // writable for the first time (i.e. the + // connection completed). While connecting + // the timeout will not be triggered. This is + // because windows XP SP2 may delay connection + // attempts, which means that the connection + // may not even have been attempted when the + // time out is reached. + bool m_connecting:1; + + // this is set to true if the last time we tried to + // pick a piece to download, we could only find + // blocks that were already requested from other + // peers. In this case, we should not try to pick + // another piece until the last one we requested is done + bool m_endgame_mode:1; + + // set to true when a piece request times out. The + // result is that the desired pending queue size + // is set to 1 + bool m_snubbed:1; + + // the peer has pieces we are interested in + bool m_interesting:1; + + // we have choked the upload to the peer + bool m_choked:1; + + // when this is set, the peer_connection socket is + // corked, similar to the linux TCP feature TCP_CORK. + // we won't send anything to the actual socket, just + // buffer messages up in the application layer send + // buffer, and send it once we're uncorked. + bool m_corked:1; + + // when this is set, the transfer stats for this connection + // is not included in the torrent or session stats + bool m_ignore_stats:1; + + // the byte offset in m_recv_buffer that we have + // are passing on to the upper layer. This is + // always <= m_recv_end + int m_recv_pos:24; + + // the size (in bytes) of the bittorrent message + // we're currently receiving + int m_packet_size; + }; + class TORRENT_EXTRA_EXPORT peer_connection - : public bandwidth_socket - , public boost::noncopyable + : public peer_connection_hot_members + , public bandwidth_socket + , public peer_class_set + , public disk_observer + , public connection_interface + , public peer_connection_interface + , public boost::enable_shared_from_this { friend class invariant_access; + friend struct network_thread_pool; + friend class torrent; public: enum connection_type @@ -164,15 +281,16 @@ namespace libtorrent num_channels }; - // this is the constructor where the we are the active part. - // The peer_conenction should handshake and verify that the - // other end has the correct id peer_connection( - aux::session_impl& ses + aux::session_interface& ses + , aux::session_settings const& sett + , buffer_allocator_interface& allocator + , disk_interface& disk_thread + , io_service& ios , boost::weak_ptr t , boost::shared_ptr s , tcp::endpoint const& remote - , policy::peer* peerinfo + , torrent_peer* peerinfo , bool outgoing = true); // this function is called after it has been constructed and properly @@ -183,12 +301,16 @@ namespace libtorrent virtual ~peer_connection(); - void set_peer_info(policy::peer* pi) + void set_peer_info(torrent_peer* pi) { TORRENT_ASSERT(m_peer_info == 0 || pi == 0 ); + TORRENT_ASSERT(pi != NULL || m_disconnect_started); m_peer_info = pi; } + torrent_peer* peer_info_struct() const + { return m_peer_info; } + // this is called when the peer object is created, in case // it was let in by the connections limit slack. This means // the peer needs to, as soon as the handshake is done, either @@ -202,9 +324,6 @@ namespace libtorrent void peer_disconnected_other() { m_exceeded_limit = false; } - policy::peer* peer_info_struct() const - { return m_peer_info; } - enum peer_speed_t { slow = 1, medium, fast }; peer_speed_t peer_speed(); @@ -227,13 +346,8 @@ namespace libtorrent void on_metadata_impl(); - int get_upload_limit() const; - int get_download_limit() const; - void set_upload_limit(int limit); - void set_download_limit(int limit); - - int upload_limit() const { return m_upload_limit; } - int download_limit() const { return m_download_limit; } + void picker_options(int o) + { m_picker_options = o; } int prefer_whole_pieces() const { @@ -241,8 +355,7 @@ namespace libtorrent return m_prefer_whole_pieces; } - bool on_parole() const - { return peer_info_struct() && peer_info_struct()->on_parole; } + bool on_parole() const; int picker_options() const; @@ -255,7 +368,7 @@ namespace libtorrent void request_large_blocks(bool b) { m_request_large_blocks = b; } - void set_endgame(bool b) { m_endgame_mode = b; } + void set_endgame(bool b); bool endgame() const { return m_endgame_mode; } bool no_download() const { return m_no_download; } @@ -264,19 +377,15 @@ namespace libtorrent bool ignore_stats() const { return m_ignore_stats; } void ignore_stats(bool b) { m_ignore_stats = b; } - void set_priority(int p) - { - TORRENT_ASSERT(p > 0); - TORRENT_ASSERT(m_priority <= 255); - if (p > 255) p = 255; - m_priority = p; - } - boost::uint32_t peer_rank() const; void fast_reconnect(bool r); bool fast_reconnect() const { return m_fast_reconnect; } + // this is called when we receive a new piece + // (and it has passed the hash check) + void received_piece(int index); + // this adds an announcement in the announcement queue // it will let the peer know that we have the given piece void announce_piece(int index); @@ -293,7 +402,7 @@ namespace libtorrent // tells if this connection has data it want to send // and has enough upload bandwidth quota left to send it. bool can_write() const; - bool can_read(boost::uint8_t* state = 0) const; + bool can_read(); bool is_seed() const; int num_have_pieces() const { return m_num_pieces; } @@ -348,8 +457,13 @@ namespace libtorrent boost::weak_ptr associated_torrent() const { return m_torrent; } - const stat& statistics() const { return m_statistics; } + stat const& statistics() const { return m_statistics; } void add_stat(size_type downloaded, size_type uploaded); + void sent_bytes(int bytes_payload, int bytes_protocol); + void received_bytes(int bytes_payload, int bytes_protocol); + void trancieve_ip_packet(int bytes, bool ipv6); + void sent_syn(bool ipv6); + void received_synack(bool ipv6); // is called once every second by the main loop void second_tick(int tick_interval_ms); @@ -358,6 +472,7 @@ namespace libtorrent boost::shared_ptr get_socket() const { return m_socket; } tcp::endpoint const& remote() const { return m_remote; } + tcp::endpoint local_endpoint() const { return m_local; } bitfield const& get_bitfield() const; std::vector const& allowed_fast(); @@ -366,9 +481,10 @@ namespace libtorrent ptime connected_time() const { return m_connect; } ptime last_received() const { return m_last_receive; } - void on_timeout(); // this will cause this peer_connection to be disconnected. - virtual void disconnect(error_code const& ec, int error = 0); + virtual void disconnect(error_code const& ec + , peer_connection_interface::operation_t op, int error = 0); + // called when a connect attempt fails (not when an // established connection fails) void connect_failed(error_code const& e); @@ -392,8 +508,12 @@ namespace libtorrent // initiate the tcp connection. This may be postponed until // the library isn't using up the limitation of half-open // tcp connections. - void on_connect(int ticket); + // implements connection_interface + void on_allow_connect(int ticket); + // implements connection_interface. Called by the connection_queue + void on_connect_timeout(); + // This is called for every peer right after the upload // bandwidth has been distributed among them // It will reset the used bandwidth to 0. @@ -414,14 +534,7 @@ namespace libtorrent { m_received_listen_port = true; } bool on_local_network() const; - bool ignore_bandwidth_limits() const - { return m_ignore_bandwidth_limits; } - void ignore_bandwidth_limits(bool i) - { m_ignore_bandwidth_limits = i; } - bool ignore_unchoke_slots() const; - void ignore_unchoke_slots(bool i) - { m_ignore_unchoke_slots = i; } bool failed() const { return m_failed; } @@ -433,12 +546,13 @@ namespace libtorrent } bool bittyrant_unchoke_compare( - boost::intrusive_ptr const& p) const; + peer_connection const* p) const; // compares this connection against the given connection // for which one is more eligible for an unchoke. // returns true if this is more eligible - bool unchoke_compare(boost::intrusive_ptr const& p) const; + bool unchoke_compare(peer_connection const* p) const; bool upload_rate_compare(peer_connection const* p) const; + int download_payload_rate() const { return m_statistics.download_payload_rate(); } // resets the byte counters that are used to measure // the number of bytes transferred within unchoke cycles @@ -446,17 +560,24 @@ namespace libtorrent // if this peer connection is useless (neither party is // interested in the other), disconnect it - void disconnect_if_redundant(); + // returns true if the connection was disconnected + bool disconnect_if_redundant(); void increase_est_reciprocation_rate(); void decrease_est_reciprocation_rate(); int est_reciprocation_rate() const { return m_est_reciprocation_rate; } #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING - void peer_log(char const* fmt, ...) const; + virtual void peer_log(char const* fmt, ...) const; boost::shared_ptr m_logger; #endif +#if defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + ptime m_connect_time; + ptime m_bitfield_time; + ptime m_unchoke_time; +#endif + // the message handlers are called // each time a recv() returns some new // data, the last time it will be called @@ -501,6 +622,9 @@ namespace libtorrent void send_suggest(int piece); void snub_peer(); + // reject any request in the request + // queue from this piece + void reject_piece(int index); bool can_request_time_critical() const; @@ -526,9 +650,6 @@ namespace libtorrent void cancel_request(piece_block const& b, bool force = false); void send_block_requests(); - int bandwidth_throttle(int channel) const - { return m_bandwidth_channel[channel].throttle(); } - void assign_bandwidth(int channel, int amount); #if TORRENT_USE_INVARIANT_CHECKS @@ -561,28 +682,22 @@ namespace libtorrent virtual void setup_send(); void cork_socket() { TORRENT_ASSERT(!m_corked); m_corked = true; } + bool is_corked() const { return m_corked; } void uncork_socket(); -#ifdef TORRENT_DISK_STATS +#ifdef TORRENT_BUFFER_STATS void log_buffer_usage(char* buffer, int size, char const* label); #endif - template - void append_send_buffer(char* buffer, int size, Destructor const& destructor - , bool encrypted) - { -#if defined TORRENT_DISK_STATS - log_buffer_usage(buffer, size, "queued send buffer"); -#endif - // bittorrent connections should never use this function, since - // they might be encrypted and this would circumvent the actual - // encryption. bt_peer_connection overrides this function with - // its own version. - TORRENT_ASSERT(encrypted || type() != bittorrent_connection); - m_send_buffer.append_buffer(buffer, size, size, destructor); - } + virtual void append_send_buffer(char* buffer, int size + , chained_buffer::free_buffer_fun destructor = &nop + , void* userdata = NULL, block_cache_reference ref + = block_cache_reference(), bool encrypted = false); - virtual void append_const_send_buffer(char const* buffer, int size); + virtual void append_const_send_buffer(char const* buffer, int size + , chained_buffer::free_buffer_fun destructor = &nop + , void* userdata = NULL, block_cache_reference ref + = block_cache_reference()); #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES void set_country(char const* c) @@ -634,11 +749,23 @@ namespace libtorrent // start downloading payload again void on_disk(); + void on_allocate_disk_buffer(char* buffer, int buffer_size); + int num_reading_bytes() const { return m_reading_bytes; } enum sync_t { read_async, read_sync }; void setup_receive(sync_t sync = read_sync); + boost::shared_ptr self() + { + TORRENT_ASSERT(!m_in_constructor); + return shared_from_this(); + } + + // TODO: 2 temporary hack until the stats counters are moved out + // from the session_interface. + aux::session_interface& ses() { return m_ses; } + protected: size_t try_read(sync_t s, error_code& ec); @@ -652,9 +779,11 @@ namespace libtorrent virtual void write_request(peer_request const& r) = 0; virtual void write_cancel(peer_request const& r) = 0; virtual void write_have(int index) = 0; + virtual void write_dont_have(int index) = 0; virtual void write_keepalive() = 0; virtual void write_piece(peer_request const& r, disk_buffer_holder& buffer) = 0; virtual void write_suggest(int piece) = 0; + virtual void write_bitfield() = 0; virtual void write_reject_request(peer_request const& r) = 0; virtual void write_allow_fast(int piece) = 0; @@ -678,8 +807,8 @@ namespace libtorrent TORRENT_ASSERT(!m_disk_recv_buffer); TORRENT_ASSERT(m_disk_recv_buffer_size == 0); int rcv_pos = (std::min)(m_recv_pos, int(m_recv_buffer.size())); - return buffer::interval(&m_recv_buffer[0] - , &m_recv_buffer[0] + rcv_pos); + return buffer::interval(&m_recv_buffer[0] + m_recv_start + , &m_recv_buffer[0] + m_recv_start + rcv_pos); } std::pair wr_recv_buffers(int bytes); @@ -693,8 +822,8 @@ namespace libtorrent return buffer::interval(0,0); } int rcv_pos = (std::min)(m_recv_pos, int(m_recv_buffer.size())); - return buffer::const_interval(&m_recv_buffer[0] - , &m_recv_buffer[0] + rcv_pos); + return buffer::const_interval(&m_recv_buffer[0] + m_recv_start + , &m_recv_buffer[0] + m_recv_start + rcv_pos); } bool allocate_disk_receive_buffer(int disk_buffer_size); @@ -702,6 +831,7 @@ namespace libtorrent bool has_disk_receive_buffer() const { return m_disk_recv_buffer; } void cut_receive_buffer(int size, int packet_size, int offset = 0); void reset_recv_buffer(int packet_size); + void normalize_receive_buffer(); void set_soft_packet_size(int size) { m_soft_packet_size = size; } // if allow_encrypted is false, and the torrent 'ih' turns out @@ -714,115 +844,6 @@ namespace libtorrent void update_desired_queue_size(); - void set_timeout(int s) { m_timeout = s; } - - boost::intrusive_ptr self() - { - TORRENT_ASSERT(!m_in_constructor); - return boost::intrusive_ptr(this); - } - - // TODO: make this private - public: - - // upload and download channel state - // enum from peer_info::bw_state - boost::uint8_t m_channel_state[2]; - - private: - - // is true if we learn the incoming connections listening - // during the extended handshake - bool m_received_listen_port:1; - - // this is set to true when a have_all - // message is received. This information - // is used to fill the bitmask in init() - bool m_have_all:1; - - // other side says that it's interested in downloading - // from us. - bool m_peer_interested:1; - - // the other side has told us that it won't send anymore - // data to us for a while - bool m_peer_choked:1; - - // the peer has pieces we are interested in - bool m_interesting:1; - - // we have choked the upload to the peer - bool m_choked:1; - - // this is set to true if the connection timed - // out or closed the connection. In that - // case we will not try to reconnect to - // this peer - bool m_failed:1; - - // this is true if this connection has been added - // to the list of connections that will be closed. - bool m_disconnecting:1; - - // this is set to true once the bitfield is received - bool m_bitfield_received:1; - - // this is set to true if the last time we tried to - // pick a piece to download, we could only find - // blocks that were already requested from other - // peers. In this case, we should not try to pick - // another piece until the last one we requested is done - bool m_endgame_mode:1; - - // set to true when we've sent the first round of suggests - bool m_sent_suggests:1; - - // set to true while we're trying to holepunch - bool m_holepunch_mode:1; - - // when this is set, the transfer stats for this connection - // is not included in the torrent or session stats - bool m_ignore_stats:1; - - // when this is set, the peer_connection socket is - // corked, similar to the linux TCP feature TCP_CORK. - // we won't send anything to the actual socket, just - // buffer messages up in the application layer send - // buffer, and send it once we're uncorked. - bool m_corked:1; - - // set to true if this peer has metadata, and false - // otherwise. - bool m_has_metadata:1; - - // this is set to true if this peer was accepted exceeding - // the connection limit. It means it has to disconnect - // itself, or some other peer, as soon as it's completed - // the handshake. We need to wait for the handshake in - // order to know which torrent it belongs to, to know which - // other peers to compare it to. - bool m_exceeded_limit:1; - - // TODO: make these private as well - protected: - - // number of bytes this peer can send and receive - int m_quota[2]; - - // statistics about upload and download speeds - // and total amount of uploads and downloads for - // this peer - stat m_statistics; - - // a back reference to the session - // the peer belongs to. - aux::session_impl& m_ses; - -#ifndef TORRENT_DISABLE_EXTENSIONS - typedef std::list > extension_list_t; - extension_list_t m_extensions; -#endif - // called from the main loop when this connection has any // work to do. void on_send_data(error_code const& error @@ -830,27 +851,120 @@ namespace libtorrent void on_receive_data(error_code const& error , std::size_t bytes_transferred); - // the average rate of receiving complete piece messages - sliding_average<20> m_piece_rate; - sliding_average<20> m_send_rate; + // _nb means null_buffers. i.e. we just know the socket is + // readable at this point, we don't know how much has been received + void on_receive_data_nb(error_code const& error + , std::size_t bytes_transferred); + + void receive_data_impl(error_code const& error + , std::size_t bytes_transferred, int read_loops); + + virtual int timeout() const; private: - std::pair preferred_caching() const; + void do_update_interest(); + int preferred_caching() const; void fill_send_buffer(); - void on_disk_read_complete(int ret, disk_io_job const& j, peer_request r); - void on_disk_write_complete(int ret, disk_io_job const& j + void on_disk_read_complete(disk_io_job const* j, peer_request r); + void on_disk_write_complete(disk_io_job const* j , peer_request r, boost::shared_ptr t); - int request_upload_bandwidth( - bandwidth_channel* bwc1 - , bandwidth_channel* bwc2 = 0 - , bandwidth_channel* bwc3 = 0 - , bandwidth_channel* bwc4 = 0); - int request_download_bandwidth( - bandwidth_channel* bwc1 - , bandwidth_channel* bwc2 = 0 - , bandwidth_channel* bwc3 = 0 - , bandwidth_channel* bwc4 = 0); + void on_seed_mode_hashed(disk_io_job const* j); + + int wanted_transfer(int channel); + int request_bandwidth(int channel, int bytes = 0); + + int get_priority(int channel) const; + + boost::shared_ptr m_socket; + + // the queue of blocks we have requested + // from this peer + std::vector m_download_queue; + + // the queue of requests we have got + // from this peer that haven't been issued + // to the disk thread yet + std::vector m_requests; + + // this peer's peer info struct. This may + // be 0, in case the connection is incoming + // and hasn't been added to a torrent yet. + torrent_peer* m_peer_info; + + // the number of pieces this peer + // has. Must be the same as + // std::count(m_have_piece.begin(), + // m_have_piece.end(), true) + int m_num_pieces; + + + public: + // upload and download channel state + // enum from peer_info::bw_state + char m_channel_state[2]; + + protected: + + // estimated round trip time to this peer + // based on the time from when async_connect + // was called to when on_connection_complete + // was called. The rtt is specified in milliseconds + boost::uint16_t m_rtt; + + buffer m_recv_buffer; + + // number of bytes this peer can send and receive + int m_quota[2]; + + // the blocks we have reserved in the piece + // picker and will request from this peer. + std::vector m_request_queue; + + // the start of the logical receive buffer + int m_recv_start:24; + + // the number of request we should queue up + // at the remote end. + boost::uint8_t m_desired_queue_size; + + // this is the limit on the number of outstanding requests + // we have to this peer. This is initialized to the settings + // in the session_settings structure. But it may be lowered + // if the peer is known to require a smaller limit (like BitComet). + // or if the extended handshake sets a limit. + // web seeds also has a limit on the queue size. + int m_max_out_request_queue; + + // this is the peer we're actually talking to + // it may not necessarily be the peer we're + // connected to, in case we use a proxy + tcp::endpoint m_remote; + + public: + chained_buffer m_send_buffer; + private: + + // the disk thread to use to issue disk jobs to + disk_interface& m_disk_thread; + + public: + buffer_allocator_interface& m_allocator; + private: + + // io service + io_service& m_ios; + + public: +#ifndef TORRENT_DISABLE_EXTENSIONS + typedef std::list > extension_list_t; + extension_list_t m_extensions; +#endif + private: + + // the average rate of receiving complete piece messages + sliding_average<20> m_piece_rate; + sliding_average<20> m_send_rate; // keep the io_service running as long as we // have peer connections @@ -919,6 +1033,18 @@ namespace libtorrent // for the round-robin unchoke algorithm. size_type m_uploaded_at_last_unchoke; + // some messages needs to be read from the socket + // buffer in multiple stages. This soft packet + // size limits the read size between message handler + // dispatch. Ignored when set to 0 + int m_soft_packet_size; + + // the number of bytes that the other + // end has to send us in order to respond + // to all outstanding piece requests we + // have sent to it + int m_outstanding_bytes; + template struct handler_storage { @@ -932,6 +1058,8 @@ namespace libtorrent handler_storage() {} #endif boost::aligned_storage bytes; + private: + handler_storage(handler_storage const&); }; handler_storage m_read_handler_storage; @@ -941,41 +1069,16 @@ namespace libtorrent std::string m_inet_as_name; #endif - buffer m_recv_buffer; - // if this peer is receiving a piece, this // points to a disk buffer that the data is // read into. This eliminates a memcopy from // the receive buffer into the disk buffer disk_buffer_holder m_disk_recv_buffer; - chained_buffer m_send_buffer; + // we have suggested these pieces to the peer + // don't suggest it again + bitfield m_sent_suggested_pieces; - boost::shared_ptr m_socket; - - // this is the torrent this connection is - // associated with. If the connection is an - // incoming connection, this is set to zero - // until the info_hash is received. Then it's - // set to the torrent it belongs to. - boost::weak_ptr m_torrent; - - // the pieces the other end have - bitfield m_have_piece; - - // the queue of requests we have got - // from this peer that haven't been issued - // to the disk thread yet - std::vector m_requests; - - // the blocks we have reserved in the piece - // picker and will request from this peer. - std::vector m_request_queue; - - // the queue of blocks we have requested - // from this peer - std::vector m_download_queue; - // the pieces we will send to the peer // if requested (regardless of choke state) std::vector m_accept_fast; @@ -997,11 +1100,6 @@ namespace libtorrent // the piece requests std::vector m_requests_in_buffer; - // this peer's peer info struct. This may - // be 0, in case the connection is incoming - // and hasn't been added to a torrent yet. - policy::peer* m_peer_info; - // the time when this peer last saw a complete copy // of this torrent time_t m_last_seen_complete; @@ -1010,29 +1108,35 @@ namespace libtorrent // (-1, -1) if we're not receiving one piece_block m_receiving_block; - // this is the peer we're actually talking to - // it may not necessarily be the peer we're - // connected to, in case we use a proxy - tcp::endpoint m_remote; + // the local endpoint for this peer, i.e. our address + // and our port. If this is set for outgoing connections + // before the connection completes, it means we want to + // force the connection to be bound to the specified interface. + // if it ends up being bound to a different local IP, the connection + // is closed. + tcp::endpoint m_local; + // remote peer's id + peer_id m_peer_id; + // the bandwidth channels, upload and download // keeps track of the current quotas bandwidth_channel m_bandwidth_channel[num_channels]; - // remote peer's id - peer_id m_peer_id; + private: + // statistics about upload and download speeds + // and total amount of uploads and downloads for + // this peer + // TODO: factor this out into its own class with a virtual interface + // torrent and session should implement this interface + stat m_statistics; + protected: // if the timeout is extended for the outstanding // requests, this is the number of seconds it was // extended. int m_timeout_extend; - // the number of bytes that the other - // end has to send us in order to respond - // to all outstanding piece requests we - // have sent to it - int m_outstanding_bytes; - // the number of outstanding bytes expected // to be received by extensions int m_extension_outstanding_bytes; @@ -1047,28 +1151,36 @@ namespace libtorrent // immediately int m_queued_time_critical; - // the number of pieces this peer - // has. Must be the same as - // std::count(m_have_piece.begin(), - // m_have_piece.end(), true) - int m_num_pieces; + // the number of valid, received bytes in m_recv_buffer + int m_recv_end:24; - // the timeout in seconds - int m_timeout; +//#error 1 byte - // the size (in bytes) of the bittorrent message - // we're currently receiving - int m_packet_size; + // recv_buf.begin (start of actual receive buffer) + // | + // | m_recv_start (logical start of current + // | | receive buffer, as perceived by upper layers) + // | | + // | | m_recv_pos (number of bytes consumed + // | | | by upper layer, from logical receive buffer) + // | | | + // | x---------x + // | | | recv_buf.end (end of actual receive buffer) + // | | | | + // v v v v + // *------==========--------- + // ^ + // | + // | + // ------------------->x m_recv_end (end of received data, + // beyond this point is garbage) + // m_recv_buffer - // some messages needs to be read from the socket - // buffer in multiple stages. This soft packet - // size limits the read size between message handler - // dispatch. Ignored when set to 0 - int m_soft_packet_size; - - // the number of bytes of the bittorrent payload - // we've received so far - int m_recv_pos; + // when not using contiguous receive buffers, there + // may be a disk_recv_buffer in the mix as well. Whenever + // m_disk_recv_buffer_size > 0 (and presumably also + // m_disk_recv_buffer != NULL) the disk buffer is imagined + // to be appended to the receive buffer right after m_recv_end. int m_disk_recv_buffer_size; @@ -1077,6 +1189,12 @@ namespace libtorrent // buffer as soon as they complete int m_reading_bytes; + // options used for the piece picker. These flags will + // be augmented with flags controlled by other settings + // like sequential download etc. These are here to + // let plugins control flags that should always be set + int m_picker_options; + // the number of invalid piece-requests // we have got from this peer. If the request // queue gets empty, and there have been @@ -1086,18 +1204,6 @@ namespace libtorrent // by sending choke, unchoke. int m_num_invalid_requests; - // this is the priority with which this peer gets - // download bandwidth quota assigned to it. - int m_priority; - - int m_upload_limit; - int m_download_limit; - - // this is a measurement of how fast the peer - // it allows some variance without changing - // back and forth between states - peer_speed_t m_speed; - // the ticket id from the connection queue. // This is used to identify the connection // so that it can be removed from the queue @@ -1127,26 +1233,12 @@ namespace libtorrent int m_download_rate_peak; int m_upload_rate_peak; - // this is the limit on the number of outstanding requests - // we have to this peer. This is initialized to the settings - // in the session_settings structure. But it may be lowered - // if the peer is known to require a smaller limit (like BitComet). - // or if the extended handshake sets a limit. - // web seeds also has a limit on the queue size. - int m_max_out_request_queue; - // when using the BitTyrant choker, this is our // estimated reciprocation rate. i.e. the rate // we need to send to this peer for it to unchoke // us int m_est_reciprocation_rate; - // estimated round trip time to this peer - // based on the time from when async_connect - // was called to when on_connection_complete - // was called. The rtt is specified in milliseconds - boost::uint16_t m_rtt; - #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES // in case the session settings is set // to resolve countries, this is set to @@ -1155,6 +1247,12 @@ namespace libtorrent char m_country[2]; #endif + // this is a measurement of how fast the peer + // it allows some variance without changing + // back and forth between states. values are enums + // from peer_speed_t. + boost::uint8_t m_speed; + // if set to non-zero, this peer will always prefer // to request entire n pieces, rather than blocks. // where n is the value of this variable. @@ -1163,9 +1261,26 @@ namespace libtorrent // are preferred. boost::uint8_t m_prefer_whole_pieces; - // the number of request we should queue up - // at the remote end. - boost::uint8_t m_desired_queue_size; + // this is the number of times this peer has had + // a request rejected because of a disk I/O failure. + // once this reaches a certain threshold, the + // peer is disconnected in order to avoid infinite + // loops of consistent failures + boost::uint8_t m_disk_read_failures; + + // this is used in seed mode whenever we trigger a hash check + // for a piece, before we read it. It's used to throttle + // the hash checks to just a few per peer at a time. + boost::uint8_t m_outstanding_piece_verification:3; + + // is true if it was we that connected to the peer + // and false if we got an incoming connection + // could be considered: true = local, false = remote + bool m_outgoing:1; + + // is true if we learn the incoming connections listening + // during the extended handshake + bool m_received_listen_port:1; // if this is true, the disconnection // timestamp is not updated when the connection @@ -1174,30 +1289,15 @@ namespace libtorrent // immediate. bool m_fast_reconnect:1; - // is true if it was we that connected to the peer - // and false if we got an incoming connection - // could be considered: true = local, false = remote - bool m_outgoing:1; + // this is set to true if the connection timed + // out or closed the connection. In that + // case we will not try to reconnect to + // this peer + bool m_failed:1; - // if this is set to true, the peer will not - // request bandwidth from the limiter, but instead - // just send and receive as much as possible. - bool m_ignore_bandwidth_limits:1; - - // set to true if this peer controls its unchoke - // state individually, regardless of the global - // unchoker - bool m_ignore_unchoke_slots:1; - - // this is true until this socket has become - // writable for the first time (i.e. the - // connection completed). While connecting - // the timeout will not be triggered. This is - // because windows XP SP2 may delay connection - // attempts, which means that the connection - // may not even have been attempted when the - // time out is reached. - bool m_connecting:1; + // this is set to true if the connection attempt + // succeeded. i.e. the TCP 3-way handshake + bool m_connected:1; // This is true until connect is called on the // peer_connection's socket. It is false on incoming @@ -1218,15 +1318,58 @@ namespace libtorrent // set to true when this peer is only uploading bool m_upload_only:1; - // set to true when a piece request times out. The - // result is that the desired pending queue size - // is set to 1 - bool m_snubbed:1; + // this is set to true once the bitfield is received + bool m_bitfield_received:1; // if this is set to true, the client will not // pick any pieces from this peer bool m_no_download:1; + // set to true when we've sent the first round of suggests + bool m_sent_suggests:1; + + // set to true while we're trying to holepunch + bool m_holepunch_mode:1; + + // the other side has told us that it won't send anymore + // data to us for a while + bool m_peer_choked:1; + + // this is set to true when a have_all + // message is received. This information + // is used to fill the bitmask in init() + bool m_have_all:1; + + // other side says that it's interested in downloading + // from us. + bool m_peer_interested:1; + + // set to true when we should recalculate interest + // for this peer. Since this is a fairly expensive + // operation, it's delayed until the second_tick is + // fired, so that multiple events that wants to recalc + // interest are coalesced into only triggering it once + // the actual computation is done in do_update_interest(). + bool m_need_interest_update:1; + + // set to true if this peer has metadata, and false + // otherwise. + bool m_has_metadata:1; + + // this is true while this connection is queued + // in the connection_queue. We may not destruct + // the connection while it is, since it's not + // held by an owning pointer, just a plain one + bool m_queued_for_connection:1; + + // this is set to true if this peer was accepted exceeding + // the connection limit. It means it has to disconnect + // itself, or some other peer, as soon as it's completed + // the handshake. We need to wait for the handshake in + // order to know which torrent it belongs to, to know which + // other peers to compare it to. + bool m_exceeded_limit:1; + template struct allocating_handler { @@ -1298,19 +1441,29 @@ namespace libtorrent #if TORRENT_USE_ASSERTS public: - bool m_in_constructor:1; - bool m_disconnect_started:1; - bool m_initialized:1; + bool m_in_constructor; + bool m_disconnect_started; + bool m_initialized; int m_in_use; int m_received_in_piece; + bool m_destructed; + // this is true while there is an outstanding + // async write job on the socket + bool m_socket_is_writing; #endif }; struct cork { - cork(peer_connection& p): m_pc(p) { m_pc.cork_socket(); } - ~cork() { m_pc.uncork_socket(); } + cork(peer_connection& p): m_pc(p), m_need_uncork(false) + { + if (m_pc.is_corked()) return; + m_pc.cork_socket(); + m_need_uncork = true; + } + ~cork() { if (m_need_uncork) m_pc.uncork_socket(); } peer_connection& m_pc; + bool m_need_uncork; }; } diff --git a/include/libtorrent/peer_connection_interface.hpp b/include/libtorrent/peer_connection_interface.hpp new file mode 100644 index 000000000..c77116e6b --- /dev/null +++ b/include/libtorrent/peer_connection_interface.hpp @@ -0,0 +1,96 @@ +/* + +Copyright (c) 2013, 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_PEER_CONNECTION_INTERFACE_HPP +#define TORRENT_PEER_CONNECTION_INTERFACE_HPP + +#include "libtorrent/socket.hpp" +#include "libtorrent/error_code.hpp" + +namespace libtorrent +{ + struct torrent_peer; + class stat; + struct peer_info; + + // TODO: make this interface smaller! + struct peer_connection_interface + { + // these constants are used to identify the operation + // that failed, causing a peer to disconnect + enum operation_t + { + // this is used when the bittorrent logic + // determines to disconnect + op_bittorrent = 0, + op_iocontrol, + op_getpeername, + op_getname, + op_alloc_recvbuf, + op_alloc_sndbuf, + op_file_write, + op_file_read, + op_file, + op_sock_write, + op_sock_read, + op_sock_open, + op_sock_bind, + op_available, + op_encryption, + op_connect, + op_ssl_handshake, + op_get_interface, + }; + + virtual tcp::endpoint const& remote() const = 0; + virtual tcp::endpoint local_endpoint() const = 0; + virtual void disconnect(error_code const& ec, operation_t op, int error = 0) = 0; + virtual peer_id const& pid() const = 0; + virtual void set_holepunch_mode() = 0; + virtual torrent_peer* peer_info_struct() const = 0; + virtual void set_peer_info(torrent_peer* pi) = 0; + virtual bool is_outgoing() const = 0; + virtual void add_stat(size_type downloaded, size_type uploaded) = 0; + virtual bool fast_reconnect() const = 0; + virtual bool is_choked() const = 0; + virtual bool failed() const = 0; + virtual stat const& statistics() const = 0; + virtual void get_peer_info(peer_info& p) const = 0; +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING + virtual void peer_log(char const* fmt, ...) const = 0; +#endif + + }; +} + +#endif + diff --git a/include/libtorrent/peer_info.hpp b/include/libtorrent/peer_info.hpp index 508c600dc..0526c433f 100644 --- a/include/libtorrent/peer_info.hpp +++ b/include/libtorrent/peer_info.hpp @@ -39,6 +39,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/size_type.hpp" #include "libtorrent/config.hpp" #include "libtorrent/bitfield.hpp" +#include "libtorrent/time.hpp" namespace libtorrent { @@ -46,6 +47,36 @@ namespace libtorrent // that libtorrent is connected to struct TORRENT_EXPORT peer_info { + // the name of the AS this peer is located in. This might be an empty + // string if there is no name in the geo ip database. + std::string inet_as_name; + + // a string describing the software at the other end of the connection. + // In some cases this information is not available, then it will contain + // a string that may give away something about which software is running + // in the other end. In the case of a web seed, the server type and + // version will be a part of this string. + std::string client; + + // a bitfield, with one bit per piece in the torrent. Each bit tells you + // if the peer has that piece (if it's set to 1) or if the peer miss that + // piece (set to 0). + bitfield pieces; + + // the total number of bytes downloaded from and uploaded to this peer. + // These numbers do not include the protocol chatter, but only the + // payload data. + size_type total_download; + size_type total_upload; + + // the time since we last sent a request to this peer and since any + // transfer occurred with this peer + time_duration last_request; + time_duration last_active; + + // the time until all blocks in the request queue will be downloaded + time_duration download_queue_time; + // flags for the peer_info::flags field. Indicates various states // the peer may be in. These flags are not mutually exclusive, but // not every combination of them makes sense either. @@ -182,42 +213,7 @@ namespace libtorrent // a combination of flags describing from which sources this peer // was received. - int source; - - // bits for the read_state and write_state - enum bw_state - { - // The peer is not waiting for any external events to - // send or receive data. - bw_idle = 0, - - // The peer is waiting for the rate limiter. - bw_limit = 1, - - // The peer has quota and is currently waiting for a - // network read or write operation to complete. This is - // the state all peers are in if there are no bandwidth - // limits. - bw_network = 2, - - // The peer is waiting for the disk I/O thread to catch - // up writing buffers to disk before downloading more. - bw_disk = 4 - }; -#ifndef TORRENT_NO_DEPRECATE - enum bw_state_deprecated { bw_torrent = bw_limit, bw_global = bw_limit }; -#endif - - // bitmasks indicating what state this peer is in with regards to sending - // and receiving data. The states are declared in the bw_state enum. - char read_state; - char write_state; - - // the IP-address to this peer. The type is an asio endpoint. For - // more info, see the asio_ documentation. - // - // .. _asio: http://asio.sourceforge.net/asio-0.3.8/doc/asio/reference.html - tcp::endpoint ip; + boost::uint32_t source; // the current upload and download speed we have to and from this peer // (including any protocol messages). updated about once per second @@ -228,36 +224,11 @@ namespace libtorrent int payload_up_speed; int payload_down_speed; - // the total number of bytes downloaded from and uploaded to this peer. - // These numbers do not include the protocol chatter, but only the - // payload data. - size_type total_download; - size_type total_upload; - // the peer's id as used in the bit torrent protocol. This id can be used // to extract 'fingerprints' from the peer. Sometimes it can tell you // which client the peer is using. See identify_client()_ peer_id pid; - // a bitfield, with one bit per piece in the torrent. - // Each bit tells you if the peer has that piece (if it's set to 1) - // or if the peer miss that piece (set to 0). - bitfield pieces; - - // the number of bytes per second we are allowed to send to or receive - // from this peer. It may be -1 if there's no local limit on the peer. - // The global limit and the torrent limit may also be enforced. - int upload_limit; - int download_limit; - - // the time since we last sent a request - // to this peer and since any transfer occurred with this peer - time_duration last_request; - time_duration last_active; - - // the time until all blocks in the request - // queue will be d - time_duration download_queue_time; int queue_bytes; // the number of seconds until the current front piece request will time @@ -276,50 +247,30 @@ namespace libtorrent int receive_buffer_size; int used_receive_buffer; - // the number of pieces this peer has participated in - // sending us that turned out to fail the hash check. + // the number of pieces this peer has participated in sending us that + // turned out to fail the hash check. int num_hashfails; - // the two letter `ISO 3166 country code`__ for the country the peer is - // connected from. If the country hasn't been resolved yet, both chars - // are set to 0. If the resolution failed for some reason, the field is - // set to "--". If the resolution service returns an invalid country - // code, it is set to "!!". The ``countries.nerd.dk`` service is used to - // look up countries. This field will remain set to 0 unless the torrent - // is set to resolve countries, see `resolve_countries()`_. - // - // __ http://www.iso.org/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html - char country[2]; - - // the name of the AS this peer is located in. This might be - // an empty string if there is no name in the geo ip database. - std::string inet_as_name; - // the AS number the peer is located in. int inet_as; - // this is the number of requests - // we have sent to this peer - // that we haven't got a response - // for yet + // this is the number of requests we have sent to this peer that we + // haven't got a response for yet int download_queue_length; - // the number of block requests that have - // timed out, and are still in the download - // queue + // the number of block requests that have timed out, and are still in the + // download queue int timed_out_requests; - // the number of busy requests in the download - // queue. A budy request is a request for a block - // we've also requested from a different peer + // the number of busy requests in the download queue. A budy request is a + // request for a block we've also requested from a different peer int busy_requests; - // the number of requests messages that are currently in the - // send buffer waiting to be sent. + // the number of requests messages that are currently in the send buffer + // waiting to be sent. int requests_in_buffer; - // the number of requests that is - // tried to be maintained (this is + // the number of requests that is tried to be maintained (this is // typically a function of download speed) int target_dl_queue_length; @@ -347,13 +298,6 @@ namespace libtorrent int downloading_progress; int downloading_total; - // a string describing the software at the other end of the connection. - // In some cases this information is not available, then it will contain - // a string that may give away something about which software is running - // in the other end. In the case of a web seed, the server type and - // version will be a part of this string. - std::string client; - // the kind of connection this is. Used for the connection_type field. enum connection_type_t { @@ -379,6 +323,10 @@ namespace libtorrent // by ``session_settings::max_queued_disk_bytes``. int pending_disk_bytes; + // number of outstanding bytes to read + // from disk + int pending_disk_read_bytes; + // the number of bytes this peer has been assigned to be allowed to send // and receive until it has to request more quota from the bandwidth // manager. @@ -412,10 +360,73 @@ namespace libtorrent // bittyrant choking algorithm. int estimated_reciprocation_rate; + // the IP-address to this peer. The type is an asio endpoint. For + // more info, see the asio_ documentation. + // + // .. _asio: http://asio.sourceforge.net/asio-0.3.8/doc/asio/reference.html + tcp::endpoint ip; + // the IP and port pair the socket is bound to locally. i.e. the IP // address of the interface it's going out over. This may be useful for // multi-homed clients with multiple interfaces to the internet. tcp::endpoint local_endpoint; + + // bits for the read_state and write_state + enum bw_state + { + // The peer is not waiting for any external events to + // send or receive data. + bw_idle = 0, + + // The peer is waiting for the rate limiter. + bw_limit = 1, + + // The peer has quota and is currently waiting for a + // network read or write operation to complete. This is + // the state all peers are in if there are no bandwidth + // limits. + bw_network = 2, + + // The peer is waiting for the disk I/O thread to catch + // up writing buffers to disk before downloading more. + bw_disk = 4 + }; +#ifndef TORRENT_NO_DEPRECATE + enum bw_state_deprecated { bw_torrent = bw_limit, bw_global = bw_limit }; +#endif + + // bitmasks indicating what state this peer + // is in with regards to sending and receiving data. The states are declared in the + // bw_state enum. + char read_state; + char write_state; + + // the two letter `ISO 3166 country code`__ for the country the peer is + // connected from. If the country hasn't been resolved yet, both chars + // are set to 0. If the resolution failed for some reason, the field is + // set to "--". If the resolution service returns an invalid country + // code, it is set to "!!". The ``countries.nerd.dk`` service is used to + // look up countries. This field will remain set to 0 unless the torrent + // is set to resolve countries, see `resolve_countries()`_. + // + // __ http://www.iso.org/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html + char country[2]; + +#ifndef TORRENT_NO_DEPRECATE + // the number of bytes per second we are allowed to send to or receive + // from this peer. It may be -1 if there's no local limit on the peer. + // The global limit and the torrent limit may also be enforced. + int upload_limit; + int download_limit; + + // a measurement of the balancing of free download (that we get) and free + // upload that we give. Every peer gets a certain amount of free upload, + // but this member says how much *extra* free upload this peer has got. + // If it is a negative number it means that this was a peer from which we + // have got this amount of free download. + size_type load_balancing; +#endif + }; // internal diff --git a/include/libtorrent/performance_counters.hpp b/include/libtorrent/performance_counters.hpp new file mode 100644 index 000000000..a03ba9c5e --- /dev/null +++ b/include/libtorrent/performance_counters.hpp @@ -0,0 +1,393 @@ +/* + +Copyright (c) 2013, 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_PERFORMANCE_COUNTERS_HPP_INCLUDED +#define TORRENT_PERFORMANCE_COUNTERS_HPP_INCLUDED + +#include +#include "libtorrent/config.hpp" + +namespace libtorrent +{ + struct TORRENT_EXTRA_EXPORT counters + { + enum stats_counter_t + { + // the number of peers that were disconnected this + // tick due to protocol error + error_peers, + disconnected_peers, + eof_peers, + connreset_peers, + connrefused_peers, + connaborted_peers, + perm_peers, + buffer_peers, + unreachable_peers, + broken_pipe_peers, + addrinuse_peers, + no_access_peers, + invalid_arg_peers, + aborted_peers, + + piece_requests, + max_piece_requests, + invalid_piece_requests, + choked_piece_requests, + cancelled_piece_requests, + piece_rejects, + error_incoming_peers, + error_outgoing_peers, + error_rc4_peers, + error_encrypted_peers, + error_tcp_peers, + error_utp_peers, + + // the number of times the piece picker was + // successfully invoked, split by the reason + // it was invoked + reject_piece_picks, + unchoke_piece_picks, + incoming_redundant_piece_picks, + incoming_piece_picks, + end_game_piece_picks, + snubbed_piece_picks, + interesting_piece_picks, + hash_fail_piece_picks, + + // these counters indicate which parts + // of the piece picker CPU is spent in + piece_picker_partial_loops, + piece_picker_suggest_loops, + piece_picker_sequential_loops, + piece_picker_reverse_rare_loops, + piece_picker_rare_loops, + piece_picker_rand_start_loops, + piece_picker_rand_loops, + piece_picker_busy_loops, + + // reasons to disconnect peers + connect_timeouts, + uninteresting_peers, + timeout_peers, + no_memory_peers, + too_many_peers, + transport_timeout_peers, + num_banned_peers, + banned_for_hash_failure, + + // connection attempts (not necessarily successful) + connection_attempts, + // the number of iterations over the peer list when finding + // a connect candidate + connection_attempt_loops, + // successful incoming connections (not rejected for any reason) + incoming_connections, + + // counts events where the network + // thread wakes up + on_read_counter, + on_write_counter, + on_tick_counter, + on_lsd_counter, + on_lsd_peer_counter, + on_udp_counter, + on_accept_counter, + on_disk_queue_counter, + on_disk_counter, + + torrent_evicted_counter, + + // bittorrent message counters + // TODO: should keepalives be in here too? + // how about dont-have, share-mode, upload-only + num_incoming_choke, + num_incoming_unchoke, + num_incoming_interested, + num_incoming_not_interested, + num_incoming_have, + num_incoming_bitfield, + num_incoming_request, + num_incoming_piece, + num_incoming_cancel, + num_incoming_dht_port, + num_incoming_suggest, + num_incoming_have_all, + num_incoming_have_none, + num_incoming_reject, + num_incoming_allowed_fast, + num_incoming_ext_handshake, + num_incoming_pex, + num_incoming_metadata, + num_incoming_extended, + + num_outgoing_choke, + num_outgoing_unchoke, + num_outgoing_interested, + num_outgoing_not_interested, + num_outgoing_have, + num_outgoing_bitfield, + num_outgoing_request, + num_outgoing_piece, + num_outgoing_cancel, + num_outgoing_dht_port, + num_outgoing_suggest, + num_outgoing_have_all, + num_outgoing_have_none, + num_outgoing_reject, + num_outgoing_allowed_fast, + num_outgoing_ext_handshake, + num_outgoing_pex, + num_outgoing_metadata, + num_outgoing_extended, + + num_piece_passed, + num_piece_failed, + + num_have_pieces, + num_total_pieces_added, + + num_blocks_written, + num_blocks_read, + num_blocks_cache_hits, + num_write_ops, + num_read_ops, + + disk_read_time, + disk_write_time, + disk_hash_time, + disk_job_time, + + sent_payload_bytes, + sent_bytes, + sent_ip_overhead_bytes, + sent_tracker_bytes, + recv_payload_bytes, + recv_bytes, + recv_ip_overhead_bytes, + recv_tracker_bytes, + + recv_failed_bytes, + recv_redundant_bytes, + + dht_messages_in, + dht_messages_out, + dht_messages_out_dropped, + dht_bytes_in, + dht_bytes_out, + + dht_ping_in, + dht_ping_out, + dht_find_node_in, + dht_find_node_out, + dht_get_peers_in, + dht_get_peers_out, + dht_announce_peer_in, + dht_announce_peer_out, + dht_get_in, + dht_get_out, + dht_put_in, + dht_put_out, + + // uTP counters + utp_packet_loss, + utp_timeout, + utp_packets_in, + utp_packets_out, + utp_fast_retransmit, + utp_packet_resend, + utp_samples_above_target, + utp_samples_below_target, + utp_payload_pkts_in, + utp_payload_pkts_out, + utp_invalid_pkts_in, + utp_redundant_pkts_in, + + // the buffer sizes accepted by + // socket send calls. The larger + // the more efficient. The size is + // 1 << n, where n is the number + // at the end of the counter name + + // 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, + // 16384, 32768, 65536, 131072, 262144, 524288, 1048576 + socket_send_size3, + socket_send_size4, + socket_send_size5, + socket_send_size6, + socket_send_size7, + socket_send_size8, + socket_send_size9, + socket_send_size10, + socket_send_size11, + socket_send_size12, + socket_send_size13, + socket_send_size14, + socket_send_size15, + socket_send_size16, + socket_send_size17, + socket_send_size18, + socket_send_size19, + socket_send_size20, + + // the buffer sizes returned by + // socket recv calls. The larger + // the more efficient. The size is + // 1 << n, where n is the number + // at the end of the counter name + + // 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, + // 16384, 32768, 65536, 131072, 262144, 524288, 1048576 + socket_recv_size3, + socket_recv_size4, + socket_recv_size5, + socket_recv_size6, + socket_recv_size7, + socket_recv_size8, + socket_recv_size9, + socket_recv_size10, + socket_recv_size11, + socket_recv_size12, + socket_recv_size13, + socket_recv_size14, + socket_recv_size15, + socket_recv_size16, + socket_recv_size17, + socket_recv_size18, + socket_recv_size19, + socket_recv_size20, + + num_stats_counters + }; + + enum stats_gauges_t + { + num_checking_torrents = num_stats_counters, + num_stopped_torrents, + num_upload_only_torrents, // i.e. finished + num_downloading_torrents, + num_seeding_torrents, + num_queued_seeding_torrents, + num_queued_download_torrents, + num_error_torrents, + + // the number of torrents that don't have the + // IP filter applied to them. + non_filter_torrents, + + // counters related to evicting torrents + num_loaded_torrents, + num_pinned_torrents, + + // these counter indices deliberatly + // match the order of socket type IDs + // defined in socket_type.hpp. + num_tcp_peers, + num_socks5_peers, + num_http_proxy_peers, + num_utp_peers, + num_i2p_peers, + num_ssl_peers, + num_ssl_socks5_peers, + num_ssl_http_proxy_peers, + num_ssl_utp_peers, + + num_peers_half_open, + num_peers_connected, + num_peers_up_interested, + num_peers_down_interested, + num_peers_up_unchoked, + num_peers_down_unchoked, + num_peers_up_requests, + num_peers_down_requests, + num_peers_up_disk, + num_peers_down_disk, + num_peers_end_game, + + write_cache_blocks, + read_cache_blocks, + pinned_blocks, + disk_blocks_in_use, + queued_disk_jobs, + num_read_jobs, + num_write_jobs, + num_jobs, + num_writing_threads, + num_running_threads, + blocked_disk_jobs, + queued_write_bytes, + num_unchoke_slots, + + arc_mru_size, + arc_mru_ghost_size, + arc_mfu_size, + arc_mfu_ghost_size, + arc_write_size, + arc_volatile_size, + + dht_nodes, + dht_node_cache, + dht_torrents, + dht_peers, + dht_immutable_data, + dht_mutable_data, + dht_allocated_observers, + + has_incoming_connections, + + limiter_up_queue, + limiter_down_queue, + limiter_up_bytes, + limiter_down_bytes, + + num_counters, + num_gauge_counters = num_counters - num_stats_counters + }; + + counters(); + + // returns the new value + boost::uint64_t inc_stats_counter(int c, boost::int64_t value = 1); + boost::int64_t operator[](int i) const; + + void set_value(int c, boost::int64_t value); + + private: + + // TODO: some space could be saved here by making gauges 32 bits + boost::int64_t m_stats_counter[num_counters]; + + }; +} + +#endif + diff --git a/include/libtorrent/piece_picker.hpp b/include/libtorrent/piece_picker.hpp index 9980f840d..a5a260691 100644 --- a/include/libtorrent/piece_picker.hpp +++ b/include/libtorrent/piece_picker.hpp @@ -53,18 +53,25 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/assert.hpp" #include "libtorrent/time.hpp" -// #define TORRENT_DEBUG_REFCOUNTS +//#define TORRENT_PICKER_LOG +//#define TORRENT_DEBUG_REFCOUNTS #ifdef TORRENT_DEBUG_REFCOUNTS #include #endif +#if TORRENT_USE_ASSERTS +#include +#endif + namespace libtorrent { class torrent; class peer_connection; struct bitfield; + struct logger; + struct counters; struct TORRENT_EXTRA_EXPORT piece_block { @@ -114,7 +121,7 @@ namespace libtorrent block_info(): peer(0), num_peers(0), state(state_none) {} // the peer this block was requested or // downloaded from. This is a pointer to - // a policy::peer object + // a torrent_peer object void* peer; // the number of peers that has this block in their // download or request queues @@ -125,6 +132,7 @@ namespace libtorrent #if TORRENT_USE_ASSERTS // to allow verifying the invariant of blocks belonging to the right piece int piece_index; + std::set peers; #endif }; @@ -154,14 +162,19 @@ namespace libtorrent // treat pieces with priority 6 and below as filtered // to trigger end-game mode until all prio 7 pieces are // completed - time_critical_mode = 128 + time_critical_mode = 128, + // only expands pieces (when prefer whole pieces is set) + // within properly aligned ranges, not the largest possible + // range of pieces. + align_expanded_pieces = 256 }; struct downloading_piece { - downloading_piece(): info(NULL), index(-1) - , finished(0), writing(0), requested(0) - , state(none) {} + downloading_piece() : info(NULL), index(-1) + , finished(0), state(none), writing(0) + , passed_hash_check(0), locked(0) + , requested(0), outstanding_hash_check(0) {} bool operator<(downloading_piece const& rhs) const { return index < rhs.index; } @@ -169,23 +182,48 @@ namespace libtorrent // this is a pointer into the m_block_info // vector owned by the piece_picker block_info* info; + // the index of the piece int index; - // the number of blocks in the finished state - boost::int16_t finished; - // the number of blocks in the writing state - boost::int16_t writing; - // the number of blocks in the requested state - boost::int16_t requested; - // one of piece_state_t values - // really just needs 2 bits - boost::uint16_t state; + // the number of blocks in the finished state + boost::uint16_t finished:14; + + // the speed state of this piece + boost::uint16_t state:2; + + // the number of blocks in the writing state + boost::uint16_t writing:14; + + // set to true when the hash check job + // returns with a valid hash for this piece. + // we might not 'have' the piece yet though, + // since it might not have been written to + // disk. This is not set of locked is + // set. + boost::uint16_t passed_hash_check:1; + + // when this is set, blocks from this piece may + // not be picked. This is used when the hash check + // fails or writing to the disk fails, while waiting + // to synchronize the disk thread and clear out any + // remaining state. Once this synchronization is + // done, restore_piece() is called to clear the + // locked flag. + boost::uint16_t locked:1; + + // the number of blocks in the requested state + boost::uint16_t requested:15; + + // set to true while there is an outstanding + // hash check for this piece + boost::uint16_t outstanding_hash_check:1; }; piece_picker(); void get_availability(std::vector& avail) const; + int get_availability(int piece) const; // increases the peer count for the given piece // (is used when a HAVE message is received) @@ -220,12 +258,7 @@ namespace libtorrent void init(int blocks_per_piece, int blocks_in_last_piece, int total_num_pieces); int num_pieces() const { return int(m_piece_map.size()); } - bool have_piece(int index) const - { - TORRENT_ASSERT(index >= 0); - TORRENT_ASSERT(index < int(m_piece_map.size())); - return m_piece_map[index].index == piece_pos::we_have_index; - } + bool have_piece(int index) const; bool is_downloading(int index) const { @@ -233,7 +266,7 @@ namespace libtorrent TORRENT_ASSERT(index < int(m_piece_map.size())); piece_pos const& p = m_piece_map[index]; - return p.downloading; + return p.downloading(); } // sets the priority of a piece. @@ -263,13 +296,15 @@ namespace libtorrent // decides to download a piece, it must mark it as being downloaded // itself, by using the mark_as_downloading() member function. // THIS IS DONE BY THE peer_connection::send_request() MEMBER FUNCTION! - // The last argument is the policy::peer pointer for the peer that + // The last argument is the torrent_peer pointer for the peer that // we'll download from. void pick_pieces(bitfield const& pieces , std::vector& interesting_blocks, int num_blocks , int prefer_whole_pieces, void* peer, piece_state_t speed , int options, std::vector const& suggested_pieces - , int num_peers) const; + , int num_peers + , counters& pc + ) const; // picks blocks from each of the pieces in the piece_list // vector that is also in the piece bitmask. The blocks @@ -299,6 +334,13 @@ namespace libtorrent // peer pointer void clear_peer(void* peer); +#if TORRENT_USE_INVARIANT_CHECKS + // this is an invariant check + void check_peers(); +#endif + + int get_block_state(piece_block block) const; + // returns true if any client is currently downloading this // piece-block, or if it's queued for downloading by some client // or if it already has been successfully downloaded @@ -315,10 +357,21 @@ namespace libtorrent // and false if the block is already finished or writing bool mark_as_writing(piece_block block, void* peer); + void mark_as_canceled(piece_block block, void* peer); void mark_as_finished(piece_block block, void* peer); + + // prevent blocks from being picked from this piece. + // to unlock the piece, call restore_piece() on it + void lock_piece(int piece); + void write_failed(piece_block block); int num_peers(piece_block block) const; + void piece_passed(int index); + + void mark_as_checking(int index); + void mark_as_done_checking(int index); + // returns information about the given piece void piece_info(int index, piece_picker::downloading_piece& st) const; @@ -336,8 +389,14 @@ namespace libtorrent // this means that this piece-block can be picked again void abort_download(piece_block block, void* peer = 0); + // returns true if all blocks in this piece are finished + // or if we have the piece bool is_piece_finished(int index) const; + // returns true if we have the piece or if the piece + // has passed the hash check + bool has_piece_passed(int index) const; + // returns the number of blocks there is in the given piece int blocks_in_piece(int index) const; @@ -345,12 +404,18 @@ namespace libtorrent // the hash-check yet int unverified_blocks() const; + // return the peer pointers for all blocks that are currently + // in requested state (i.e. requested but not received) + void get_requestors(std::vector& d, int index) const; + + // return the peer pointers to all peers that participated in + // this piece void get_downloaders(std::vector& d, int index) const; - std::vector const& get_download_queue() const - { return m_downloads; } + std::vector get_download_queue() const; + int get_download_queue_size() const; - int num_downloading_pieces() const { return int(m_downloads.size()); } + void get_download_queue_sizes(int* partial, int* full, int* finished, int* zero_prio) const; void* get_downloader(piece_block block) const; @@ -360,16 +425,30 @@ namespace libtorrent // the number of filtered pieces we already have int num_have_filtered() const { return m_num_have_filtered; } + // number of pieces whose hash has passed _and_ they have + // been successfully flushed to disk int num_have() const { return m_num_have; } + // number of pieces whose hash has passed (but haven't necessarily + // been flushed to disk yet) + int num_passed() const { return m_num_passed; } + + // return true if we have all the pieces we wanted + bool is_finished() const { return m_num_have - m_num_have_filtered == int(m_piece_map.size()) - m_num_filtered; } + + bool is_seeding() const { return m_num_have == int(m_piece_map.size()); } + // the number of pieces we want and don't have - int num_want_left() const { return num_pieces() - m_num_have - m_num_filtered; } + int num_want_left() const { return num_pieces() - m_num_have - m_num_filtered + m_num_have_filtered; } #if TORRENT_USE_INVARIANT_CHECKS + void check_piece_state() const; // used in debug mode void verify_priority(int start, int end, int prio) const; void verify_pick(std::vector const& picked , bitfield const& bits) const; + + void check_peer_invariant(bitfield const& have, void const* p) const; void check_invariant(const torrent* t = 0) const; #endif #if defined TORRENT_PICKER_LOG @@ -397,7 +476,7 @@ namespace libtorrent bool can_pick(int piece, bitfield const& bitmask) const; bool is_piece_free(int piece, bitfield const& bitmask) const; std::pair expand_piece(int piece, int whole_pieces - , bitfield const& have) const; + , bitfield const& have, int options) const; public: @@ -406,8 +485,7 @@ namespace libtorrent piece_pos() {} piece_pos(int peer_count_, int index_) : peer_count(peer_count_) - , downloading(0) - , full(0) + , state(piece_pos::piece_open) , piece_priority(1) , index(index_) { @@ -415,17 +493,32 @@ namespace libtorrent TORRENT_ASSERT(index_ >= 0); } + // state of this piece. + enum state_t + { + // the piece is open to be picked + piece_open, + // the piece is partially downloaded or requested + piece_downloading, + // all blocks in the piece have been requested + piece_full, + // all blocks in the piece have been received and + // are either finished or writing + piece_finished, + // pieces whose priority is 0 + piece_zero_prio + }; + // the number of peers that has this piece // (availability) -#if TORRENT_COMPACT_PICKER +#if TORRENT_OPTIMIZE_MEMORY_USAGE boost::uint32_t peer_count : 9; #else boost::uint32_t peer_count : 16; #endif - // is 1 if the piece is marked as being downloaded - boost::uint32_t downloading : 1; - // set when downloading, but no free blocks to request left - boost::uint32_t full : 1; + + boost::uint32_t state : 3; + // is 0 if the piece is filtered (not to be downloaded) // 1 is normal priority (default) // 2 is higher priority than pieces at the same availability level @@ -435,8 +528,8 @@ namespace libtorrent // 7 is maximum priority (ignores availability) boost::uint32_t piece_priority : 3; // index in to the piece_info vector -#if TORRENT_COMPACT_PICKER - boost::uint32_t index : 18; +#if TORRENT_OPTIMIZE_MEMORY_USAGE + boost::uint32_t index : 17; #else boost::uint32_t index; #endif @@ -445,12 +538,13 @@ namespace libtorrent // all the peers that have this piece std::set have_peers; #endif + enum { // index is set to this to indicate that we have the // piece. There is no entry for the piece in the // buckets if this is the case. -#if TORRENT_COMPACT_PICKER +#if TORRENT_OPTIMIZE_MEMORY_USAGE we_have_index = 0x3ffff, #else we_have_index = 0xffffffff, @@ -458,7 +552,7 @@ namespace libtorrent // the priority value that means the piece is filtered filter_priority = 0, // the max number the peer count can hold -#if TORRENT_COMPACT_PICKER +#if TORRENT_OPTIMIZE_MEMORY_USAGE max_peer_count = 0x1ff #else max_peer_count = 0xffff @@ -468,6 +562,7 @@ namespace libtorrent bool have() const { return index == we_have_index; } void set_have() { index = we_have_index; TORRENT_ASSERT(have()); } void set_not_have() { index = 0; TORRENT_ASSERT(!have()); } + bool downloading() const { return state > 0; } bool filtered() const { return piece_priority == filter_priority; } @@ -489,11 +584,12 @@ namespace libtorrent // filtered pieces (prio = 0), pieces we have or pieces with // availability = 0 should not be present in the piece list // returning -1 indicates that they shouldn't. - if (filtered() || have() || peer_count + picker->m_seeds == 0) + if (filtered() || have() || peer_count + picker->m_seeds == 0 + || state == piece_full || state == piece_finished) return -1; // prio 7 disregards availability - if (piece_priority == priority_levels - 1) return 1 - downloading; + if (piece_priority == priority_levels - 1) return 1 - downloading(); // prio 4,5,6 halves the availability of a piece int availability = peer_count; @@ -504,7 +600,7 @@ namespace libtorrent p -= (priority_levels - 2) / 2; } - if (downloading) return availability * prio_factor; + if (downloading()) return availability * prio_factor; return availability * prio_factor + (priority_levels / 2) - p; } @@ -520,7 +616,7 @@ namespace libtorrent private: #ifndef TORRENT_DEBUG_REFCOUNTS -#if TORRENT_COMPACT_PICKER +#if TORRENT_OPTIMIZE_MEMORY_USAGE BOOST_STATIC_ASSERT(sizeof(piece_pos) == sizeof(char) * 4); #else BOOST_STATIC_ASSERT(sizeof(piece_pos) == sizeof(char) * 8); @@ -546,27 +642,40 @@ namespace libtorrent // shuffles the given piece inside it's priority range void shuffle(int priority, int elem_index); -// void sort_piece(std::vector::iterator dp); + typedef std::vector::iterator dlpiece_iter; + dlpiece_iter add_download_piece(int index); + void erase_download_piece(dlpiece_iter i); - downloading_piece& add_download_piece(int index); - void erase_download_piece(std::vector::iterator i); + std::vector::const_iterator find_dl_piece(int queue, int index) const; + std::vector::iterator find_dl_piece(int queue, int index); - std::vector::const_iterator find_dl_piece(int index) const; - std::vector::iterator find_dl_piece(int index); - - void update_full(downloading_piece& dp); + // returns an iterator to the downloading piece, whichever + // download list it may live in now + std::vector::iterator update_piece_state(std::vector::iterator dp); // some compilers (e.g. gcc 2.95, does not inherit access // privileges to nested classes) - public: - // the number of seeds. These are not added to - // the availability counters of the pieces - int m_seeds; private: // the following vectors are mutable because they sometimes may // be updated lazily, triggered by const functions + // this maps indices to number of peers that has this piece and + // index into the m_piece_info vectors. + // piece_pos::we_have_index means that we have the piece, so it + // doesn't exist in the piece_info buckets + // pieces with the filtered flag set doesn't have entries in + // the m_piece_info buckets either + // TODO: should this be allocated lazily? + mutable std::vector m_piece_map; + + // the number of seeds. These are not added to + // the availability counters of the pieces + int m_seeds; + + // the number of pieces that have passed the hash check + int m_num_passed; + // this vector contains all piece indices that are pickable // sorted by priority. Pieces are in random random order // among pieces with the same priority @@ -577,28 +686,26 @@ namespace libtorrent // 0, priority 1 starts at m_priority_boundries[0] etc. mutable std::vector m_priority_boundries; - // this maps indices to number of peers that has this piece and - // index into the m_piece_info vectors. - // piece_pos::we_have_index means that we have the piece, so it - // doesn't exist in the piece_info buckets - // pieces with the filtered flag set doesn't have entries in - // the m_piece_info buckets either - mutable std::vector m_piece_map; - // each piece that's currently being downloaded // has an entry in this list with block allocations. // i.e. it says wich parts of the piece that // is being downloaded. This list is ordered // by piece index to make lookups efficient - std::vector m_downloads; + // there are 3 buckets of downloading pieces, each + // is individually sorted by piece index. + // 0: downloading pieces with unrequested blocks + // 1: downloading pieces where every block is busy + // and some are still in the requested state + // 2: downloading pieces where every block is + // finished or writing + // 3: partial pieces whose priority is 0 + enum { num_download_categories = 4 }; + std::vector m_downloads[num_download_categories]; // this holds the information of the // blocks in partially downloaded pieces. - // the first m_blocks_per_piece entries - // in the vector belongs to the first - // entry in m_downloads, the second - // m_blocks_per_piece entries to the - // second entry in m_downloads and so on. + // the downloading_piece::info pointers + // point into this vector for its storage std::vector m_block_info; boost::uint16_t m_blocks_per_piece; @@ -613,9 +720,6 @@ namespace libtorrent // the number of pieces we have that also are filtered int m_num_have_filtered; - // the number of pieces we have - int m_num_have; - // we have all pieces in the range [0, m_cursor) // m_cursor is the first piece we don't have int m_cursor; @@ -628,6 +732,9 @@ namespace libtorrent // the number of regions of pieces we don't have. int m_sparse_regions; + // the number of pieces we have (i.e. passed + flushed) + int m_num_have; + // this is the number of partial download pieces // that may be caused by pad files. We raise the limit // of number of partial pieces by this amount, to not @@ -640,7 +747,7 @@ namespace libtorrent mutable bool m_dirty; public: -#if TORRENT_COMPACT_PICKER +#if TORRENT_OPTIMIZE_MEMORY_USAGE enum { max_pieces = piece_pos::we_have_index - 1 }; #else // still limited by piece_block diff --git a/include/libtorrent/platform_util.hpp b/include/libtorrent/platform_util.hpp new file mode 100644 index 000000000..443b9a681 --- /dev/null +++ b/include/libtorrent/platform_util.hpp @@ -0,0 +1,12 @@ +#ifndef TORRENT_PLATFORM_UTIL_HPP +#define TORRENT_PLATFORM_UTIL_HPP + +#include + +namespace libtorrent +{ + boost::uint64_t total_physical_ram(); +} + +#endif // TORRENT_PLATFORM_UTIL_HPP + diff --git a/include/libtorrent/policy.hpp b/include/libtorrent/policy.hpp index c25236456..5a89a1e83 100644 --- a/include/libtorrent/policy.hpp +++ b/include/libtorrent/policy.hpp @@ -37,357 +37,136 @@ POSSIBILITY OF SUCH DAMAGE. #include #include "libtorrent/string_util.hpp" // for allocate_string_copy -#include "libtorrent/peer.hpp" +#include "libtorrent/torrent_peer.hpp" #include "libtorrent/piece_picker.hpp" #include "libtorrent/socket.hpp" #include "libtorrent/address.hpp" #include "libtorrent/size_type.hpp" #include "libtorrent/invariant_check.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/debug.hpp" +#include "libtorrent/peer_connection_interface.hpp" namespace libtorrent { - class torrent; - class peer_connection; + struct logger; struct external_ip; + struct ip_filter; + class port_filter; + struct torrent_peer_allocator_interface; - // this is compressed as an unsigned floating point value - // the top 13 bits are the mantissa and the low - // 3 bits is the unsigned exponent. The exponent - // has an implicit + 4 as well. - // This means that the resolution is no less than 16 - // The actual rate is: (upload_rate >> 4) << ((upload_rate & 0xf) + 4) - // the resolution gets worse the higher the value is - // min value is 0, max value is 16775168 - struct ufloat16 + // this object is used to communicate torrent state and + // some configuration to the policy object. This make + // the policy type not depend on the torrent type directly. + struct torrent_state { - ufloat16():m_val(0) {} - ufloat16(int v) - { *this = v; } - operator int() - { - return (m_val >> 3) << ((m_val & 7) + 4); - } + torrent_state() + : is_paused(false), is_finished(false) + , allow_multiple_connections_per_ip(false) + , first_time_seen(false) + , max_peerlist_size(1000) + , min_reconnect_time(60) + , loop_counter(0) + , ip(NULL), port(0) + , peer_allocator(NULL) + {} + bool is_paused; + bool is_finished; + bool allow_multiple_connections_per_ip; - ufloat16& operator=(int v) - { - if (v > 0x1fff << (7 + 4)) m_val = 0xffff; - else if (v <= 0) m_val = 0; - else - { - int exp = 4; - v >>= 4; - while (v > 0x1fff) - { - v >>= 1; - ++exp; - } - TORRENT_ASSERT(exp <= 7); - m_val = (v << 3) || (exp & 7); - } - return *this; - } - private: - boost::uint16_t m_val; + // this is set by policy::add_peer to either true or false + // true means the peer we just added was new, false means + // we already knew about the peer + bool first_time_seen; + + int max_peerlist_size; + int min_reconnect_time; + + // the number of iterations over the peer list for this operation + int loop_counter; + + // these are used only by find_connect_candidates in order + // to implement peer ranking. See: + // http://blog.libtorrent.org/2012/12/swarm-connectivity/ + external_ip const* ip; + int port; + + // this must be set to a torrent_peer allocator + torrent_peer_allocator_interface* peer_allocator; + + // if any peer were removed during this call, they are returned in + // this vector. The caller would want to make sure there are no + // references to these torrent_peers anywhere + std::vector erased; }; - enum - { - // the limits of the download queue size - min_request_queue = 2 - }; - - // calculate the priority of a peer based on its address. One of the - // endpoint should be our own. The priority is symmetric, so it doesn't - // matter which is which - TORRENT_EXTRA_EXPORT boost::uint32_t peer_priority( - tcp::endpoint e1, tcp::endpoint e2); - - void request_a_block(torrent& t, peer_connection& c); - - class TORRENT_EXTRA_EXPORT policy + // TODO: 3 this class should be renamed peer_list + class TORRENT_EXTRA_EXPORT policy : single_threaded { public: - policy(torrent* t); - - struct peer; + policy(); #if TORRENT_USE_I2P - policy::peer* add_i2p_peer(char const* destination, int source, char flags); + torrent_peer* add_i2p_peer(char const* destination, int src, char flags + , torrent_state* state); #endif - // this is called once for every peer we get from + enum + { + // these flags match the flags passed in ut_pex + // messages + flag_encryption = 0x1, + flag_seed = 0x2, + flag_utp = 0x4, + flag_holepunch = 0x8, + }; + + // this is called once for every torrent_peer we get from // the tracker, pex, lsd or dht. - policy::peer* add_peer(const tcp::endpoint& remote, const peer_id& pid - , int source, char flags); + torrent_peer* add_peer(const tcp::endpoint& remote + , int source, char flags, torrent_state* state); // false means duplicate connection - bool update_peer_port(int port, policy::peer* p, int src); + bool update_peer_port(int port, torrent_peer* p, int src, torrent_state* state); // called when an incoming connection is accepted // false means the connection was refused or failed - bool new_connection(peer_connection& c, int session_time); + bool new_connection(peer_connection_interface& c, int session_time, torrent_state* state); // the given connection was just closed - void connection_closed(const peer_connection& c, int session_time); + void connection_closed(const peer_connection_interface& c, int session_time, torrent_state* state); - void ban_peer(policy::peer* p); - void set_connection(policy::peer* p, peer_connection* c); - void set_failcount(policy::peer* p, int f); + bool ban_peer(torrent_peer* p); + void set_connection(torrent_peer* p, peer_connection_interface* c); + void set_failcount(torrent_peer* p, int f); + void inc_failcount(torrent_peer* p); - // the peer has got at least one interesting piece - void peer_is_interesting(peer_connection& c); + void apply_ip_filter(ip_filter const& filter, torrent_state* state, std::vector
& banned); + void apply_port_filter(port_filter const& filter, torrent_state* state, std::vector
& banned); - void ip_filter_updated(); - - void set_seed(policy::peer* p, bool s); + void set_seed(torrent_peer* p, bool s); // this clears all cached peer priorities. It's called when // our external IP changes void clear_peer_prio(); #if TORRENT_USE_ASSERTS - bool has_connection(const peer_connection* p); + bool has_connection(const peer_connection_interface* p); #endif #if TORRENT_USE_INVARIANT_CHECKS void check_invariant() const; #endif - struct TORRENT_EXTRA_EXPORT peer - { - peer(boost::uint16_t port, bool connectable, int src); - - size_type total_download() const; - size_type total_upload() const; - - boost::uint32_t rank(external_ip const& external, int external_port) const; - - libtorrent::address address() const; - char const* dest() const; - - tcp::endpoint ip() const { return tcp::endpoint(address(), port); } - - // this is the accumulated amount of - // uploaded and downloaded data to this - // peer. It only accounts for what was - // shared during the last connection to - // this peer. i.e. These are only updated - // when the connection is closed. For the - // total amount of upload and download - // we'll have to add thes figures with the - // statistics from the peer_connection. - // since these values don't need to be stored - // with byte-precision, they specify the number - // of kiB. i.e. shift left 10 bits to compare to - // byte counters. - boost::uint32_t prev_amount_upload; - boost::uint32_t prev_amount_download; - - // if the peer is connected now, this - // will refer to a valid peer_connection - peer_connection* connection; - -#ifndef TORRENT_DISABLE_GEO_IP -#ifdef TORRENT_DEBUG - // only used in debug mode to assert that - // the first entry in the AS pair keeps the same - boost::uint16_t inet_as_num; -#endif - // The AS this peer belongs to - std::pair* inet_as; -#endif - - // as computed by hashing our IP with the remote - // IP of this peer - // calculated lazily - mutable boost::uint32_t peer_rank; - - // the time when this peer was optimistically unchoked - // the last time. in seconds since session was created - // 16 bits is enough to last for 18.2 hours - // when the session time reaches 18 hours, it jumps back by - // 9 hours, and all peers' times are updated to be - // relative to that new time offset - boost::uint16_t last_optimistically_unchoked; - - // the time when the peer connected to us - // or disconnected if it isn't connected right now - // in number of seconds since session was created - boost::uint16_t last_connected; - - // the port this peer is or was connected on - boost::uint16_t port; - - // the upload and download rate limits set for this peer - ufloat16 upload_rate_limit; - ufloat16 download_rate_limit; - - // the number of times this peer has been - // part of a piece that failed the hash check - boost::uint8_t hashfails; - - // the number of failed connection attempts - // this peer has - unsigned failcount:5; // [0, 31] - - // incoming peers (that don't advertize their listen port) - // will not be considered connectable. Peers that - // we have a listen port for will be assumed to be. - bool connectable:1; - - // true if this peer currently is unchoked - // because of an optimistic unchoke. - // when the optimistic unchoke is moved to - // another peer, this peer will be choked - // if this is true - bool optimistically_unchoked:1; - - // this is true if the peer is a seed - bool seed:1; - - // the number of times we have allowed a fast - // reconnect for this peer. - unsigned fast_reconnects:4; - - // for every valid piece we receive where this - // peer was one of the participants, we increase - // this value. For every invalid piece we receive - // where this peer was a participant, we decrease - // this value. If it sinks below a threshold, its - // considered a bad peer and will be banned. - signed trust_points:4; // [-7, 8] - - // a bitmap combining the peer_source flags - // from peer_info. - unsigned source:6; - -#ifndef TORRENT_DISABLE_ENCRYPTION - // Hints encryption support of peer. Only effective - // for and when the outgoing encryption policy - // allows both encrypted and non encrypted - // connections (pe_settings::out_enc_policy - // == enabled). The initial state of this flag - // determines the initial connection attempt - // type (true = encrypted, false = standard). - // This will be toggled everytime either an - // encrypted or non-encrypted handshake fails. - bool pe_support:1; -#endif - -#if TORRENT_USE_IPV6 - // this is true if the v6 union member in addr is - // the one to use, false if it's the v4 one - bool is_v6_addr:1; -#endif -#if TORRENT_USE_I2P - // set if the i2p_destination is in use in the addr union - bool is_i2p_addr:1; -#endif - - // if this is true, the peer has previously - // participated in a piece that failed the piece - // hash check. This will put the peer on parole - // and only request entire pieces. If a piece pass - // that was partially requested from this peer it - // will leave parole mode and continue download - // pieces as normal peers. - bool on_parole:1; - - // is set to true if this peer has been banned - bool banned:1; - -#ifndef TORRENT_DISABLE_DHT - // this is set to true when this peer as been - // pinged by the DHT - bool added_to_dht:1; -#endif - // we think this peer supports uTP - bool supports_utp:1; - // we have been connected via uTP at least once - bool confirmed_supports_utp:1; - bool supports_holepunch:1; - // this is set to one for web seeds. Web seeds - // are not stored in the policy m_peers list, - // and are excempt from connect candidate bookkeeping - // so, any peer with the web_seed bit set, is - // never considered a connect candidate - bool web_seed:1; -#if TORRENT_USE_ASSERTS - bool in_use:1; -#endif - }; - - struct TORRENT_EXTRA_EXPORT ipv4_peer : peer - { - ipv4_peer(tcp::endpoint const& ip, bool connectable, int src); - - address_v4 addr; - }; - -#if TORRENT_USE_I2P - struct TORRENT_EXTRA_EXPORT i2p_peer : peer - { - i2p_peer(char const* destination, bool connectable, int src); - ~i2p_peer(); - - char* destination; - }; -#endif - -#if TORRENT_USE_IPV6 - struct TORRENT_EXTRA_EXPORT ipv6_peer : peer - { - ipv6_peer(tcp::endpoint const& ip, bool connectable, int src); - - const address_v6::bytes_type addr; - }; -#endif - int num_peers() const { return m_peers.size(); } - struct peer_address_compare - { - bool operator()( - peer const* lhs, libtorrent::address const& rhs) const - { - return lhs->address() < rhs; - } - - bool operator()( - libtorrent::address const& lhs, peer const* rhs) const - { - return lhs < rhs->address(); - } - -#if TORRENT_USE_I2P - bool operator()( - peer const* lhs, char const* rhs) const - { - return strcmp(lhs->dest(), rhs) < 0; - } - - bool operator()( - char const* lhs, peer const* rhs) const - { - return strcmp(lhs, rhs->dest()) < 0; - } +#ifdef TORRENT_OPTIMIZE_MEMORY_USAGE + typedef std::vector peers_t; +#else + typedef std::deque peers_t; #endif - bool operator()( - peer const* lhs, peer const* rhs) const - { -#if TORRENT_USE_I2P - if (rhs->is_i2p_addr == lhs->is_i2p_addr) - return strcmp(lhs->dest(), rhs->dest()) < 0; -#endif - return lhs->address() < rhs->address(); - } - }; - - typedef std::deque peers_t; - typedef peers_t::iterator iterator; typedef peers_t::const_iterator const_iterator; iterator begin_peer() { return m_peers.begin(); } @@ -407,63 +186,52 @@ namespace libtorrent m_peers.begin(), m_peers.end(), a, peer_address_compare()); } - bool connect_one_peer(int session_time); + torrent_peer* connect_one_peer(int session_time, torrent_state* state); - bool has_peer(policy::peer const* p) const; + bool has_peer(torrent_peer const* p) const; int num_seeds() const { return m_num_seeds; } int num_connect_candidates() const { return m_num_connect_candidates; } - void recalculate_connect_candidates(); - void erase_peer(policy::peer* p); - void erase_peer(iterator i); + void erase_peer(torrent_peer* p, torrent_state* state); + void erase_peer(iterator i, torrent_state* state); private: - void update_peer(policy::peer* p, int src, int flags - , tcp::endpoint const& remote, char const* destination); - bool insert_peer(policy::peer* p, iterator iter, int flags); + void recalculate_connect_candidates(torrent_state* state); - bool compare_peer_erase(policy::peer const& lhs, policy::peer const& rhs) const; - bool compare_peer(policy::peer const& lhs, policy::peer const& rhs + void update_connect_candidates(int delta); + + void update_peer(torrent_peer* p, int src, int flags + , tcp::endpoint const& remote, char const* destination); + bool insert_peer(torrent_peer* p, iterator iter, int flags, torrent_state* state); + + bool compare_peer_erase(torrent_peer const& lhs, torrent_peer const& rhs) const; + bool compare_peer(torrent_peer const* lhs, torrent_peer const* rhs , external_ip const& external, int source_port) const; - iterator find_connect_candidate(int session_time); + void find_connect_candidates(std::vector& peers, int session_time, torrent_state* state); - bool is_connect_candidate(peer const& p, bool finished) const; - bool is_erase_candidate(peer const& p, bool finished) const; - bool is_force_erase_candidate(peer const& pe) const; - bool should_erase_immediately(peer const& p) const; + bool is_connect_candidate(torrent_peer const& p) const; + bool is_erase_candidate(torrent_peer const& p) const; + bool is_force_erase_candidate(torrent_peer const& pe) const; + bool should_erase_immediately(torrent_peer const& p) const; enum flags_t { force_erase = 1 }; - void erase_peers(int flags = 0); + void erase_peers(torrent_state* state, int flags = 0); peers_t m_peers; - torrent* m_torrent; - - // this shouldbe NULL for the most part. It's set + // this should be NULL for the most part. It's set // to point to a valid torrent_peer object if that // object needs to be kept alive. If we ever feel // like removing a torrent_peer from m_peers, we // first check if the peer matches this one, and // if so, don't delete it. - peer* m_locked_peer; + torrent_peer* m_locked_peer; - // since the peer list can grow too large - // to scan all of it, start at this iterator - int m_round_robin; - - // The number of peers in our peer list - // that are connect candidates. i.e. they're - // not already connected and they have not - // yet reached their max try count and they - // have the connectable state (we have a listen - // port for them). - int m_num_connect_candidates; - - // the number of seeds in the peer list - int m_num_seeds; + // the number of seeds in the torrent_peer list + boost::uint32_t m_num_seeds:31; // this was the state of the torrent the // last time we recalculated the number of @@ -473,76 +241,25 @@ namespace libtorrent // this state. Every time m_torrent->is_finished() // is different from this state, we need to // recalculate the connect candidates. - bool m_finished:1; + boost::uint32_t m_finished:1; + + // since the torrent_peer list can grow too large + // to scan all of it, start at this index + int m_round_robin; + + // a list of good connect candidates + std::vector m_candidate_cache; + + // The number of peers in our torrent_peer list + // that are connect candidates. i.e. they're + // not already connected and they have not + // yet reached their max try count and they + // have the connectable state (we have a listen + // port for them). + int m_num_connect_candidates; + }; - inline policy::ipv4_peer::ipv4_peer( - tcp::endpoint const& ep, bool c, int src - ) - : peer(ep.port(), c, src) - , addr(ep.address().to_v4()) - { -#if TORRENT_USE_IPV6 - is_v6_addr = false; -#endif -#if TORRENT_USE_I2P - is_i2p_addr = false; -#endif - } - -#if TORRENT_USE_I2P - inline policy::i2p_peer::i2p_peer(char const* dest, bool connectable, int src) - : peer(0, connectable, src), destination(allocate_string_copy(dest)) - { -#if TORRENT_USE_IPV6 - is_v6_addr = false; -#endif - is_i2p_addr = true; - } - - inline policy::i2p_peer::~i2p_peer() - { free(destination); } -#endif // TORRENT_USE_I2P - -#if TORRENT_USE_IPV6 - inline policy::ipv6_peer::ipv6_peer( - tcp::endpoint const& ep, bool c, int src - ) - : peer(ep.port(), c, src) - , addr(ep.address().to_v6().to_bytes()) - { - is_v6_addr = true; -#if TORRENT_USE_I2P - is_i2p_addr = false; -#endif - } - -#endif // TORRENT_USE_IPV6 - -#if TORRENT_USE_I2P - inline char const* policy::peer::dest() const - { - if (is_i2p_addr) - return static_cast(this)->destination; - return ""; - } -#endif - - inline libtorrent::address policy::peer::address() const - { -#if TORRENT_USE_IPV6 - if (is_v6_addr) - return libtorrent::address_v6( - static_cast(this)->addr); - else -#endif -#if TORRENT_USE_I2P - if (is_i2p_addr) return libtorrent::address(); - else -#endif - return static_cast(this)->addr; - } - } #endif // TORRENT_POLICY_HPP_INCLUDED diff --git a/include/libtorrent/proxy_base.hpp b/include/libtorrent/proxy_base.hpp index a9903cc22..0a058a139 100644 --- a/include/libtorrent/proxy_base.hpp +++ b/include/libtorrent/proxy_base.hpp @@ -39,6 +39,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/address.hpp" #include "libtorrent/escape_string.hpp" #include "libtorrent/error_code.hpp" +#include namespace libtorrent { @@ -46,16 +47,15 @@ class proxy_base : boost::noncopyable { public: + typedef boost::function handler_type; + typedef stream_socket next_layer_type; typedef stream_socket::lowest_layer_type lowest_layer_type; typedef stream_socket::endpoint_type endpoint_type; typedef stream_socket::protocol_type protocol_type; - explicit proxy_base(io_service& io_service) - : m_sock(io_service) - , m_port(0) - , m_resolver(io_service) - {} + explicit proxy_base(io_service& io_service); + ~proxy_base(); void set_proxy(std::string hostname, int port) { @@ -241,6 +241,8 @@ public: protected: + bool handle_error(error_code const& e, boost::shared_ptr const& h); + stream_socket m_sock; std::string m_hostname; int m_port; diff --git a/include/libtorrent/ptime.hpp b/include/libtorrent/ptime.hpp deleted file mode 100644 index 444bbb678..000000000 --- a/include/libtorrent/ptime.hpp +++ /dev/null @@ -1,139 +0,0 @@ -/* - -Copyright (c) 2009-2014, 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_PTIME_HPP_INCLUDED -#define TORRENT_PTIME_HPP_INCLUDED - -#include "libtorrent/config.hpp" -#include - -#if defined TORRENT_USE_BOOST_DATE_TIME - -#include -#include -#include - -namespace libtorrent -{ - typedef boost::posix_time::ptime ptime; - typedef boost::posix_time::time_duration time_duration; -} - -#else // TORRENT_USE_BOOST_DATE_TIME - -#include - -namespace libtorrent -{ - // libtorrent time_duration type - struct TORRENT_EXPORT time_duration - { - // hidden - time_duration() {} - - // all operators have the same semantics as a 64 bit signed integer - time_duration operator/(int rhs) const { return time_duration(diff / rhs); } - explicit time_duration(boost::int64_t d) : diff(d) {} - time_duration& operator-=(time_duration const& c) - { diff -= c.diff; return *this; } - time_duration& operator+=(time_duration const& c) - { diff += c.diff; return *this; } - time_duration& operator*=(int v) { diff *= v; return *this; } - time_duration operator+(time_duration const& c) - { return time_duration(diff + c.diff); } - time_duration operator-(time_duration const& c) - { return time_duration(diff - c.diff); } - - // internal - boost::int64_t diff; - }; - - // This type represents a point in time. - struct TORRENT_EXPORT ptime - { - // hidden - ptime() {} - explicit ptime(boost::uint64_t t): time(t) {} - - // these operators have the same semantics as signed 64 bit integers - ptime& operator+=(time_duration rhs) { time += rhs.diff; return *this; } - ptime& operator-=(time_duration rhs) { time -= rhs.diff; return *this; } - - // internal - boost::uint64_t time; - }; - - // returns true of the time duration is less than 0 - inline bool is_negative(time_duration dt) { return dt.diff < 0; } - - // all operators have the same semantics as signed 64 bit integers - inline bool operator>(ptime lhs, ptime rhs) - { return lhs.time > rhs.time; } - inline bool operator>=(ptime lhs, ptime rhs) - { return lhs.time >= rhs.time; } - inline bool operator<=(ptime lhs, ptime rhs) - { return lhs.time <= rhs.time; } - inline bool operator<(ptime lhs, ptime rhs) - { return lhs.time < rhs.time; } - inline bool operator!=(ptime lhs, ptime rhs) - { return lhs.time != rhs.time;} - inline bool operator==(ptime lhs, ptime rhs) - { return lhs.time == rhs.time;} - inline bool operator==(time_duration lhs, time_duration rhs) - { return lhs.diff == rhs.diff; } - inline bool operator<(time_duration lhs, time_duration rhs) - { return lhs.diff < rhs.diff; } - inline bool operator<=(time_duration lhs, time_duration rhs) - { return lhs.diff <= rhs.diff; } - inline bool operator>(time_duration lhs, time_duration rhs) - { return lhs.diff > rhs.diff; } - inline bool operator>=(time_duration lhs, time_duration rhs) - { return lhs.diff >= rhs.diff; } - inline time_duration operator*(time_duration lhs, int rhs) - { return time_duration(boost::int64_t(lhs.diff * rhs)); } - inline time_duration operator*(int lhs, time_duration rhs) - { return time_duration(boost::int64_t(lhs * rhs.diff)); } - inline time_duration operator-(ptime lhs, ptime rhs) - { return time_duration(lhs.time - rhs.time); } - inline ptime operator+(ptime lhs, time_duration rhs) - { return ptime(lhs.time + rhs.diff); } - inline ptime operator+(time_duration lhs, ptime rhs) - { return ptime(rhs.time + lhs.diff); } - inline ptime operator-(ptime lhs, time_duration rhs) - { return ptime(lhs.time - rhs.diff); } - -} - -#endif // TORRENT_USE_BOOST_DATE_TIME - -#endif - diff --git a/include/libtorrent/request_blocks.hpp b/include/libtorrent/request_blocks.hpp new file mode 100644 index 000000000..04bc4cedf --- /dev/null +++ b/include/libtorrent/request_blocks.hpp @@ -0,0 +1,49 @@ +/* + +Copyright (c) 2003-2013, 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_REQUEST_BLOCKS_HPP_INCLUDED +#define TORRENT_REQUEST_BLOCKS_HPP_INCLUDED + +namespace libtorrent +{ + class torrent; + class peer_connection; + + // returns false if the piece picker was not invoked, because + // of an early exit condition. In this case, the stats counter + // shouldn't be incremented, since it won't use any significant + // amount of CPU + bool request_a_block(torrent& t, peer_connection& c); +} + +#endif + diff --git a/include/libtorrent/resolver.hpp b/include/libtorrent/resolver.hpp new file mode 100644 index 000000000..bbb0cc3b2 --- /dev/null +++ b/include/libtorrent/resolver.hpp @@ -0,0 +1,81 @@ +/* + +Copyright (c) 2013, 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_RESOLVER_HPP_INCLUDE +#define TORRENT_RESOLVER_HPP_INCLUDE + +#include +#include +#include +#include +#include "libtorrent/error_code.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/resolver_interface.hpp" +#include "libtorrent/address.hpp" + +namespace libtorrent +{ + +struct TORRENT_EXTRA_EXPORT resolver : resolver_interface +{ + resolver(io_service& ios); + + void async_resolve(std::string const& host, int flags + , callback_t const& h); + +private: + + void on_lookup(error_code const& ec, tcp::resolver::iterator i + , resolver_interface::callback_t h, std::string hostname); + + struct dns_cache_entry + { + time_t last_seen; + std::vector
addresses; + }; + + typedef boost::unordered_map cache_t; + cache_t m_cache; + io_service& m_ios; + tcp::resolver m_resolver; + + // max number of cached entries + int m_max_size; + + // timeout (in seconds) of cache entries + int m_timeout; +}; + +} + +#endif + diff --git a/include/libtorrent/resolver_interface.hpp b/include/libtorrent/resolver_interface.hpp new file mode 100644 index 000000000..d55322d5d --- /dev/null +++ b/include/libtorrent/resolver_interface.hpp @@ -0,0 +1,64 @@ +/* + +Copyright (c) 2013, 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_RESOLVER_INTERFACE_HPP_INCLUDE +#define TORRENT_RESOLVER_INTERFACE_HPP_INCLUDE + +#include +#include +#include "libtorrent/error_code.hpp" +#include "libtorrent/address.hpp" + +namespace libtorrent +{ + +using boost::asio::ip::tcp; + +struct resolver_interface +{ + typedef boost::function const&)> + callback_t; + + // prefer_cache will always use the cache if we have + // an entry, regardless of how old it is. This is usefull + // when completing the lookup quickly is more important + // than accuracy + enum flags_t { prefer_cache = 1 }; + + virtual void async_resolve(std::string const& host, int flags + , callback_t const& h) = 0; +}; + +} + +#endif + diff --git a/include/libtorrent/session.hpp b/include/libtorrent/session.hpp index a6be3993c..3c908a0c3 100644 --- a/include/libtorrent/session.hpp +++ b/include/libtorrent/session.hpp @@ -57,9 +57,12 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/alert.hpp" // alert::error_notification #include "libtorrent/add_torrent_params.hpp" #include "libtorrent/rss.hpp" +#include "libtorrent/peer_class.hpp" +#include "libtorrent/peer_class_type_filter.hpp" #include "libtorrent/build_config.hpp" #include "libtorrent/storage.hpp" +#include "libtorrent/session_settings.hpp" #ifdef _MSC_VER # include @@ -82,6 +85,19 @@ namespace libtorrent class connection_queue; class alert; + // describes one statistics metric from the session. For more information, + // see the session-statistics_ section. + struct TORRENT_EXPORT stats_metric + { + char const* name; + int value_index; + enum { type_counter, type_gauge }; + int type; + }; + + typedef boost::function& + , error_code&)> user_load_function_t; + // The default values of the session settings are set for a regular // bittorrent client running on a desktop system. There are functions that // can set the session settings to pre set settings for other environments. @@ -105,13 +121,25 @@ namespace libtorrent // serving many peers and that doesn't do any downloading. It has a 128 MB // disk cache and has a limit of 400 files in its file pool. It support fast // upload rates by allowing large send buffers. + TORRENT_EXPORT void min_memory_usage(settings_pack& set); + TORRENT_EXPORT void high_performance_seed(settings_pack& set); + +#ifndef TORRENT_NO_DEPRECATE TORRENT_EXPORT session_settings min_memory_usage(); TORRENT_EXPORT session_settings high_performance_seed(); +#endif #ifndef TORRENT_CFG #error TORRENT_CFG is not defined! #endif + // given a vector if stats_metric objects (as returned by + // session_stats_metrics()) and a name of a metric, this function returns + // the counter index of it, or -1 if it could not be found. The counter + // index is the index into the values array returned by session_stats_alert. + TORRENT_EXPORT int find_metric_idx(std::vector const& metrics + , char const* name); + void TORRENT_EXPORT TORRENT_CFG(); namespace aux @@ -144,11 +172,25 @@ namespace libtorrent #define TORRENT_LOGPATH_ARG_DEFAULT #endif + // This free function returns the list of available metrics exposed by + // libtorrent's statistics API. Each metric has a name and a *value index*. + // The value index is the index into the array in session_stats_alert where + // this metric's value can be found when the session stats is sampled (by + // calling post_session_stats()). + TORRENT_EXPORT std::vector session_stats_metrics(); + // The session holds all state that spans multiple torrents. Among other // things it runs the network loop and manages all torrents. Once it's // created, the session object will spawn the main thread that will do all // the work. The main thread will be idle as long it doesn't have any // torrents to participate in. + // + // You have some control over session configuration through the + // ``session::apply_settings()`` member function. To change one or more + // configuration options, create a settings_pack. object and fill it with + // the settings to be set and pass it in to ``session::apply_settings()``. + // + // see apply_settings(). class TORRENT_EXPORT session: public boost::noncopyable { public: @@ -157,20 +199,27 @@ namespace libtorrent // get a default fingerprint stating the version of libtorrent. The // fingerprint is a short string that will be used in the peer-id to // identify the client and the client's version. For more details see the - // fingerprint class. The constructor that only takes a fingerprint will - // not open a listen port for the session, to get it running you'll have - // to call ``session::listen_on()``. The other constructor, that takes a - // port range and an interface as well as the fingerprint will - // automatically try to listen on a port on the given interface. For more - // information about the parameters, see ``listen_on()`` function. + // fingerprint class. // // The flags paramater can be used to start default features (upnp & // nat-pmp) and default plugins (ut_metadata, ut_pex and smart_ban). The - // default is to start those things. If you do not want them to start, + // default is to start those features. If you do not want them to start, // pass 0 as the flags parameter. // // The ``alert_mask`` is the same mask that you would send to // set_alert_mask(). + + // TODO: 3 could the fingerprint be a setting as well? And should the + // settings_pack be optional? + session(settings_pack const& pack + , fingerprint const& print = fingerprint("LT" + , LIBTORRENT_VERSION_MAJOR, LIBTORRENT_VERSION_MINOR, 0, 0) + , int flags = start_default_features | add_default_plugins) + { + TORRENT_CFG(); + init(print); + start(flags, pack); + } session(fingerprint const& print = fingerprint("LT" , LIBTORRENT_VERSION_MAJOR, LIBTORRENT_VERSION_MINOR, 0, 0) , int flags = start_default_features | add_default_plugins @@ -178,11 +227,21 @@ namespace libtorrent TORRENT_LOGPATH_ARG_DEFAULT) { TORRENT_CFG(); - init(std::make_pair(0, 0), "0.0.0.0", print, alert_mask); + settings_pack pack; + pack.set_int(settings_pack::alert_mask, alert_mask); + if ((flags & start_default_features) == 0) + { + pack.set_bool(settings_pack::enable_upnp, false); + pack.set_bool(settings_pack::enable_natpmp, false); + pack.set_bool(settings_pack::enable_lsd, false); + pack.set_bool(settings_pack::enable_dht, false); + } + + init(print); #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING set_log_path(logpath); #endif - start(flags); + start(flags, pack); } session(fingerprint const& print , std::pair listen_port_range @@ -193,12 +252,27 @@ namespace libtorrent { TORRENT_CFG(); TORRENT_ASSERT(listen_port_range.first > 0); - TORRENT_ASSERT(listen_port_range.first < listen_port_range.second); - init(listen_port_range, listen_interface, print, alert_mask); + TORRENT_ASSERT(listen_port_range.first <= listen_port_range.second); + + settings_pack pack; + pack.set_int(settings_pack::alert_mask, alert_mask); + pack.set_int(settings_pack::max_retry_port_bind, listen_port_range.second - listen_port_range.first); + char if_string[100]; + snprintf(if_string, sizeof(if_string), "%s:%d", listen_interface, listen_port_range.first); + pack.set_str(settings_pack::listen_interfaces, if_string); + + if ((flags & start_default_features) == 0) + { + pack.set_bool(settings_pack::enable_upnp, false); + pack.set_bool(settings_pack::enable_natpmp, false); + pack.set_bool(settings_pack::enable_lsd, false); + pack.set_bool(settings_pack::enable_dht, false); + } + init(print); #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING set_log_path(logpath); #endif - start(flags); + start(flags, pack); } // The destructor of session will notify all trackers that our torrents @@ -206,7 +280,7 @@ namespace libtorrent // All this before the destructor of session returns. So, it's advised // that any kind of interface (such as windows) are closed before // destructing the session object. Because it can take a few second for - // it to finish. The timeout can be set with ``set_settings()``. + // it to finish. The timeout can be set with apply_settings(). ~session(); // flags that determines which aspects of the session should be @@ -223,12 +297,6 @@ namespace libtorrent // joining the DHT if provided at next session startup. save_dht_state = 0x004, - // save proxy_settings - save_proxy = 0x008, - - // save i2p_proxy settings - save_i2p_proxy = 0x010, - // save pe_settings save_encryption_settings = 0x020, @@ -240,6 +308,8 @@ namespace libtorrent #ifndef TORRENT_NO_DEPRECATE , + save_proxy = 0x008, + save_i2p_proxy = 0x010, save_dht_proxy = save_proxy, save_peer_proxy = save_proxy, save_web_proxy = save_proxy, @@ -300,6 +370,13 @@ namespace libtorrent // included. This flag is on by default. See add_torrent_params. void post_torrent_updates(); + // This function will post a session_stats_alert object, containing a snapshot of + // the performance counters from the internals of libtorrent. To interpret these counters, + // query the session via session_stats_metrics(). + // + // For more information, see the session-statistics_ section. + void post_session_stats(); + // internal io_service& get_io_service(); @@ -355,17 +432,6 @@ namespace libtorrent , bool paused = false , storage_constructor_type sc = default_storage_constructor) TORRENT_DEPRECATED; - // deprecated in 0.14 - TORRENT_DEPRECATED_PREFIX - torrent_handle add_torrent( - boost::intrusive_ptr ti - , std::string const& save_path - , entry const& resume_data = entry() - , storage_mode_t storage_mode = storage_mode_sparse - , bool paused = false - , storage_constructor_type sc = default_storage_constructor - , void* userdata = 0) TORRENT_DEPRECATED; - // deprecated in 0.14 TORRENT_DEPRECATED_PREFIX torrent_handle add_torrent( @@ -411,19 +477,53 @@ namespace libtorrent void resume(); bool is_paused() const; + // This function enables dynamic-loading-of-torrent-files_. When a + // torrent is unloaded but needs to be availabe in memory, this function + // is called **from within the libtorrent network thread**. From within + // this thread, you can **not** use any of the public APIs of libtorrent + // itself. The the info-hash of the torrent is passed in to the function + // and it is expected to fill in the passed in ``vector`` with the + // .torrent file corresponding to it. + // + // If there is an error loading the torrent file, the ``error_code`` + // (``ec``) should be set to reflect the error. In such case, the torrent + // itself is stopped and set to an error state with the corresponding + // error code. + // + // Given that the function is called from the internal network thread of + // libtorrent, it's important to not stall. libtorrent will not be able + // to send nor receive any data until the function call returns. + // + // The signature of the function to pass in is:: + // + // void fun(sha1_hash const& info_hash, std::vector& buf, error_code& ec); + void set_load_function(user_load_function_t fun); + // returns session wide-statistics and status. For more information, see // the ``session_status`` struct. session_status status() const; - // Returns status of the disk cache for this session. For more - // information, see the cache_status type. - cache_status get_cache_status() const; - - // fills out the supplied vector with information for - // each piece that is currently in the disk cache for the torrent with the - // specified info-hash (``ih``). +#ifndef TORRENT_NO_DEPRECATE + // deprecated in aio branch + TORRENT_DEPRECATED_PREFIX + // fills out the supplied vector with information for each piece that is + // currently in the disk cache for the torrent with the specified + // info-hash (``ih``). void get_cache_info(sha1_hash const& ih - , std::vector& ret) const; + , std::vector& ret) const TORRENT_DEPRECATED; + + // Returns status of the disk cache for this session. + // For more information, see the cache_status type. + TORRENT_DEPRECATED_PREFIX + cache_status get_cache_status() const TORRENT_DEPRECATED; +#endif + + enum { disk_cache_no_pieces = 1 }; + + // Fills in the cache_status struct with information about the given torrent. + // If ``flags`` is ``session::disk_cache_no_pieces`` the ``cache_status::pieces`` field + // will not be set. This may significantly reduce the cost of this call. + void get_cache_info(cache_status* ret, torrent_handle h = torrent_handle(), int flags = 0) const; // This adds an RSS feed to the session. The feed will be refreshed // regularly and optionally add all torrents from the feed, as they @@ -444,46 +544,24 @@ namespace libtorrent // Returns a list of all RSS feeds that are being watched by the session. void get_feeds(std::vector& f) const; - // starts/stops UPnP, NATPMP or LSD port mappers they are stopped by - // default These functions are not available in case - // ``TORRENT_DISABLE_DHT`` is defined. ``start_dht`` starts the dht node - // and makes the trackerless service available to torrents. The startup - // state is optional and can contain nodes and the node id from the - // previous session. The dht node state is a bencoded dictionary with the - // following entries: - // - // nodes - // A list of strings, where each string is a node endpoint encoded in - // binary. If the string is 6 bytes long, it is an IPv4 address of 4 - // bytes, encoded in network byte order (big endian), followed by a 2 - // byte port number (also network byte order). If the string is 18 - // bytes long, it is 16 bytes of IPv6 address followed by a 2 bytes - // port number (also network byte order). - // - // node-id - // The node id written as a readable string as a hexadecimal number. - // - // ``dht_state`` will return the current state of the dht node, this can - // be used to start up the node again, passing this entry to - // ``start_dht``. It is a good idea to save this to disk when the session - // is closed, and read it up again when starting. - // - // If the port the DHT is supposed to listen on is already in use, and - // exception is thrown, ``asio::error``. +#ifndef TORRENT_NO_DEPRECATE + // ``start_dht`` starts the dht node and makes the trackerless service + // available to torrents. // // ``stop_dht`` stops the dht node. - // - // ``add_dht_node`` adds a node to the routing table. This can be used if - // your client has its own source of bootstrapping nodes. - // + // deprecated. use settings_pack::enable_dht instead + TORRENT_DEPRECATED_PREFIX + void start_dht() TORRENT_DEPRECATED; + TORRENT_DEPRECATED_PREFIX + void stop_dht() TORRENT_DEPRECATED; +#endif + // ``set_dht_settings`` sets some parameters availavle to the dht node. // See dht_settings for more information. // // ``is_dht_running()`` returns true if the DHT support has been started // and false // otherwise. - void start_dht(); - void stop_dht(); void set_dht_settings(dht_settings const& settings); bool is_dht_running() const; @@ -491,11 +569,12 @@ namespace libtorrent // pinged, and if a valid DHT reply is received, the node will be added to // the routing table. // - // ``add_dht_router`` adds the given endpoint to a list of DHT router nodes. - // If a search is ever made while the routing table is empty, those nodes will - // be used as backups. Nodes in the router node list will also never be added - // to the regular routing table, which effectively means they are only used - // for bootstrapping, to keep the load off them. + // ``add_dht_router`` adds the given endpoint to a list of DHT router + // nodes. If a search is ever made while the routing table is empty, + // those nodes will be used as backups. Nodes in the router node list + // will also never be added to the regular routing table, which + // effectively means they are only used for bootstrapping, to keep the + // load off them. // // An example routing node that you could typically add is // ``router.bittorrent.com``. @@ -684,29 +763,25 @@ namespace libtorrent // peer potentially across you changing your IP. void set_key(int key); + // built-in peer classes + enum { + global_peer_class_id, + tcp_peer_class_id, + local_peer_class_id + }; // ``is_listening()`` will tell you whether or not the session has // successfully opened a listening port. If it hasn't, this function will - // return false, and then you can use ``listen_on()`` to make another - // attempt. + // return false, and then you can set a new + // settings_pack::listen_interfaces to try another interface and port to + // bind to. // - // ``listen_port()`` returns the port we ended up listening on. Since you - // just pass a port-range to the constructor and to ``listen_on()``, to - // know which port it ended up using, you have to ask the session using - // this function. - // - // ``listen_on()`` will change the listen port and/or the listen - // interface. If the session is already listening on a port, this socket - // will be closed and a new socket will be opened with these new - // settings. The port range is the ports it will try to listen on, if the - // first port fails, it will continue trying the next port within the - // range and so on. The interface parameter can be left as 0, in that - // case the os will decide which interface to listen on, otherwise it - // should be the ip-address of the interface you want the listener socket - // bound to. ``listen_on()`` returns the error code of the operation in - // ``ec``. If this indicates success, the session is listening on a port - // within the specified range. If it fails, it will also generate an - // appropriate alert (listen_failed_alert). + // ``listen_port()`` returns the port we ended up listening on. If the + // port specified in settings_pack::listen_interfaces failed, libtorrent + // will try to bind to the next port, and so on. If it fails + // settings_pack::max_retry_port_bind times, it will bind to port 0 + // (meaning the OS picks the port). The only way to know which port it + // ended up binding to is to ask for it by calling ``listen_port()``. // // If all ports in the specified range fails to be opened for listening, // libtorrent will try to use port 0 (which tells the operating system to @@ -729,37 +804,110 @@ namespace libtorrent // socket option on the listen socket(s). By default, the listen socket // does not use reuse address. If you're running a service that needs to // run on a specific port no matter if it's in use, set this flag. - // - // If you're also starting the DHT, it is a good idea to do that after - // you've called ``listen_on()``, since the default listen port for the - // DHT is the same as the tcp listen socket. If you start the DHT first, - // it will assume the tcp port is free and open the udp socket on that - // port, then later, when ``listen_on()`` is called, it may turn out that - // the tcp port is in use. That results in the DHT and the bittorrent - // socket listening on different ports. If the DHT is active when - // ``listen_on`` is called, the udp port will be rebound to the new port, - // if it was configured to use the same port as the tcp socket, and if - // the listen_on call failed to bind to the same port that the udp uses. - // - // If you want the OS to pick a port for you, pass in 0 as both first and - // second. - // - // The reason why it's a good idea to run the DHT and the bittorrent - // socket on the same port is because that is an assumption that may be - // used to increase performance. One way to accelerate the connecting of - // peers on windows may be to first ping all peers with a DHT ping - // packet, and connect to those that responds first. On windows one can - // only connect to a few peers at a time because of a built in limitation - // (in XP Service pack 2). - void listen_on( - std::pair const& port_range - , error_code& ec - , const char* net_interface = 0 - , int flags = 0); unsigned short listen_port() const; unsigned short ssl_listen_port() const; bool is_listening() const; + // Sets the peer class filter for this session. All new peer connections + // will take this into account and be added to the peer classes specified + // by this filter, based on the peer's IP address. + // + // The ip-filter essentially maps an IP -> uint32. Each bit in that 32 + // bit integer represents a peer class. The least significant bit + // represents class 0, the next bit class 1 and so on. + // + // For more info, see ip_filter. + // + // For example, to make all peers in the range 200.1.1.0 - 200.1.255.255 + // belong to their own peer class, apply the following filter:: + // + // ip_filter f; + // int my_class = ses.create_peer_class("200.1.x.x IP range"); + // f.add_rule(address_v4::from_string("200.1.1.0") + // , address_v4::from_string("200.1.255.255") + // , 1 << my_class); + // ses.set_peer_class_filter(f); + // + // This setting only applies to new connections, it won't affect existing + // peer connections. + // + // This function is limited to only peer class 0-31, since there are only + // 32 bits in the IP range mapping. Only the set bits matter; no peer + // class will be removed from a peer as a result of this call, peer + // classes are only added. + // + // The ``peer_class`` argument cannot be greater than 31. The bitmasks + // representing peer classes in the ``peer_class_filter`` are 32 bits. + // + // For more information, see peer-classes_. + void set_peer_class_filter(ip_filter const& f); + + // Sets and gets the *peer class type filter*. This is controls automatic + // peer class assignments to peers based on what kind of socket it is. + // + // It does not only support assigning peer classes, it also supports + // removing peer classes based on socket type. + // + // The order of these rules being applied are: + // + // 1. peer-class IP filter + // 2. peer-class type filter, removing classes + // 3. peer-class type filter, adding classes + // + // For more information, see peer-classes_. + // TODO: add get_peer_class_type_filter() as well + void set_peer_class_type_filter(peer_class_type_filter const& f); + + // Creates a new peer class (see peer-classes_) with the given name. The + // returned integer is the new peer class' identifier. Peer classes may + // have the same name, so each invocation of this function creates a new + // class and returns a unique identifier. + // + // Identifiers are assigned from low numbers to higher. So if you plan on + // using certain peer classes in a call to `set_peer_class_filter()`_, + // make sure to create those early on, to get low identifiers. + // + // For more information on peer classes, see peer-classes_. + int create_peer_class(char const* name); + + // This call dereferences the reference count of the specified peer + // class. When creating a peer class it's automatically referenced by 1. + // If you want to recycle a peer class, you may call this function. You + // may only call this function **once** per peer class you create. + // Calling it more than once for the same class will lead to memory + // corruption. + // + // Since peer classes are reference counted, this function will not + // remove the peer class if it's still assigned to torrents or peers. It + // will however remove it once the last peer and torrent drops their + // references to it. + // + // There is no need to call this function for custom peer classes. All + // peer classes will be properly destructed when the session object + // destructs. + // + // For more information on peer classes, see peer-classes_. + void delete_peer_class(int cid); + + // These functions queries information from a peer class and updates the + // configuration of a peer class, respectively. + // + // ``cid`` must refer to an existing peer class. If it does not, the + // return value of ``get_peer_class()`` is undefined. + // + // ``set_peer_class()`` sets all the information in the + // ``peer_class_info`` object in the specified peer class. There is no + // option to only update a single property. + // + // A peer or torrent balonging to more than one class, the highest + // priority among any of its classes is the one that is taken into + // account. + // + // For more information, see peer-classes_. + peer_class_info get_peer_class(int cid); + void set_peer_class(int cid, peer_class_info const& pci); + +#ifndef TORRENT_NO_DEPRECATE // if the listen port failed in some way you can retry to listen on // another port- range with this function. If the listener succeeded and // is currently listening, a call to this function will shut down the @@ -769,20 +917,27 @@ namespace libtorrent // generate alerts describing the error. It will return true on success. enum listen_on_flags_t { -#ifndef TORRENT_NO_DEPRECATE // this is always on starting with 0.16.2 listen_reuse_address = 0x01, -#endif listen_no_system_port = 0x02 }; -#ifndef TORRENT_NO_DEPRECATE // deprecated in 0.16 + + // specify which interfaces to bind outgoing connections to + // This has been moved to a session setting TORRENT_DEPRECATED_PREFIX - bool listen_on( + void use_interfaces(char const* interfaces) TORRENT_DEPRECATED; + + // instead of using this, specify listen interface and port in + // the settings_pack::listen_interfaces setting + TORRENT_DEPRECATED_PREFIX + void listen_on( std::pair const& port_range + , error_code& ec , const char* net_interface = 0 , int flags = 0) TORRENT_DEPRECATED; + #endif // flags to be passed in to remove_torrent(). @@ -817,13 +972,47 @@ namespace libtorrent // the torrent is deleted, a torrent_deleted_alert is posted. void remove_torrent(const torrent_handle& h, int options = 0); +#ifndef TORRENT_NO_DEPRECATE + // deprecated in aio-branch // Sets the session settings and the packet encryption settings // respectively. See session_settings and pe_settings for more // information on available options. - void set_settings(session_settings const& s); - session_settings settings() const; - void set_pe_settings(pe_settings const& settings); - pe_settings get_pe_settings() const; + TORRENT_DEPRECATED_PREFIX + void set_settings(session_settings const& s) TORRENT_DEPRECATED; + TORRENT_DEPRECATED_PREFIX + session_settings settings() const TORRENT_DEPRECATED; + + // deprecated in libtorrent 1.1. use settings_pack instead + TORRENT_DEPRECATED_PREFIX + void set_pe_settings(pe_settings const& settings) TORRENT_DEPRECATED; + TORRENT_DEPRECATED_PREFIX + pe_settings get_pe_settings() const TORRENT_DEPRECATED; +#endif + + // Applies the settings specified by the settings_pack ``s``. This is an + // asynchronous operation that will return immediately and actually apply + // the settings to the main thread of libtorrent some time later. + void apply_settings(settings_pack const& s); + aux::session_settings get_settings() const; + +#ifdef TORRENT_STATS + // internal + void enable_stats_logging(bool s); +#endif + +#ifndef TORRENT_NO_DEPRECATE + // ``set_i2p_proxy`` sets the i2p_ proxy, and tries to open a persistant + // connection to it. The only used fields in the proxy settings structs + // are ``hostname`` and ``port``. + // + // ``i2p_proxy`` returns the current i2p proxy in use. + // + // .. _i2p: http://www.i2p2.de + + TORRENT_DEPRECATED_PREFIX + void set_i2p_proxy(proxy_settings const& s) TORRENT_DEPRECATED; + TORRENT_DEPRECATED_PREFIX + proxy_settings i2p_proxy() const TORRENT_DEPRECATED; // These functions sets and queries the proxy settings to be used for the // session. @@ -834,25 +1023,11 @@ namespace libtorrent // will flow without using any proxy. If you want to enforce using a // proxy, even when the proxy doesn't work, enable anonymous_mode in // session_settings. - void set_proxy(proxy_settings const& s); - proxy_settings proxy() const; + TORRENT_DEPRECATED_PREFIX + void set_proxy(proxy_settings const& s) TORRENT_DEPRECATED; + TORRENT_DEPRECATED_PREFIX + proxy_settings proxy() const TORRENT_DEPRECATED; -#ifdef TORRENT_STATS - // internal - void enable_stats_logging(bool s); -#endif - - // ``set_i2p_proxy`` sets the i2p_ proxy, and tries to open a persistant - // connection to it. The only used fields in the proxy settings structs - // are ``hostname`` and ``port``. - // - // ``i2p_proxy`` returns the current i2p proxy in use. - // - // .. _i2p: http://www.i2p2.de - void set_i2p_proxy(proxy_settings const& s); - proxy_settings i2p_proxy() const; - -#ifndef TORRENT_NO_DEPRECATE // deprecated in 0.16 // Get the number of uploads. TORRENT_DEPRECATED_PREFIX @@ -917,9 +1092,9 @@ namespace libtorrent #endif // ``pop_alert()`` is used to ask the session if any errors or events has - // occurred. With set_alert_mask() you can filter which alerts to receive - // through ``pop_alert()``. For information about the alert categories, - // see alerts_. + // occurred. With settings_pack::alert_mask you can filter which alerts + // to receive through ``pop_alert()``. For information about the alert + // categories, see alerts_. // // ``pop_alerts()`` pops all pending alerts in a single call. In high // performance environments with a very high alert churn rate, this can @@ -975,16 +1150,22 @@ namespace libtorrent TORRENT_DEPRECATED_PREFIX void set_severity_level(alert::severity_t s) TORRENT_DEPRECATED; + // use the setting instead TORRENT_DEPRECATED_PREFIX size_t set_alert_queue_size_limit(size_t queue_size_limit_) TORRENT_DEPRECATED; -#endif // Changes the mask of which alerts to receive. By default only errors // are reported. ``m`` is a bitmask where each bit represents a category // of alerts. // + // ``get_alert_mask()`` returns the current mask; + // // See category_t enum for options. - void set_alert_mask(boost::uint32_t m); + TORRENT_DEPRECATED_PREFIX + void set_alert_mask(boost::uint32_t m) TORRENT_DEPRECATED; + TORRENT_DEPRECATED_PREFIX + boost::uint32_t get_alert_mask() const TORRENT_DEPRECATED; +#endif // This sets a function to be called (from within libtorrent's netowrk // thread) every time an alert is posted. Since the function (``fun``) is @@ -1000,13 +1181,16 @@ namespace libtorrent // internal connection_queue& get_connection_queue(); +#ifndef TORRENT_NO_DEPRECATE // Starts and stops Local Service Discovery. This service will broadcast // the infohashes of all the non-private torrents on the local network to // look for peers on the same swarm within multicast reach. // - // It is turned off by default. - void start_lsd(); - void stop_lsd(); + // deprecated. use settings_pack::enable_lsd instead + TORRENT_DEPRECATED_PREFIX + void start_lsd() TORRENT_DEPRECATED; + TORRENT_DEPRECATED_PREFIX + void stop_lsd() TORRENT_DEPRECATED; // Starts and stops the UPnP service. When started, the listen port and // the DHT port are attempted to be forwarded on local UPnP router @@ -1017,9 +1201,27 @@ namespace libtorrent // portmap_alert and the portmap_error_alert. The object will be valid // until ``stop_upnp()`` is called. See upnp-and-nat-pmp_. // - // It is off by default. - void start_upnp(); - void stop_upnp(); + // deprecated. use settings_pack::enable_upnp instead + TORRENT_DEPRECATED_PREFIX + void start_upnp() TORRENT_DEPRECATED; + TORRENT_DEPRECATED_PREFIX + void stop_upnp() TORRENT_DEPRECATED; + + // Starts and stops the NAT-PMP service. When started, the listen port + // and the DHT port are attempted to be forwarded on the router through + // NAT-PMP. + // + // The natpmp object returned by ``start_natpmp()`` can be used to add + // and remove arbitrary port mappings. Mapping status is returned through + // the portmap_alert and the portmap_error_alert. The object will be + // valid until ``stop_natpmp()`` is called. See upnp-and-nat-pmp_. + // + // deprecated. use settings_pack::enable_natpmp instead + TORRENT_DEPRECATED_PREFIX + void start_natpmp() TORRENT_DEPRECATED; + TORRENT_DEPRECATED_PREFIX + void stop_natpmp() TORRENT_DEPRECATED; +#endif // protocols used by add_port_mapping() enum protocol_type { udp = 1, tcp = 2 }; @@ -1031,25 +1233,11 @@ namespace libtorrent int add_port_mapping(protocol_type t, int external_port, int local_port); void delete_port_mapping(int handle); - // Starts and stops the NAT-PMP service. When started, the listen port - // and the DHT port are attempted to be forwarded on the router through - // NAT-PMP. - // - // The natpmp object returned by ``start_natpmp()`` can be used to add - // and remove arbitrary port mappings. Mapping status is returned through - // the portmap_alert and the portmap_error_alert. The object will be - // valid until ``stop_natpmp()`` is called. See upnp-and-nat-pmp_. - // - // It is off by default. - void start_natpmp(); - void stop_natpmp(); - private: - void init(std::pair listen_range, char const* listen_interface - , fingerprint const& id, boost::uint32_t alert_mask); + void init(fingerprint const& id); void set_log_path(std::string const& p); - void start(int flags); + void start(int flags, settings_pack const& pack); // data shared between the main thread // and the working thread diff --git a/include/libtorrent/session_settings.hpp b/include/libtorrent/session_settings.hpp index e63c2b1e6..cc3175292 100644 --- a/include/libtorrent/session_settings.hpp +++ b/include/libtorrent/session_settings.hpp @@ -35,21 +35,28 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/version.hpp" #include "libtorrent/config.hpp" -#include "libtorrent/version.hpp" #include #include +#include +#include namespace libtorrent { +#ifndef TORRENT_NO_DEPRECATE +#define TORRENT_EXPORT_DEPRECATED TORRENT_EXPORT +#else +#define TORRENT_EXPORT_DEPRECATED +#endif + // The ``proxy_settings`` structs contains the information needed to // direct certain traffic to a proxy. - struct TORRENT_EXPORT proxy_settings + struct TORRENT_EXPORT_DEPRECATED proxy_settings { // defaults constructs proxy settings, initializing it to the default // settings. - proxy_settings() : type(none) + proxy_settings() : type(0) , port(0), proxy_hostnames(true) , proxy_peer_connections(true) {} @@ -64,6 +71,7 @@ namespace libtorrent std::string username; std::string password; +#ifndef TORRENT_NO_DEPRECATE // the type of proxy to use. Assign one of these to the // proxy_settings::type field. enum proxy_type @@ -106,9 +114,10 @@ namespace libtorrent // authorization. The username and password will be sent to the proxy. http_pw, - // route through a i2p SAM proxy + // route through an i2p SAM proxy i2p_proxy }; +#endif // tells libtorrent what kind of proxy server it is. See proxy_type // enum for options @@ -128,6 +137,8 @@ namespace libtorrent bool proxy_peer_connections; }; +#ifndef TORRENT_NO_DEPRECATE + // This holds most of the session-wide settings in libtorrent. Pass this // to session::set_settings() to change the settings, initialize it from // session::get_settings() to get the current settings. @@ -265,9 +276,12 @@ namespace libtorrent // may delay the connection of other peers considerably. int peer_connect_timeout; - // if set to true, upload, download and unchoke limits are ignored for - // peers on the local network. +#ifndef TORRENT_NO_DEPRECATE + // deprecated, use set_peer_class_filter() instead + // if set to true, upload, download and unchoke limits + // are ignored for peers on the local network. bool ignore_limits_on_local_network; +#endif // the number of connection attempts that are made per second. If a // number < 0 is specified, it will default to 200 connections per @@ -354,12 +368,10 @@ namespace libtorrent // this too low will severly limit your download rate. int max_queued_disk_bytes; - // this is the low watermark for the disk buffer queue. whenever the - // number of queued bytes exceed the max_queued_disk_bytes, libtorrent - // will wait for it to drop below this value before issuing more reads - // from the sockets. If set to 0, the low watermark will be half of the - // max queued disk bytes +#ifndef TORRENT_NO_DEPRECATE + // not used anymore int max_queued_disk_bytes_low_watermark; +#endif // the number of seconds to wait for a handshake response from a peer. If // no response is received within this time, the peer is disconnected. @@ -416,23 +428,6 @@ namespace libtorrent // create a bias towards read jobs over write jobs. int send_buffer_watermark_factor; -#ifndef TORRENT_NO_DEPRECATE - // deprecated in 0.16 defaults to true. When true, if there is a global - // upload limit set and the current upload rate is less than 90% of that, - // another upload slot is opened. If the upload rate has been saturated - // for an extended period of time, on upload slot is closed. The number - // of upload slots will never be less than what has been set by - // ``session::set_max_uploads()``. To query the current number of upload - // slots, see ``session_status::allowed_upload_slots``. - bool auto_upload_slots; - - // When set, and ``auto_upload_slots`` is set, the max upload slots - // setting is used as a minimum number of unchoked slots. This algorithm - // is designed to prevent the peer from spreading its upload capacity too - // thin, but still open more slots in order to utilize the full capacity. - bool auto_upload_slots_rate_based; -#endif - // the different choking algorithms available. Set // session_settings::choking_algorithm to one of these enum choking_algorithm_t @@ -520,6 +515,8 @@ namespace libtorrent // this is the number of disk buffer blocks (16 kiB) that should be // allocated at a time. It must be at least 1. Lower number saves memory // at the expense of more heap allocations + // setting this to zero means 'automatic', i.e. proportional + // to the total disk cache size int cache_buffer_chunk_size; // the number of seconds a write cache entry sits idle in the cache @@ -529,6 +526,12 @@ namespace libtorrent // when set to true (default), the disk cache is also used to cache // pieces read from disk. Blocks for writing pieces takes presedence. bool use_read_cache; + bool use_write_cache; + + // this will make the disk cache never flush a write + // piece if it would cause is to have to re-read it + // once we want to calculate the piece hash + bool dont_flush_write_cache; // defaults to 0. If set to something greater than 0, the disk read cache // will not be evicted by cache misses and will explicitly be controlled @@ -578,12 +581,9 @@ namespace libtorrent int disk_io_write_mode; int disk_io_read_mode; - // when set to true, instead of issuing multiple adjacent reads or writes - // to the disk, allocate a larger buffer, copy all writes into it and - // issue a single write. For reads, read into a larger buffer and copy - // the buffer into the smaller individual read buffers afterwards. This - // may save system calls, but will cost in additional memory allocation - // and copying. + // allocate separate, contiguous, buffers for read and write calls. Only + // used where writev/readv cannot be used will use more RAM but may + // improve performance bool coalesce_reads; bool coalesce_writes; @@ -600,7 +600,8 @@ namespace libtorrent // recommended to change this setting. Its main purpose is to use as an // escape hatch for cheap routers with QoS capability but can only // classify flows based on port numbers. - std::pair outgoing_ports; + int outgoing_port; + int num_outgoing_ports; // determines the TOS byte set in the IP header of every packet sent to // peers (including web seeds). The default value for this is ``0x0`` (no @@ -677,7 +678,8 @@ namespace libtorrent // downloader) or the seed time limit (seconds as seed) it is considered // done, and it will leave room for other torrents the default value for // share ratio is 2 the default seed time ratio is 7, because that's a - // common asymmetry ratio on connections + // common asymmetry ratio on connections. these are specified as + // percentages // //.. note:: // This is an out-dated option that doesn't make much sense. It will be @@ -688,6 +690,8 @@ namespace libtorrent // seeding torrent to have met the seed limit criteria. See queuing_. float seed_time_ratio_limit; + // seed time limit is specified in seconds + // // the limit on the time a torrent has been an active seed (specified in // seconds) before it is considered having met the seed limit criteria. // See queuing_. @@ -708,7 +712,17 @@ namespace libtorrent // limit, the optimistic unchoke is triggered. This defaults to 90% (i.e. // 0.9f). int peer_turnover_interval; + + // the percentage of peers to disconnect every + // turnoever interval (if we're at the peer limit) + // defaults to 4% + // this is specified in percent float peer_turnover; + + // when we are connected to more than + // limit * peer_turnover_cutoff peers + // disconnect peer_turnover fraction + // of the peers. It is specified in percent float peer_turnover_cutoff; // specifies whether libtorrent should close connections where both ends @@ -896,11 +910,12 @@ namespace libtorrent // regular bittorrent clients. bool disable_hash_checks; - // if this is true, disk read operations may be re-ordered based on their - // physical disk read offset. This greatly improves throughput when - // uploading to many peers. This assumes a traditional hard drive with a - // read head and spinning platters. If your storage medium is a solid - // state drive, this optimization doesn't give you an benefits + // if this is true, disk read operations are sorted by their physical + // offset on disk before issued to the operating system. This is useful + // if async I/O is not supported. It defaults to true if async I/O is not + // supported and fals otherwise. disk I/O operations are likely to be + // reordered regardless of this setting when async I/O is supported by + // the OS. bool allow_reordered_disk_operations; // if this is true, i2p torrents are allowed to also get peers from other @@ -1241,9 +1256,12 @@ namespace libtorrent // see bandwidth_mixed_algo_t for options. int mixed_mode_algorithm; - // determines if uTP connections should be throttled by the global rate - // limiter or not. By default they are. +#ifndef TORRENT_NO_DEPRECATE + // deprecated, use set_peer_class_filter() instead + // set to true if uTP connections should be rate limited + // defaults to false bool rate_limit_utp; +#endif // the value passed in to listen() for the listen socket. It is the // number of outstanding incoming connections to queue up while we're not @@ -1332,6 +1350,45 @@ namespace libtorrent // party processes from corrupting the files under libtorrent's feet. bool lock_files; + // the number of threads to use for hash checking of pieces + // defaults to 1. If set to 0, the disk thread is used for hashing + int hashing_threads; + + // the number of blocks to keep outstanding at any given time when + // checking torrents. Higher numbers give faster re-checks but uses + // more memory. Specified in number of 16 kiB blocks + int checking_mem_usage; + + // if set to > 0, pieces will be announced to other peers before they are + // fully downloaded (and before they are hash checked). The intention is + // to gain 1.5 potential round trip times per downloaded piece. When + // non-zero, this indicates how many milliseconds in advance pieces + // should be announced, before they are expected to be completed. + int predictive_piece_announce; + + // when false, bytes off the socket is received directly into the disk + // buffer. This requires many more calls to recv(). When using a + // contiguous recv buffer, the download rate can be much higher + bool contiguous_recv_buffer; + + //#error this should not be an option, it should depend on whether or not we're seeding or downloading + + // for some aio back-ends, the number of io-threads to use + int aio_threads; + // for some aio back-ends, the max number of outstanding jobs + int aio_max; + + // the number of threads to use to call async_write_some on peer sockets. + // When seeding at extremely high speeds, using 2 or more threads here + // may make sense. Also when using SSL peer connections + int network_threads; + + // if this is set, it is interpreted as a file path to where to create an + // mmaped file to back the disk cache. this is mostly useful to introduce + // another caching layer between RAM and hard drives. Typically you would + // point this to an SSD drive. + std::string mmap_cache; + // sets the listen port for SSL connections. If this is set to 0, no SSL // listen port is opened. Otherwise a socket is opened on this port. This // setting is only taken into account when opening the regular listen @@ -1398,6 +1455,7 @@ namespace libtorrent int inactive_down_rate; int inactive_up_rate; }; +#endif // structure used to hold configuration options for the DHT // @@ -1504,6 +1562,7 @@ namespace libtorrent }; +#ifndef TORRENT_NO_DEPRECATE // The ``pe_settings`` structure is used to control the settings related // to peer protocol encryption. struct TORRENT_EXPORT pe_settings @@ -1563,6 +1622,7 @@ namespace libtorrent // otherwise bool prefer_rc4; }; +#endif // TORRENT_NO_DEPRECATE } diff --git a/include/libtorrent/session_status.hpp b/include/libtorrent/session_status.hpp index 54c45052c..09884acc1 100644 --- a/include/libtorrent/session_status.hpp +++ b/include/libtorrent/session_status.hpp @@ -108,7 +108,11 @@ namespace libtorrent int num_fin_sent; int num_close_wait; - // counters. These are monotonically increasing +#ifndef TORRENT_NO_DEPRECATE + // deprecated in libtorrent 1.1 + // use session_stats/performance_counters instead + // counters + // These are monotonically increasing // and cumulative counters for their respective event. boost::uint64_t packet_loss; boost::uint64_t timeout; @@ -122,6 +126,7 @@ namespace libtorrent boost::uint64_t payload_pkts_out; boost::uint64_t invalid_pkts_in; boost::uint64_t redundant_pkts_in; +#endif }; // contains session wide state and counters @@ -191,6 +196,8 @@ namespace libtorrent // be assigned a torrent yet. int num_peers; + int num_dead_peers; + // the current number of unchoked peers. int num_unchoked; @@ -255,6 +262,11 @@ namespace libtorrent // the number of known peers across all torrents. These are not necessarily // connected peers, just peers we know of. int peerlist_size; + + // the number of torrents in the + // session and the number of them that are currently paused, respectively. + int num_torrents; + int num_paused_torrents; }; } diff --git a/include/libtorrent/settings_pack.hpp b/include/libtorrent/settings_pack.hpp new file mode 100644 index 000000000..046ef6ec4 --- /dev/null +++ b/include/libtorrent/settings_pack.hpp @@ -0,0 +1,1577 @@ +/* + +Copyright (c) 2012-2013, 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_SETTINGS_PACK_HPP_INCLUDED +#define TORRENT_SETTINGS_PACK_HPP_INCLUDED + +#include "libtorrent/entry.hpp" +#include + +// OVERVIEW +// +// You have some control over session configuration through the session::apply_settings() +// member function. To change one or more configuration options, create a settings_pack. +// object and fill it with the settings to be set and pass it in to session::apply_settings(). +// +// You have control over proxy and authorization settings and also the user-agent +// that will be sent to the tracker. The user-agent will also be used to identify the +// client with other peers. +// +namespace libtorrent +{ + namespace aux { struct session_impl; struct session_settings; } + + struct settings_pack; + struct lazy_entry; + + TORRENT_EXTRA_EXPORT settings_pack* load_pack_from_dict(lazy_entry const* settings); + TORRENT_EXTRA_EXPORT void save_settings_to_dict(aux::session_settings const& s, entry::dictionary_type& sett); + TORRENT_EXPORT void initialize_default_settings(aux::session_settings& s); + TORRENT_EXTRA_EXPORT void apply_pack(settings_pack const* pack, aux::session_settings& sett, aux::session_impl* ses = 0); + + TORRENT_EXPORT int setting_by_name(std::string const& name); + TORRENT_EXPORT char const* name_for_setting(int s); + +#ifndef TORRENT_NO_DEPRECATE + struct session_settings; + settings_pack* load_pack_from_struct(aux::session_settings const& current, session_settings const& s); + void load_struct_from_settings(aux::session_settings const& current, session_settings& ret); +#endif + + // TODO: 2 add an API to query a settings_pack as well + // TODO: 2 maybe convert all bool types into int-types as well + + // The ``settings_pack`` struct, contains the names of all settings as + // enum values. These values are passed in to the ``set_str()``, + // ``set_int()``, ``set_bool()`` functions, to specify the setting to + // change. + // + // These are the available settings: + // + // .. include:: settings-ref.rst + // + struct TORRENT_EXPORT settings_pack + { + friend struct disk_io_thread; + friend void apply_pack(settings_pack const* pack, aux::session_settings& sett, aux::session_impl* ses); + + void set_str(int name, std::string val); + void set_int(int name, int val); + void set_bool(int name, bool val); + bool has_val(int name) const; + void clear(); + + std::string get_str(int name) const; + int get_int(int name) const; + bool get_bool(int name) const; + + // setting names (indices) are 16 bits. The two most significant + // bits indicate what type the setting has. (string, int, bool) + enum type_bases + { + string_type_base = 0x0000, + int_type_base = 0x4000, + bool_type_base = 0x8000, + type_mask = 0xc000, + index_mask = 0x3fff, + }; + + enum string_types + { + // this is the client identification to the tracker. + // The recommended format of this string is: + // "ClientName/ClientVersion libtorrent/libtorrentVersion". + // This name will not only be used when making HTTP requests, but also when + // sending extended headers to peers that support that extension. + // It may not contain \r or \n + user_agent = string_type_base, + + // ``announce_ip`` is the ip address passed along to trackers as the ``&ip=`` parameter. + // If left as the default, that parameter is omitted. + announce_ip, + + // ``mmap_cache`` may be set to a filename where the disk cache will be mmapped + // to. This could be useful, for instance, to map the disk cache from regular + // rotating hard drives onto an SSD drive. Doing that effectively introduces + // a second layer of caching, allowing the disk cache to be as big as can + // fit on an SSD drive (probably about one order of magnitude more than the + // available RAM). The intention of this setting is to set it up once at the + // start up and not change it while running. The setting may not be changed + // as long as there are any disk buffers in use. This default to the empty + // string, which means use regular RAM allocations for the disk cache. The file + // specified will be created and truncated to the disk cache size (``cache_size``). + // Any existing file with the same name will be replaced. + // + // Since this setting sets a hard upper limit on cache usage, it cannot be combined + // with ``session_settings::contiguous_recv_buffer``, since that feature treats the + // ``cache_size`` setting as a soft (but still pretty hard) limit. The result of combining + // the two is peers being disconnected after failing to allocate more disk buffers. + // + // This feature requires the ``mmap`` system call, on systems that don't have ``mmap`` + // this setting is ignored. + mmap_cache, + + // this is the client name and version identifier sent to peers in the handshake + // message. If this is an empty string, the user_agent is used instead + handshake_client_version, + + // sets the network interface this session will use when it opens + // outgoing connections. By default, it binds outgoing connections to + // INADDR_ANY and port 0 (i.e. let the OS decide). Ths parameter must + // be a string containing one or more, comma separated, adapter names. + // Adapter names on unix systems are of the form "eth0", "eth1", "tun0", + // etc. When specifying multiple + // interfaces, they will be assigned in round-robin order. This may be + // useful for clients that are multi-homed. Binding an outgoing + // connection to a local IP does not necessarily make the connection + // via the associated NIC/Adapter. Setting this to an empty string + // will disable binding of outgoing connections. + outgoing_interfaces, + + // a comma-separated list of (IP or device name, port) pairs. These + // are the listen ports that will be opened for accepting incoming uTP + // and TCP connections. It is possible to listen on multiple + // interfaces and multiple ports. Binding to port 0 will make the + // operating system pick the port. The default is "0.0.0.0:0", which + // binds to all interfaces on a port the OS picks. + // + // if binding fails, the listen_failed_alert is posted, otherwise the + // listen_succeeded_alert. + // + // If the DHT is running, it will also have its socket rebound to the + // same port as the main listen port. + // + // The reason why it's a good idea to run the DHT and the bittorrent + // socket on the same port is because that is an assumption that may + // be used to increase performance. One way to accelerate the + // connecting of peers on windows may be to first ping all peers with + // a DHT ping packet, and connect to those that responds first. On + // windows one can only connect to a few peers at a time because of a + // built in limitation (in XP Service pack 2). + listen_interfaces, + + // when using a poxy, this is the hostname where the proxy is running + // see proxy_type. + proxy_hostname, + + // when using a proxy, these are the credentials (if any) to use + // whne connecting to it. see proxy_type + proxy_username, + proxy_password, + + // sets the i2p_ SAM bridge to connect to. set the port with the + // ``i2p_port`` setting. + // + // .. _i2p: http://www.i2p2.de + i2p_hostname, + + max_string_setting_internal, + num_string_settings = max_string_setting_internal - string_type_base + }; + + enum bool_types + { + // determines if connections from the same IP address as + // existing connections should be rejected or not. Multiple + // connections from the same IP address is not allowed by + // default, to prevent abusive behavior by peers. It may + // be useful to allow such connections in cases where + // simulations are run on the same machie, and all peers + // in a swarm has the same IP address. + allow_multiple_connections_per_ip = bool_type_base, + + // if set to true, upload, download and unchoke limits + // are ignored for peers on the local network. + // This option is *DEPRECATED*, please use set_peer_class_filter() instead. +#ifndef TORRENT_NO_DEPRECATE + ignore_limits_on_local_network, +#else + deprecated1, +#endif + + // ``send_redundant_have`` controls if have messages will be sent + // to peers that already have the piece. This is typically not necessary, + // but it might be necessary for collecting statistics in some cases. + // Default is false. + send_redundant_have, + + // if this is true, outgoing bitfields will never be fuil. If the + // client is seed, a few bits will be set to 0, and later filled + // in with have messages. This is to prevent certain ISPs + // from stopping people from seeding. + lazy_bitfields, + + // ``use_dht_as_fallback`` determines how the DHT is used. If this is true, + // the DHT will only be used for torrents where all trackers in its tracker + // list has failed. Either by an explicit error message or a time out. This + // is false by default, which means the DHT is used by default regardless of + //if the trackers fail or not. + use_dht_as_fallback, + + // ``upnp_ignore_nonrouters`` indicates whether or not the UPnP implementation + // should ignore any broadcast response from a device whose address is not the + // configured router for this machine. i.e. it's a way to not talk to other + // people's routers by mistake. + upnp_ignore_nonrouters, + + // ``use_parole_mode`` specifies if parole mode should be used. Parole mode means + // that peers that participate in pieces that fail the hash check are put in a mode + // where they are only allowed to download whole pieces. If the whole piece a peer + // in parole mode fails the hash check, it is banned. If a peer participates in a + // piece that passes the hash check, it is taken out of parole mode. + use_parole_mode, + + // enable and disable caching of read blocks and + // blocks to be written to disk respsectively. + // the purpose of the read cache is partly read-ahead of requests + // but also to avoid reading blocks back from the disk multiple + // times for popular pieces. + // the write cache purpose is to hold off writing blocks to disk until + // they have been hashed, to avoid having to read them back in again. + use_read_cache, + use_write_cache, + + // this will make the disk cache never flush a write + // piece if it would cause is to have to re-read it + // once we want to calculate the piece hash + dont_flush_write_cache, + + // ``explicit_read_cache`` defaults to 0. If set to something greater than 0, the + // disk read cache will not be evicted by cache misses and will explicitly be + // controlled based on the rarity of pieces. Rare pieces are more likely to be + // cached. This would typically be used together with ``suggest_mode`` set to + // ``suggest_read_cache``. The value is the number of pieces to keep in the read + // cache. If the actual read cache can't fit as many, it will essentially be clamped. + explicit_read_cache, + + // allocate separate, contiguous, buffers for read and + // write calls. Only used where writev/readv cannot be used + // will use more RAM but may improve performance + coalesce_reads, + coalesce_writes, + + // prefer seeding torrents when determining which torrents to give + // active slots to, the default is false which gives preference to + // downloading torrents + auto_manage_prefer_seeds, + + // if ``dont_count_slow_torrents`` is true, torrents without any payload transfers are + // not subject to the ``active_seeds`` and ``active_downloads`` limits. This is intended + // to make it more likely to utilize all available bandwidth, and avoid having torrents + // that don't transfer anything block the active slots. + dont_count_slow_torrents, + + // ``close_redundant_connections`` specifies whether libtorrent should close + // connections where both ends have no utility in keeping the connection open. + // For instance if both ends have completed their downloads, there's no point + // in keeping it open. + close_redundant_connections, + + // If ``prioritize_partial_pieces`` is true, partial pieces are picked + // before pieces that are more rare. If false, rare pieces are always + // prioritized, unless the number of partial pieces is growing out of + // proportion. + prioritize_partial_pieces, + + // if set to true, the estimated TCP/IP overhead is + // drained from the rate limiters, to avoid exceeding + // the limits with the total traffic + rate_limit_ip_overhead, + + // ``announce_to_all_trackers`` controls how multi tracker torrents are + // treated. If this is set to true, all trackers in the same tier are + // announced to in parallel. If all trackers in tier 0 fails, all trackers + // in tier 1 are announced as well. If it's set to false, the behavior is as + // defined by the multi tracker specification. It defaults to false, which + // is the same behavior previous versions of libtorrent has had as well. + // + // ``announce_to_all_tiers`` also controls how multi tracker torrents are + // treated. When this is set to true, one tracker from each tier is announced + // to. This is the uTorrent behavior. This is false by default in order + // to comply with the multi-tracker specification. + announce_to_all_tiers, + announce_to_all_trackers, + + // ``prefer_udp_trackers`` is true by default. It means that trackers may + // be rearranged in a way that udp trackers are always tried before http + // trackers for the same hostname. Setting this to false means that the + // trackers' tier is respected and there's no preference of one protocol + // over another. + prefer_udp_trackers, + + // ``strict_super_seeding`` when this is set to true, a piece has to + // have been forwarded to a third peer before another one is handed out. + // This is the traditional definition of super seeding. + strict_super_seeding, + + // if this is set to true, the memory allocated for the + // disk cache will be locked in physical RAM, never to + // be swapped out. Every time a disk buffer is allocated + // and freed, there will be the extra overhead of a system call. + lock_disk_cache, + + // when set to true, all data downloaded from + // peers will be assumed to be correct, and not + // tested to match the hashes in the torrent + // this is only useful for simulation and + // testing purposes (typically combined with + // disabled_storage) + disable_hash_checks, + + // if this is true, i2p torrents are allowed + // to also get peers from other sources than + // the tracker, and connect to regular IPs, + // not providing any anonymization. This may + // be useful if the user is not interested in + // the anonymization of i2p, but still wants to + // be able to connect to i2p peers. + allow_i2p_mixed, + + // ``low_prio_disk`` determines if the disk I/O should use a normal + // or low priority policy. This defaults to true, which means that + // it's low priority by default. Other processes doing disk I/O will + // normally take priority in this mode. This is meant to improve the + // overall responsiveness of the system while downloading in the + // background. For high-performance server setups, this might not + // be desirable. + low_prio_disk, + + // ``volatile_read_cache``, if this is set to true, read cache blocks + // that are hit by peer read requests are removed from the disk cache + // to free up more space. This is useful if you don't expect the disk + // cache to create any cache hits from other peers than the one who + // triggered the cache line to be read into the cache in the first place. + volatile_read_cache, + + // ``guided_read_cache`` enables the disk cache to adjust the size + // of a cache line generated by peers to depend on the upload rate + // you are sending to that peer. The intention is to optimize the RAM + // usage of the cache, to read ahead further for peers that you're + // sending faster to. + guided_read_cache, + + // ``no_atime_storage`` this is a linux-only option and passes in the + // ``O_NOATIME`` to ``open()`` when opening files. This may lead to + // some disk performance improvements. + no_atime_storage, + + // ``incoming_starts_queued_torrents`` defaults to false. If a torrent + // has been paused by the auto managed feature in libtorrent, i.e. + // the torrent is paused and auto managed, this feature affects whether + // or not it is automatically started on an incoming connection. The + // main reason to queue torrents, is not to make them unavailable, but + // to save on the overhead of announcing to the trackers, the DHT and to + // avoid spreading one's unchoke slots too thin. If a peer managed to + // find us, even though we're no in the torrent anymore, this setting + // can make us start the torrent and serve it. + incoming_starts_queued_torrents, + + // when set to true, the downloaded counter sent to trackers + // will include the actual number of payload bytes donwnloaded + // including redundant bytes. If set to false, it will not include + // any redundany bytes + report_true_downloaded, + + // ``strict_end_game_mode`` defaults to true, and controls when a block + // may be requested twice. If this is ``true``, a block may only be requested + // twice when there's ay least one request to every piece that's left to + // download in the torrent. This may slow down progress on some pieces + // sometimes, but it may also avoid downloading a lot of redundant bytes. + // If this is ``false``, libtorrent attempts to use each peer connection + // to its max, by always requesting something, even if it means requesting + // something that has been requested from another peer already. + strict_end_game_mode, + + // if ``broadcast_lsd`` is set to true, the local peer discovery + // (or Local Service Discovery) will not only use IP multicast, but also + // broadcast its messages. This can be useful when running on networks + // that don't support multicast. Since broadcast messages might be + // expensive and disruptive on networks, only every 8th announce uses + // broadcast. + broadcast_lsd, + + // when set to true, libtorrent will try to make outgoing utp connections + // controls whether libtorrent will accept incoming connections or make + // outgoing connections of specific type. + enable_outgoing_utp, + enable_incoming_utp, + enable_outgoing_tcp, + enable_incoming_tcp, + + // ``ignore_resume_timestamps`` determines if the storage, when loading + // resume data files, should verify that the file modification time + // with the timestamps in the resume data. This defaults to false, which + // means timestamps are taken into account, and resume data is less likely + // to accepted (torrents are more likely to be fully checked when loaded). + // It might be useful to set this to true if your network is faster than your + // disk, and it would be faster to redownload potentially missed pieces than + // to go through the whole storage to look for them. + ignore_resume_timestamps, + + // ``no_recheck_incomplete_resume`` determines if the storage should check + // the whole files when resume data is incomplete or missing or whether + // it should simply assume we don't have any of the data. By default, this + // is determined by the existance of any of the files. By setting this setting + // to true, the files won't be checked, but will go straight to download + // mode. + no_recheck_incomplete_resume, + + // ``anonymous_mode`` defaults to false. When set to true, the client tries + // to hide its identity to a certain degree. The peer-ID will no longer + // include the client's fingerprint. The user-agent will be reset to an + // empty string. Trackers will only be used if they are using a proxy + // server. The listen sockets are closed, and incoming connections will + // only be accepted through a SOCKS5 or I2P proxy (if a peer proxy is set up and + // is run on the same machine as the tracker proxy). Since no incoming connections + // are accepted, NAT-PMP, UPnP, DHT and local peer discovery are all turned off + // when this setting is enabled. + // + // If you're using I2P, it might make sense to enable anonymous mode as well. + anonymous_mode, + + // specifies whether downloads from web seeds is reported to the + // tracker or not. Defaults to on + report_web_seed_downloads, + + // controls if the uTP socket manager is allowed to increase + // the socket buffer if a network interface with a large MTU is used (such as loopback + // or ethernet jumbo frames). This defaults to true and might improve uTP throughput. + // For RAM constrained systems, disabling this typically saves around 30kB in user space + // and probably around 400kB in kernel socket buffers (it adjusts the send and receive + // buffer size on the kernel socket, both for IPv4 and IPv6). + utp_dynamic_sock_buf, + + // set to true if uTP connections should be rate limited + // This option is *DEPRECATED*, please use set_peer_class_filter() instead. +#ifndef TORRENT_NO_DEPRECATE + rate_limit_utp, +#else + deprecated2, +#endif + + // if this is true, the ``&ip=`` argument in tracker requests + // (unless otherwise specified) will be set to the intermediate + // IP address if the user is double NATed. If ther user is not + // double NATed, this option does not have an affect + announce_double_nat, + + // ``seeding_outgoing_connections`` determines if seeding (and finished) torrents + // should attempt to make outgoing connections or not. By default this is true. It + // may be set to false in very specific applications where the cost of making + // outgoing connections is high, and there are no or small benefits of doing so. + // For instance, if no nodes are behind a firewall or a NAT, seeds don't need to + // make outgoing connections. + seeding_outgoing_connections, + + // when this is true, libtorrent will not attempt to make outgoing + // connections to peers whose port is < 1024. This is a safety + // precaution to avoid being part of a DDoS attack + no_connect_privileged_ports, + + // ``smooth_connects`` is true by default, which means the number of connection + // attempts per second may be limited to below the ``connection_speed``, in case + // we're close to bump up against the limit of number of connections. The intention + // of this setting is to more evenly distribute our connection attempts over time, + // instead of attempting to connectin in batches, and timing them out in batches. + smooth_connects, + + // always send user-agent in every web seed request. If false, only + // the first request per http connection will include the user agent + always_send_user_agent, + + // ``apply_ip_filter_to_trackers`` defaults to true. It determines whether the + // IP filter applies to trackers as well as peers. If this is set to false, + // trackers are exempt from the IP filter (if there is one). If no IP filter + // is set, this setting is irrelevant. + apply_ip_filter_to_trackers, + + // ``use_disk_read_ahead`` defaults to true and will attempt to optimize disk reads + // by giving the operating system heads up of disk read requests as they are queued + // in the disk job queue. + use_disk_read_ahead, + + // ``lock_files`` determines whether or not to lock files which libtorrent is downloading + // to or seeding from. This is implemented using ``fcntl(F_SETLK)`` on unix systems and + // by not passing in ``SHARE_READ`` and ``SHARE_WRITE`` on windows. This might prevent + // 3rd party processes from corrupting the files under libtorrent's feet. + lock_files, + + // ``contiguous_recv_buffer`` determines whether or not libtorrent should receive + // data from peers into a contiguous intermediate buffer, to then copy blocks into + // disk buffers from, or to make many smaller calls to ``read()``, each time passing + // in the specific buffer the data belongs in. When downloading at high rates, the latter + // may save some time copying data. When seeding at high rates, all incoming traffic + // consists of a very large number of tiny packets, and enabling ``contiguous_recv_buffer`` + // will provide higher performance. When this is enabled, it will only be used when + // seeding to peers, since that's when it provides performance improvements. + contiguous_recv_buffer, + + // when true, web seeds sending bad data will be banned + ban_web_seeds, + + // when set to false, the ``write_cache_line_size`` will apply across piece boundaries. + // this is a bad idea unless the piece picker also is configured to have an affinity + // to pick pieces belonging to the same write cache line as is configured in the + // disk cache. + allow_partial_disk_writes, + + // If true, disables any communication that's not going over a proxy. + // Enabling this requires a proxy to be configured as well, see ``set_proxy_settings``. + // The listen sockets are closed, and incoming connections will + // only be accepted through a SOCKS5 or I2P proxy (if a peer proxy is set up and + // is run on the same machine as the tracker proxy). This setting also + // disabled peer country lookups, since those are done via DNS lookups that + // aren't supported by proxies. + force_proxy, + + // if false, prevents libtorrent to advertise share-mode support + support_share_mode, + + // if this is false, don't advertise support for + // the Tribler merkle tree piece message + support_merkle_torrents, + + // if this is true, the number of redundant bytes + // is sent to the tracker + report_redundant_bytes, + + // if this is true, libtorrent will fall back to listening on a port chosen + // by the operating system (i.e. binding to port 0). If a failure is preferred, + // set this to false. + listen_system_port_fallback, + + // ``use_disk_cache_pool`` enables using a pool allocator for disk cache blocks. + // Enabling it makes the cache perform better at high throughput. + // It also makes the cache less likely and slower at returning memory back to the system, + // once allocated. + use_disk_cache_pool, + + // when this is true, and incoming encrypted connections are enabled, &supportcrypt=1 + // is included in http tracker announces + announce_crypto_support, + + // Starts and stops the UPnP service. When started, the listen port and the DHT + // port are attempted to be forwarded on local UPnP router devices. + // + // The upnp object returned by ``start_upnp()`` can be used to add and remove + // arbitrary port mappings. Mapping status is returned through the + // portmap_alert and the portmap_error_alert. The object will be valid until + // ``stop_upnp()`` is called. See upnp-and-nat-pmp_. + enable_upnp, + + // Starts and stops the NAT-PMP service. When started, the listen port and the DHT + // port are attempted to be forwarded on the router through NAT-PMP. + // + // The natpmp object returned by ``start_natpmp()`` can be used to add and remove + // arbitrary port mappings. Mapping status is returned through the + // portmap_alert and the portmap_error_alert. The object will be valid until + // ``stop_natpmp()`` is called. See upnp-and-nat-pmp_. + enable_natpmp, + + // Starts and stops Local Service Discovery. This service will broadcast + // the infohashes of all the non-private torrents on the local network to + // look for peers on the same swarm within multicast reach. + enable_lsd, + + // starts the dht node and makes the trackerless service + // available to torrents. + enable_dht, + + // if the allowed encryption level is both, setting this to + // true will prefer rc4 if both methods are offered, plaintext + // otherwise + prefer_rc4, + + // if true, hostname lookups are done via the configured proxy (if + // any). This is only supported by SOCKS5 and HTTP. + proxy_hostnames, + + // if true, peer connections are made (and accepted) over the + // configured proxy, if any. + proxy_peer_connections, + + max_bool_setting_internal, + num_bool_settings = max_bool_setting_internal - bool_type_base + }; + + enum int_types + { + // ``tracker_completion_timeout`` is the number of seconds the tracker + // connection will wait from when it sent the request until it considers the + // tracker to have timed-out. Default value is 60 seconds. + tracker_completion_timeout = int_type_base, + + // ``tracker_receive_timeout`` is the number of seconds to wait to receive + // any data from the tracker. If no data is received for this number of + // seconds, the tracker will be considered as having timed out. If a tracker + // is down, this is the kind of timeout that will occur. + tracker_receive_timeout, + + // the time to wait when sending a stopped message + // before considering a tracker to have timed out. + // this is usually shorter, to make the client quit + // faster + stop_tracker_timeout, + + // this is the maximum number of bytes in a tracker + // response. If a response size passes this number + // of bytes it will be rejected and the connection + // will be closed. On gzipped responses this size is + // measured on the uncompressed data. So, if you get + // 20 bytes of gzip response that'll expand to 2 megabytes, + // it will be interrupted before the entire response + // has been uncompressed (assuming the limit is lower + // than 2 megs). + tracker_maximum_response_length, + + // the number of seconds from a request is sent until + // it times out if no piece response is returned. + piece_timeout, + + // the number of seconds one block (16kB) is expected + // to be received within. If it's not, the block is + // requested from a different peer + request_timeout, + + // the length of the request queue given in the number + // of seconds it should take for the other end to send + // all the pieces. i.e. the actual number of requests + // depends on the download rate and this number. + request_queue_time, + + // the number of outstanding block requests a peer is + // allowed to queue up in the client. If a peer sends + // more requests than this (before the first one has + // been sent) the last request will be dropped. + // the higher this is, the faster upload speeds the + // client can get to a single peer. + max_allowed_in_request_queue, + + // ``max_out_request_queue`` is the maximum number of outstanding requests to + // send to a peer. This limit takes precedence over ``request_queue_time``. i.e. + // no matter the download speed, the number of outstanding requests will never + // exceed this limit. + max_out_request_queue, + + // if a whole piece can be downloaded in this number + // of seconds, or less, the peer_connection will prefer + // to request whole pieces at a time from this peer. + // The benefit of this is to better utilize disk caches by + // doing localized accesses and also to make it easier + // to identify bad peers if a piece fails the hash check. + whole_pieces_threshold, + + // ``peer_timeout`` is the number of seconds the peer connection should + // wait (for any activity on the peer connection) before closing it due + // to time out. This defaults to 120 seconds, since that's what's specified + // in the protocol specification. After half the time out, a keep alive message + // is sent. + peer_timeout, + + // same as peer_timeout, but only applies to url-seeds. + // this is usually set lower, because web servers are + // expected to be more reliable. + urlseed_timeout, + + // controls the pipelining size of url-seeds. i.e. the number + // of HTTP request to keep outstanding before waiting for + // the first one to complete. It's common for web servers + // to limit this to a relatively low number, like 5 + urlseed_pipeline_size, + + // time to wait until a new retry of a web seed takes place + urlseed_wait_retry, + + // sets the upper limit on the total number of files this + // session will keep open. The reason why files are + // left open at all is that some anti virus software + // hooks on every file close, and scans the file for + // viruses. deferring the closing of the files will + // be the difference between a usable system and + // a completely hogged down system. Most operating + // systems also has a limit on the total number of + // file descriptors a process may have open. It is + // usually a good idea to find this limit and set the + // number of connections and the number of files + // limits so their sum is slightly below it. + file_pool_size, + + // ``max_failcount`` is the maximum times we try to connect to a peer before + // stop connecting again. If a peer succeeds, the failcounter is reset. If + // a peer is retrieved from a peer source (other than DHT) the failcount is + // decremented by one, allowing another try. + max_failcount, + + // the number of seconds to wait to reconnect to a peer. + // this time is multiplied with the failcount. + min_reconnect_time, + + // ``peer_connect_timeout`` the number of seconds to wait after a connection + // attempt is initiated to a peer until it is considered as having timed out. + // This setting is especially important in case the number of half-open + // connections are limited, since stale half-open + // connection may delay the connection of other peers considerably. + peer_connect_timeout, + + // ``connection_speed`` is the number of connection attempts that + // are made per second. If a number < 0 is specified, it will default to + // 200 connections per second. If 0 is specified, it means don't make + // outgoing connections at all. + connection_speed, + + // if a peer is uninteresting and uninterested for longer + // than this number of seconds, it will be disconnected. + // default is 10 minutes + inactivity_timeout, + + // ``unchoke_interval`` is the number of seconds between chokes/unchokes. + // On this interval, peers are re-evaluated for being choked/unchoked. This + // is defined as 30 seconds in the protocol, and it should be significantly + // longer than what it takes for TCP to ramp up to it's max rate. + unchoke_interval, + + // ``optimistic_unchoke_interval`` is the number of seconds between + // each *optimistic* unchoke. On this timer, the currently optimistically + // unchoked peer will change. + optimistic_unchoke_interval, + + // ``num_want`` is the number of peers we want from each tracker request. It defines + // what is sent as the ``&num_want=`` parameter to the tracker. + num_want, + + // ``initial_picker_threshold`` specifies the number of pieces we need before we + // switch to rarest first picking. This defaults to 4, which means the 4 first + // pieces in any torrent are picked at random, the following pieces are picked + // in rarest first order. + initial_picker_threshold, + + // the number of allowed pieces to send to peers + // that supports the fast extensions + allowed_fast_set_size, + + // ``suggest_mode`` controls whether or not libtorrent will send out suggest + // messages to create a bias of its peers to request certain pieces. The modes + // are: + // + // * ``no_piece_suggestsions`` which is the default and will not send out suggest + // messages. + // * ``suggest_read_cache`` which will send out suggest messages for the most + // recent pieces that are in the read cache. + suggest_mode, + + // ``max_queued_disk_bytes`` is the number maximum number of bytes, to be + // written to disk, that can wait in the disk I/O thread queue. This queue + // is only for waiting for the disk I/O thread to receive the job and either + // write it to disk or insert it in the write cache. When this limit is reached, + // the peer connections will stop reading data from their sockets, until the disk + // thread catches up. Setting this too low will severly limit your download rate. + max_queued_disk_bytes, + + // the number of seconds to wait for a handshake + // response from a peer. If no response is received + // within this time, the peer is disconnected. + handshake_timeout, + + // ``send_buffer_low_watermark`` the minimum send buffer target + // size (send buffer includes bytes pending being read from disk). + // For good and snappy seeding performance, set this fairly high, to + // at least fit a few blocks. This is essentially the initial + // window size which will determine how fast we can ramp up + // the send rate + // + // if the send buffer has fewer bytes than ``send_buffer_watermark``, + // we'll read another 16kB block onto it. If set too small, + // upload rate capacity will suffer. If set too high, + // memory will be wasted. + // The actual watermark may be lower than this in case + // the upload rate is low, this is the upper limit. + // + // the current upload rate to a peer is multiplied by + // this factor to get the send buffer watermark. The + // factor is specified as a percentage. i.e. 50 -> 0.5 + // This product is clamped to the ``send_buffer_watermark`` + // setting to not exceed the max. For high speed + // upload, this should be set to a greater value than + // 100. For high capacity connections, setting this + // higher can improve upload performance and disk throughput. Setting it too + // high may waste RAM and create a bias towards read jobs over write jobs. + send_buffer_low_watermark, + send_buffer_watermark, + send_buffer_watermark_factor, + + // ``choking_algorithm`` specifies which algorithm to use to determine which peers + // to unchoke. + // + // The options for choking algorithms are: + // + // * ``fixed_slots_choker`` is the traditional choker with a fixed number of unchoke + // slots (as specified by ``session::set_max_uploads()``). + // + // * ``auto_expand_choker`` opens at least the number of slots as specified by + // ``session::set_max_uploads()`` but opens up more slots if the upload capacity + // is not saturated. This unchoker will work just like the ``fixed_slots_choker`` + // if there's no global upload rate limit set. + // + // * ``rate_based_choker`` opens up unchoke slots based on the upload rate + // achieved to peers. The more slots that are opened, the marginal upload + // rate required to open up another slot increases. + // + // * ``bittyrant_choker`` attempts to optimize download rate by finding the + // reciprocation rate of each peer individually and prefers peers that gives + // the highest *return on investment*. It still allocates all upload capacity, + // but shuffles it around to the best peers first. For this choker to be + // efficient, you need to set a global upload rate limit + // (``session::set_upload_rate_limit()``). For more information about this + // choker, see the paper_. This choker is not fully implemented nor tested. + // + // .. _paper: http://bittyrant.cs.washington.edu/#papers + // + // ``seed_choking_algorithm`` controls the seeding unchoke behavior. The available + // options are: + // + // * ``round_robin`` which round-robins the peers that are unchoked when seeding. This + // distributes the upload bandwidht uniformly and fairly. It minimizes the ability + // for a peer to download everything without redistributing it. + // + // * ``fastest_upload`` unchokes the peers we can send to the fastest. This might be + // a bit more reliable in utilizing all available capacity. + // + // * ``anti_leech`` prioritizes peers who have just started or are just about to finish + // the download. The intention is to force peers in the middle of the download to + // trade with each other. + choking_algorithm, + seed_choking_algorithm, + + // ``cache_size`` is the disk write and read cache. It is specified + // in units of 16 KiB blocks. Buffers that are part of a peer's send + // or receive buffer also count against this limit. Send and receive + // buffers will never be denied to be allocated, but they will cause + // the actual cached blocks to be flushed or evicted. If this is set + // to -1, the cache size is automatically set to the amount of + // physical RAM available in the machine divided by 8. If the amount + // of physical RAM cannot be determined, it's set to 1024 (= 16 MiB). + // + // Disk buffers are allocated using a pool allocator, the number of + // blocks that are allocated at a time when the pool needs to grow can + // be specified in ``cache_buffer_chunk_size``. Lower numbers saves + // memory at the expense of more heap allocations. If it is set to 0, + // the effective chunk size is proportional to the total cache size, + // attempting to strike a good balance between performance and memory + // usage. It defaults to 0. ``cache_expiry`` is the number of seconds + // from the last cached write to a piece in the write cache, to when + // it's forcefully flushed to disk. Default is 60 second. + cache_size, + cache_buffer_chunk_size, + cache_expiry, + + // ``explicit_cache_interval`` is the number of seconds in between + // each refresh of a part of the explicit read cache. Torrents take + // turns in refreshing and this is the time in between each torrent + // refresh. Refreshing a torrent's explicit read cache means scanning + // all pieces and picking a random set of the rarest ones. There is an + // affinity to pick pieces that are already in the cache, so that + // subsequent refreshes only swaps in pieces that are rarer than + // whatever is in + // the cache at the time. + explicit_cache_interval, + + // determines how files are opened when they're in read only mode versus + // read and write mode. The options are: + // + // * enable_os_cache + // This is the default and files are opened normally, with the OS caching + // reads and writes. + // * disable_os_cache + // This opens all files in no-cache mode. This corresponds to the + // OS not letting blocks for the files linger in the cache. This + // makes sense in order to avoid the bittorrent client to + // potentially evict all other processes' cache by simply handling + // high throughput and large files. If libtorrent's read cache is + // disabled, enabling this may reduce performance. + // + // One reason to disable caching is that it may help the operating + // system from growing its file cache indefinitely. Since some OSes + // only allow aligned files to be opened in unbuffered mode, It is + // recommended to make the largest file in a torrent the first file + // (with offset 0) or use pad files to align all files to piece + // boundries. + disk_io_write_mode, + disk_io_read_mode, + + // this is the first port to use for binding + // outgoing connections to. This is useful + // for users that have routers that + // allow QoS settings based on local port. + // when binding outgoing connections to specific + // ports, ``num_outgoing_ports`` is the size of + // the range. It should be more than a few + // + // .. warning:: setting outgoing ports will limit the ability to keep multiple + // connections to the same client, even for different torrents. It is not + // recommended to change this setting. Its main purpose is to use as an + // escape hatch for cheap routers with QoS capability but can only classify + // flows based on port numbers. + // + // It is a range instead of a single port because of the problems with + // failing to reconnect to peers if a previous socket to that peer and + // port is in ``TIME_WAIT`` state. + outgoing_port, + num_outgoing_ports, + + // ``peer_tos`` determines the TOS byte set in the IP header of every packet + // sent to peers (including web seeds). The default value for this is ``0x0`` + // (no marking). One potentially useful TOS mark is ``0x20``, this represents + // the *QBone scavenger service*. For more details, see QBSS_. + // + // .. _`QBSS`: http://qbone.internet2.edu/qbss/ + peer_tos, + + // for auto managed torrents, these are the limits + // they are subject to. If there are too many torrents + // some of the auto managed ones will be paused until + // some slots free up. + // ``active_downloads`` and ``active_seeds`` controls how many active seeding and + // downloading torrents the queuing mechanism allows. The target number of active + // torrents is ``min(active_downloads + active_seeds, active_limit)``. + // ``active_downloads`` and ``active_seeds`` are upper limits on the number of + // downloading torrents and seeding torrents respectively. Setting the value to + // -1 means unlimited. + // + // For example if there are 10 seeding torrents and 10 downloading torrents, and + // ``active_downloads`` is 4 and ``active_seeds`` is 4, there will be 4 seeds + // active and 4 downloading torrents. If the settings are ``active_downloads`` = 2 + // and ``active_seeds`` = 4, then there will be 2 downloading torrents and 4 seeding + // torrents active. Torrents that are not auto managed are not counted against these + // limits. + // + // ``active_limit`` is a hard limit on the number of active torrents. This applies even to + // slow torrents. + // + // ``active_dht_limit`` is the max number of torrents to announce to the DHT. By default + // this is set to 88, which is no more than one DHT announce every 10 seconds. + // + // ``active_tracker_limit`` is the max number of torrents to announce to their trackers. + // By default this is 360, which is no more than one announce every 5 seconds. + // + // ``active_lsd_limit`` is the max number of torrents to announce to the local network + // over the local service discovery protocol. By default this is 80, which is no more + // than one announce every 5 seconds (assuming the default announce interval of 5 minutes). + // + // You can have more torrents *active*, even though they are not announced to the DHT, + // lsd or their tracker. If some peer knows about you for any reason and tries to connect, + // it will still be accepted, unless the torrent is paused, which means it won't accept + // any connections. + // + // ``active_loaded_limit`` is the number of torrents that are allowed to be *loaded* + // at any given time. Note that a torrent can be active even though it's not loaded. + // if an unloaded torrents finds a peer that wants to access it, the torrent will be + // loaded on demand, using a user-supplied callback function. If the feature of unloading + // torrents is not enabled, this setting have no effect. If this limit is set to 0, it + // means unlimited. For more information, see dynamic-loading-of-torrent-files_. + active_downloads, + active_seeds, + active_dht_limit, + active_tracker_limit, + active_lsd_limit, + active_limit, + active_loaded_limit, + + // ``auto_manage_interval`` is the number of seconds between the torrent queue + // is updated, and rotated. + auto_manage_interval, + + // this is the limit on the time a torrent has been an active seed + // (specified in seconds) before it is considered having met the seed limit criteria. + // See queuing_. + seed_time_limit, + + // ``auto_scrape_interval`` is the number of seconds between scrapes of + // queued torrents (auto managed and paused torrents). Auto managed + // torrents that are paused, are scraped regularly in order to keep + // track of their downloader/seed ratio. This ratio is used to determine + // which torrents to seed and which to pause. + // + // ``auto_scrape_min_interval`` is the minimum number of seconds between any + // automatic scrape (regardless of torrent). In case there are a large number + // of paused auto managed torrents, this puts a limit on how often a scrape + // request is sent. + auto_scrape_interval, + auto_scrape_min_interval, + + // ``max_peerlist_size`` is the maximum number of peers in the list of + // known peers. These peers are not necessarily connected, so this number + // should be much greater than the maximum number of connected peers. + // Peers are evicted from the cache when the list grows passed 90% of + // this limit, and once the size hits the limit, peers are no longer + // added to the list. If this limit is set to 0, there is no limit on + // how many peers we'll keep in the peer list. + // + // ``max_paused_peerlist_size`` is the max peer list size used for torrents + // that are paused. This default to the same as ``max_peerlist_size``, but + // can be used to save memory for paused torrents, since it's not as + // important for them to keep a large peer list. + max_peerlist_size, + max_paused_peerlist_size, + + // this is the minimum allowed announce interval for a tracker. This + // is specified in seconds and is used as a sanity check on what is + // returned from a tracker. It mitigates hammering misconfigured trackers. + min_announce_interval, + + // this is the number of seconds a torrent is considered + // active after it was started, regardless of upload and download speed. This + // is so that newly started torrents are not considered inactive until they + // have a fair chance to start downloading. + auto_manage_startup, + + // ``seeding_piece_quota`` is the number of pieces to send to a peer, + // when seeding, before rotating in another peer to the unchoke set. + // It defaults to 3 pieces, which means that when seeding, any peer we've + // sent more than this number of pieces to will be unchoked in favour of + // a choked peer. + seeding_piece_quota, + + // ``max_sparse_regions`` is a limit of the number of *sparse regions* in + // a torrent. A sparse region is defined as a hole of pieces we have not + // yet downloaded, in between pieces that have been downloaded. This is + // used as a hack for windows vista which has a bug where you cannot + // write files with more than a certain number of sparse regions. This + // limit is not hard, it will be exceeded. Once it's exceeded, pieces + // that will maintain or decrease the number of sparse regions are + // prioritized. To disable this functionality, set this to 0. It defaults + // to 0 on all platforms except windows. + max_sparse_regions, + + // TODO: deprecate this + // ``max_rejects`` is the number of piece requests we will reject in a row + // while a peer is choked before the peer is considered abusive and is + // disconnected. + max_rejects, + + // ``recv_socket_buffer_size`` and ``send_socket_buffer_size`` specifies + // the buffer sizes set on peer sockets. 0 (which is the default) means + // the OS default (i.e. don't change the buffer sizes). The socket buffer + // sizes are changed using setsockopt() with SOL_SOCKET/SO_RCVBUF and + // SO_SNDBUFFER. + recv_socket_buffer_size, + send_socket_buffer_size, + + // ``file_checks_delay_per_block`` is the number of milliseconds to sleep + // in between disk read operations when checking torrents. This defaults + // to 0, but can be set to higher numbers to slow down the rate at which + // data is read from the disk while checking. This may be useful for + // background tasks that doesn't matter if they take a bit longer, as long + // as they leave disk I/O time for other processes. + file_checks_delay_per_block, + + // ``read_cache_line_size`` is the number of blocks to read into the read + // cache when a read cache miss occurs. Setting this to 0 is essentially + // the same thing as disabling read cache. The number of blocks read + // into the read cache is always capped by the piece boundry. + // + // When a piece in the write cache has ``write_cache_line_size`` contiguous + // blocks in it, they will be flushed. Setting this to 1 effectively + // disables the write cache. + read_cache_line_size, + write_cache_line_size, + + // ``optimistic_disk_retry`` is the number of seconds from a disk write + // errors occur on a torrent until libtorrent will take it out of the + // upload mode, to test if the error condition has been fixed. + // + // libtorrent will only do this automatically for auto managed torrents. + // + // You can explicitly take a torrent out of upload only mode using + // set_upload_mode(). + optimistic_disk_retry, + + // ``max_suggest_pieces`` is the max number of suggested piece indices received + // from a peer that's remembered. If a peer floods suggest messages, this limit + // prevents libtorrent from using too much RAM. It defaults to 10. + max_suggest_pieces, + + // ``local_service_announce_interval`` is the time between local + // network announces for a torrent. By default, when local service + // discovery is enabled a torrent announces itself every 5 minutes. + // This interval is specified in seconds. + local_service_announce_interval, + + // ``dht_announce_interval`` is the number of seconds between announcing + // torrents to the distributed hash table (DHT). + dht_announce_interval, + + // ``udp_tracker_token_expiry`` is the number of seconds libtorrent + // will keep UDP tracker connection tokens around for. This is specified + // to be 60 seconds, and defaults to that. The higher this value is, the + // fewer packets have to be sent to the UDP tracker. In order for higher + // values to work, the tracker needs to be configured to match the + // expiration time for tokens. + udp_tracker_token_expiry, + + // ``default_cache_min_age`` is the minimum number of seconds any read + // cache line is kept in the cache. This defaults to one second but + // may be greater if ``guided_read_cache`` is enabled. Having a lower + // bound on the time a cache line stays in the cache is an attempt + // to avoid swapping the same pieces in and out of the cache in case + // there is a shortage of spare cache space. + default_cache_min_age, + + // ``num_optimistic_unchoke_slots`` is the number of optimistic unchoke + // slots to use. It defaults to 0, which means automatic. Having a higher + // number of optimistic unchoke slots mean you will find the good peers + // faster but with the trade-off to use up more bandwidth. When this is + // set to 0, libtorrent opens up 20% of your allowed upload slots as + // optimistic unchoke slots. + num_optimistic_unchoke_slots, + + // ``default_est_reciprocation_rate`` is the assumed reciprocation rate + // from peers when using the BitTyrant choker. This defaults to 14 kiB/s. + // If set too high, you will over-estimate your peers and be more altruistic + // while finding the true reciprocation rate, if it's set too low, you'll + // be too stingy and waste finding the true reciprocation rate. + // + // ``increase_est_reciprocation_rate`` specifies how many percent the + // extimated reciprocation rate should be increased by each unchoke + // interval a peer is still choking us back. This defaults to 20%. + // This only applies to the BitTyrant choker. + // + // ``decrease_est_reciprocation_rate`` specifies how many percent the + // estimated reciprocation rate should be decreased by each unchoke + // interval a peer unchokes us. This default to 3%. + // This only applies to the BitTyrant choker. + default_est_reciprocation_rate, + increase_est_reciprocation_rate, + decrease_est_reciprocation_rate, + + // the max number of peers we accept from pex messages from a single peer. + // this limits the number of concurrent peers any of our peers claims to + // be connected to. If they clain to be connected to more than this, we'll + // ignore any peer that exceeds this limit + max_pex_peers, + + // ``tick_interval`` specifies the number of milliseconds between internal + // ticks. This is the frequency with which bandwidth quota is distributed to + // peers. It should not be more than one second (i.e. 1000 ms). Setting this + // to a low value (around 100) means higher resolution bandwidth quota distribution, + // setting it to a higher value saves CPU cycles. + tick_interval, + + // ``share_mode_target`` specifies the target share ratio for share mode torrents. + // This defaults to 3, meaning we'll try to upload 3 times as much as we download. + // Setting this very high, will make it very conservative and you might end up + // not downloading anything ever (and not affecting your share ratio). It does + // not make any sense to set this any lower than 2. For instance, if only 3 peers + // need to download the rarest piece, it's impossible to download a single piece + // and upload it more than 3 times. If the share_mode_target is set to more than 3, + // nothing is downloaded. + share_mode_target, + + // ``upload_rate_limit``, ``download_rate_limit``, ``local_upload_rate_limit`` + // and ``local_download_rate_limit`` sets the session-global limits of upload + // and download rate limits, in bytes per second. The local rates refer to peers + // on the local network. By default peers on the local network are not rate limited. + // + // These rate limits are only used for local peers (peers within the same subnet as + // the client itself) and it is only used when ``ignore_limits_on_local_network`` + // is set to true (which it is by default). These rate limits default to unthrottled, + // but can be useful in case you want to treat local peers preferentially, but not + // quite unthrottled. + // + // A value of 0 means unlimited. + upload_rate_limit, + download_rate_limit, +#ifndef TORRENT_NO_DEPRECATE + local_upload_rate_limit, + local_download_rate_limit, +#else + deprecated3, + deprecated4, +#endif + + // ``dht_upload_rate_limit`` sets the rate limit on the DHT. This is specified in + // bytes per second and defaults to 4000. For busy boxes with lots of torrents + // that requires more DHT traffic, this should be raised. + dht_upload_rate_limit, + + // ``unchoke_slots_limit`` is the max number of unchoked peers in the session. + // The number of unchoke slots may be ignored depending on what + // ``choking_algorithm`` is set to. + unchoke_slots_limit, + + // ``half_open_limit`` sets the maximum number of half-open connections + // libtorrent will have when connecting to peers. A half-open connection is one + // where connect() has been called, but the connection still hasn't been established + // (nor failed). Windows XP Service Pack 2 sets a default, system wide, limit of + // the number of half-open connections to 10. So, this limit can be used to work + // nicer together with other network applications on that system. The default is + // to have no limit, and passing -1 as the limit, means to have no limit. When + // limiting the number of simultaneous connection attempts, peers will be put in + // a queue waiting for their turn to get connected. + half_open_limit, + + // ``connections_limit`` sets a global limit on the number of connections + // opened. The number of connections is set to a hard minimum of at least two per + // torrent, so if you set a too low connections limit, and open too many torrents, + // the limit will not be met. + connections_limit, + + // ``connections_slack`` is the the number of incoming connections exceeding the + // connection limit to accept in order to potentially replace existing ones. + connections_slack, + + // ``utp_target_delay`` is the target delay for uTP sockets in milliseconds. A high + // value will make uTP connections more aggressive and cause longer queues in the upload + // bottleneck. It cannot be too low, since the noise in the measurements would cause + // it to send too slow. The default is 50 milliseconds. + // ``utp_gain_factor`` is the number of bytes the uTP congestion window can increase + // at the most in one RTT. This defaults to 300 bytes. If this is set too high, + // the congestion controller reacts too hard to noise and will not be stable, if it's + // set too low, it will react slow to congestion and not back off as fast. + // + // ``utp_min_timeout`` is the shortest allowed uTP socket timeout, specified in milliseconds. + // This defaults to 500 milliseconds. The timeout depends on the RTT of the connection, but + // is never smaller than this value. A connection times out when every packet in a window + // is lost, or when a packet is lost twice in a row (i.e. the resent packet is lost as well). + // + // The shorter the timeout is, the faster the connection will recover from this situation, + // assuming the RTT is low enough. + // ``utp_syn_resends`` is the number of SYN packets that are sent (and timed out) before + // giving up and closing the socket. + // ``utp_num_resends`` is the number of times a packet is sent (and lossed or timed out) + // before giving up and closing the connection. + // ``utp_connect_timeout`` is the number of milliseconds of timeout for the initial SYN + // packet for uTP connections. For each timed out packet (in a row), the timeout is doubled. + // ``utp_loss_multiplier`` controls how the congestion window is changed when a packet + // loss is experienced. It's specified as a percentage multiplier for ``cwnd``. By default + // it's set to 50 (i.e. cut in half). Do not change this value unless you know what + // you're doing. Never set it higher than 100. + utp_target_delay, + utp_gain_factor, + utp_min_timeout, + utp_syn_resends, + utp_fin_resends, + utp_num_resends, + utp_connect_timeout, +#ifndef TORRENT_NO_DEPRECATE + utp_delayed_ack, +#else + deprecated5, +#endif + utp_loss_multiplier, + + // The ``mixed_mode_algorithm`` determines how to treat TCP connections when there are + // uTP connections. Since uTP is designed to yield to TCP, there's an inherent problem + // when using swarms that have both TCP and uTP connections. If nothing is done, uTP + // connections would often be starved out for bandwidth by the TCP connections. This mode + // is ``prefer_tcp``. The ``peer_proportional`` mode simply looks at the current throughput + // and rate limits all TCP connections to their proportional share based on how many of + // the connections are TCP. This works best if uTP connections are not rate limited by + // the global rate limiter (which they aren't by default). + mixed_mode_algorithm, + + // ``listen_queue_size`` is the value passed in to listen() for the listen socket. + // It is the number of outstanding incoming connections to queue up while we're not + // actively waiting for a connection to be accepted. The default is 5 which should + // be sufficient for any normal client. If this is a high performance server which + // expects to receive a lot of connections, or used in a simulator or test, it + // might make sense to raise this number. It will not take affect until listen_on() + // is called again (or for the first time). + listen_queue_size, + + // ``torrent_connect_boost`` is the number of peers to try to connect to immediately + // when the first tracker response is received for a torrent. This is a boost to + // given to new torrents to accelerate them starting up. The normal connect scheduler + // is run once every second, this allows peers to be connected immediately instead + // of waiting for the session tick to trigger connections. + torrent_connect_boost, + + // ``alert_queue_size`` is the maximum number of alerts queued up internally. If + // alerts are not popped, the queue will eventually fill up to this level. + alert_queue_size, + + // ``max_metadata_size`` is the maximum allowed size (in bytes) to be received + // by the metadata extension, i.e. magnet links. It defaults to 1 MiB. + max_metadata_size, + + // ``hashing_threads`` is the number of threads to use for piece hash verification. It + // defaults to 1. For very high download rates, on machines with multiple cores, this + // could be incremented. Setting it higher than the number of CPU cores would presumably + // not provide any benefit of setting it to the number of cores. If it's set to 0, + // hashing is done in the disk thread. + hashing_threads, + + // the number of blocks to keep outstanding at any given time when + // checking torrents. Higher numbers give faster re-checks but uses + // more memory. Specified in number of 16 kiB blocks + checking_mem_usage, + + // if set to > 0, pieces will be announced to other peers before they + // are fully downloaded (and before they are hash checked). The intention + // is to gain 1.5 potential round trip times per downloaded piece. When + // non-zero, this indicates how many milliseconds in advance pieces + // should be announced, before they are expected to be completed. + predictive_piece_announce, + + // for some aio back-ends, ``aio_threads`` specifies the number of + // io-threads to use, and ``aio_max`` the max number of outstanding jobs. + aio_threads, + aio_max, + + // ``network_threads`` is the number of threads to use to call ``async_write_some`` + // (i.e. send) on peer connection sockets. When seeding at extremely high rates, + // this may become a bottleneck, and setting this to 2 or more may parallelize + // that cost. When using SSL torrents, all encryption for outgoing traffic is + // done withint the socket send functions, and this will help parallelizing the + // cost of SSL encryption as well. + network_threads, + + // ``ssl_listen`` sets the listen port for SSL connections. If this is set to 0, + // no SSL listen port is opened. Otherwise a socket is opened on this port. This + // setting is only taken into account when opening the regular listen port, and + // won't re-open the listen socket simply by changing this setting. + ssl_listen, + + // ``tracker_backoff`` determines how aggressively to back off from retrying + // failing trackers. This value determines *x* in the following formula, determining + // the number of seconds to wait until the next retry: + // + // delay = 5 + 5 * x / 100 * fails^2 + // + // This setting may be useful to make libtorrent more or less aggressive in hitting + // trackers. + tracker_backoff, + + // when a seeding torrent reaches eaither the share ratio + // (bytes up / bytes down) or the seed time ratio + // (seconds as seed / seconds as downloader) or the seed + // time limit (seconds as seed) it is considered + // done, and it will leave room for other torrents + // these are specified as percentages + share_ratio_limit, + seed_time_ratio_limit, + + // peer_turnover is the percentage of peers to disconnect + // every turnover peer_turnover_interval (if we're at + // the peer limit), this is specified in percent + // when we are connected to more than + // limit * peer_turnover_cutoff peers + // disconnect peer_turnover fraction + // of the peers. It is specified in percent + // peer_turnover_interval is the interval (in seconds) + // between optimistic disconnects + // if the disconnects happen and how many peers are disconnected + // is controlled by peer_turnover and peer_turnover_cutoff + peer_turnover, + peer_turnover_cutoff, + peer_turnover_interval, + + // this setting controls the priority of downloading torrents + // over seeding or finished torrents when it comes to making + // peer connections. Peer connections are throttled by the + // connection_speed and the half-open connection limit. This + // makes peer connections a limited resource. Torrents that + // still have pieces to download are prioritized by default, + // to avoid having many seeding torrents use most of the connection + // attempts and only give one peer every now and then to the + // downloading torrent. libtorrent will loop over the downloading + // torrents to connect a peer each, and every n:th connection + // attempt, a finished torrent is picked to be allowed to connect + // to a peer. This setting controls n. + connect_seed_every_n_download, + + // the max number of bytes to allow an HTTP response to be when + // announcing to trackers or downloading .torrent files via + // the ``url`` provided in ``add_torrent_params``. + max_http_recv_buffer_size, + + // if binding to a specific port fails, should the port be incremented + // by one and tried again? This setting specifies how many times to + // retry a failed port bind + max_retry_port_bind, + + // a bitmask combining flags from alert::category_t defining + // which kinds of alerts to receive + alert_mask, + + // control the settings for incoming + // and outgoing connections respectively. + // see enc_policy enum for the available options. + out_enc_policy, + in_enc_policy, + + // determines the encryption level of the + // connections. This setting will adjust which encryption scheme is + // offered to the other peer, as well as which encryption scheme is + // selected by the client. See enc_level enum for options. + allowed_enc_level, + + // the download and upload rate limits for a torrent to be considered + // active by the queuing mechanism. A torrent whose download rate is less + // than ``inactive_down_rate`` and whose upload rate is less than + // ``inactive_up_rate`` for ``auto_manage_startup`` seconds, is + // considered inactive, and another queued torrent may be startert. + // This logic is disabled if ``dont_count_slow_torrents`` is false. + inactive_down_rate, + inactive_up_rate, + + // proxy to use, defaults to none. see proxy_type_t. + proxy_type, + + // the port of the proxy server + proxy_port, + + // sets the i2p_ SAM bridge port to connect to. set the hostname with + // the ``i2p_hostname`` setting. + // + // .. _i2p: http://www.i2p2.de + i2p_port, + + max_int_setting_internal, + + num_int_settings = max_int_setting_internal - int_type_base + }; + + enum { no_piece_suggestions = 0, suggest_read_cache = 1 }; + + enum choking_algorithm_t + { + fixed_slots_choker, + auto_expand_choker, + rate_based_choker, + bittyrant_choker + }; + + enum seed_choking_algorithm_t + { + round_robin, + fastest_upload, + anti_leech + }; + + enum io_buffer_mode_t + { + enable_os_cache = 0, +#ifndef TORRENT_NO_DEPRECATE + disable_os_cache_for_aligned_files = 2, +#else + deprecated = 1, +#endif + disable_os_cache = 2 + }; + + enum bandwidth_mixed_algo_t + { + // disables the mixed mode bandwidth balancing + prefer_tcp = 0, + + // does not throttle uTP, throttles TCP to the same proportion + // of throughput as there are TCP connections + peer_proportional = 1 + }; + + // the encoding policy options for use with settings_pack::pe_out_enc_policy + // and settings_pack::pe_in_enc_policy. + enum enc_policy + { + // Only encrypted connections are allowed. Incoming connections that + // are not encrypted are closed and if the encrypted outgoing + // connection fails, a non-encrypted retry will not be made. + pe_forced, + + // encrypted connections are enabled, but non-encrypted connections + // are allowed. An incoming non-encrypted connection will be accepted, + // and if an outgoing encrypted connection fails, a non- encrypted + // connection will be tried. + pe_enabled, + + // only non-encrypted connections are allowed. + pe_disabled + }; + + // the encryption levels, to be used with settings_pack::pe_allowed_enc_level. + enum enc_level + { + // use only plaintext encryption + pe_plaintext = 1, + // use only rc4 encryption + pe_rc4 = 2, + // allow both + pe_both = 3 + }; + + enum proxy_type_t + { + // This is the default, no proxy server is used, all other fields + // are ignored. + none, + + // The server is assumed to be a `SOCKS4 server`_ that + // requires a username. + // + // .. _`SOCKS4 server`: http://www.ufasoft.com/doc/socks4_protocol.htm + socks4, + // The server is assumed to be a SOCKS5 server (`RFC 1928`_) that + // does not require any authentication. The username and password are ignored. + // + // .. _`RFC 1928`: http://www.faqs.org/rfcs/rfc1928.html + socks5, + + // The server is assumed to be a SOCKS5 server that supports + // plain text username and password authentication (`RFC 1929`_). The username + // and password specified may be sent to the proxy if it requires. + // + // .. _`RFC 1929`: http://www.faqs.org/rfcs/rfc1929.html + socks5_pw, + // The server is assumed to be an HTTP proxy. If the transport used + // for the connection is non-HTTP, the server is assumed to support the + // CONNECT_ method. i.e. for web seeds and HTTP trackers, a plain proxy will + // suffice. The proxy is assumed to not require authorization. The username + // and password will not be used. + // + // .. _CONNECT: http://tools.ietf.org/html/draft-luotonen-web-proxy-tunneling-01 + http, + // The server is assumed to be an HTTP proxy that requires + // user authorization. The username and password will be sent to the proxy. + http_pw, + // route through a i2p SAM proxy + i2p_proxy + }; + private: + + std::vector > m_strings; + std::vector > m_ints; + std::vector > m_bools; + }; +} + +#endif + diff --git a/include/libtorrent/sha1_hash.hpp b/include/libtorrent/sha1_hash.hpp index 5be33db42..1dc1f05e7 100644 --- a/include/libtorrent/sha1_hash.hpp +++ b/include/libtorrent/sha1_hash.hpp @@ -40,6 +40,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/config.hpp" #include "libtorrent/assert.hpp" +#include "libtorrent/byteswap.hpp" #if TORRENT_USE_IOSTREAM #include "libtorrent/escape_string.hpp" // to_hex, from_hex @@ -66,10 +67,10 @@ namespace libtorrent // peer IDs, node IDs etc. class TORRENT_EXPORT sha1_hash { - enum { number_size = 20 }; + enum { number_size = 5 }; public: // the number of bytes of the number - static const int size = number_size; + static const int size = number_size * sizeof(boost::uint32_t); // constructs an all-sero sha1-hash sha1_hash() { clear(); } @@ -117,13 +118,13 @@ namespace libtorrent void assign(char const* str) { std::memcpy(m_number, str, size); } // set the sha1-hash to all zeroes. - void clear() { std::memset(m_number, 0, number_size); } + void clear() { std::memset(m_number, 0, size); } // return true if the sha1-hash is all zero. bool is_all_zeros() const { - for (const unsigned char* i = m_number; i < m_number+number_size; ++i) - if (*i != 0) return false; + for (int i = 0; i < number_size; ++i) + if (m_number[i] != 0) return false; return true; } @@ -131,27 +132,37 @@ namespace libtorrent sha1_hash& operator<<=(int n) { TORRENT_ASSERT(n >= 0); - int num_bytes = n / 8; - if (num_bytes >= number_size) + int num_words = n / 32; + if (num_words >= number_size) { - std::memset(m_number, 0, number_size); + std::memset(m_number, 0, size); return *this; } - if (num_bytes > 0) + if (num_words > 0) { - std::memmove(m_number, m_number + num_bytes, number_size - num_bytes); - std::memset(m_number + number_size - num_bytes, 0, num_bytes); - n -= num_bytes * 8; + std::memmove(m_number, m_number + num_words + , (number_size - num_words) * sizeof(boost::uint32_t)); + std::memset(m_number + (number_size - num_words) + , 0, num_words * sizeof(boost::uint32_t)); + n -= num_words * 32; } if (n > 0) { + // keep in mind that the uint32_t are stored in network + // byte order, so they have to be byteswapped before + // applying the shift operations, and then byteswapped + // back again. + m_number[0] = ntohl(m_number[0]); for (int i = 0; i < number_size - 1; ++i) { m_number[i] <<= n; - m_number[i] |= m_number[i+1] >> (8 - n); + m_number[i+1] = ntohl(m_number[i+1]); + m_number[i] |= m_number[i+1] >> (32 - n); + m_number[i] = htonl(m_number[i]); } m_number[number_size-1] <<= n; + m_number[number_size-1] = htonl(m_number[number_size-1]); } return *this; } @@ -160,26 +171,36 @@ namespace libtorrent sha1_hash& operator>>=(int n) { TORRENT_ASSERT(n >= 0); - int num_bytes = n / 8; - if (num_bytes >= number_size) + int num_words = n / 32; + if (num_words >= number_size) { - std::memset(m_number, 0, number_size); + std::memset(m_number, 0, size); return *this; } - if (num_bytes > 0) + if (num_words > 0) { - std::memmove(m_number + num_bytes, m_number, number_size - num_bytes); - std::memset(m_number, 0, num_bytes); - n -= num_bytes * 8; + std::memmove(m_number + num_words + , m_number, (number_size - num_words) * sizeof(boost::uint32_t)); + std::memset(m_number, 0, num_words * sizeof(boost::uint32_t)); + n -= num_words * 32; } if (n > 0) { + // keep in mind that the uint32_t are stored in network + // byte order, so they have to be byteswapped before + // applying the shift operations, and then byteswapped + // back again. + m_number[number_size-1] = ntohl(m_number[number_size-1]); + for (int i = number_size - 1; i > 0; --i) { m_number[i] >>= n; - m_number[i] |= (m_number[i-1] << (8 - n)) & 0xff; + m_number[i-1] = ntohl(m_number[i-1]); + m_number[i] |= (m_number[i-1] << (32 - n)) & 0xffffffff; + m_number[i] = htonl(m_number[i]); } m_number[0] >>= n; + m_number[0] = htonl(m_number[0]); } return *this; } @@ -197,8 +218,10 @@ namespace libtorrent { for (int i = 0; i < number_size; ++i) { - if (m_number[i] < n.m_number[i]) return true; - if (m_number[i] > n.m_number[i]) return false; + boost::uint32_t lhs = ntohl(m_number[i]); + boost::uint32_t rhs = ntohl(n.m_number[i]); + if (lhs < rhs) return true; + if (lhs > rhs) return false; } return false; } @@ -207,7 +230,7 @@ namespace libtorrent sha1_hash operator~() { sha1_hash ret; - for (int i = 0; i< number_size; ++i) + for (int i = 0; i < number_size; ++i) ret.m_number[i] = ~m_number[i]; return ret; } @@ -223,7 +246,7 @@ namespace libtorrent // in-place bit-wise XOR with the passed in sha1_hash. sha1_hash& operator^=(sha1_hash const& n) { - for (int i = 0; i< number_size; ++i) + for (int i = 0; i < number_size; ++i) m_number[i] ^= n.m_number[i]; return *this; } @@ -239,7 +262,7 @@ namespace libtorrent // in-place bit-wise AND of the passed in sha1_hash sha1_hash& operator&=(sha1_hash const& n) { - for (int i = 0; i< number_size; ++i) + for (int i = 0; i < number_size; ++i) m_number[i] &= n.m_number[i]; return *this; } @@ -247,39 +270,55 @@ namespace libtorrent // in-place bit-wise OR of the two sha1-hash. sha1_hash& operator|=(sha1_hash const& n) { - for (int i = 0; i< number_size; ++i) + for (int i = 0; i < number_size; ++i) m_number[i] |= n.m_number[i]; return *this; } // accessors for specific bytes unsigned char& operator[](int i) - { TORRENT_ASSERT(i >= 0 && i < number_size); return m_number[i]; } + { + TORRENT_ASSERT(i >= 0 && i < size); + return reinterpret_cast(m_number)[i]; + } unsigned char const& operator[](int i) const - { TORRENT_ASSERT(i >= 0 && i < number_size); return m_number[i]; } + { + TORRENT_ASSERT(i >= 0 && i < size); + return reinterpret_cast(m_number)[i]; + } typedef const unsigned char* const_iterator; typedef unsigned char* iterator; // start and end iterators for the hash. The value type // of these iterators is ``unsigned char``. - const_iterator begin() const { return m_number; } - const_iterator end() const { return m_number+number_size; } - iterator begin() { return m_number; } - iterator end() { return m_number+number_size; } + const_iterator begin() const + { return reinterpret_cast(m_number); } + const_iterator end() const + { return reinterpret_cast(m_number) + size; } + iterator begin() + { return reinterpret_cast(m_number); } + iterator end() + { return reinterpret_cast(m_number) + size; } // return a copy of the 20 bytes representing the sha1-hash as a std::string. // It's still a binary string with 20 binary characters. std::string to_string() const - { return std::string((char const*)&m_number[0], number_size); } + { return std::string((char const*)&m_number[0], size); } private: - unsigned char m_number[number_size]; + boost::uint32_t m_number[number_size]; }; typedef sha1_hash peer_id; + inline std::size_t hash_value(sha1_hash const& b) + { + std::size_t ret; + std::memcpy(&ret, &b[0], sizeof(ret)); + return ret; + } #if TORRENT_USE_IOSTREAM diff --git a/include/libtorrent/size_type.hpp b/include/libtorrent/size_type.hpp index f4238c7d5..bd179c1eb 100644 --- a/include/libtorrent/size_type.hpp +++ b/include/libtorrent/size_type.hpp @@ -45,6 +45,7 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { + // TODO: remove these and just use boost's types directly typedef boost::int64_t size_type; typedef boost::uint64_t unsigned_size_type; } diff --git a/include/libtorrent/sliding_average.hpp b/include/libtorrent/sliding_average.hpp index 8ad00331e..90c589b1f 100644 --- a/include/libtorrent/sliding_average.hpp +++ b/include/libtorrent/sliding_average.hpp @@ -45,6 +45,8 @@ struct sliding_average void add_sample(int s) { + TORRENT_ASSERT(s >= 0); + if (s < 0) s = 0; if (m_mean == -1) { m_mean = s; @@ -89,8 +91,11 @@ struct average_accumulator int ret; if (m_num_samples == 0) ret = 0; else ret = int(m_sample_sum / m_num_samples); - m_num_samples = 0; - m_sample_sum = 0; + // in case we don't get any more samples, at least + // let the average roll over, but only be worth a + // single sample + m_num_samples = 1; + m_sample_sum = ret; return ret; } diff --git a/include/libtorrent/socket_io.hpp b/include/libtorrent/socket_io.hpp index 6b11d4b7e..3faaf3b94 100644 --- a/include/libtorrent/socket_io.hpp +++ b/include/libtorrent/socket_io.hpp @@ -47,6 +47,8 @@ namespace libtorrent TORRENT_EXTRA_EXPORT std::string print_address(address const& addr); TORRENT_EXTRA_EXPORT std::string print_endpoint(tcp::endpoint const& ep); TORRENT_EXTRA_EXPORT std::string print_endpoint(udp::endpoint const& ep); + TORRENT_EXTRA_EXPORT tcp::endpoint parse_endpoint(std::string str, error_code& ec); + TORRENT_EXTRA_EXPORT std::string address_to_bytes(address const& a); // internal TORRENT_EXPORT std::string endpoint_to_bytes(udp::endpoint const& ep); diff --git a/include/libtorrent/socks5_stream.hpp b/include/libtorrent/socks5_stream.hpp index df5f8727c..846c14f5d 100644 --- a/include/libtorrent/socks5_stream.hpp +++ b/include/libtorrent/socks5_stream.hpp @@ -33,9 +33,9 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_SOCKS5_STREAM_HPP_INCLUDED #define TORRENT_SOCKS5_STREAM_HPP_INCLUDED -#include #include #include + #include "libtorrent/proxy_base.hpp" #if defined TORRENT_ASIO_DEBUGGING #include "libtorrent/debug.hpp" @@ -113,8 +113,6 @@ public: } #endif - typedef boost::function handler_type; - //#error fix error messages to use custom error_code category //#error add async_connect() that takes a hostname and port as well template diff --git a/include/libtorrent/ssl_stream.hpp b/include/libtorrent/ssl_stream.hpp index 9644b8b17..5c475952e 100644 --- a/include/libtorrent/ssl_stream.hpp +++ b/include/libtorrent/ssl_stream.hpp @@ -33,6 +33,8 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TORRENT_SSL_STREAM_HPP_INCLUDED #define TORRENT_SSL_STREAM_HPP_INCLUDED +#ifdef TORRENT_USE_OPENSSL + #include "libtorrent/socket.hpp" #include #if BOOST_VERSION < 103500 @@ -304,5 +306,7 @@ private: } +#endif // TORRENT_USE_OPENSSL + #endif diff --git a/include/libtorrent/stat.hpp b/include/libtorrent/stat.hpp index a64b0c9b3..d18ad54c5 100644 --- a/include/libtorrent/stat.hpp +++ b/include/libtorrent/stat.hpp @@ -50,21 +50,17 @@ namespace libtorrent public: stat_channel() - : m_counter(0) + : m_total_counter(0) + , m_counter(0) , m_5_sec_average(0) - , m_30_sec_average(0) - , m_total_counter(0) {} void operator+=(stat_channel const& s) { - TORRENT_ASSERT(m_counter >= 0); TORRENT_ASSERT(m_total_counter >= 0); - TORRENT_ASSERT(s.m_counter >= 0); m_counter += s.m_counter; m_total_counter += s.m_counter; - TORRENT_ASSERT(m_counter >= 0); - TORRENT_ASSERT(m_total_counter >= 0); + TORRENT_ASSERT(m_counter >= m_counter - s.m_counter); } void add(int count) @@ -72,15 +68,16 @@ namespace libtorrent TORRENT_ASSERT(count >= 0); m_counter += count; - TORRENT_ASSERT(m_counter >= 0); + TORRENT_ASSERT(m_counter >= m_counter - count); m_total_counter += count; - TORRENT_ASSERT(m_total_counter >= 0); + TORRENT_ASSERT(m_total_counter >= m_total_counter - count); } // should be called once every second void second_tick(int tick_interval_ms); int rate() const { return m_5_sec_average; } - int low_pass_rate() const { return m_30_sec_average; } + int low_pass_rate() const { return m_5_sec_average; } + size_type total() const { return m_total_counter; } void offset(size_type c) @@ -97,23 +94,19 @@ namespace libtorrent { m_counter = 0; m_5_sec_average = 0; - m_30_sec_average = 0; m_total_counter = 0; } private: + // total counters + boost::uint64_t m_total_counter; + // the accumulator for this second. - int m_counter; + boost::uint32_t m_counter; // sliding average - int m_5_sec_average; - int m_30_sec_average; - - // TODO: this is 4 bytes of padding! - - // total counters - size_type m_total_counter; + boost::uint32_t m_5_sec_average; }; class TORRENT_EXTRA_EXPORT stat @@ -130,6 +123,8 @@ namespace libtorrent { #ifndef TORRENT_DISABLE_FULL_STATS m_stat[upload_ip_protocol].add(ipv6 ? 60 : 40); +#else + m_stat[upload_protocol].add(ipv6 ? 60 : 40); #endif } @@ -139,38 +134,49 @@ namespace libtorrent // we received SYN-ACK and also sent ACK back m_stat[download_ip_protocol].add(ipv6 ? 60 : 40); m_stat[upload_ip_protocol].add(ipv6 ? 60 : 40); +#else + m_stat[download_protocol].add(ipv6 ? 60 : 40); + m_stat[upload_protocol].add(ipv6 ? 60 : 40); #endif } void received_dht_bytes(int bytes) { -#ifndef TORRENT_DISABLE_FULL_STATS TORRENT_ASSERT(bytes >= 0); +#ifndef TORRENT_DISABLE_FULL_STATS m_stat[download_dht_protocol].add(bytes); +#else + m_stat[download_protocol].add(bytes); #endif } void sent_dht_bytes(int bytes) { -#ifndef TORRENT_DISABLE_FULL_STATS TORRENT_ASSERT(bytes >= 0); +#ifndef TORRENT_DISABLE_FULL_STATS m_stat[upload_dht_protocol].add(bytes); +#else + m_stat[upload_protocol].add(bytes); #endif } void received_tracker_bytes(int bytes) { -#ifndef TORRENT_DISABLE_FULL_STATS TORRENT_ASSERT(bytes >= 0); +#ifndef TORRENT_DISABLE_FULL_STATS m_stat[download_tracker_protocol].add(bytes); +#else + m_stat[download_protocol].add(bytes); #endif } void sent_tracker_bytes(int bytes) { -#ifndef TORRENT_DISABLE_FULL_STATS TORRENT_ASSERT(bytes >= 0); +#ifndef TORRENT_DISABLE_FULL_STATS m_stat[upload_tracker_protocol].add(bytes); +#else + m_stat[upload_protocol].add(bytes); #endif } @@ -196,7 +202,6 @@ namespace libtorrent // account for the overhead caused by it void trancieve_ip_packet(int bytes_transferred, bool ipv6) { -#ifndef TORRENT_DISABLE_FULL_STATS // one TCP/IP packet header for the packet // sent or received, and one for the ACK // The IPv4 header is 20 bytes @@ -205,8 +210,12 @@ namespace libtorrent const int mtu = 1500; const int packet_size = mtu - header; const int overhead = (std::max)(1, (bytes_transferred + packet_size - 1) / packet_size) * header; +#ifndef TORRENT_DISABLE_FULL_STATS m_stat[download_ip_protocol].add(overhead); m_stat[upload_ip_protocol].add(overhead); +#else + m_stat[download_protocol].add(overhead); + m_stat[upload_protocol].add(overhead); #endif } diff --git a/include/libtorrent/stat_cache.hpp b/include/libtorrent/stat_cache.hpp new file mode 100644 index 000000000..15d9239df --- /dev/null +++ b/include/libtorrent/stat_cache.hpp @@ -0,0 +1,82 @@ +/* + +Copyright (c) 2012-2013, Arvid Norberg, Daniel Wallin +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_STAT_CACHE_HPP +#define TORRENT_STAT_CACHE_HPP + +#include +#include +#include "libtorrent/config.hpp" +#include "libtorrent/size_type.hpp" + +namespace libtorrent +{ + struct TORRENT_EXTRA_EXPORT stat_cache + { + stat_cache(); + ~stat_cache(); + + void init(int num_files); + + enum + { + cache_error = -1, + not_in_cache = -2, + no_exist = -3, + }; + + // returns the size of the file or one + // of the enums, noent or not_in_cache + size_type get_filesize(int i) const; + time_t get_filetime(int i) const; + + void set_cache(int i, size_type size, time_t time); + void set_noexist(int i); + void set_error(int i); + void set_dirty(int i); + + void clear(); + + private: + + struct stat_cache_t + { + stat_cache_t(size_type s, time_t t = 0): file_size(s), file_time(t) {} + size_type file_size; + time_t file_time; + }; + std::vector m_stat_cache; + }; +} + +#endif // TORRENT_STAT_CACHE_HPP + diff --git a/include/libtorrent/storage.hpp b/include/libtorrent/storage.hpp index cce7d0022..6c7350c7e 100644 --- a/include/libtorrent/storage.hpp +++ b/include/libtorrent/storage.hpp @@ -41,27 +41,33 @@ POSSIBILITY OF SUCH DAMAGE. #endif #include +#include #include #include +#include #include #include +#include #ifdef _MSC_VER #pragma warning(pop) #endif -#include "libtorrent/torrent_info.hpp" #include "libtorrent/piece_picker.hpp" -#include "libtorrent/intrusive_ptr_base.hpp" #include "libtorrent/peer_request.hpp" #include "libtorrent/hasher.hpp" #include "libtorrent/config.hpp" #include "libtorrent/file.hpp" #include "libtorrent/disk_buffer_holder.hpp" #include "libtorrent/thread.hpp" +#include "libtorrent/atomic.hpp" #include "libtorrent/storage_defs.hpp" #include "libtorrent/allocator.hpp" +#include "libtorrent/file_pool.hpp" // pool_file_status +#include "libtorrent/part_file.hpp" +#include "libtorrent/stat_cache.hpp" +#include "libtorrent/lazy_entry.hpp" #include "libtorrent/bitfield.hpp" // OVERVIEW @@ -87,8 +93,7 @@ POSSIBILITY OF SUCH DAMAGE. // struct temp_storage : storage_interface // { // temp_storage(file_storage const& fs) : m_files(fs) {} -// void set_file_priority(std::vector const& prio) {} -// virtual bool initialize(bool allocate_files) { return false; } +// virtual bool initialize(storage_error& se) { return false; } // virtual bool has_any_file() { return false; } // virtual int read(char* buf, int slot, int offset, int size) // { @@ -110,11 +115,8 @@ POSSIBILITY OF SUCH DAMAGE. // virtual bool rename_file(int file, std::string const& new_name) // { assert(false); return false; } // virtual bool move_storage(std::string const& save_path) { return false; } -// virtual bool verify_resume_data(lazy_entry const& rd, error_code& error) { return false; } +// virtual bool verify_resume_data(lazy_entry const& rd, storage_error& error) { return false; } // virtual bool write_resume_data(entry& rd) const { return false; } -// virtual bool move_slot(int src_slot, int dst_slot) { assert(false); return false; } -// virtual bool swap_slots(int slot1, int slot2) { assert(false); return false; } -// virtual bool swap_slots3(int slot1, int slot2, int slot3) { assert(false); return false; } // virtual size_type physical_offset(int slot, int offset) // { return slot * m_files.piece_length() + offset; }; // virtual sha1_hash hash_for_slot(int slot, partial_hash& ph, int piece_size) @@ -140,21 +142,19 @@ POSSIBILITY OF SUCH DAMAGE. // file_storage m_files; // }; // -// storage_interface* temp_storage_constructor( -// file_storage const& fs, file_storage const* mapped -// , std::string const& path, file_pool& fp -// , std::vector const& prio) +// storage_interface* temp_storage_constructor(storage_params const& params) // { -// return new temp_storage(fs); +// return new temp_storage(*params.files); // } - namespace libtorrent { class session; struct file_pool; struct disk_io_job; struct disk_buffer_pool; - struct session_settings; + struct cache_status; + namespace aux { struct session_settings; } + struct cached_piece_entry; TORRENT_EXTRA_EXPORT std::vector > get_filesizes( file_storage const& t @@ -167,381 +167,7 @@ namespace libtorrent , bool compact_mode , std::string* error = 0); - struct TORRENT_EXTRA_EXPORT partial_hash - { - partial_hash(): offset(0) {} - // the number of bytes in the piece that has been hashed - int offset; - // the sha-1 context - hasher h; - }; - - // The storage interface is a pure virtual class that can be implemented to - // customize how and where data for a torrent is stored. The default storage - // implementation uses regular files in the filesystem, mapping the files in - // the torrent in the way one would assume a torrent is saved to disk. - // Implementing your own storage interface makes it possible to store all - // data in RAM, or in some optimized order on disk (the order the pieces are - // received for instance), or saving multifile torrents in a single file in - // order to be able to take advantage of optimized disk-I/O. - // - // It is also possible to write a thin class that uses the default storage - // but modifies some particular behavior, for instance encrypting the data - // before it's written to disk, and decrypting it when it's read again. - // - // The storage interface is based on slots, each slot is 'piece_size' number - // of bytes. All access is done by writing and reading whole or partial - // slots. One slot is one piece in the torrent, but the data in the slot - // does not necessarily correspond to the piece with the same index (in - // compact allocation mode it won't). - // - // libtorrent comes with two built-in storage implementations; - // ``default_storage`` and ``disabled_storage``. Their constructor functions - // are called default_storage_constructor() and - // ``disabled_storage_constructor`` respectively. The disabled storage does - // just what it sounds like. It throws away data that's written, and it - // reads garbage. It's useful mostly for benchmarking and profiling purpose. - // - struct TORRENT_EXPORT storage_interface - { - // hidden - storage_interface(): m_disk_pool(0), m_settings(0) {} - - - // This function is called when the storage is to be initialized. The - // default storage will create directories and empty files at this point. - // If ``allocate_files`` is true, it will also ``ftruncate`` all files to - // their target size. - // - // Returning ``true`` indicates an error occurred. - virtual bool initialize(bool allocate_files) = 0; - - // This function is called when first checking (or re-checking) the - // storage for a torrent. It should return true if any of the files that - // is used in this storage exists on disk. If so, the storage will be - // checked for existing pieces before starting the download. - virtual bool has_any_file() = 0; - - - // change the priorities of files. - virtual void set_file_priority(std::vector const& prio) = 0; - - // These functions should read or write the data in or to the given - // ``slot`` at the given ``offset``. It should read or write ``num_bufs`` - // buffers sequentially, where the size of each buffer is specified in - // the buffer array ``bufs``. The file::iovec_t type has the following - // members:: - // - // struct iovec_t { void* iov_base; size_t iov_len; }; - // - // The return value is the number of bytes actually read or written, or - // -1 on failure. If it returns -1, the error code is expected to be set - // to - // - // Every buffer in ``bufs`` can be assumed to be page aligned and be of a - // page aligned size, except for the last buffer of the torrent. The - // allocated buffer can be assumed to fit a fully page aligned number of - // bytes though. This is useful when reading and writing the last piece - // of a file in unbuffered mode. - // - // The ``offset`` is aligned to 16 kiB boundries *most of the time*, but - // there are rare exceptions when it's not. Specifically if the read - // cache is disabled/or full and a client requests unaligned data, or the - // file itself is not aligned in the torrent. Most clients request - // aligned data. - virtual int readv(file::iovec_t const* bufs, int slot, int offset, int num_bufs, int flags = file::random_access); - virtual int writev(file::iovec_t const* bufs, int slot, int offset, int num_bufs, int flags = file::random_access); - - // This function is called when a read job is queued. It gives the - // storage wrapper an opportunity to hint the operating system about this - // coming read. For instance, the storage may call - // ``posix_fadvise(POSIX_FADV_WILLNEED)`` or ``fcntl(F_RDADVISE)``. - virtual void hint_read(int, int, int) {} - - // negative return value indicates an error - virtual int read(char* buf, int slot, int offset, int size) = 0; - - // negative return value indicates an error - virtual int write(const char* buf, int slot, int offset, int size) = 0; - - // returns the offset on the physical storage medium for the - // byte at offset ``offset`` in slot ``slot``. - virtual size_type physical_offset(int slot, int offset) = 0; - - // This function is optional. It is supposed to return the first piece, - // starting at ``start`` that is fully contained within a data-region on - // disk (i.e. non-sparse region). The purpose of this is to skip parts of - // files that can be known to contain zeros when checking files. - virtual int sparse_end(int start) const { return start; } - - // This function should move all the files belonging to the storage to - // the new save_path. The default storage moves the single file or the - // directory of the torrent. - // - // Before moving the files, any open file handles may have to be closed, - // like ``release_files()``. - // - // returns one of: - // | no_error = 0 - // | need_full_check = -1 - // | fatal_disk_error = -2 - // | file_exist = -4 - virtual int move_storage(std::string const& save_path, int flags) = 0; - - // This function should verify the resume data ``rd`` with the files - // on disk. If the resume data seems to be up-to-date, return true. If - // not, set ``error`` to a description of what mismatched and return false. - // - // The default storage may compare file sizes and time stamps of the files. - // - // Returning ``false`` indicates an error occurred. - virtual bool verify_resume_data(lazy_entry const& rd, error_code& error) = 0; - - // This function should fill in resume data, the current state of the - // storage, in ``rd``. The default storage adds file timestamps and - // sizes. - // - // Returning ``true`` indicates an error occurred. - virtual bool write_resume_data(entry& rd) const = 0; - - // This function should copy or move the data in slot ``src_slot`` to - // the slot ``dst_slot``. This is only used in compact mode. - // - // If the storage caches slots, this could be implemented more - // efficient than reading and writing the data. - // - // Returning ``true`` indicates an error occurred. - virtual bool move_slot(int src_slot, int dst_slot) = 0; - - // This function should swap the data in ``slot1`` and ``slot2``. The - // default storage uses a scratch buffer to read the data into, then - // moving the other slot and finally writing back the temporary slot's - // data - // - // This is only used in compact mode. - // - // Returning ``true`` indicates an error occurred. - virtual bool swap_slots(int slot1, int slot2) = 0; - - // This function should do a 3-way swap, or shift of the slots. ``slot1`` - // should move to ``slot2``, which should be moved to ``slot3`` which in - // turn should be moved to ``slot1``. - // - // This is only used in compact mode. - // - // Returning ``true`` indicates an error occurred. - virtual bool swap_slots3(int slot1, int slot2, int slot3) = 0; - - // This function should release all the file handles that it keeps open to files - // belonging to this storage. The default implementation just calls - // ``file_pool::release_files(this)``. - // - // Returning ``true`` indicates an error occurred. - virtual bool release_files() = 0; - - // Rename file with index ``file`` to the thame ``new_name``. If there is an error, - // ``true`` should be returned. - virtual bool rename_file(int index, std::string const& new_filename) = 0; - - // This function should delete all files and directories belonging to - // this storage. - // - // Returning ``true`` indicates an error occurred. - // - // The ``disk_buffer_pool`` is used to allocate and free disk buffers. It - // has the following members:: - // - // struct disk_buffer_pool : boost::noncopyable - // { - // char* allocate_buffer(char const* category); - // void free_buffer(char* buf); - // - // char* allocate_buffers(int blocks, char const* category); - // void free_buffers(char* buf, int blocks); - // - // int block_size() const { return m_block_size; } - // - // void release_memory(); - // }; - virtual bool delete_files() = 0; - -#ifndef TORRENT_NO_DEPRECATE - // This function is called each time a file is completely downloaded. The - // storage implementation can perform last operations on a file. The file - // will not be opened for writing after this. - // - // ``index`` is the index of the file that completed. - // - // On windows the default storage implementation clears the sparse file - // flag on the specified file. - virtual void finalize_file(int) {} -#endif - - // access global disk_buffer_pool, for allocating and freeing disk buffers - disk_buffer_pool* disk_pool() { return m_disk_pool; } - - // access global session_settings - session_settings const& settings() const { return *m_settings; } - - // called by the storage implementation to set it into an - // error state. Typically whenever a critical file operation - // fails. - void set_error(std::string const& file, error_code const& ec) const; - - // returns the currently set error code and file path associated with it, - // if set. - error_code const& error() const { return m_error; } - std::string const& error_file() const { return m_error_file; } - - // reset the error state to allow continuing reading and writing - // to the storage - virtual void clear_error() { m_error = error_code(); m_error_file.resize(0); } - - // hidden - mutable error_code m_error; - mutable std::string m_error_file; - - // hidden - virtual ~storage_interface() {} - - // hidden - disk_buffer_pool* m_disk_pool; - session_settings* m_settings; - }; - - // The default implementation of storage_interface. Behaves as a normal - // bittorrent client. It is possible to derive from this class in order to - // override some of its behavior, when implementing a custom storage. - class TORRENT_EXPORT default_storage : public storage_interface, boost::noncopyable - { - public: - // constructs the default_storage based on the give file_storage (fs). - // ``mapped`` is an optional argument (it may be NULL). If non-NULL it - // represents the file mappsing that have been made to the torrent before - // adding it. That's where files are supposed to be saved and looked for - // on disk. ``save_path`` is the root save folder for this torrent. - // ``file_pool`` is the cache of file handles that the storage will use. - // All files it opens will ask the file_pool to open them. ``file_prio`` - // is a vector indicating the priority of files on startup. It may be - // an empty vector. Any file whose index is not represented by the vector - // (because the vector is too short) are assumed to have priority 1. - // this is used to treat files with priority 0 slightly differently. - default_storage(file_storage const& fs, file_storage const* mapped - , std::string const& path, file_pool& fp - , std::vector const& file_prio); - - // hidden - ~default_storage(); - - void set_file_priority(std::vector const& prio); -#ifndef TORRENT_NO_DEPRECATE - void finalize_file(int file); -#endif - bool has_any_file(); - bool rename_file(int index, std::string const& new_filename); - bool release_files(); - bool delete_files(); - bool initialize(bool allocate_files); - int move_storage(std::string const& save_path, int flags); - int read(char* buf, int slot, int offset, int size); - int write(char const* buf, int slot, int offset, int size); - int sparse_end(int start) const; - void hint_read(int slot, int offset, int len); - int readv(file::iovec_t const* bufs, int slot, int offset, int num_bufs, int flags = file::random_access); - int writev(file::iovec_t const* buf, int slot, int offset, int num_bufs, int flags = file::random_access); - size_type physical_offset(int slot, int offset); - bool move_slot(int src_slot, int dst_slot); - bool swap_slots(int slot1, int slot2); - bool swap_slots3(int slot1, int slot2, int slot3); - bool verify_resume_data(lazy_entry const& rd, error_code& error); - bool write_resume_data(entry& rd) const; - - // if the files in this storage are mapped, returns the mapped - // file_storage, otherwise returns the original file_storage object. - file_storage const& files() const { return m_mapped_files?*m_mapped_files:m_files; } - - private: - - // this identifies a read or write operation - // so that default_storage::readwritev() knows what to - // do when it's actually touching the file - struct fileop - { - size_type (file::*regular_op)(size_type file_offset - , file::iovec_t const* bufs, int num_bufs, error_code& ec); - size_type (default_storage::*unaligned_op)(boost::intrusive_ptr const& f - , size_type file_offset, file::iovec_t const* bufs, int num_bufs - , error_code& ec); - int cache_setting; - int mode; - }; - - void delete_one_file(std::string const& p); - int readwritev(file::iovec_t const* bufs, int slot, int offset - , int num_bufs, fileop const&); - - size_type read_unaligned(boost::intrusive_ptr const& file_handle - , size_type file_offset, file::iovec_t const* bufs, int num_bufs, error_code& ec); - size_type write_unaligned(boost::intrusive_ptr const& file_handle - , size_type file_offset, file::iovec_t const* bufs, int num_bufs, error_code& ec); - - boost::scoped_ptr m_mapped_files; - file_storage const& m_files; - - // helper function to open a file in the file pool with the right mode - boost::intrusive_ptr open_file(int file, int mode - , error_code& ec) const; - - std::vector m_file_priority; - std::string m_save_path; - // the file pool is typically stored in - // the session, to make all storage - // instances use the same pool - file_pool& m_pool; - - // this is a bitfield with one bit per file. A bit being set means - // we've written to that file previously. If we do write to a file - // whose bit is 0, we set the file size, to make the file allocated - // on disk (in full allocation mode) and just sparsely allocated in - // case of sparse allocation mode - bitfield m_file_created; - - int m_page_size; - bool m_allocate_files; - }; - - // this storage implementation does not write anything to disk - // and it pretends to read, and just leaves garbage in the buffers - // this is useful when simulating many clients on the same machine - // or when running stress tests and want to take the cost of the - // disk I/O out of the picture. This cannot be used for any kind - // of normal bittorrent operation, since it will just send garbage - // to peers and throw away all the data it downloads. It would end - // up being banned immediately - class disabled_storage : public storage_interface, boost::noncopyable - { - public: - disabled_storage(int piece_size) : m_piece_size(piece_size) {} - void set_file_priority(std::vector const&) {} - bool has_any_file() { return false; } - bool rename_file(int, std::string const&) { return false; } - bool release_files() { return false; } - bool delete_files() { return false; } - bool initialize(bool) { return false; } - int move_storage(std::string const&, int) { return 0; } - int read(char*, int, int, int size) { return size; } - int write(char const*, int, int, int size) { return size; } - size_type physical_offset(int, int) { return 0; } - int readv(file::iovec_t const* bufs, int slot, int offset, int num_bufs, int flags = file::random_access); - int writev(file::iovec_t const* bufs, int slot, int offset, int num_bufs, int flags = file::random_access); - bool move_slot(int, int) { return false; } - bool swap_slots(int, int) { return false; } - bool swap_slots3(int, int, int) { return false; } - bool verify_resume_data(lazy_entry const&, error_code&) { return false; } - bool write_resume_data(entry&) const { return false; } - - int m_piece_size; - }; + TORRENT_EXTRA_EXPORT int bufs_size(file::iovec_t const* bufs, int num_bufs); // flags for async_move_storage enum move_flags_t @@ -564,270 +190,505 @@ namespace libtorrent dont_replace }; + // The storage interface is a pure virtual class that can be implemented to + // customize how and where data for a torrent is stored. The default storage + // implementation uses regular files in the filesystem, mapping the files in + // the torrent in the way one would assume a torrent is saved to disk. + // Implementing your own storage interface makes it possible to store all + // data in RAM, or in some optimized order on disk (the order the pieces are + // received for instance), or saving multifile torrents in a single file in + // order to be able to take advantage of optimized disk-I/O. + // + // It is also possible to write a thin class that uses the default storage + // but modifies some particular behavior, for instance encrypting the data + // before it's written to disk, and decrypting it when it's read again. + // + // The storage interface is based on slots, each slot is 'piece_size' number + // of bytes. All access is done by writing and reading whole or partial + // slots. One slot is one piece in the torrent. + // + // libtorrent comes with two built-in storage implementations; + // ``default_storage`` and ``disabled_storage``. Their constructor functions + // are called default_storage_constructor() and + // ``disabled_storage_constructor`` respectively. The disabled storage does + // just what it sounds like. It throws away data that's written, and it + // reads garbage. It's useful mostly for benchmarking and profiling purpose. + // + struct TORRENT_EXPORT storage_interface + { + // hidden + storage_interface(): m_settings(0) {} + + + // This function is called when the storage is to be initialized. The + // default storage will create directories and empty files at this point. + // If ``allocate_files`` is true, it will also ``ftruncate`` all files to + // their target size. + // + // If an error occurs, ``storage_error`` should be set to reflect it. + virtual void initialize(storage_error& ec) = 0; + + // These functions should read and write the data in or to the given + // ``piece`` at the given ``offset``. It should read or write + // ``num_bufs`` buffers sequentially, where the size of each buffer is + // specified in the buffer array ``bufs``. The file::iovec_t type has the + // following members:: + // + // struct iovec_t { void* iov_base; size_t iov_len; }; + // + // These functions may be called simultaneously from multiple threads. + // Make sure they are thread safe. The ``file`` in libtorrent is thread + // safe when it can fall back to ``pread``, ``preadv`` or the windows + // equivalents. On targets where read operations cannot be thread safe + // (i.e one has to seek first and then read), only one disk thread is + // used. + // + // Every buffer in ``bufs`` can be assumed to be page aligned and be of a + // page aligned size, except for the last buffer of the torrent. The + // allocated buffer can be assumed to fit a fully page aligned number of + // bytes though. This is useful when reading and writing the last piece + // of a file in unbuffered mode. + // + // The ``offset`` is aligned to 16 kiB boundries *most of the time*, but + // there are rare exceptions when it's not. Specifically if the read + // cache is disabled/or full and a peer requests unaligned data. Most + // clients request aligned data. + // + // The number of bytes read or written should be returned, or -1 on + // error. If there's an error, the ``storage_error`` must be filled out + // to represent the error that occurred. + virtual int readv(file::iovec_t const* bufs, int num_bufs + , int piece, int offset, int flags, storage_error& ec) = 0; + virtual int writev(file::iovec_t const* bufs, int num_bufs + , int piece, int offset, int flags, storage_error& ec) = 0; + + // This function is called when first checking (or re-checking) the + // storage for a torrent. It should return true if any of the files that + // is used in this storage exists on disk. If so, the storage will be + // checked for existing pieces before starting the download. + // + // If an error occurs, ``storage_error`` should be set to reflect it. + virtual bool has_any_file(storage_error& ec) = 0; + + // change the priorities of files. This is a fenced job and is + // guaranteed to be the only running function on this storage + // when called + virtual void set_file_priority(std::vector const& prio + , storage_error& ec) = 0; + + // This function should move all the files belonging to the storage to + // the new save_path. The default storage moves the single file or the + // directory of the torrent. + // + // Before moving the files, any open file handles may have to be closed, + // like ``release_files()``. + // + //If an error occurs, ``storage_error`` should be set to reflect it. + // + // returns one of: + // | no_error = 0 + // | need_full_check = -1 + // | fatal_disk_error = -2 + // | file_exist = -4 + virtual int move_storage(std::string const& save_path, int flags + , storage_error& ec) = 0; + + // This function should verify the resume data ``rd`` with the files + // on disk. If the resume data seems to be up-to-date, return true. If + // not, set ``error`` to a description of what mismatched and return false. + // + // The default storage may compare file sizes and time stamps of the files. + // + // If an error occurs, ``storage_error`` should be set to reflect it. + // + // This function should verify the resume data ``rd`` with the files + // on disk. If the resume data seems to be up-to-date, return true. If + // not, set ``error`` to a description of what mismatched and return false. + virtual bool verify_resume_data(lazy_entry const& rd, storage_error& ec) = 0; + + // This function should fill in resume data, the current state of the + // storage, in ``rd``. The default storage adds file timestamps and + // sizes. + // + // Returning ``true`` indicates an error occurred. + // + // If an error occurs, ``storage_error`` should be set to reflect it. + // + virtual void write_resume_data(entry& rd, storage_error& ec) const = 0; + + // This function should release all the file handles that it keeps open + // to files belonging to this storage. The default implementation just + // calls file_pool::release_files(). + // + // If an error occurs, ``storage_error`` should be set to reflect it. + // + virtual void release_files(storage_error& ec) = 0; + + // Rename file with index ``file`` to the thame ``new_name``. + // + // If an error occurs, ``storage_error`` should be set to reflect it. + // + virtual void rename_file(int index, std::string const& new_filenamem + , storage_error& ec) = 0; + + // This function should delete all files and directories belonging to + // this storage. + // + // If an error occurs, ``storage_error`` should be set to reflect it. + // + // The ``disk_buffer_pool`` is used to allocate and free disk buffers. It + // has the following members:: + // + // struct disk_buffer_pool : boost::noncopyable + // { + // char* allocate_buffer(char const* category); + // void free_buffer(char* buf); + // + // char* allocate_buffers(int blocks, char const* category); + // void free_buffers(char* buf, int blocks); + // + // int block_size() const { return m_block_size; } + // + // void release_memory(); + // }; + // + virtual void delete_files(storage_error& ec) = 0; + +#ifndef TORRENT_NO_DEPRECATE + // This function is called each time a file is completely downloaded. The + // storage implementation can perform last operations on a file. The file + // will not be opened for writing after this. + // + // ``index`` is the index of the file that completed. + // + // On windows the default storage implementation clears the sparse file + // flag on the specified file. + // + // If an error occurs, ``storage_error`` should be set to reflect it. + // + virtual void finalize_file(int, storage_error&) {} +#endif + + // called periodically (useful for deferred flushing). When returning + // false, it means no more ticks are necessary. Any disk job submitted + // will re-enable ticking. The default will always turn ticking back + // off again. + virtual bool tick() { return false; } + + // access global session_settings + aux::session_settings const& settings() const { return *m_settings; } + + // hidden + virtual ~storage_interface() {} + + // initialized in disk_io_thread::perform_async_job + aux::session_settings* m_settings; + }; + + // The default implementation of storage_interface. Behaves as a normal + // bittorrent client. It is possible to derive from this class in order to + // override some of its behavior, when implementing a custom storage. + class TORRENT_EXPORT default_storage : public storage_interface, boost::noncopyable + { + public: + // constructs the default_storage based on the give file_storage (fs). + // ``mapped`` is an optional argument (it may be NULL). If non-NULL it + // represents the file mappsing that have been made to the torrent before + // adding it. That's where files are supposed to be saved and looked for + // on disk. ``save_path`` is the root save folder for this torrent. + // ``file_pool`` is the cache of file handles that the storage will use. + // All files it opens will ask the file_pool to open them. ``file_prio`` + // is a vector indicating the priority of files on startup. It may be + // an empty vector. Any file whose index is not represented by the vector + // (because the vector is too short) are assumed to have priority 1. + // this is used to treat files with priority 0 slightly differently. + default_storage(storage_params const& params); + + // hidden + ~default_storage(); + + void set_file_priority(std::vector const& prio); +#ifndef TORRENT_NO_DEPRECATE + void finalize_file(int file, storage_error& ec); +#endif + bool has_any_file(storage_error& ec); + void set_file_priority(std::vector const& prio, storage_error& ec); + void rename_file(int index, std::string const& new_filename, storage_error& ec); + void release_files(storage_error& ec); + void delete_files(storage_error& ec); + void initialize(storage_error& ec); + int move_storage(std::string const& save_path, int flags, storage_error& ec); + int sparse_end(int start) const; + bool verify_resume_data(lazy_entry const& rd, storage_error& error); + void write_resume_data(entry& rd, storage_error& ec) const; + bool tick(); + + int readv(file::iovec_t const* bufs, int num_bufs + , int piece, int offset, int flags, storage_error& ec); + int writev(file::iovec_t const* bufs, int num_bufs + , int piece, int offset, int flags, storage_error& ec); + + // if the files in this storage are mapped, returns the mapped + // file_storage, otherwise returns the original file_storage object. + file_storage const& files() const { return m_mapped_files?*m_mapped_files:m_files; } + + // we need access to these for logging purposes +#if !defined TORRENT_VERBOSE_LOGGING && !defined TORRENT_LOGGING && !defined TORRENT_ERROR_LOGGING + private: +#endif + + // this identifies a read or write operation + // so that default_storage::readwritev() knows what to + // do when it's actually touching the file + struct fileop + { + // file operation + size_type (file::*op)(size_type file_offset + , file::iovec_t const* bufs, int num_bufs, error_code& ec, int flags); + // file open mode (file::read_only, file::write_only etc.) + // this is used to open the file, but also passed along as the + // flags argument to the file operation (readv or writev) + int mode; + // used for error reporting + int operation_type; + }; + + void delete_one_file(std::string const& p, error_code& ec); + int readwritev(file::iovec_t const* bufs, int slot, int offset + , int num_bufs, fileop const& op, storage_error& ec); + + void need_partfile(); + + boost::scoped_ptr m_mapped_files; + file_storage const& m_files; + + // in order to avoid calling stat() on each file multiple times + // during startup, cache the results in here, and clear it all + // out once the torrent starts (to avoid getting stale results) + // each slot represents the size and timestamp of the file + mutable stat_cache m_stat_cache; + + // helper function to open a file in the file pool with the right mode + file_handle open_file(int file, int mode + , error_code& ec) const; + + std::vector m_file_priority; + std::string m_save_path; + std::string m_part_file_name; + // the file pool is typically stored in + // the session, to make all storage + // instances use the same pool + file_pool& m_pool; + + // used for skipped files + boost::scoped_ptr m_part_file; + + // this is a bitfield with one bit per file. A bit being set means + // we've written to that file previously. If we do write to a file + // whose bit is 0, we set the file size, to make the file allocated + // on disk (in full allocation mode) and just sparsely allocated in + // case of sparse allocation mode + bitfield m_file_created; + + bool m_allocate_files; + }; + + // this storage implementation does not write anything to disk + // and it pretends to read, and just leaves garbage in the buffers + // this is useful when simulating many clients on the same machine + // or when running stress tests and want to take the cost of the + // disk I/O out of the picture. This cannot be used for any kind + // of normal bittorrent operation, since it will just send garbage + // to peers and throw away all the data it downloads. It would end + // up being banned immediately + class disabled_storage : public storage_interface, boost::noncopyable + { + public: + disabled_storage(int piece_size) : m_piece_size(piece_size) {} + bool has_any_file(storage_error&) { return false; } + void set_file_priority(std::vector const&, storage_error&) {} + void rename_file(int, std::string const&, storage_error&) {} + void release_files(storage_error&) {} + void delete_files(storage_error&) {} + void initialize(storage_error&) {} + int move_storage(std::string const&, int flags, storage_error&) { return 0; } + + int readv(file::iovec_t const* bufs, int num_bufs, int piece + , int offset, int flags, storage_error& ec); + int writev(file::iovec_t const* bufs, int num_bufs, int piece + , int offset, int flags, storage_error& ec); + + bool verify_resume_data(lazy_entry const& rd, storage_error& error) { return false; } + void write_resume_data(entry& rd, storage_error& ec) const {} + + int m_piece_size; + }; + + // this storage implementation always reads zeroes, and always discards + // anything written to it + struct zero_storage : storage_interface + { + virtual void initialize(storage_error& ec) {} + + virtual int readv(file::iovec_t const* bufs, int num_bufs + , int piece, int offset, int flags, storage_error& ec); + virtual int writev(file::iovec_t const* bufs, int num_bufs + , int piece, int offset, int flags, storage_error& ec); + + virtual bool has_any_file(storage_error&) { return false; } + virtual void set_file_priority(std::vector const& /* prio */ + , storage_error&) {} + virtual int move_storage(std::string const& /* save_path */ + , int /* flags */, storage_error&) { return 0; } + virtual bool verify_resume_data(lazy_entry const& /* rd */, storage_error&) + { return false; } + virtual void write_resume_data(entry&, storage_error&) const {} + virtual void release_files(storage_error& ec) {} + virtual void rename_file(int /* index */ + , std::string const& /* new_filenamem */, storage_error&) {} + virtual void delete_files(storage_error&) {} + }; + struct disk_io_thread; + // implements the disk I/O job fence used by the piece_manager + // to provide to the disk thread. Whenever a disk job needs + // exclusive access to the storage for that torrent, it raises + // the fence, blocking all new jobs, until there are no longer + // any outstanding jobs on the torrent, then the fence is lowered + // and it can be performed, along with the backlog of jobs that + // accrued while the fence was up + struct TORRENT_EXTRA_EXPORT disk_job_fence + { + disk_job_fence(); + ~disk_job_fence() + { + TORRENT_ASSERT(int(m_outstanding_jobs) == 0); + TORRENT_ASSERT(m_blocked_jobs.size() == 0); + } + + // returns one of the fence_* enums. + // if there are no outstanding jobs on the + // storage, fence_post_fence is returned, the flush job is expected + // to be discarded by the caller. + // fence_post_flush is returned if the fence job was blocked and queued, + // but the flush job should be posted (i.e. put on the job queue) + // fence_post_none if both the fence and the flush jobs were queued. + enum { fence_post_fence = 0, fence_post_flush = 1, fence_post_none = 2 }; + int raise_fence(disk_io_job* fence_job, disk_io_job* flush_job, atomic_count* blocked_counter); + bool has_fence() const; + + // called whenever a job completes and is posted back to the + // main network thread. the tailqueue of jobs will have the + // backed-up jobs prepended to it in case this resulted in the + // fence being lowered. + int job_complete(disk_io_job* j, tailqueue& job_queue); + int num_outstanding_jobs() const { return m_outstanding_jobs; } + + // if there is a fence up, returns true and adds the job + // to the queue of blocked jobs + bool is_blocked(disk_io_job* j); + + // the number of blocked jobs + int num_blocked() const; + + private: + // when > 0, this storage is blocked for new async + // operations until all outstanding jobs have completed. + // at that point, the m_blocked_jobs are issued + // the count is the number of fence job currently in the queue + int m_has_fence; + + // when there's a fence up, jobs are queued up in here + // until the fence is lowered + tailqueue m_blocked_jobs; + + // the number of disk_io_job objects there are, belonging + // to this torrent, currently pending, hanging off of + // cached_piece_entry objects. This is used to determine + // when the fence can be lowered + atomic_count m_outstanding_jobs; + + // must be held when accessing m_has_fence and + // m_blocked_jobs + mutable mutex m_mutex; + }; + + // this class keeps track of which pieces, belonging to + // a specific storage, are in the cache right now. It's + // used for quickly being able to evict all pieces for a + // specific torrent + struct TORRENT_EXTRA_EXPORT storage_piece_set + { + void add_piece(cached_piece_entry* p); + void remove_piece(cached_piece_entry* p); + bool has_piece(cached_piece_entry* p) const; + int num_pieces() const { return m_cached_pieces.size(); } + boost::unordered_set const& cached_pieces() const + { return m_cached_pieces; } + private: + // these are cached pieces belonging to this storage + boost::unordered_set m_cached_pieces; + }; + class TORRENT_EXTRA_EXPORT piece_manager - : public intrusive_ptr_base + : public boost::enable_shared_from_this + , public disk_job_fence + , public storage_piece_set , boost::noncopyable { - friend class invariant_access; friend struct disk_io_thread; public: piece_manager( - boost::shared_ptr const& torrent - , boost::intrusive_ptr info - , std::string const& path - , file_pool& fp - , disk_io_thread& io - , storage_constructor_type sc - , storage_mode_t sm - , std::vector const& file_prio); + storage_interface* storage_impl + , boost::shared_ptr const& torrent + , file_storage* files); ~piece_manager(); - boost::intrusive_ptr info() const { return m_info; } - void write_resume_data(entry& rd) const; - - void async_check_fastresume(lazy_entry const* resume_data - , boost::function const& handler); - - void async_check_files(boost::function const& handler); - - void async_rename_file(int index, std::string const& name - , boost::function const& handler); - - void async_read( - peer_request const& r - , boost::function const& handler - , int cache_line_size = 0 - , int cache_expiry = 0); - - void async_read_and_hash( - peer_request const& r - , boost::function const& handler - , int cache_expiry = 0); - - void async_cache(int piece - , boost::function const& handler - , int cache_expiry = 0); - - // returns the write queue size - int async_write( - peer_request const& r - , disk_buffer_holder& buffer - , boost::function const& f); - - void async_hash(int piece, boost::function const& f); - - void async_release_files( - boost::function const& handler - = boost::function()); - - void abort_disk_io(); - - void async_clear_read_cache( - boost::function const& handler - = boost::function()); - - void async_delete_files( - boost::function const& handler - = boost::function()); - - void async_move_storage(std::string const& p, int flags - , boost::function const& handler); - - void async_set_file_priority( - std::vector const& prios - , boost::function const& handler); - - void async_save_resume_data( - boost::function const& handler); + file_storage const* files() const { return &m_files; } enum return_t { - // return values from check_fastresume and check_files + // return values from check_fastresume no_error = 0, - need_full_check = -1, - fatal_disk_error = -2, + fatal_disk_error = -1, + need_full_check = -2, disk_check_aborted = -3, file_exist = -4 }; storage_interface* get_storage_impl() { return m_storage.get(); } + void write_resume_data(entry& rd, storage_error& ec) const; + +#ifdef TORRENT_DEBUG + void assert_torrent_refcount() const; +#endif private: - std::string save_path() const; - - bool verify_resume_data(lazy_entry const& rd, error_code& e) - { return m_storage->verify_resume_data(rd, e); } - - bool is_allocating() const - { return m_state == state_expand_pieces; } - - void mark_failed(int index); - - error_code const& error() const { return m_storage->error(); } - std::string const& error_file() const { return m_storage->error_file(); } - int last_piece() const { return m_last_piece; } - void clear_error() { m_storage->clear_error(); } - - int slot_for(int piece) const; - int piece_for(int slot) const; - - // helper functions for check_dastresume - int check_no_fastresume(error_code& error); - int check_init_storage(error_code& error); - // if error is set and return value is 'no_error' or 'need_full_check' // the error message indicates that the fast resume data was rejected // if 'fatal_disk_error' is returned, the error message indicates what // when wrong in the disk access - int check_fastresume(lazy_entry const& rd, error_code& error); + int check_fastresume(lazy_entry const& rd, storage_error& error); - // this function returns true if the checking is complete - int check_files(int& current_slot, int& have_piece, error_code& error); - -#ifndef TORRENT_NO_DEPRECATE - bool compact_allocation() const - { return m_storage_mode == storage_mode_compact; } -#endif + // helper functions for check_fastresume + int check_no_fastresume(storage_error& error); + int check_init_storage(storage_error& error); #ifdef TORRENT_DEBUG - std::string name() const { return m_info->name(); } + std::string name() const { return m_files.name(); } #endif - bool allocate_slots_impl(int num_slots, mutex::scoped_lock& l, bool abort_on_disk = false); - - // updates the ph.h hasher object with the data at the given slot - // and optionally a 'small hash' as well, the hash for - // the partial slot. Returns the number of bytes read - int hash_for_slot(int slot, partial_hash& h, int piece_size - , int small_piece_size = 0, sha1_hash* small_hash = 0); - - void hint_read_impl(int piece_index, int offset, int size); - - int read_impl( - file::iovec_t* bufs - , int piece_index - , int offset - , int num_bufs); - - int write_impl( - file::iovec_t* bufs - , int piece_index - , int offset - , int num_bufs); - - size_type physical_offset(int piece_index, int offset); - - // returns the number of pieces left in the - // file currently being checked - int skip_file() const; - // -1=error 0=ok >0=skip this many pieces - int check_one_piece(int& have_piece); - int identify_data( - sha1_hash const& large_hash - , sha1_hash const& small_hash - , int current_slot); - - void switch_to_full_mode(); - sha1_hash hash_for_piece_impl(int piece, int* readback = 0); - - int release_files_impl() { return m_storage->release_files(); } - int delete_files_impl() { return m_storage->delete_files(); } - int rename_file_impl(int index, std::string const& new_filename) - { return m_storage->rename_file(index, new_filename); } - void set_file_priority_impl(std::vector const& p) - { m_storage->set_file_priority(p); } - - int move_storage_impl(std::string const& save_path, int flags); - - int allocate_slot_for_piece(int piece_index); #if TORRENT_USE_INVARIANT_CHECKS void check_invariant() const; #endif -#ifdef TORRENT_STORAGE_DEBUG - void debug_log() const; -#endif - boost::intrusive_ptr m_info; file_storage const& m_files; boost::scoped_ptr m_storage; - storage_mode_t m_storage_mode; - - // slots that haven't had any file storage allocated - std::vector m_unallocated_slots; - // slots that have file storage, but isn't assigned to a piece - std::vector m_free_slots; - - enum - { - has_no_slot = -3 // the piece has no storage - }; - - // maps piece indices to slots. If a piece doesn't - // have any storage, it is set to 'has_no_slot' - std::vector m_piece_to_slot; - - enum - { - unallocated = -1, // the slot is unallocated - unassigned = -2 // the slot is allocated but not assigned to a piece - }; - - // maps slots to piece indices, if a slot doesn't have a piece - // it can either be 'unassigned' or 'unallocated' - std::vector m_slot_to_piece; - - std::string m_save_path; - - mutable mutex m_mutex; - - enum { - // the default initial state - state_none, - // the file checking is complete - state_finished, - // checking the files - state_full_check, - // move pieces to their final position - state_expand_pieces - } m_state; - int m_current_slot; - // used during check. If any piece is found - // that is not in its final position, this - // is set to true - bool m_out_of_place; - // used to move pieces while expanding - // the storage from compact allocation - // to full allocation - aligned_holder m_scratch_buffer; - aligned_holder m_scratch_buffer2; - // the piece that is in the scratch buffer - int m_scratch_piece; - - // the last piece we wrote to or read from - int m_last_piece; - - // this is saved in case we need to instantiate a new - // storage (osed when remapping files) - storage_constructor_type m_storage_constructor; - - // this maps a piece hash to piece index. It will be - // build the first time it is used (to save time if it - // isn't needed) - std::multimap m_hash_to_piece; - - // this map contains partial hashes for downloading - // pieces. This is only accessed from within the - // disk-io thread. - std::map m_piece_hasher; - - disk_io_thread& m_io_thread; - // the reason for this to be a void pointer // is to avoid creating a dependency on the // torrent. This shared_ptr is here only diff --git a/include/libtorrent/storage_defs.hpp b/include/libtorrent/storage_defs.hpp index 330b06510..1f18d41be 100644 --- a/include/libtorrent/storage_defs.hpp +++ b/include/libtorrent/storage_defs.hpp @@ -36,12 +36,14 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/config.hpp" #include #include +#include namespace libtorrent { struct storage_interface; class file_storage; struct file_pool; + class torrent_info; // types of storage allocation used for add_torrent_params::storage_mode. enum storage_mode_t @@ -61,23 +63,33 @@ namespace libtorrent storage_mode_compact = internal_storage_mode_compact_deprecated #endif }; + + // see default_storage::default_storage() + struct TORRENT_EXPORT storage_params + { + storage_params(): files(NULL), mapped_files(NULL), pool(NULL) + , mode(storage_mode_sparse), priorities(NULL), info(NULL) {} + file_storage const* files; + file_storage const* mapped_files; // optional + std::string path; + file_pool* pool; + storage_mode_t mode; + std::vector const* priorities; // optional + torrent_info const* info; // optional + }; - typedef boost::function const&)> storage_constructor_type; + typedef boost::function storage_constructor_type; // the constructor function for the regular file storage. This is the // default value for add_torrent_params::storage. - TORRENT_EXPORT storage_interface* default_storage_constructor( - file_storage const&, file_storage const* mapped, std::string const&, file_pool& - , std::vector const&); + TORRENT_EXPORT storage_interface* default_storage_constructor(storage_params const&); // the constructor function for the disabled storage. This can be used for // testing and benchmarking. It will throw away any data written to // it and return garbage for anything read from it. - TORRENT_EXPORT storage_interface* disabled_storage_constructor( - file_storage const&, file_storage const* mapped, std::string const&, file_pool& - , std::vector const&); + TORRENT_EXPORT storage_interface* disabled_storage_constructor(storage_params const&); + TORRENT_EXPORT storage_interface* zero_storage_constructor(storage_params const&); } #endif diff --git a/include/libtorrent/string_util.hpp b/include/libtorrent/string_util.hpp index 9113410bb..a6420b66e 100644 --- a/include/libtorrent/string_util.hpp +++ b/include/libtorrent/string_util.hpp @@ -34,6 +34,8 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_STRING_UTIL_HPP_INCLUDED #include "libtorrent/config.hpp" +#include +#include namespace libtorrent { @@ -50,6 +52,18 @@ namespace libtorrent TORRENT_EXTRA_EXPORT void url_random(char* begin, char* end); + // this parses the string that's used as the liste_interfaces setting. + // it is a comma-separated list of IP or device names with ports. For + // example: "eth0:6881,eth1:6881" or "127.0.0.1:6881" + TORRENT_EXTRA_EXPORT void parse_comma_separated_string_port( + std::string const& in, std::vector >& out); + + // this parses the string that's used as the outgoing_interfaces setting. + // it is a comma separated list of IPs and device names. For example: + // "eth0, eth1, 127.0.0.1" + TORRENT_EXTRA_EXPORT void parse_comma_separated_string( + std::string const& in, std::vector& out); + // strdup is not part of the C standard. Some systems // don't have it and it won't be available when building // in strict ansi mode diff --git a/include/libtorrent/tailqueue.hpp b/include/libtorrent/tailqueue.hpp new file mode 100644 index 000000000..85bc7596e --- /dev/null +++ b/include/libtorrent/tailqueue.hpp @@ -0,0 +1,93 @@ +/* + +Copyright (c) 2011-2013, 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_TAILQUEUE_HPP +#define TORRENT_TAILQUEUE_HPP + +#include "libtorrent/assert.hpp" + +namespace libtorrent +{ + struct tailqueue_node + { + tailqueue_node() : next(0) {} + tailqueue_node* next; + }; + + template + inline N* postinc(N*& e) + { + N* ret = e; + e = (N*)ret->next; + return ret; + } + + struct tailqueue_iterator + { + friend struct tailqueue; + tailqueue_node const* get() const { return m_current; } + void next() { m_current = m_current->next; } + + private: + tailqueue_iterator(tailqueue_node const* cur) + : m_current(cur) {} + // the current element + tailqueue_node const* m_current; + }; + + struct TORRENT_EXTRA_EXPORT tailqueue + { + tailqueue(); + + tailqueue_iterator iterate() const + { return tailqueue_iterator(m_first); } + + void append(tailqueue& rhs); + void prepend(tailqueue& rhs); + tailqueue_node* pop_front(); + void push_front(tailqueue_node* e); + void push_back(tailqueue_node* e); + tailqueue_node* get_all(); + int size() const { return m_size; } + bool empty() const { return m_size == 0; } + void swap(tailqueue& rhs); + tailqueue_node* first() const { TORRENT_ASSERT(m_size > 0); return m_first; } + tailqueue_node* last() const { TORRENT_ASSERT(m_size > 0); return m_last; } + private: + tailqueue_node* m_first; + tailqueue_node* m_last; + int m_size; + }; +}; + +#endif // TAILQUEUE_HPP + diff --git a/include/libtorrent/thread.hpp b/include/libtorrent/thread.hpp index 70a2b85dd..8350cc76e 100644 --- a/include/libtorrent/thread.hpp +++ b/include/libtorrent/thread.hpp @@ -34,8 +34,11 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_THREAD_HPP_INCLUDED #include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" #include "libtorrent/time.hpp" +#include + #if defined TORRENT_WINDOWS || defined TORRENT_CYGWIN // asio assumes that the windows error codes are defined already #include @@ -50,6 +53,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include namespace libtorrent { @@ -68,6 +72,7 @@ namespace libtorrent void wait(mutex::scoped_lock& l); void wait_for(mutex::scoped_lock& l, time_duration rel_time); void notify_all(); + void notify(); private: #ifdef BOOST_HAS_PTHREADS pthread_cond_t m_cond; diff --git a/include/libtorrent/thread_pool.hpp b/include/libtorrent/thread_pool.hpp new file mode 100644 index 000000000..a2a1c82db --- /dev/null +++ b/include/libtorrent/thread_pool.hpp @@ -0,0 +1,161 @@ +/* + +Copyright (c) 2011-2013, 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_THREAD_POOL +#define TORRENT_THREAD_POOL + +#include "libtorrent/config.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/atomic.hpp" +#include +#include +#include +#include + +namespace libtorrent +{ + + template + struct thread_pool + { + thread_pool() : m_num_threads(0) {} + virtual ~thread_pool() {} + void stop() { set_num_threads(0, true); } + void set_num_threads(int i, bool wait = true) + { + if (i == m_num_threads) return; + + if (i > m_num_threads) + { + while (m_num_threads < i) + { + ++m_num_threads; + m_threads.push_back(boost::shared_ptr( + new thread(boost::bind(&thread_pool::thread_fun, this, int(m_num_threads)-1)))); + } + } + else + { + while (m_num_threads > i) { --m_num_threads; } + mutex::scoped_lock l(m_mutex); + m_cond.notify_all(); + l.unlock(); + if (wait) for (int i = m_num_threads; i < int(m_threads.size()); ++i) m_threads[i]->join(); + // this will detach the threads + m_threads.resize(m_num_threads); + } + } + + // returns true if the job was posted, and false if it was + // processed immediately + bool post_job(T& e) + { + if (m_num_threads == 0) + { + // if we don't have any worker threads + // just do the work immediately + process_job(e, false); + return false; + } + else + { + retain_job(e); + mutex::scoped_lock l(m_mutex); + m_queue.push_back(e); + // we only need to signal if the threads + // may have been put to sleep. If the size + // previous to adding the new job was > 0 + // they don't need waking up. + if (m_queue.size() == 1) + m_cond.notify(); + return true; + } + } + + protected: + + virtual void process_job(T const& j, bool post) = 0; + virtual void retain_job(T& j) {} + + private: + + void thread_fun(int thread_id) + { + for (;;) + { + mutex::scoped_lock l(m_mutex); + while (m_queue.empty() && thread_id < m_num_threads) m_cond.wait(l); + + // if the number of wanted thread is decreased, + // we may stop this thread + // when we're terminating the last hasher thread (id=0), make sure + // we finish up all queud jobs first + if ((thread_id != 0 || m_queue.empty()) && thread_id >= m_num_threads) break; + + TORRENT_ASSERT(!m_queue.empty()); + T e = m_queue.front(); + m_queue.pop_front(); + l.unlock(); + + process_job(e, true); + } + +#ifdef TORRENT_DEBUG + if (thread_id == 0) + { + // when we're terminating the last hasher thread, make sure + // there are no more scheduled jobs + mutex::scoped_lock l(m_mutex); + TORRENT_ASSERT(m_queue.empty()); + } +#endif + } + + // the mutex only protects m_cond and m_queue + // all other members are only used from a single + // thread (the user of this class, i.e. the disk + // thread). + mutex m_mutex; + condition_variable m_cond; + std::deque m_queue; + + std::vector > m_threads; + // this is a counter which is atomically incremented + // by each thread as it's started up, in order to + // assign a unique id to each thread + atomic_count m_num_threads; + }; + +} + +#endif // TORRENT_THREAD_POOL + diff --git a/include/libtorrent/time.hpp b/include/libtorrent/time.hpp index 1b3382cb9..1b530ce91 100644 --- a/include/libtorrent/time.hpp +++ b/include/libtorrent/time.hpp @@ -35,8 +35,14 @@ POSSIBILITY OF SUCH DAMAGE. #include #include "libtorrent/config.hpp" -#include "libtorrent/ptime.hpp" #include + +#if defined BOOST_ASIO_HAS_STD_CHRONO +#include +#else +#include +#endif + #include // OVERVIEW @@ -63,6 +69,15 @@ namespace libtorrent TORRENT_EXTRA_EXPORT char const* time_now_string(); std::string log_time(); +#if defined BOOST_ASIO_HAS_STD_CHRONO + typedef std::chrono::high_resolution_clock clock_type; +#else + typedef boost::chrono::high_resolution_clock clock_type; +#endif + + typedef clock_type::time_point ptime; + typedef clock_type::duration time_duration; + // returns the current time, as represented by ptime. The // resolution of this timer is about 100 ms. TORRENT_EXPORT ptime const& time_now(); @@ -70,58 +85,40 @@ namespace libtorrent // returns the current time as represented by ptime. This is // more expensive than time_now(), but provides as high resolution // as the operating system can provide. - TORRENT_EXPORT ptime time_now_hires(); + inline ptime time_now_hires() { return clock_type::now(); } // the earliest and latest possible time points // representable by ptime. - TORRENT_EXPORT ptime min_time(); - TORRENT_EXPORT ptime max_time(); + inline ptime min_time() { return (clock_type::time_point::min)(); } + inline ptime max_time() { return (clock_type::time_point::max)(); } -#if defined TORRENT_USE_BOOST_DATE_TIME || defined TORRENT_USE_QUERY_PERFORMANCE_TIMER +#if defined BOOST_ASIO_HAS_STD_CHRONO + using std::chrono::seconds; + using std::chrono::milliseconds; + using std::chrono::microseconds; + using std::chrono::minutes; + using std::chrono::hours; + using std::chrono::duration_cast; +#else + using boost::chrono::seconds; + using boost::chrono::milliseconds; + using boost::chrono::microseconds; + using boost::chrono::minutes; + using boost::chrono::hours; + using boost::chrono::duration_cast; +#endif - // returns a time_duration representing the specified number of seconds, milliseconds - // microseconds, minutes and hours. - TORRENT_EXPORT time_duration seconds(boost::int64_t s); - TORRENT_EXPORT time_duration milliseconds(boost::int64_t s); - TORRENT_EXPORT time_duration microsec(boost::int64_t s); - TORRENT_EXPORT time_duration minutes(boost::int64_t s); - TORRENT_EXPORT time_duration hours(boost::int64_t s); + template + boost::int64_t total_seconds(T td) + { return duration_cast(td).count(); } - // returns the number of seconds, milliseconds and microseconds - // a time_duration represents. - TORRENT_EXPORT boost::int64_t total_seconds(time_duration td); - TORRENT_EXPORT boost::int64_t total_milliseconds(time_duration td); - TORRENT_EXPORT boost::int64_t total_microseconds(time_duration td); + template + boost::int64_t total_milliseconds(T td) + { return duration_cast(td).count(); } -#elif TORRENT_USE_CLOCK_GETTIME || TORRENT_USE_SYSTEM_TIME || TORRENT_USE_ABSOLUTE_TIME - - // hidden - inline int total_seconds(time_duration td) - { return td.diff / 1000000; } - // hidden - inline int total_milliseconds(time_duration td) - { return td.diff / 1000; } - // hidden - inline boost::int64_t total_microseconds(time_duration td) - { return td.diff; } - - // hidden - inline time_duration microsec(boost::int64_t s) - { return time_duration(s); } - // hidden - inline time_duration milliseconds(boost::int64_t s) - { return time_duration(s * 1000); } - // hidden - inline time_duration seconds(boost::int64_t s) - { return time_duration(s * 1000000); } - // hidden - inline time_duration minutes(boost::int64_t s) - { return time_duration(s * 1000000 * 60); } - // hidden - inline time_duration hours(boost::int64_t s) - { return time_duration(s * 1000000 * 60 * 60); } - -#endif // TORRENT_USE_CLOCK_GETTIME + template + boost::int64_t total_microseconds(T td) + { return duration_cast(td).count(); } } diff --git a/include/libtorrent/timestamp_history.hpp b/include/libtorrent/timestamp_history.hpp index f40583925..e4eedaaf8 100644 --- a/include/libtorrent/timestamp_history.hpp +++ b/include/libtorrent/timestamp_history.hpp @@ -45,13 +45,13 @@ struct TORRENT_EXTRA_EXPORT timestamp_history { enum { history_size = 20 }; - timestamp_history() : m_index(0), m_initialized(false), m_base(0), m_num_samples(0) {} - bool initialized() const { return m_initialized; } + timestamp_history() : m_base(0), m_index(0), m_num_samples(not_initialized) {} + bool initialized() const { return m_num_samples != not_initialized; } // add a sample to the timestamp history. If step is true, it's been // a minute since the last step boost::uint32_t add_sample(boost::uint32_t sample, bool step); - boost::uint32_t base() const { TORRENT_ASSERT(m_initialized); return m_base; } + boost::uint32_t base() const { TORRENT_ASSERT(initialized()); return m_base; } void adjust_base(int change); private: @@ -59,20 +59,23 @@ private: // this is a circular buffer boost::uint32_t m_history[history_size]; - // and this is the index we're currently at - // in the circular buffer - boost::uint16_t m_index; - - bool m_initialized:1; - // this is the lowest sample seen in the // last 'history_size' minutes boost::uint32_t m_base; + // and this is the index we're currently at + // in the circular buffer + boost::uint16_t m_index; + + enum { not_initialized = 0xffff }; + // this is the number of samples since the // last time we stepped one minute. If we // don't have enough samples, we won't step - int m_num_samples; + // if this is set to 'not_initialized' we + // have bit seen any samples at all yet + // and m_base is not initialized yet + boost::uint16_t m_num_samples; }; } diff --git a/include/libtorrent/torrent.hpp b/include/libtorrent/torrent.hpp index 7ed639f5f..499f07a38 100644 --- a/include/libtorrent/torrent.hpp +++ b/include/libtorrent/torrent.hpp @@ -47,7 +47,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include +#include #include #ifdef _MSC_VER @@ -72,9 +72,12 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/hasher.hpp" #include "libtorrent/assert.hpp" #include "libtorrent/bitfield.hpp" -#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/aux_/session_interface.hpp" #include "libtorrent/deadline_timer.hpp" -#include "libtorrent/union_endpoint.hpp" +#include "libtorrent/peer_class_set.hpp" +#include "libtorrent/link.hpp" +#include "libtorrent/vector_utils.hpp" +#include "libtorrent/linked_list.hpp" #if TORRENT_COMPLETE_TYPES_REQUIRED #include "libtorrent/peer_connection.hpp" @@ -105,10 +108,15 @@ namespace libtorrent namespace aux { - struct session_impl; struct piece_checker_data; } + struct resume_data_t + { + std::vector buf; + lazy_entry entry; + }; + struct time_critical_piece { // when this piece was first requested @@ -132,24 +140,112 @@ namespace libtorrent { return deadline < rhs.deadline; } }; + struct torrent_hot_members + { + torrent_hot_members(aux::session_interface& ses + , add_torrent_params const& p, int block_size); + + protected: + // the piece picker. This is allocated lazily. When we don't + // have anything in the torrent (for instance, if it hasn't + // been started yet) or if we have everything, there is no + // picker. It's allocated on-demand the first time we need + // it in torrent::need_picker(). In order to tell the + // difference between having everything and nothing in + // the case there is no piece picker, see m_have_all. + boost::scoped_ptr m_picker; + + // TOOD: make this a raw pointer. perhaps keep the shared_ptr + // around further down the object to maintain an owner + boost::shared_ptr m_torrent_file; + + // a back reference to the session + // this torrent belongs to. + aux::session_interface& m_ses; + + // this vector is sorted at all times, by the pointer value. + // use sorted_insert() and sorted_find() on it. The GNU STL + // implementation on Darwin uses significantly less memory to + // represent a vector than a set, and this set is typically + // relaitvely small, and it's cheap to copy pointers. + std::vector m_connections; + + // the scrape data from the tracker response, this + // is optional and may be 0xffffff + boost::uint32_t m_complete:24; + + // set to true when this torrent may not download anything + bool m_upload_mode:1; + + // this is set to false as long as the connections + // of this torrent hasn't been initialized. If we + // have metadata from the start, connections are + // initialized immediately, if we didn't have metadata, + // they are initialized right after files_checked(). + // valid_resume_data() will return false as long as + // the connections aren't initialized, to avoid + // them from altering the piece-picker before it + // has been initialized with files_checked(). + bool m_connections_initialized:1; + + // is set to true when the torrent has + // been aborted. + bool m_abort:1; + + // is true if this torrent has allows having peers + bool m_allow_peers:1; + + // this is set when the torrent is in share-mode + bool m_share_mode:1; + + // this is true if we have all pieces. If it's false, + // it means we either don't have any pieces, or, if + // there is a piece_picker object present, it contans + // the state of how many pieces we have + bool m_have_all:1; + + // set to true when this torrent has been paused but + // is waiting to finish all current download requests + // before actually closing all connections + bool m_graceful_pause_mode:1; + + // state subscription. If set, a pointer to this torrent + // will be added to the m_state_updates set in session_impl + // whenever this torrent's state changes (any state). + bool m_state_subscription:1; + + // the maximum number of connections for this torrent + boost::uint32_t m_max_connections:24; + + // the size of a request block + // each piece is divided into these + // blocks when requested. The block size is + // 1 << m_block_size_shift + boost::uint32_t m_block_size_shift:5; + + // the state of this torrent (queued, checking, downloading, etc.) + boost::uint32_t m_state:3; + + boost::scoped_ptr m_policy; + }; + // a torrent is a class that holds information // for a specific download. It updates itself against // the tracker - class TORRENT_EXTRA_EXPORT torrent: public request_callback + class TORRENT_EXTRA_EXPORT torrent + : public torrent_hot_members + , public request_callback + , public peer_class_set , public boost::enable_shared_from_this + , public list_node // used for torrent activity LRU { public: - torrent(aux::session_impl& ses, tcp::endpoint const& net_interface - , int block_size, int seq, add_torrent_params const& p + torrent(aux::session_interface& ses, int block_size + , int seq, add_torrent_params const& p , sha1_hash const& info_hash); ~torrent(); -#ifndef TORRENT_DISABLE_ENCRYPTION - sha1_hash const& obfuscated_hash() const - { return m_obfuscated_hash; } -#endif - sha1_hash const& info_hash() const { static sha1_hash empty; @@ -163,8 +259,13 @@ namespace libtorrent void start_download_url(); + // returns which stats gauge this torrent currently + // has incremented. + int current_stats_state() const; + #ifndef TORRENT_DISABLE_EXTENSIONS void add_extension(boost::shared_ptr); + void remove_extension(boost::shared_ptr); void add_extension(boost::function(torrent*, void*)> const& ext , void* userdata); void notify_extension_add_peer(tcp::endpoint const& ip, int src, int flags); @@ -173,14 +274,33 @@ namespace libtorrent peer_connection* find_lowest_ranking_peer() const; #if TORRENT_USE_ASSERTS - bool has_peer(peer_connection* p) const - { return m_connections.find(p) != m_connections.end(); } + bool has_peer(peer_connection const* p) const + { return sorted_find(m_connections, (peer_connection*)p) != m_connections.end(); } #endif // this is called when the torrent has metadata. // it will initialize the storage and the piece-picker void init(); + // called every time we actually need the torrent_info + // object to be fully loaded. If it isn't, this triggers + // loading it from disk + // the return value indicates success. If it failed to + // load, the torrent will be set to an error state and + // return false + bool need_loaded(); + + // unload the torrent file to save memory + void unload(); + // returns true if parsed successfully + bool load(std::vector& buffer); + + // pinned torrents may not be unloaded + bool is_pinned() const { return m_pinned; } + void set_pinned(bool p); + bool is_loaded() const { return m_torrent_file->is_loaded(); } + bool should_be_loaded() const { return m_should_be_loaded; } + // find the peer that introduced us to the given endpoint. This is // used when trying to holepunch. We need the introducer so that we // can send a rendezvous connect message @@ -189,10 +309,11 @@ namespace libtorrent // if we're connected to a peer at ep, return its peer connection // only count BitTorrent peers bt_peer_connection* find_peer(tcp::endpoint const& ep) const; + peer_connection* find_peer(sha1_hash const& pid); - void on_resume_data_checked(int ret, disk_io_job const& j); - void on_force_recheck(int ret, disk_io_job const& j); - void on_piece_checked(int ret, disk_io_job const& j); + void on_resume_data_checked(disk_io_job const* j); + void on_force_recheck(disk_io_job const* j); + void on_piece_hashed(disk_io_job const* j); void files_checked(); void start_checking(); @@ -205,19 +326,24 @@ namespace libtorrent void set_share_mode(bool s); bool share_mode() const { return m_share_mode; } + // TOOD: make graceful pause also finish all sending blocks + // before disconnecting bool graceful_pause() const { return m_graceful_pause_mode; } void set_upload_mode(bool b); bool upload_mode() const { return m_upload_mode || m_graceful_pause_mode; } bool is_upload_only() const { return is_finished() || upload_mode(); } - int seed_rank(session_settings const& s) const; + int seed_rank(aux::session_settings const& s) const; enum flags_t { overwrite_existing = 1 }; void add_piece(int piece, char const* data, int flags = 0); - void on_disk_write_complete(int ret, disk_io_job const& j + void on_disk_write_complete(disk_io_job const* j , peer_request p); - void on_disk_cache_complete(int ret, disk_io_job const& j); + void on_disk_cache_complete(disk_io_job const* j); + void on_disk_tick_done(disk_io_job const* j); + + void schedule_storage_tick(); void set_progress_ppm(int p) { m_progress_ppm = p; } struct read_piece_struct @@ -228,13 +354,13 @@ namespace libtorrent error_code error; }; void read_piece(int piece); - void on_disk_read_complete(int ret, disk_io_job const& j, peer_request r, read_piece_struct* rp); + void on_disk_read_complete(disk_io_job const* j, peer_request r, read_piece_struct* rp); storage_mode_t storage_mode() const { return (storage_mode_t)m_storage_mode; } storage_interface* get_storage() { - if (!m_owning_storage) return 0; - return m_owning_storage->get_storage_impl(); + if (!m_storage) return 0; + return m_storage->get_storage_impl(); } // this will flag the torrent as aborted. The main @@ -249,10 +375,9 @@ namespace libtorrent torrent_status::state_t state() const { return (torrent_status::state_t)m_state; } void set_state(torrent_status::state_t s); - session_settings const& settings() const; - - aux::session_impl& session() { return m_ses; } - + aux::session_settings const& settings() const; + aux::session_interface& session() { return m_ses; } + void set_sequential_download(bool sd); bool is_sequential_download() const { return m_sequential_download; } @@ -261,8 +386,10 @@ namespace libtorrent void queue_down(); void set_queue_position(int p); int queue_position() const { return m_sequence_number; } + // used internally + void set_queue_position_impl(int p) { m_sequence_number = p; } - void second_tick(stat& accumulator, int tick_interval_ms); + void second_tick(int tick_interval_ms, int residual); // see if we need to connect to web seeds, and if so, // connect to them @@ -271,17 +398,39 @@ namespace libtorrent std::string name() const; stat statistics() const { return m_stat; } - void add_stats(stat const& s); size_type bytes_left() const; int block_bytes_wanted(piece_block const& p) const; void bytes_done(torrent_status& st, bool accurate) const; size_type quantized_bytes_done() const; - void ip_filter_updated() { m_policy.ip_filter_updated(); } + void sent_bytes(int bytes_payload, int bytes_protocol); + void received_bytes(int bytes_payload, int bytes_protocol); + void trancieve_ip_packet(int bytes, bool ipv6); + void sent_syn(bool ipv6); + void received_synack(bool ipv6); - void handle_disk_error(disk_io_job const& j, peer_connection* c = 0); + void ip_filter_updated(); + void port_filter_updated(); + + std::string resolve_filename(int file) const; + void handle_disk_error(disk_io_job const* j, peer_connection* c = 0); void clear_error(); - void set_error(error_code const& ec, std::string const& file); + + enum { + // the error did not occur on a file + error_file_none = -1, + + // the error occurred on m_url + error_file_url = -2, + + // the error occurred setting up the SSL context + error_file_ssl_ctx = -3, + + // the error occurred while loading the .torrent file via the user + // supplied load function + error_file_metadata = -4, + }; + void set_error(error_code const& ec, int file); bool has_error() const { return !!m_error; } error_code error() const { return m_error; } @@ -293,7 +442,8 @@ namespace libtorrent void set_announce_to_trackers(bool b) { m_announce_to_trackers = b; } void set_announce_to_lsd(bool b) { m_announce_to_lsd = b; } - ptime started() const { return m_started; } + int started() const { return m_started; } + void step_session_time(int seconds); void do_pause(); void do_resume(); @@ -302,16 +452,13 @@ namespace libtorrent bool is_torrent_paused() const { return !m_allow_peers || m_graceful_pause_mode; } void force_recheck(); void save_resume_data(int flags); - - bool is_active_download() const; - bool is_active_finished() const; - void update_guage(); + bool do_async_save_resume_data(); bool need_save_resume_data() const { // save resume data every 15 minutes regardless, just to // keep stats up to date - return m_need_save_resume_data || time(0) - m_last_saved_resume > 15 * 60; + return m_need_save_resume_data || m_ses.session_time() - m_last_saved_resume > 15 * 60; } bool is_auto_managed() const { return m_auto_managed; } @@ -320,6 +467,7 @@ namespace libtorrent bool should_check_files() const; bool delete_files(); + void peers_erased(std::vector const& peers); // ============ start deprecation ============= void filter_piece(int index, bool filter); @@ -328,7 +476,7 @@ namespace libtorrent void filtered_pieces(std::vector& bitmask) const; void filter_files(std::vector const& files); #if !TORRENT_NO_FPU - void file_progress(std::vector& fp) const; + void file_progress(std::vector& fp); #endif // ============ end deprecation ============= @@ -338,11 +486,13 @@ namespace libtorrent int piece_priority(int index) const; void prioritize_pieces(std::vector const& pieces); + void prioritize_piece_list(std::vector > const& pieces); void piece_priorities(std::vector*) const; void set_file_priority(int index, int priority); int file_priority(int index) const; + void on_file_priority(); void prioritize_files(std::vector const& files); void file_priorities(std::vector*) const; @@ -358,13 +508,14 @@ namespace libtorrent // it, add it to the m_state_updates list in session_impl void state_updated(); - void file_progress(std::vector& fp, int flags = 0) const; + void file_progress(std::vector& fp, int flags = 0); +#ifndef TORRENT_NO_DEPRECATED void use_interface(std::string net_interface); - tcp::endpoint get_interface() const; +#endif void connect_to_url_seed(std::list::iterator url); - bool connect_to_peer(policy::peer* peerinfo, bool ignore_limit = false); + bool connect_to_peer(torrent_peer* peerinfo, bool ignore_limit = false); int priority() const { return m_priority; } void set_priority(int prio) @@ -377,29 +528,33 @@ namespace libtorrent } #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES - void resolve_countries(bool r) - { m_resolve_countries = r; } + void resolve_countries(bool r); + bool resolving_countries() const; - bool resolving_countries() const - { - return m_resolve_countries && !m_ses.settings().force_proxy; - } + void resolve_peer_country(boost::shared_ptr const& p) const; + void on_country_lookup(error_code const& error + , std::vector
const& host_list + , boost::shared_ptr p) const; #endif // -------------------------------------------- // BANDWIDTH MANAGEMENT - bandwidth_channel m_bandwidth_channel[2]; + void set_upload_limit(int limit); + int upload_limit() const; + void set_download_limit(int limit); + int download_limit() const; - int bandwidth_throttle(int channel) const; + peer_class_t peer_class() const { return (peer_class_t)m_peer_class; } + + void set_max_uploads(int limit, bool state_update = true); + int max_uploads() const { return m_max_uploads; } + void set_max_connections(int limit, bool state_update = true); + int max_connections() const { return m_max_connections; } // -------------------------------------------- // PEER MANAGEMENT -#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING || defined TORRENT_LOGGING - void log_to_all_peers(char const* message); -#endif - // add or remove a url that will be attempted for // finding the file(s) in this torrent. void add_web_seed(std::string const& url, web_seed_entry::type_t type); @@ -411,7 +566,8 @@ namespace libtorrent void retry_web_seed(peer_connection* p, int retry = 0); - void remove_web_seed(peer_connection* p); + void remove_web_seed(peer_connection* p, error_code const& ec + , peer_connection_interface::operation_t op, int error = 0); std::list web_seeds() const { return m_web_seeds; } @@ -437,16 +593,31 @@ namespace libtorrent void cancel_block(piece_block block); - bool want_more_peers() const; + bool want_tick() const; + void update_want_tick(); + + bool want_peers() const; + bool want_peers_download() const; + bool want_peers_finished() const; + + void update_want_peers(); + void update_want_scrape(); + void update_gauge(); + bool try_connect_peer(); - void add_peer(tcp::endpoint const& adr, int source); + torrent_peer* add_peer(tcp::endpoint const& adr, int source, int flags = 0); + bool ban_peer(torrent_peer* tp); + void update_peer_port(int port, torrent_peer* p, int src); + void set_seed(torrent_peer* p, bool s); + void clear_failcount(torrent_peer* p); + std::pair find_peers(address const& a); // the number of peers that belong to this torrent int num_peers() const { return (int)m_connections.size(); } int num_seeds() const; - typedef std::set::iterator peer_iterator; - typedef std::set::const_iterator const_peer_iterator; + typedef std::vector::iterator peer_iterator; + typedef std::vector::const_iterator const_peer_iterator; const_peer_iterator begin() const { return m_connections.begin(); } const_peer_iterator end() const { return m_connections.end(); } @@ -454,14 +625,18 @@ namespace libtorrent peer_iterator begin() { return m_connections.begin(); } peer_iterator end() { return m_connections.end(); } - void resolve_peer_country(boost::intrusive_ptr const& p) const; - void get_full_peer_list(std::vector& v) const; void get_peer_info(std::vector& v); void get_download_queue(std::vector* queue); void refresh_explicit_cache(int cache_size); + void add_suggest_piece(int piece); + void update_suggest_piece(int index, int change); + void refresh_suggest_pieces(); + void do_refresh_suggest_pieces(); + void on_cache_info(disk_io_job const* j); + // -------------------------------------------- // TRACKER MANAGEMENT @@ -508,7 +683,7 @@ namespace libtorrent // forcefully sets next_announce to the current time void force_tracker_request(ptime, int tracker_idx); void scrape_tracker(); - void announce_with_tracker(tracker_request::event_t e + void announce_with_tracker(boost::uint8_t e = tracker_request::none , address const& bind_interface = address_v4::any()); int seconds_since_last_scrape() const { return m_last_scrape; } @@ -521,10 +696,6 @@ namespace libtorrent // the tracker void set_tracker_login(std::string const& name, std::string const& pw); - // the tcp::endpoint of the tracker that we managed to - // announce ourself at the last time we tried to announce - tcp::endpoint current_tracker() const; - announce_entry* find_tracker(tracker_request const& r); // -------------------------------------------- @@ -534,7 +705,15 @@ namespace libtorrent void update_sparse_piece_prio(int piece, int cursor, int reverse_cursor); - void get_suggested_pieces(std::vector& s) const; + struct suggest_piece_t + { + int piece_index; + int num_peers; + bool operator<(suggest_piece_t const& p) const { return num_peers < p.num_peers; } + }; + + std::vector const& get_suggested_pieces() const + { return m_suggested_pieces; } bool super_seeding() const { @@ -549,10 +728,26 @@ namespace libtorrent bool have_piece(int index) const { if (!valid_metadata()) return false; - if (!has_picker()) return true; + if (!has_picker()) return m_have_all; return m_picker->have_piece(index); } + // returns true if we have downloaded the given piece + bool has_piece_passed(int index) const + { + if (!valid_metadata()) return false; + if (!has_picker()) return m_have_all; + return m_picker->has_piece_passed(index); + } + + // a predictive piece is a piece that we might + // not have yet, but still announced to peers, anticipating that + // we'll have it very soon + bool is_predictive_piece(int index) const + { + return std::binary_search(m_predictive_pieces.begin(), m_predictive_pieces.end(), index); + } + // called when we learn that we have a piece // only once per piece void we_have(int index); @@ -566,93 +761,41 @@ namespace libtorrent return has_picker() ? m_picker->num_have() - : m_torrent_file->num_pieces(); + : m_have_all ? m_torrent_file->num_pieces() : 0; + } + + // the number of pieces that have passed + // hash check, but aren't necessarily + // flushed to disk yet + int num_passed() const + { + return has_picker() + ? m_picker->num_passed() + : m_have_all ? m_torrent_file->num_pieces() : 0; } // when we get a have message, this is called for that piece - void peer_has(int index, peer_connection const* peer) - { - if (has_picker()) - { - m_picker->inc_refcount(index, peer); - } -#ifdef TORRENT_DEBUG - else - { - TORRENT_ASSERT(is_seed()); - } -#endif - } - + void peer_has(int index, peer_connection const* peer); + // when we get a bitfield message, this is called for that piece - void peer_has(bitfield const& bits, peer_connection const* peer) - { - if (has_picker()) - { - if (bits.all_set() && bits.size() > 0) - m_picker->inc_refcount_all(peer); - else - m_picker->inc_refcount(bits, peer); - } -#ifdef TORRENT_DEBUG - else - { - TORRENT_ASSERT(is_seed()); - } -#endif - } + void peer_has(bitfield const& bits, peer_connection const* peer); - void peer_has_all(peer_connection const* peer) - { - if (has_picker()) - { - m_picker->inc_refcount_all(peer); - } -#ifdef TORRENT_DEBUG - else - { - TORRENT_ASSERT(is_seed()); - } -#endif - } + void peer_has_all(peer_connection const* peer); - void peer_lost(bitfield const& bits, peer_connection const* peer) - { - if (has_picker()) - { - if (bits.all_set() && bits.size() > 0) - m_picker->dec_refcount_all(peer); - else - m_picker->dec_refcount(bits, peer); - } -#ifdef TORRENT_DEBUG - else - { - TORRENT_ASSERT(is_seed()); - } -#endif - } - - void peer_lost(int index, peer_connection const* peer) - { - if (has_picker()) - { - m_picker->dec_refcount(index, peer); - } -#ifdef TORRENT_DEBUG - else - { - TORRENT_ASSERT(is_seed()); - } -#endif - } + void peer_lost(int index, peer_connection const* peer); + void peer_lost(bitfield const& bits, peer_connection const* peer); int block_size() const { TORRENT_ASSERT(m_block_size_shift > 0); return 1 << m_block_size_shift; } peer_request to_req(piece_block const& p) const; - void disconnect_all(error_code const& ec); + void disconnect_all(error_code const& ec, peer_connection_interface::operation_t op); int disconnect_peers(int num, error_code const& ec); + // called every time a block is marked as finished in the + // piece picker. We might have completed the torrent and + // we can delete the piece picker + void maybe_done_flushing(); + // this is called wheh the torrent has completed // the download. It will post an event, disconnect // all seeds and let the tracker know we're finished. @@ -660,12 +803,14 @@ namespace libtorrent #if TORRENT_USE_I2P void on_i2p_resolve(error_code const& ec, char const* dest); + bool is_i2p() const { return m_torrent_file && m_torrent_file->is_i2p(); } #endif // this is the asio callback that is called when a name // lookup for a PEER is completed. - void on_peer_name_lookup(error_code const& e, tcp::resolver::iterator i - , peer_id pid); + void on_peer_name_lookup(error_code const& e + , std::vector
const& host_list + , int port); // this is the asio callback that is called when a name // lookup for a WEB SEED is completed. @@ -694,12 +839,12 @@ namespace libtorrent // we wasn't finished anymore. void resume_download(); - void async_verify_piece(int piece_index, boost::function const&); + void verify_piece(int piece); + void on_piece_verified(disk_io_job const* j); - // this is called from the peer_connection - // each time a piece has failed the hash - // test - void piece_finished(int index, int passed_hash_check); + // this is called whenever a peer in this swarm becomes interesting + // it is responsible for issuing a block request, if appropriate + void peer_is_interesting(peer_connection& c); // piece_passed is called when a piece passes the hash check // this will tell all peers that we just got his piece @@ -710,11 +855,12 @@ namespace libtorrent // piece_failed is called when a piece fails the hash check void piece_failed(int index); - // this will restore the piece picker state for a piece - // by re marking all the requests to blocks in this piece - // that are still outstanding in peers' download queues. - // this is done when a piece fails - void restore_piece_state(int index); + // this is the handler for hash failure piece synchronization + // i.e. resetting the piece + void on_piece_sync(disk_io_job const* j); + + // this is the handler for write failure piece synchronization + void on_piece_fail_sync(disk_io_job const* j, piece_block b); enum wasted_reason_t { @@ -724,21 +870,28 @@ namespace libtorrent void add_redundant_bytes(int b, wasted_reason_t reason); void add_failed_bytes(int b); - // this is true if we have all the pieces + // this is true if we have all the pieces, but not necessarily flushed them to disk bool is_seed() const { - return valid_metadata() - && (!m_picker - || m_state == torrent_status::seeding - || m_picker->num_have() == m_picker->num_pieces()); + if (!valid_metadata()) return false; + if (m_have_all) return true; + if (m_picker && m_picker->num_passed() == m_picker->num_pieces()) return true; + return m_state == torrent_status::seeding; } // this is true if we have all the pieces that we want + // the pieces don't necessarily need to be flushed to disk bool is_finished() const { if (is_seed()) return true; - return valid_metadata() && m_torrent_file->num_pieces() - - m_picker->num_have() - m_picker->num_filtered() == 0; + + // this is slightly different from m_picker->is_finished() + // because any piece that has *passed* is considered here, + // which may be more than the piece we *have* (i.e. written to disk) + // keep in mind that num_filtered() does not include pieces we + // have that are filtered + return valid_metadata() && has_picker() + && m_torrent_file->num_pieces() - m_picker->num_filtered() - m_picker->num_passed() == 0; } bool is_inactive() const @@ -751,16 +904,22 @@ namespace libtorrent TORRENT_ASSERT(m_picker.get()); return *m_picker; } + void need_picker(); bool has_picker() const { return m_picker.get() != 0; } - policy& get_policy() { return m_policy; } - piece_manager& filesystem(); + + int num_known_peers() const { return m_policy ? m_policy->num_peers() : 0; } + int num_connect_candidates() const { return m_policy ? m_policy->num_connect_candidates() : 0; } + + piece_manager& storage(); + bool has_storage() const { return m_storage.get(); } + torrent_info const& torrent_file() const { return *m_torrent_file; } - boost::intrusive_ptr get_torrent_copy(); + boost::shared_ptr get_torrent_copy(); std::string const& uuid() const { return m_uuid; } void set_uuid(std::string const& s) { m_uuid = s; } @@ -791,6 +950,10 @@ namespace libtorrent // LOGGING #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING virtual void debug_log(const char* fmt, ...) const; + void log_to_all_peers(char const* message); + boost::shared_ptr m_logger; + ptime m_logger_time; + ptime m_dht_start_time; #endif // DEBUG @@ -801,21 +964,6 @@ namespace libtorrent // -------------------------------------------- // RESOURCE MANAGEMENT - int get_peer_upload_limit(tcp::endpoint ip) const; - int get_peer_download_limit(tcp::endpoint ip) const; - void set_peer_upload_limit(tcp::endpoint ip, int limit); - void set_peer_download_limit(tcp::endpoint ip, int limit); - - void set_upload_limit(int limit, bool state_update = true); - int upload_limit() const; - void set_download_limit(int limit, bool state_update = true); - int download_limit() const; - - void set_max_uploads(int limit, bool state_update = true); - int max_uploads() const { return m_max_uploads; } - void set_max_connections(int limit, bool state_update = true); - int max_connections() const { return m_max_connections; } - // flags are defined in storage.hpp void move_storage(std::string const& save_path, int flags); @@ -832,6 +980,8 @@ namespace libtorrent { return m_torrent_file->is_valid(); } bool are_files_checked() const { return m_files_checked; } + bool valid_storage() const + { return m_storage.get(); } // parses the info section from the given // bencoded tree and moves the torrent @@ -850,6 +1000,19 @@ namespace libtorrent bool all_verified() const { return int(m_num_verified) == m_torrent_file->num_pieces(); } + bool verifying_piece(int piece) const + { + TORRENT_ASSERT(piece < int(m_verifying.size())); + TORRENT_ASSERT(piece >= 0); + return m_verifying.get_bit(piece); + } + void verifying(int piece) + { + TORRENT_ASSERT(piece < int(m_verifying.size())); + TORRENT_ASSERT(piece >= 0); + TORRENT_ASSERT(m_verifying.get_bit(piece) == false); + m_verifying.set_bit(piece); + } bool verified_piece(int piece) const { TORRENT_ASSERT(piece < int(m_verified.size())); @@ -869,11 +1032,22 @@ namespace libtorrent void set_apply_ip_filter(bool b); bool apply_ip_filter() const { return m_apply_ip_filter; } - void queue_torrent_check(); - void dequeue_torrent_check(); + std::vector const& predictive_pieces() const + { return m_predictive_pieces; } + + // this is called whenever we predict to have this piece + // within one second + void predicted_have_piece(int index, int milliseconds); void clear_in_state_update() - { m_in_state_updates = false; } + { + TORRENT_ASSERT(m_links[aux::session_interface::torrent_state_updates].in_list()); + m_links[aux::session_interface::torrent_state_updates].clear(); + } + + void dec_refcount(char const* purpose); + void inc_refcount(char const* purpose); + int refcount() const { return m_refcount; } void inc_num_connecting() { ++m_num_connecting; } @@ -900,29 +1074,35 @@ namespace libtorrent private: - void on_files_deleted(int ret, disk_io_job const& j); - void on_files_released(int ret, disk_io_job const& j); - void on_torrent_paused(int ret, disk_io_job const& j); - void on_storage_moved(int ret, disk_io_job const& j); - void on_save_resume_data(int ret, disk_io_job const& j); - void on_file_renamed(int ret, disk_io_job const& j); - void on_cache_flushed(int ret, disk_io_job const& j); + // initialize the torrent_state structure passed to policy + // member functions. Don't forget to also call peers_erased() + // on the erased member after the policy call + torrent_state get_policy_state(); + + void construct_storage(); + void update_list(int list, bool in); + + void on_files_deleted(disk_io_job const* j); + void on_torrent_paused(disk_io_job const* j); + void on_storage_moved(disk_io_job const* j); + void on_save_resume_data(disk_io_job const* j); + void on_file_renamed(disk_io_job const* j); + void on_cache_flushed(disk_io_job const* j); + + // upload and download rate limits for the torrent + void set_limit_impl(int limit, int channel, bool state_update = true); + int limit_impl(int channel) const; + + void refresh_explicit_cache_impl(disk_io_job const* j, int cache_size); - void on_piece_verified(int ret, disk_io_job const& j - , boost::function f); - int prioritize_tracker(int tracker_index); int deprioritize_tracker(int tracker_index); - void on_country_lookup(error_code const& error, tcp::resolver::iterator i - , boost::intrusive_ptr p) const; bool request_bandwidth_from_session(int channel) const; void update_peer_interest(bool was_finished); void prioritize_udp_trackers(); - void parse_response(const entry& e, std::vector& peer_list); - void update_tracker_timer(ptime now); static void on_tracker_announce_disp(boost::weak_ptr p @@ -941,28 +1121,20 @@ namespace libtorrent void remove_time_critical_pieces(std::vector const& priority); void request_time_critical_pieces(); - policy m_policy; + void need_policy(); // all time totals of uploaded and downloaded payload // stored in resume data size_type m_total_uploaded; size_type m_total_downloaded; - // if this torrent is running, this was the time - // when it was started. This is used to have a - // bias towards keeping seeding torrents that - // recently was started, to avoid oscillation - ptime m_started; - - boost::intrusive_ptr m_torrent_file; - // if this pointer is 0, the torrent is in // a state where the metadata hasn't been // received yet, or during shutdown. // the piece_manager keeps the torrent object // alive by holding a shared_ptr to it and // the torrent keeps the piece manager alive - // with this intrusive_ptr. This cycle is + // with this shared_ptr. This cycle is // broken when torrent::abort() is called // Then the torrent releases the piece_manager // and when the piece_manager is complete with all @@ -973,13 +1145,7 @@ namespace libtorrent // the piece_manager, and stored in the // torrent, so the torrent cannot destruct // before the piece_manager. - boost::intrusive_ptr m_owning_storage; - - // this is a weak (non owninig) pointer to - // the piece_manager. This is used after the torrent - // has been aborted, and it can no longer own - // the object. - piece_manager* m_storage; + boost::shared_ptr m_storage; #ifdef TORRENT_USE_OPENSSL boost::shared_ptr m_ssl_ctx; @@ -991,14 +1157,7 @@ namespace libtorrent void init_ssl(std::string const& cert); #endif - std::set m_connections; - - // of all peers in m_connections, this is the number - // of peers that are outgoing and still waiting to - // complete the connection. This is used to possibly - // kick out these connections when we get incoming - // connections (if we've reached the connection limit) - int m_num_connecting; + void setup_peer_class(); // The list of web seeds in this torrent. Seeds // with fatal errors are removed from the set @@ -1018,37 +1177,37 @@ namespace libtorrent // ----------------------------- - // a back reference to the session - // this torrent belongs to. - aux::session_impl& m_ses; - // used to resolve hostnames for web seeds + // TODO: 2 replace all usage of this with m_ses.get_resolver() mutable tcp::resolver m_host_resolver; + // this vector is allocated lazily. If no file priorities are + // ever changed, this remains empty. Any unallocated slot + // implicitly means the file has priority 1. + // TODO: this wastes 5 bits per file std::vector m_file_priority; // this vector contains the number of bytes completely // downloaded (as in passed-hash-check) in each file. // this lets us trigger on individual files completing - std::vector m_file_progress; - - boost::scoped_ptr m_picker; + // the vector is allocated lazily, when file progress + // is first queried by the client + std::vector m_file_progress; + // these are the pieces we're currently + // suggesting to peers. + std::vector m_suggested_pieces; + std::vector m_trackers; // this is an index into m_trackers // this list is sorted by time_critical_piece::deadline - std::deque m_time_critical_pieces; + std::vector m_time_critical_pieces; std::string m_trackerid; std::string m_username; std::string m_password; - // the network interfaces outgoing connections - // are opened through. If there is more then one, - // they are used in a round-robin fasion - std::vector m_net_interfaces; - std::string m_save_path; // if we don't have the metadata, this is a url to @@ -1065,22 +1224,33 @@ namespace libtorrent // this is used as temporary storage while downloading // the .torrent file from m_url - std::vector m_torrent_file_buf; +// std::vector m_torrent_file_buf; + + // this is a list of all pieces that we have announced + // as having, without actually having yet. If we receive + // a request for a piece in this list, we need to hold off + // on responding until we have completed the piece and + // verified its hash. If the hash fails, send reject to + // peers with outstanding requests, and dont_have to other + // peers. This vector is ordered, to make lookups fast. + std::vector m_predictive_pieces; // each bit represents a piece. a set bit means // the piece has had its hash verified. This // is only used in seed mode (when m_seed_mode // is true) + + // TODO: These two bitfields should probably be coalesced into one bitfield m_verified; + // this means there is an outstanding, async, operation + // to verify each piece that has a 1 + bitfield m_verifying; // set if there's an error on this torrent error_code m_error; - // if the error ocurred on a file, this is the file - std::string m_error_file; // used if there is any resume data - std::vector m_resume_data; - lazy_entry m_resume_entry; + boost::scoped_ptr m_resume_data; // if the torrent is started without metadata, it may // still be given a name until the metadata is received @@ -1095,7 +1265,6 @@ namespace libtorrent // completed, m_completed_time is 0 time_t m_added_time; time_t m_completed_time; - time_t m_last_saved_resume; // this was the last time _we_ saw a seed in this swarm time_t m_last_seen_complete; @@ -1104,17 +1273,54 @@ namespace libtorrent // in this swarm time_t m_swarm_last_seen_complete; + public: + // these are the lists this torrent belongs to. For more + // details about each list, see session_impl.hpp. Each list + // represents a group this torrent belongs to and makes it + // efficient to enumerate only torrents belonging to a specific + // group. Such as torrents that want peer connections or want + // to be ticked etc. + link m_links[aux::session_interface::num_torrent_lists]; + + private: + // m_num_verified = m_verified.count() boost::uint32_t m_num_verified; -#ifndef TORRENT_DISABLE_ENCRYPTION - // this is SHA1("req2" + info-hash), used for - // encrypted hand shakes - sha1_hash m_obfuscated_hash; -#endif + // this timestamp is kept in session-time, to + // make it fit in 16 bits + boost::uint16_t m_last_saved_resume; + + // if this torrent is running, this was the time + // when it was started. This is used to have a + // bias towards keeping seeding torrents that + // recently was started, to avoid oscillation + // this is specified at a second granularity + // in session-time. see session_impl for details. + // the reference point is stepped forward every 4 + // hours to keep the timestamps fit in 16 bits + boost::uint16_t m_started; + + // when checking, this is the first piece we have not + // issued a hash job for + int m_checking_piece; + + // the number of pieces we completed the check of + int m_num_checked_pieces; + + // the number of async. operations that need this torrent + // loaded in RAM. having a refcount > 0 prevents it from + // being unloaded. + int m_refcount; + + // if the error ocurred on a file, this is the index of that file + // there are a few special cases, when this is negative. See + // set_error() + int m_error_file; // the average time it takes to download one time critical piece boost::uint32_t m_average_piece_time; + // the average piece download time deviation boost::uint32_t m_piece_time_deviation; @@ -1127,6 +1333,17 @@ namespace libtorrent // monotonically increasing number for each added torrent int m_sequence_number; + // for torrents who have a bandwidth limit, this is != 0 + // and refers to a peer_class in the session. + boost::uint16_t m_peer_class; + + // of all peers in m_connections, this is the number + // of peers that are outgoing and still waiting to + // complete the connection. This is used to possibly + // kick out these connections when we get incoming + // connections (if we've reached the connection limit) + boost::uint16_t m_num_connecting; + // ============================== // The following members are specifically // ordered to make the 24 bit members @@ -1137,8 +1354,22 @@ namespace libtorrent // the number of seconds we've been in upload mode unsigned int m_upload_mode_time:24; - // the state of this torrent (queued, checking, downloading, etc.) - unsigned int m_state:3; + // true when this torrent should anncounce to + // trackers + bool m_announce_to_trackers:1; + + // true when this torrent should anncounce to + // the local network + bool m_announce_to_lsd:1; + + // is set to true every time there is an incoming + // connection to this torrent + bool m_has_incoming:1; + + // this is set to true when the files are checked + // before the files are checked, we don't try to + // connect to peers + bool m_files_checked:1; // determines the storage state for this torrent. unsigned int m_storage_mode:2; @@ -1152,12 +1383,6 @@ namespace libtorrent // for a reannounce bool m_waiting_tracker:1; - // this means we haven't verified the file content - // of the files we're seeding. the m_verified bitfield - // indicates which pieces have been verified and which - // haven't - bool m_seed_mode:1; - // ---- // total time we've been available on this torrent @@ -1184,16 +1409,11 @@ namespace libtorrent // is received bool m_got_tracker_response:1; - // this is set to false as long as the connections - // of this torrent hasn't been initialized. If we - // have metadata from the start, connections are - // initialized immediately, if we didn't have metadata, - // they are initialized right after files_checked(). - // valid_resume_data() will return false as long as - // the connections aren't initialized, to avoid - // them from altering the piece-picker before it - // has been initialized with files_checked(). - bool m_connections_initialized:1; + // this means we haven't verified the file content + // of the files we're seeding. the m_verified bitfield + // indicates which pieces have been verified and which + // haven't + bool m_seed_mode:1; // if this is true, we're currently super seeding this // torrent. @@ -1242,34 +1462,9 @@ namespace libtorrent // the number of unchoked peers in this torrent unsigned int m_num_uploads:24; - // the size of a request block - // each piece is divided into these - // blocks when requested. The block size is - // 1 << m_block_size_shift - unsigned int m_block_size_shift:5; - - // is set to true every time there is an incoming - // connection to this torrent - bool m_has_incoming:1; - - // this is set to true when the files are checked - // before the files are checked, we don't try to - // connect to peers - bool m_files_checked:1; - - // this is true if the torrent has been added to - // checking queue in the session - bool m_queued_for_checking:1; - -// ---- - - // the maximum number of connections for this torrent - unsigned int m_max_connections:24; - - // set to true when this torrent has been paused but - // is waiting to finish all current download requests - // before actually closing all connections - bool m_graceful_pause_mode:1; + // when this is set, second_tick will perform the actual + // work of refreshing the suggest pieces + bool m_need_suggest_pieces_refresh:1; // this is set to true when the torrent starts up // The first tracker response, when this is true, @@ -1310,12 +1505,12 @@ namespace libtorrent // the scrape data from the tracker response, this // is optional and may be 0xffffff - boost::uint32_t m_complete:24; + boost::uint32_t m_incomplete:24; - // state subscription. If set, a pointer to this torrent - // will be added to the m_state_updates set in session_impl - // whenever this torrent's state changes (any state). - bool m_state_subscription:1; + + // true when the torrent should announce to + // the DHT + bool m_announce_to_dht:1; // in state_updates list. When adding a torrent to the // session_impl's m_state_update list, this bit is set @@ -1338,41 +1533,35 @@ namespace libtorrent // more blocks to disk! bool m_deleted:1; - // set to true while moving the storage - bool m_moving_storage:1; + // pinned torrents are locked in RAM and won't be unloaded + // in favor of more active torrents. When the torrent is added, + // the user may choose to initialize this to 1, in which case + // it will never be unloaded from RAM + bool m_pinned:1; - // this is true if this torrent is considered inactive from the - // queuing mechanism's point of view. If a torrent doesn't transfer - // at high enough rates, it's inactive. - bool m_inactive:1; + // when this is false, we should unload the torrent as soon + // as the no other async. job needs the torrent loaded + bool m_should_be_loaded:1; // ---- - // the scrape data from the tracker response, this - // is optional and may be 0xffffff - boost::uint32_t m_incomplete:24; + // the number of seconds since the last piece passed for + // this torrent + boost::uint64_t m_last_download:24; - // is set to true when the torrent has - // been aborted. - bool m_abort:1; + // this is a second count-down to when we should tick the + // storage for this torrent. Ticking the storage is used + // to periodically flush the partfile metadata and possibly + // other deferred flushing. Any disk operation starts this + // counter (unless it's already counting down). 0 means no + // ticking is needed. + boost::uint8_t m_storage_tick; - // true when the torrent should announce to - // the DHT - bool m_announce_to_dht:1; +// ---- - // true when this torrent should anncounce to - // trackers - bool m_announce_to_trackers:1; - - // true when this torrent should anncounce to - // the local network - bool m_announce_to_lsd:1; - - // is true if this torrent has allows having peers - bool m_allow_peers:1; - - // set to true when this torrent may not download anything - bool m_upload_mode:1; + // the number of seconds since the last byte was uploaded + // from this torrent + boost::uint64_t m_last_upload:24; // if this is true, libtorrent may pause and resume // this torrent depending on queuing rules. Torrents @@ -1381,22 +1570,19 @@ namespace libtorrent // slots. bool m_auto_managed:1; - // this is set when the torrent is in share-mode - bool m_share_mode:1; + enum { no_gauge_state = 0xf }; + // the current stats gauge this torrent counts against + boost::uint32_t m_current_gauge_state:4; -// ---- + // set to true while moving the storage + bool m_moving_storage:1; - // the number of seconds since the last piece passed for - // this torrent - boost::uint64_t m_last_download:24; + // this is true if this torrent is considered inactive from the + // queuing mechanism's point of view. If a torrent doesn't transfer + // at high enough rates, it's inactive. + bool m_inactive:1; - // the number of seconds since the last scrape request to - // one of the trackers in this torrent - boost::uint64_t m_last_scrape:16; - - // the number of seconds since the last byte was uploaded - // from this torrent - boost::uint64_t m_last_upload:24; + // TODO: there's space for 1 bits here // ---- @@ -1404,8 +1590,9 @@ namespace libtorrent // is optional and may be 0xffffff unsigned int m_downloaded:24; - // round-robin index into m_interfaces - mutable boost::uint8_t m_interface_index; + // the number of seconds since the last scrape request to + // one of the trackers in this torrent + boost::uint64_t m_last_scrape:16; // ---- @@ -1430,6 +1617,24 @@ namespace libtorrent bool m_resume_data_loaded; #endif }; + + struct torrent_ref_holder + { + torrent_ref_holder(torrent* t, char const* p) + : m_torrent(t) + , m_purpose(p) + { + if (m_torrent) m_torrent->inc_refcount(m_purpose); + } + + ~torrent_ref_holder() + { + if (m_torrent) m_torrent->dec_refcount(m_purpose); + } + torrent* m_torrent; + char const* m_purpose; + }; + } #endif // TORRENT_TORRENT_HPP_INCLUDED diff --git a/include/libtorrent/torrent_handle.hpp b/include/libtorrent/torrent_handle.hpp index 94fd8070d..3e234c046 100644 --- a/include/libtorrent/torrent_handle.hpp +++ b/include/libtorrent/torrent_handle.hpp @@ -53,12 +53,13 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/peer_id.hpp" #include "libtorrent/piece_picker.hpp" #include "libtorrent/torrent_info.hpp" -#include "libtorrent/ptime.hpp" +#include "libtorrent/time.hpp" #include "libtorrent/config.hpp" #include "libtorrent/storage.hpp" #include "libtorrent/address.hpp" #include "libtorrent/bitfield.hpp" #include "libtorrent/socket.hpp" // tcp::endpoint +#include "libtorrent/file_pool.hpp" namespace libtorrent { @@ -71,6 +72,7 @@ namespace libtorrent struct peer_info; struct peer_list_entry; struct torrent_status; + struct torrent_handle; class torrent; // allows torrent_handle to be used in unordered_map and unordered_set. @@ -204,6 +206,9 @@ namespace libtorrent state_t piece_state; }; + // for boost::hash (and to support using this type in unordered_map etc.) + std::size_t hash_value(torrent_handle const& h); + // You will usually have to store your torrent handles somewhere, since it's // the object through which you retrieve information about the torrent and // aborts the torrent. @@ -234,6 +239,7 @@ namespace libtorrent { friend class invariant_access; friend struct aux::session_impl; + friend class session; friend struct feed; friend class torrent; friend std::size_t hash_value(torrent_handle const& th); @@ -242,6 +248,9 @@ namespace libtorrent // i.e. is_valid() will return false. torrent_handle() {} + torrent_handle(torrent_handle const& t) + { if (!t.m_torrent.expired()) m_torrent = t.m_torrent; } + // flags for add_piece(). enum flags_t { overwrite_existing = 1 }; @@ -428,6 +437,15 @@ namespace libtorrent // already keeps track of this internally and no calculation is required. void file_progress(std::vector& progress, int flags = 0) const; + // This function fills in the passed in vector with status about files + // that are open for this torrent. Any file that is not open in this + // torrent, will not be reported in the vector, i.e. it's possible that + // the vector is empty when returning, if none of the files in the + // torrent are currently open. + // + // see pool_file_status. + void file_status(std::vector& status) const; + // If the torrent is in an error state (i.e. ``torrent_status::error`` is // non-empty), this will clear the error and start the torrent again. void clear_error() const; @@ -588,7 +606,15 @@ namespace libtorrent // the resume data will contain the metadata from the torrent file as // well. This is default for any torrent that's added without a // torrent file (such as a magnet link or a URL). - save_info_dict = 2 + save_info_dict = 2, + + // if nothing significant has changed in the torrent since the last + // time resume data was saved, fail this attempt. Significant changes + // primarily include more data having been downloaded, file or piece + // priorities having changed etc. If the resume data doesn't need + // saving, a save_resume_data_failed_alert is posted with the error + // resume_data_not_modified. + only_if_modified = 4, }; // ``save_resume_data()`` generates fast-resume data and returns it as an @@ -816,7 +842,7 @@ namespace libtorrent // without metadata only if it was started without a .torrent file, e.g. // by using the libtorrent extension of just supplying a tracker and // info-hash. - boost::intrusive_ptr torrent_file() const; + boost::shared_ptr torrent_file() const; #ifndef TORRENT_NO_DEPRECATE @@ -942,12 +968,19 @@ namespace libtorrent // ``prioritize_pieces`` takes a vector of integers, one integer per // piece in the torrent. All the piece priorities will be updated with // the priorities in the vector. + // The second overload of ``prioritize_pieces`` that takes a vector of pairs + // will update the priorities of only select pieces, and leave all other + // unaffected. Each pair is (piece, priority). That is, the first item is + // the piece index and the second item is the priority of that piece. + // Invalid entries, where the piece index or priority is out of range, are + // not allowed. // // ``piece_priorities`` returns a vector with one element for each piece // in the torrent. Each element is the current priority of that piece. void piece_priority(int index, int priority) const; int piece_priority(int index) const; void prioritize_pieces(std::vector const& pieces) const; + void prioritize_pieces(std::vector > const& pieces) const; std::vector piece_priorities() const; // ``index`` must be in the range [0, number_of_files). @@ -1030,6 +1063,22 @@ namespace libtorrent void set_download_limit(int limit) const; int download_limit() const; + // A pinned torrent may not be unloaded by libtorrent. When the dynamic + // loading and unloading of torrents is enabled (by setting a load + // function on the session), this can be used to exempt certain torrents + // from the unloading logic. + // + // Magnet links, and other torrents that start out without having + // metadata are pinned automatically. This is to give the client a chance + // to get the metadata and save it before it's unloaded. In this case, it + // may be useful to un-pin the torrent once its metadata has been saved + // to disk. + // + // For more information about dynamically loading and unloading torrents, + // see dynamic-loading-of-torrent-files_. + // + void set_pinned(bool p) const; + // ``set_sequential_download()`` enables or disables *sequential // download*. When enabled, the piece picker will pick pieces in sequence // instead of rarest first. In this mode, piece priorities are ignored, @@ -1049,7 +1098,24 @@ namespace libtorrent // argument will be bitwised ORed into the source mask of this peer. // Typically this is one of the source flags in peer_info. i.e. // ``tracker``, ``pex``, ``dht`` etc. - void connect_peer(tcp::endpoint const& adr, int source = 0) const; + // + // ``flags`` are the same flags that are passed along with the ``ut_pex`` extension. + // + // ==== ========================================== + // 0x01 peer supports encryption + // + // 0x02 peer is a seed + // + // 0x04 supports uTP. This is only a positive flags + // passing 0 doesn't mean the peer doesn't + // support uTP + // + // 0x08 supports holepunching protocol. If this + // flag is received from a peer, it can be + // used as a rendezvous point in case direct + // connections to the peer fail + // ==== ========================================== + void connect_peer(tcp::endpoint const& adr, int source = 0, int flags = 0) const; // ``set_max_uploads()`` sets the maximum number of peers that's unchoked // at the same time on this torrent. If you set this to -1, there will be @@ -1160,6 +1226,14 @@ namespace libtorrent bool operator<(const torrent_handle& h) const { return m_torrent.lock() < h.m_torrent.lock(); } + boost::uint32_t id() const + { + uintptr_t ret = (uintptr_t)m_torrent.lock().get(); + // a torrent object is about 1024 bytes, so + // it's safe to shift 11 bits + return boost::uint32_t(ret >> 11); + } + // This function is intended only for use by plugins and the alert // dispatch function. Any code that runs in libtorrent's network thread // may not use the public API of torrent_handle. Doing so results in a @@ -1171,8 +1245,7 @@ namespace libtorrent private: torrent_handle(boost::weak_ptr const& t) - : m_torrent(t) - {} + { if (!t.expired()) m_torrent = t; } boost::weak_ptr m_torrent; @@ -1197,10 +1270,14 @@ namespace libtorrent // the different overall states a torrent can be in enum state_t { +#ifndef TORRENT_NO_DEPRECATE // The torrent is in the queue for being checked. But there // currently is another torrent that are being checked. // This torrent will wait for its turn. queued_for_checking, +#else + unused_enum_for_backwards_compatibility, +#endif // The torrent has not started its download yet, and is // currently checking existing files. @@ -1261,7 +1338,7 @@ namespace libtorrent // set to point to the ``torrent_info`` object for this torrent. It's // only included if the torrent status is queried with // ``torrent_handle::query_torrent_file``. - boost::intrusive_ptr torrent_file; + boost::weak_ptr torrent_file; // the time until the torrent will announce itself to the tracker. boost::posix_time::time_duration next_announce; @@ -1356,7 +1433,7 @@ namespace libtorrent // progress parts per million (progress * 1000000) when disabling // floating point operations, this is the only option to query progress - + // // reflects the same value as ``progress``, but instead in a range [0, // 1000000] (ppm = parts per million). When floating point operations are // disabled, this is the only alternative to the floating point value in @@ -1583,6 +1660,11 @@ namespace libtorrent // if a large file ends up being copied from one drive to another. bool moving_storage; + // true if this torrent is loaded into RAM. A torrent can be started + // and still not loaded into RAM, in case it has not had any peers interested in it + // yet. Torrents are loaded on demand. + bool is_loaded; + // the info-hash for this torrent sha1_hash info_hash; }; diff --git a/include/libtorrent/torrent_info.hpp b/include/libtorrent/torrent_info.hpp index bf2844f3a..15317abe1 100644 --- a/include/libtorrent/torrent_info.hpp +++ b/include/libtorrent/torrent_info.hpp @@ -52,18 +52,20 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/lazy_entry.hpp" #include "libtorrent/peer_id.hpp" #include "libtorrent/size_type.hpp" -#include "libtorrent/ptime.hpp" -#include "libtorrent/intrusive_ptr_base.hpp" +#include "libtorrent/time.hpp" #include "libtorrent/assert.hpp" #include "libtorrent/file_storage.hpp" #include "libtorrent/copy_ptr.hpp" #include "libtorrent/socket.hpp" -#include "libtorrent/policy.hpp" // for policy::peer +#include "libtorrent/torrent_peer.hpp" namespace libtorrent { class peer_connection; - struct session_settings; + + namespace aux { struct session_settings; } + // exposed for the unit test + TORRENT_EXTRA_EXPORT void sanitize_append_path_element(std::string& path, char const* element, int element_len); enum { @@ -78,7 +80,6 @@ namespace libtorrent TORRENT_EXTRA_EXPORT int merkle_num_nodes(int); TORRENT_EXTRA_EXPORT int merkle_get_parent(int); TORRENT_EXTRA_EXPORT int merkle_get_sibling(int); - TORRENT_EXTRA_EXPORT void trim_path_element(std::string& path_element); // this class holds information about one bittorrent tracker, as it // relates to a specific torrent. @@ -184,16 +185,11 @@ namespace libtorrent // reset announce counters and clears the started sent flag. // The announce_entry will look like we've never talked to // the tracker. - void reset() - { - start_sent = false; - next_announce = min_time(); - min_announce = min_time(); - } + void reset(); // updates the failure counter and time-outs for re-trying. // This is called when the tracker announce fails. - void failed(session_settings const& sett, int retry_interval = 0); + void failed(aux::session_settings const& sett, int retry_interval = 0); #ifndef TORRENT_NO_DEPRECATE // deprecated in 1.0 @@ -253,9 +249,6 @@ namespace libtorrent // The URL of the web seed std::string url; - // The type of web seed (see type_t) - type_t type; - // Optional authentication. If this is set, it's passed // in as HTTP basic auth to the web seed. The format is: // username:password. @@ -267,6 +260,19 @@ namespace libtorrent // if this is > now, we can't reconnect yet ptime retry; + // if the hostname of the web seed has been resolved, + // this is its IP address + tcp::endpoint endpoint; + + // this is the peer_info field used for the + // connection, just to count hash failures + // it's also used to hold the peer_connection + // pointer, when the web seed is connected + ipv4_peer peer_info; + + // The type of web seed (see type_t) + boost::uint8_t type; + // this is initialized to true, but if we discover the // server not to support it, it's set to false, and we // make larger requests. @@ -282,16 +288,6 @@ namespace libtorrent // callback remove it bool removed; - // if the hostname of the web seed has been resolved, - // this is its IP address - tcp::endpoint endpoint; - - // this is the peer_info field used for the - // connection, just to count hash failures - // it's also used to hold the peer_connection - // pointer, when the web seed is connected - policy::ipv4_peer peer_info; - // if the web server doesn't support keepalive or a block request was // interrupted, the block received so far is kept here for the next // connection to pick up @@ -304,8 +300,9 @@ namespace libtorrent typedef libtorrent_exception invalid_torrent_file; #endif - // This class represents the information stored in a .torrent file - class TORRENT_EXPORT torrent_info : public intrusive_ptr_base + // TODO: 2 there may be some opportunities to optimize the size if torrent_info. + // specifically to turn some std::string and std::vector into pointers + class TORRENT_EXPORT torrent_info { public: @@ -347,7 +344,7 @@ namespace libtorrent #endif // TORRENT_USE_WSTRING #endif // TORRENT_NO_DEPRECATE #endif - torrent_info(torrent_info const& t, int flags = 0); + torrent_info(torrent_info const& t); torrent_info(sha1_hash const& info_hash, int flags = 0); torrent_info(lazy_entry const& torrent_file, error_code& ec, int flags = 0); torrent_info(char const* buffer, int size, error_code& ec, int flags = 0); @@ -378,7 +375,11 @@ namespace libtorrent // For more information on the file_storage object, see the separate document on how // to create torrents. file_storage const& files() const { return m_files; } - file_storage const& orig_files() const { return m_orig_files ? *m_orig_files : m_files; } + file_storage const& orig_files() const + { + TORRENT_ASSERT(is_loaded()); + return m_orig_files ? *m_orig_files : m_files; + } // Renames a the file with the specified index to the new name. The new filename is // reflected by the ``file_storage`` returned by ``files()`` but not by the one @@ -395,6 +396,7 @@ namespace libtorrent // not moved when move_storage() is invoked. void rename_file(int index, std::string const& new_filename) { + TORRENT_ASSERT(is_loaded()); copy_on_write(); m_files.rename_file(index, new_filename); } @@ -509,7 +511,10 @@ namespace libtorrent // a size (in bytes) into the corresponding files with offsets where that data // for that piece is supposed to be stored. See file_slice. std::vector map_block(int piece, size_type offset, int size) const - { return m_files.map_block(piece, offset, size); } + { + TORRENT_ASSERT(is_loaded()); + return m_files.map_block(piece, offset, size); + } // This function will map a range in a specific file into a range in the torrent. // The ``file_offset`` parameter is the offset in the file, given in bytes, where @@ -519,8 +524,15 @@ namespace libtorrent // + ``size`` is not allowed to be greater than the file size. ``file_index`` // must refer to a valid file, i.e. it cannot be >= ``num_files()``. peer_request map_file(int file, size_type offset, int size) const - { return m_files.map_file(file, offset, size); } - + { + TORRENT_ASSERT(is_loaded()); + return m_files.map_file(file, offset, size); + } + + // load and unload this torrent info + void load(char const* buffer, int size, error_code& ec); + void unload(); + #ifndef TORRENT_NO_DEPRECATE // ------- start deprecation ------- // these functions will be removed in a future version @@ -563,6 +575,7 @@ namespace libtorrent { TORRENT_ASSERT(index >= 0); TORRENT_ASSERT(index < m_files.num_pieces()); + TORRENT_ASSERT(is_loaded()); if (is_merkle_torrent()) { TORRENT_ASSERT(index < int(m_merkle_tree.size() - m_merkle_first_leaf)); @@ -578,6 +591,8 @@ namespace libtorrent } } + bool is_loaded() const { return m_piece_hashes || !m_merkle_tree.empty(); } + // ``merkle_tree()`` returns a reference to the merkle tree for this torrent, if any. // // ``set_merkle_tree()`` moves the passed in merkle tree into the torrent_info object. @@ -665,9 +680,13 @@ namespace libtorrent // __ http://bittorrent.org/beps/bep_0030.html bool is_merkle_torrent() const { return !m_merkle_tree.empty(); } + bool parse_torrent_file(lazy_entry const& libtorrent, error_code& ec, int flags); + // if we're logging member offsets, we need access to them private: + void resolve_duplicate_filenames(); + #if TORRENT_USE_INVARIANT_CHECKS friend class invariant_access; void check_invariant() const; @@ -677,11 +696,6 @@ namespace libtorrent torrent_info const& operator=(torrent_info const&); void copy_on_write(); - bool parse_torrent_file(lazy_entry const& libtorrent, error_code& ec, int flags); - - // the index to the first leaf. This is where the hash for the - // first piece is stored - boost::uint32_t m_merkle_first_leaf; file_storage m_files; @@ -729,6 +743,10 @@ namespace libtorrent // the hash that identifies this torrent sha1_hash m_info_hash; + // the index to the first leaf. This is where the hash for the + // first piece is stored + boost::uint32_t m_merkle_first_leaf; + // the number of bytes in m_info_section boost::uint32_t m_info_section_size:24; diff --git a/include/libtorrent/torrent_peer.hpp b/include/libtorrent/torrent_peer.hpp new file mode 100644 index 000000000..45122f1d9 --- /dev/null +++ b/include/libtorrent/torrent_peer.hpp @@ -0,0 +1,273 @@ +/* + +Copyright (c) 2012-2013, 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_TORRENT_PEER_HPP_INCLUDED +#define TORRENT_TORRENT_PEER_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/socket.hpp" + +namespace libtorrent +{ + struct peer_connection_interface; + struct external_ip; + + // calculate the priority of a peer based on its address. One of the + // endpoint should be our own. The priority is symmetric, so it doesn't + // matter which is which + TORRENT_EXTRA_EXPORT boost::uint32_t peer_priority( + tcp::endpoint e1, tcp::endpoint e2); + + struct TORRENT_EXTRA_EXPORT torrent_peer + { + torrent_peer(boost::uint16_t port, bool connectable, int src); + + boost::uint64_t total_download() const; + boost::uint64_t total_upload() const; + + boost::uint32_t rank(external_ip const& external, int external_port) const; + + libtorrent::address address() const; + char const* dest() const; + + tcp::endpoint ip() const { return tcp::endpoint(address(), port); } + + // this is the accumulated amount of + // uploaded and downloaded data to this + // torrent_peer. It only accounts for what was + // shared during the last connection to + // this torrent_peer. i.e. These are only updated + // when the connection is closed. For the + // total amount of upload and download + // we'll have to add thes figures with the + // statistics from the peer_connection. + // since these values don't need to be stored + // with byte-precision, they specify the number + // of kiB. i.e. shift left 10 bits to compare to + // byte counters. + boost::uint32_t prev_amount_upload; + boost::uint32_t prev_amount_download; + + // if the torrent_peer is connected now, this + // will refer to a valid peer_connection + peer_connection_interface* connection; + + // as computed by hashing our IP with the remote + // IP of this peer + // calculated lazily + mutable boost::uint32_t peer_rank; + +#ifndef TORRENT_DISABLE_GEO_IP +#ifdef TORRENT_DEBUG + // only used in debug mode to assert that + // the first entry in the AS pair keeps the same + boost::uint16_t inet_as_num; +#endif + // The AS this torrent_peer belongs to + std::pair* inet_as; +#endif + + // the time when this torrent_peer was optimistically unchoked + // the last time. in seconds since session was created + // 16 bits is enough to last for 18.2 hours + // when the session time reaches 18 hours, it jumps back by + // 9 hours, and all peers' times are updated to be + // relative to that new time offset + boost::uint16_t last_optimistically_unchoked; + + // the time when the torrent_peer connected to us + // or disconnected if it isn't connected right now + // in number of seconds since session was created + boost::uint16_t last_connected; + + // the port this torrent_peer is or was connected on + boost::uint16_t port; + + // the number of times this torrent_peer has been + // part of a piece that failed the hash check + boost::uint8_t hashfails; + + // the number of failed connection attempts + // this torrent_peer has + unsigned failcount:5; // [0, 31] + + // incoming peers (that don't advertize their listen port) + // will not be considered connectable. Peers that + // we have a listen port for will be assumed to be. + bool connectable:1; + + // true if this torrent_peer currently is unchoked + // because of an optimistic unchoke. + // when the optimistic unchoke is moved to + // another torrent_peer, this torrent_peer will be choked + // if this is true + bool optimistically_unchoked:1; + + // this is true if the torrent_peer is a seed + bool seed:1; + + // the number of times we have allowed a fast + // reconnect for this torrent_peer. + unsigned fast_reconnects:4; + + // for every valid piece we receive where this + // torrent_peer was one of the participants, we increase + // this value. For every invalid piece we receive + // where this torrent_peer was a participant, we decrease + // this value. If it sinks below a threshold, its + // considered a bad torrent_peer and will be banned. + signed trust_points:4; // [-7, 8] + + // a bitmap combining the peer_source flags + // from peer_info. + unsigned source:6; + +#ifndef TORRENT_DISABLE_ENCRYPTION + // Hints encryption support of torrent_peer. Only effective + // for and when the outgoing encryption policy + // allows both encrypted and non encrypted + // connections (pe_settings::out_enc_policy + // == enabled). The initial state of this flag + // determines the initial connection attempt + // type (true = encrypted, false = standard). + // This will be toggled everytime either an + // encrypted or non-encrypted handshake fails. + bool pe_support:1; +#endif + +#if TORRENT_USE_IPV6 + // this is true if the v6 union member in addr is + // the one to use, false if it's the v4 one + bool is_v6_addr:1; +#endif +#if TORRENT_USE_I2P + // set if the i2p_destination is in use in the addr union + bool is_i2p_addr:1; +#endif + + // if this is true, the torrent_peer has previously + // participated in a piece that failed the piece + // hash check. This will put the torrent_peer on parole + // and only request entire pieces. If a piece pass + // that was partially requested from this torrent_peer it + // will leave parole mode and continue download + // pieces as normal peers. + bool on_parole:1; + + // is set to true if this torrent_peer has been banned + bool banned:1; + + // we think this torrent_peer supports uTP + bool supports_utp:1; + // we have been connected via uTP at least once + bool confirmed_supports_utp:1; + bool supports_holepunch:1; + // this is set to one for web seeds. Web seeds + // are not stored in the policy m_peers list, + // and are excempt from connect candidate bookkeeping + // so, any torrent_peer with the web_seed bit set, is + // never considered a connect candidate + bool web_seed:1; +#if TORRENT_USE_ASSERTS + bool in_use:1; +#endif + }; + + struct TORRENT_EXTRA_EXPORT ipv4_peer : torrent_peer + { + ipv4_peer(tcp::endpoint const& ip, bool connectable, int src); + + address_v4 addr; + }; + +#if TORRENT_USE_I2P + struct TORRENT_EXTRA_EXPORT i2p_peer : torrent_peer + { + i2p_peer(char const* destination, bool connectable, int src); + ~i2p_peer(); + + char* destination; + }; +#endif + +#if TORRENT_USE_IPV6 + struct TORRENT_EXTRA_EXPORT ipv6_peer : torrent_peer + { + ipv6_peer(tcp::endpoint const& ip, bool connectable, int src); + + const address_v6::bytes_type addr; + }; +#endif + + struct peer_address_compare + { + bool operator()( + torrent_peer const* lhs, libtorrent::address const& rhs) const + { + return lhs->address() < rhs; + } + + bool operator()( + libtorrent::address const& lhs, torrent_peer const* rhs) const + { + return lhs < rhs->address(); + } + +#if TORRENT_USE_I2P + bool operator()( + torrent_peer const* lhs, char const* rhs) const + { + return strcmp(lhs->dest(), rhs) < 0; + } + + bool operator()( + char const* lhs, torrent_peer const* rhs) const + { + return strcmp(lhs, rhs->dest()) < 0; + } +#endif + + bool operator()( + torrent_peer const* lhs, torrent_peer const* rhs) const + { +#if TORRENT_USE_I2P + if (rhs->is_i2p_addr == lhs->is_i2p_addr) + return strcmp(lhs->dest(), rhs->dest()) < 0; +#endif + return lhs->address() < rhs->address(); + } + }; +} + +#endif + diff --git a/include/libtorrent/torrent_peer_allocator.hpp b/include/libtorrent/torrent_peer_allocator.hpp new file mode 100644 index 000000000..e10eb9c37 --- /dev/null +++ b/include/libtorrent/torrent_peer_allocator.hpp @@ -0,0 +1,96 @@ +/* + +Copyright (c) 2003-2013, 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_PEER_ALLOCATOR_HPP_INCLUDED +#define TORRENT_PEER_ALLOCATOR_HPP_INCLUDED + +#include "libtorrent/config.hpp" +#include "libtorrent/torrent_peer.hpp" + +#include + +namespace libtorrent +{ + + struct torrent_peer_allocator_interface + { + enum peer_type_t + { + ipv4_peer_type, + ipv6_peer_type, + i2p_peer_type + }; + + virtual torrent_peer* allocate_peer_entry(int type) = 0; + virtual void free_peer_entry(torrent_peer* p) = 0; + }; + + struct TORRENT_EXTRA_EXPORT torrent_peer_allocator : torrent_peer_allocator_interface + { + torrent_peer_allocator(); + + torrent_peer* allocate_peer_entry(int type); + void free_peer_entry(torrent_peer* p); + + boost::uint64_t total_bytes() const { return m_total_bytes; } + boost::uint64_t total_allocations() const { return m_total_allocations; } + int live_bytes() const { return m_live_bytes; } + int live_allocations() const { return m_live_allocations; } + + private: + + // this is a shared pool where torrent_peer objects + // are allocated. It's a pool since we're likely + // to have tens of thousands of peers, and a pool + // saves significant overhead + + boost::pool<> m_ipv4_peer_pool; +#if TORRENT_USE_IPV6 + boost::pool<> m_ipv6_peer_pool; +#endif +#if TORRENT_USE_I2P + boost::pool<> m_i2p_peer_pool; +#endif + + // the total number of bytes allocated (cumulative) + boost::uint64_t m_total_bytes; + // the total number of allocations (cumulative) + boost::uint64_t m_total_allocations; + // the number of currently live bytes + int m_live_bytes; + // the number of currently live allocations + int m_live_allocations; + }; +} + +#endif + diff --git a/include/libtorrent/tracker_manager.hpp b/include/libtorrent/tracker_manager.hpp index a48dab216..514bc3006 100644 --- a/include/libtorrent/tracker_manager.hpp +++ b/include/libtorrent/tracker_manager.hpp @@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include #include @@ -57,7 +58,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/address.hpp" #include "libtorrent/peer_id.hpp" #include "libtorrent/peer.hpp" // peer_entry -#include "libtorrent/session_settings.hpp" // proxy_settings #include "libtorrent/deadline_timer.hpp" #include "libtorrent/connection_queue.hpp" #include "libtorrent/intrusive_ptr_base.hpp" @@ -82,14 +82,14 @@ namespace libtorrent struct TORRENT_EXTRA_EXPORT tracker_request { tracker_request() - : kind(announce_request) - , downloaded(-1) + : downloaded(-1) , uploaded(-1) , left(-1) , corrupt(0) , redundant(0) , listen_port(0) , event(none) + , kind(announce_request) , key(0) , num_want(0) , send_stats(true) @@ -99,12 +99,6 @@ namespace libtorrent #endif {} - enum - { - announce_request, - scrape_request - } kind; - enum event_t { none, @@ -114,20 +108,34 @@ namespace libtorrent paused }; - sha1_hash info_hash; - peer_id pid; + enum kind_t + { + announce_request, + scrape_request + }; + + std::string url; + std::string trackerid; + size_type downloaded; size_type uploaded; size_type left; size_type corrupt; size_type redundant; - unsigned short listen_port; - event_t event; - std::string url; - std::string trackerid; + boost::uint16_t listen_port; + + // values from event_t + boost::uint8_t event; + + // values from kind_t + boost::uint8_t kind; + boost::uint32_t key; int num_want; + sha1_hash info_hash; + peer_id pid; address bind_ip; + bool send_stats; bool apply_ip_filter; #ifdef TORRENT_USE_OPENSSL @@ -138,7 +146,7 @@ namespace libtorrent struct TORRENT_EXTRA_EXPORT request_callback { friend class tracker_manager; - request_callback(): m_manager(0) {} + request_callback() {} virtual ~request_callback() {} virtual void tracker_warning(tracker_request const& req , std::string const& msg) = 0; @@ -164,14 +172,9 @@ namespace libtorrent , const std::string& msg , int retry_interval) = 0; - union_endpoint m_tracker_address; - #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING virtual void debug_log(const char* fmt, ...) const = 0; -#else - private: #endif - tracker_manager* m_manager; }; struct TORRENT_EXTRA_EXPORT timeout_handler @@ -197,19 +200,23 @@ namespace libtorrent boost::intrusive_ptr self() { return boost::intrusive_ptr(this); } - // used for timeouts - // this is set when the request has been sent - ptime m_start_time; - // this is set every time something is received - ptime m_read_time; - // the asio async operation - deadline_timer m_timeout; - int m_completion_timeout; - int m_read_timeout; typedef mutex mutex_t; mutable mutex_t m_mutex; + + // used for timeouts + // this is set when the request has been sent + ptime m_start_time; + + // this is set every time something is received + ptime m_read_time; + + // the asio async operation + deadline_timer m_timeout; + + int m_read_timeout; + bool m_abort; }; @@ -241,6 +248,10 @@ namespace libtorrent boost::intrusive_ptr self() { return boost::intrusive_ptr(this); } + private: + + const tracker_request m_req; + protected: void fail_impl(error_code const& ec, int code = -1, std::string msg = std::string() @@ -249,19 +260,14 @@ namespace libtorrent boost::weak_ptr m_requester; tracker_manager& m_man; - - private: - - const tracker_request m_req; }; class TORRENT_EXTRA_EXPORT tracker_manager: public udp_socket_observer, boost::noncopyable { public: - tracker_manager(aux::session_impl& ses, proxy_settings const& ps) + tracker_manager(aux::session_impl& ses) : m_ses(ses) - , m_proxy(ps) , m_abort(false) {} ~tracker_manager(); @@ -298,7 +304,6 @@ namespace libtorrent tracker_connections_t; tracker_connections_t m_connections; aux::session_impl& m_ses; - proxy_settings const& m_proxy; bool m_abort; }; } diff --git a/include/libtorrent/udp_socket.hpp b/include/libtorrent/udp_socket.hpp index 7a2d77530..8ec74d792 100644 --- a/include/libtorrent/udp_socket.hpp +++ b/include/libtorrent/udp_socket.hpp @@ -39,7 +39,9 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/session_settings.hpp" #include "libtorrent/buffer.hpp" #include "libtorrent/thread.hpp" +#include "libtorrent/connection_interface.hpp" #include "libtorrent/deadline_timer.hpp" +#include "libtorrent/debug.hpp" #include @@ -64,7 +66,7 @@ namespace libtorrent virtual void socket_drained() {} }; - class udp_socket + class udp_socket : connection_interface, single_threaded { public: udp_socket(io_service& ios, connection_queue& cc); @@ -183,8 +185,8 @@ namespace libtorrent void on_read_impl(udp::socket* sock, udp::endpoint const& ep , error_code const& e, std::size_t bytes_transferred); void on_name_lookup(error_code const& e, tcp::resolver::iterator i); - void on_timeout(); - void on_connect(int ticket); + void on_connect_timeout(); + void on_allow_connect(int ticket); void on_connected(error_code const& ec, int ticket); void handshake1(error_code const& e); void handshake2(error_code const& e); @@ -201,22 +203,6 @@ namespace libtorrent void wrap(char const* hostname, int port, char const* p, int len, error_code& ec); void unwrap(error_code const& e, char const* buf, int size); -#if TORRENT_USE_ASSERTS - -#if defined BOOST_HAS_PTHREADS - mutable pthread_t m_thread; -#endif - bool is_single_thread() const - { -#if defined BOOST_HAS_PTHREADS - if (m_thread == 0) - m_thread = pthread_self(); - return m_thread == pthread_self(); -#endif - return true; - } -#endif - udp::socket m_ipv4_sock; int m_buf_size; @@ -294,6 +280,7 @@ namespace libtorrent void set_rate_limit(int limit) { m_rate_limit = limit; } bool send(udp::endpoint const& ep, char const* p, int len , error_code& ec, int flags = 0); + bool has_quota(); private: diff --git a/include/libtorrent/udp_tracker_connection.hpp b/include/libtorrent/udp_tracker_connection.hpp index adc2b8941..e80834710 100644 --- a/include/libtorrent/udp_tracker_connection.hpp +++ b/include/libtorrent/udp_tracker_connection.hpp @@ -91,7 +91,8 @@ namespace libtorrent boost::intrusive_ptr self() { return boost::intrusive_ptr(this); } - void name_lookup(error_code const& error, tcp::resolver::iterator i); + void name_lookup(error_code const& error + , std::vector
const& addresses, int port); void timeout(error_code const& error); void start_announce(); @@ -115,14 +116,10 @@ namespace libtorrent udp::endpoint pick_target_endpoint() const; - bool m_abort; std::string m_hostname; - udp::endpoint m_target; std::list m_endpoints; - int m_transaction_id; aux::session_impl& m_ses; - int m_attempts; struct connection_cache_entry { @@ -133,9 +130,17 @@ namespace libtorrent static std::map m_connection_cache; static mutex m_cache_mutex; - action_t m_state; - proxy_settings m_proxy; + + udp::endpoint m_target; + + int m_transaction_id; + int m_attempts; + + // action_t + boost::uint8_t m_state; + + bool m_abort; }; } diff --git a/include/libtorrent/uncork_interface.hpp b/include/libtorrent/uncork_interface.hpp new file mode 100644 index 000000000..24cd8182b --- /dev/null +++ b/include/libtorrent/uncork_interface.hpp @@ -0,0 +1,55 @@ +/* + +Copyright (c) 2012-2013, 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_UNCORK_INTERFACE_HPP +#define TORRENT_UNCORK_INTERFACE_HPP + +namespace libtorrent +{ + // the uncork interface is used by the disk_io_thread + // to indicate that it has called all the disk job handlers + // in the current batch. The intention is for the peer + // connections to be able to not issue any sends on their + // sockets until they have recevied all the disk jobs + // that are ready first. This makes the networking more + // efficient since it can send larger buffers down to the + // kernel per system call. + // uncorking refers to releasing the "cork" in the peers + // preventing them to issue sends + struct uncork_interface + { + virtual void do_delayed_uncork() = 0; + }; +} + +#endif + diff --git a/include/libtorrent/upnp.hpp b/include/libtorrent/upnp.hpp index deb6cb1cf..9c48b0c1e 100644 --- a/include/libtorrent/upnp.hpp +++ b/include/libtorrent/upnp.hpp @@ -41,6 +41,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/intrusive_ptr_base.hpp" #include "libtorrent/thread.hpp" #include "libtorrent/deadline_timer.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/resolver.hpp" #include #include @@ -341,6 +343,8 @@ private: io_service& m_io_service; + resolver m_resolver; + // the udp socket used to send and receive // multicast messages on the network broadcast_socket m_socket; @@ -368,6 +372,10 @@ private: mutex m_mutex; std::string m_model; + + // cache of interfaces + mutable std::vector m_interfaces; + mutable ptime m_last_if_update; }; } diff --git a/include/libtorrent/utp_socket_manager.hpp b/include/libtorrent/utp_socket_manager.hpp index 0c0f06a76..6305429e5 100644 --- a/include/libtorrent/utp_socket_manager.hpp +++ b/include/libtorrent/utp_socket_manager.hpp @@ -38,18 +38,20 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket_type.hpp" #include "libtorrent/session_status.hpp" #include "libtorrent/enum_net.hpp" +#include "libtorrent/aux_/session_settings.hpp" namespace libtorrent { class udp_socket; class utp_stream; struct utp_socket_impl; + struct counters; typedef boost::function const&)> incoming_utp_callback_t; struct utp_socket_manager : udp_socket_observer { - utp_socket_manager(session_settings const& sett, udp_socket& s, incoming_utp_callback_t cb); + utp_socket_manager(aux::session_settings const& sett, udp_socket& s, counters& cnt, incoming_utp_callback_t cb); ~utp_socket_manager(); void get_status(utp_status& s) const; @@ -78,15 +80,15 @@ namespace libtorrent void remove_socket(boost::uint16_t id); utp_socket_impl* new_utp_socket(utp_stream* str); - int gain_factor() const { return m_sett.utp_gain_factor; } - int target_delay() const { return m_sett.utp_target_delay * 1000; } - int syn_resends() const { return m_sett.utp_syn_resends; } - int fin_resends() const { return m_sett.utp_fin_resends; } - int num_resends() const { return m_sett.utp_num_resends; } - int connect_timeout() const { return m_sett.utp_connect_timeout; } - int min_timeout() const { return m_sett.utp_min_timeout; } - int loss_multiplier() const { return m_sett.utp_loss_multiplier; } - bool allow_dynamic_sock_buf() const { return m_sett.utp_dynamic_sock_buf; } + int gain_factor() const { return m_sett.get_int(settings_pack::utp_gain_factor); } + int target_delay() const { return m_sett.get_int(settings_pack::utp_target_delay) * 1000; } + int syn_resends() const { return m_sett.get_int(settings_pack::utp_syn_resends); } + int fin_resends() const { return m_sett.get_int(settings_pack::utp_fin_resends); } + int num_resends() const { return m_sett.get_int(settings_pack::utp_num_resends); } + int connect_timeout() const { return m_sett.get_int(settings_pack::utp_connect_timeout); } + int min_timeout() const { return m_sett.get_int(settings_pack::utp_min_timeout); } + int loss_multiplier() const { return m_sett.get_int(settings_pack::utp_loss_multiplier); } + bool allow_dynamic_sock_buf() const { return m_sett.get_bool(settings_pack::utp_dynamic_sock_buf); } void mtu_for_dest(address const& addr, int& link_mtu, int& utp_mtu); void set_sock_buf(int size); @@ -95,25 +97,8 @@ namespace libtorrent void defer_ack(utp_socket_impl* s); void subscribe_drained(utp_socket_impl* s); - enum counter_t - { - packet_loss = 0, - timeout, - packets_in, - packets_out, - fast_retransmit, - packet_resend, - samples_above_target, - samples_below_target, - payload_pkts_in, - payload_pkts_out, - invalid_pkts_in, - redundant_pkts_in, - - num_counters - }; - // used to keep stats of uTP events + // the counter is the enum from ``counters``. void inc_stats_counter(int counter); private: @@ -147,7 +132,7 @@ namespace libtorrent int m_new_connection; - session_settings const& m_sett; + aux::session_settings const& m_sett; // this is a copy of the routing table, used // to initialize MTU sizes of uTP sockets @@ -166,7 +151,7 @@ namespace libtorrent int m_sock_buf_size; // stats counters - boost::uint64_t m_counters[num_counters]; + counters& m_counters; }; } diff --git a/include/libtorrent/utp_stream.hpp b/include/libtorrent/utp_stream.hpp index 7d8afe019..5761c81fa 100644 --- a/include/libtorrent/utp_stream.hpp +++ b/include/libtorrent/utp_stream.hpp @@ -145,7 +145,7 @@ utp_socket_impl* construct_utp_impl(boost::uint16_t recv_id void detach_utp_impl(utp_socket_impl* s); void delete_utp_impl(utp_socket_impl* s); bool should_delete(utp_socket_impl* s); -void tick_utp_impl(utp_socket_impl* s, ptime const& now); +void tick_utp_impl(utp_socket_impl* s, ptime now); void utp_init_mtu(utp_socket_impl* s, int link_mtu, int utp_mtu); bool utp_incoming_packet(utp_socket_impl* s, char const* p , int size, udp::endpoint const& ep, ptime receive_time); @@ -282,12 +282,6 @@ public: do_connect(endpoint, &utp_stream::on_connect); } - template - void async_read_some(boost::asio::null_buffers const& buffers, Handler const& handler) - { - TORRENT_ASSERT(false); - } - template void async_read_some(Mutable_Buffers const& buffers, Handler const& handler) { @@ -325,6 +319,25 @@ public: set_read_handler(&utp_stream::on_read); } + template + void async_read_some(boost::asio::null_buffers const&, Handler const& handler) + { + if (m_impl == 0) + { + m_io_service.post(boost::bind(handler, asio::error::not_connected, 0)); + return; + } + + TORRENT_ASSERT(!m_read_handler); + if (m_read_handler) + { + m_io_service.post(boost::bind(handler, asio::error::operation_not_supported, 0)); + return; + } + m_read_handler = handler; + set_read_handler(&utp_stream::on_read); + } + void do_async_connect(endpoint_type const& ep , boost::function const& handler); @@ -401,12 +414,6 @@ public: } #endif - template - void async_write_some(boost::asio::null_buffers const& buffers, Handler const& handler) - { - TORRENT_ASSERT(false); - } - template void async_write_some(Const_Buffers const& buffers, Handler const& handler) { diff --git a/include/libtorrent/vector_utils.hpp b/include/libtorrent/vector_utils.hpp new file mode 100644 index 000000000..884208d28 --- /dev/null +++ b/include/libtorrent/vector_utils.hpp @@ -0,0 +1,69 @@ +/* + +Copyright (c) 2012-2013, 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_VECTOR_UTILS_HPP_INCLUDE +#define TORRENT_VECTOR_UTILS_HPP_INCLUDE + +#include +#include + +namespace libtorrent { + + template + typename std::vector::iterator sorted_find(std::vector& container + , T v) + { + typename std::vector::iterator i = std::lower_bound(container.begin() + , container.end(), v); + if (i == container.end()) return container.end(); + if (*i != v) return container.end(); + return i; + } + + template + typename std::vector::const_iterator sorted_find(std::vector const& container + , T v) + { + return sorted_find(const_cast&>(container), v); + } + + template + void sorted_insert(std::vector& container, T v) + { + typename std::vector::iterator i = std::lower_bound(container.begin() + , container.end(), v); + container.insert(i, v); + } +} + +#endif + diff --git a/include/libtorrent/version.hpp b/include/libtorrent/version.hpp index 601d73467..194442738 100644 --- a/include/libtorrent/version.hpp +++ b/include/libtorrent/version.hpp @@ -34,14 +34,14 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_VERSION_HPP_INCLUDED #define LIBTORRENT_VERSION_MAJOR 1 -#define LIBTORRENT_VERSION_MINOR 0 +#define LIBTORRENT_VERSION_MINOR 1 #define LIBTORRENT_VERSION_TINY 0 // the format of this version is: MMmmtt // M = Major version, m = minor version, t = tiny version #define LIBTORRENT_VERSION_NUM ((LIBTORRENT_VERSION_MAJOR * 10000) + (LIBTORRENT_VERSION_MINOR * 100) + LIBTORRENT_VERSION_TINY) -#define LIBTORRENT_VERSION "1.0.0.0" +#define LIBTORRENT_VERSION "1.1.0.0" #define LIBTORRENT_REVISION "$Rev$" #endif diff --git a/include/libtorrent/web_connection_base.hpp b/include/libtorrent/web_connection_base.hpp index 1299b1ebd..a31125772 100644 --- a/include/libtorrent/web_connection_base.hpp +++ b/include/libtorrent/web_connection_base.hpp @@ -76,11 +76,6 @@ namespace libtorrent { class torrent; - namespace detail - { - struct session_impl; - } - class TORRENT_EXTRA_EXPORT web_connection_base : public peer_connection { @@ -91,11 +86,15 @@ namespace libtorrent // The peer_conenction should handshake and verify that the // other end has the correct id web_connection_base( - aux::session_impl& ses + aux::session_interface& ses + , aux::session_settings const& sett + , buffer_allocator_interface& allocator + , disk_interface& disk_thread , boost::weak_ptr t , boost::shared_ptr s - , tcp::endpoint const& remote , web_seed_entry& web); + + virtual int timeout() const; void start(); ~web_connection_base(); @@ -118,12 +117,14 @@ namespace libtorrent virtual void write_request(peer_request const& r) = 0; void write_cancel(peer_request const& r) {} void write_have(int index) {} + void write_dont_have(int index) {} void write_piece(peer_request const& r, disk_buffer_holder& buffer) { TORRENT_ASSERT(false); } void write_keepalive() {} void on_connected(); void write_reject_request(peer_request const&) {} void write_allow_fast(int) {} void write_suggest(int piece) {} + void write_bitfield() {} #if TORRENT_USE_INVARIANT_CHECKS void check_invariant() const; @@ -134,21 +135,8 @@ namespace libtorrent protected: virtual void add_headers(std::string& request - , proxy_settings const& ps, bool using_proxy) const; + , aux::session_settings const& sett, bool using_proxy) const; - // this has one entry per bittorrent request - std::deque m_requests; - - std::string m_server_string; - http_parser m_parser; - std::string m_basic_auth; - std::string m_host; - int m_port; - std::string m_path; - - std::string m_external_auth; - web_seed_entry::headers_t m_extra_headers; - // the first request will contain a little bit more data // than subsequent ones, things that aren't critical are left // out to save bandwidth. @@ -157,6 +145,21 @@ namespace libtorrent // true if we're using ssl bool m_ssl; + // this has one entry per bittorrent request + std::deque m_requests; + + std::string m_server_string; + std::string m_basic_auth; + std::string m_host; + std::string m_path; + + std::string m_external_auth; + web_seed_entry::headers_t m_extra_headers; + + http_parser m_parser; + + int m_port; + // the number of bytes into the receive buffer where // current read cursor is. int m_body_start; diff --git a/include/libtorrent/web_peer_connection.hpp b/include/libtorrent/web_peer_connection.hpp index 40d881eee..fcec076fc 100644 --- a/include/libtorrent/web_peer_connection.hpp +++ b/include/libtorrent/web_peer_connection.hpp @@ -65,11 +65,6 @@ namespace libtorrent { class torrent; - namespace detail - { - struct session_impl; - } - class TORRENT_EXTRA_EXPORT web_peer_connection : public web_connection_base { @@ -80,10 +75,12 @@ namespace libtorrent // The peer_conenction should handshake and verify that the // other end has the correct id web_peer_connection( - aux::session_impl& ses + aux::session_interface& ses + , aux::session_settings const& sett + , buffer_allocator_interface& allocator + , disk_interface& disk_thread , boost::weak_ptr t , boost::shared_ptr s - , tcp::endpoint const& remote , web_seed_entry& web); virtual void on_connected(); @@ -98,7 +95,7 @@ namespace libtorrent std::string const& url() const { return m_url; } virtual void get_specific_peer_info(peer_info& p) const; - virtual void disconnect(error_code const& ec, int error = 0); + virtual void disconnect(error_code const& ec, peer_connection_interface::operation_t op, int error = 0); virtual void write_request(peer_request const& r); @@ -140,9 +137,6 @@ namespace libtorrent // position in the current range response size_type m_range_pos; - // the position in the current block - int m_block_pos; - // this is the offset inside the current receive // buffer where the next chunk header will be. // this is updated for each chunk header that's @@ -151,6 +145,9 @@ namespace libtorrent // it yet. This offset never includes the HTTP header size_type m_chunk_pos; + // the position in the current block + int m_block_pos; + // this is the number of bytes we've already received // from the next chunk header we're waiting for int m_partial_chunk_header; diff --git a/parse_requests.py b/parse_requests.py new file mode 100644 index 000000000..5ca68cfa4 --- /dev/null +++ b/parse_requests.py @@ -0,0 +1,51 @@ + +import os, sys, time + +# logfile format: +# + +f = open(sys.argv[1], 'r') + +out = open('requests.dat', 'w+') + +peers = {} + +num_columns = 1 + +try: + for l in f: + dat = l.split('\t') + + peer = dat[2] + time = dat[0] + tor = dat[1] + piece = dat[3] + offset = dat[4] + if tor != sys.argv[2]: continue + + if peer not in peers: + num_columns += 1 + peers[peer] = num_columns + + print >>out, '%s %s %s' % (dat[0], ' -' * (peers[peer] - 2), float(piece) + float(offset) / (128.0 * 16.0 * 1024.0)) +except: + pass + +out.close() + +out = open('peer_requests.gnuplot', 'wb') +print >>out, "set term png size 12000,7000" +print >>out, 'set output "peer_requests.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 p in peers: + print >>out, ' "requests.dat" using 1:%d title "%s" with points,' % (peers[p], p), + +print >>out, 'x=0' + +os.system('gnuplot peer_requests.gnuplot') diff --git a/src/Makefile.am b/src/Makefile.am index f323fc382..41d25c175 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -13,6 +13,7 @@ KADEMLIA_SOURCES = \ kademlia/rpc_manager.cpp \ kademlia/logging.cpp \ kademlia/traversal_algorithm.cpp \ + kademlia/dos_blocker.cpp \ kademlia/get_peers.cpp \ kademlia/get_item.cpp \ kademlia/item.cpp \ @@ -37,7 +38,7 @@ ASIO_OPENSSL_SOURCES = asio_ssl.cpp endif libtorrent_rasterbar_la_SOURCES = \ - web_connection_base.cpp \ + web_connection_base.cpp \ alert.cpp \ alert_manager.cpp \ allocator.cpp \ @@ -48,14 +49,18 @@ libtorrent_rasterbar_la_SOURCES = \ bandwidth_queue_entry.cpp \ bloom_filter.cpp \ broadcast_socket.cpp \ + block_cache.cpp \ bt_peer_connection.cpp \ chained_buffer.cpp \ connection_queue.cpp \ ConvertUTF.cpp \ + crc32c.cpp \ create_torrent.cpp \ disk_buffer_holder.cpp \ disk_buffer_pool.cpp \ + disk_io_job.cpp \ disk_io_thread.cpp \ + disk_job_pool.cpp \ entry.cpp \ enum_net.cpp \ error_code.cpp \ @@ -84,29 +89,42 @@ libtorrent_rasterbar_la_SOURCES = \ mpi.c \ natpmp.cpp \ parse_url.cpp \ + part_file.cpp \ pe_crypto.cpp \ + performance_counters.cpp \ peer_connection.cpp \ + peer_class.cpp \ + peer_class_set.cpp \ piece_picker.cpp \ + platform_util.cpp \ packet_buffer.cpp \ + proxy_base.cpp \ policy.cpp \ puff.cpp \ random.cpp \ + request_blocks.cpp \ + resolver.cpp \ rss.cpp \ session.cpp \ session_impl.cpp \ - settings.cpp \ + settings_pack.cpp \ sha1.cpp \ smart_ban.cpp \ socket_io.cpp \ socket_type.cpp \ socks5_stream.cpp \ stat.cpp \ + stat_cache.cpp \ storage.cpp \ + session_stats.cpp \ string_util.cpp \ + tailqueue.cpp \ thread.cpp \ torrent.cpp \ torrent_handle.cpp \ torrent_info.cpp \ + torrent_peer.cpp \ + torrent_peer_allocator.cpp \ time.cpp \ timestamp_history.cpp \ tracker_manager.cpp \ diff --git a/src/alert.cpp b/src/alert.cpp index e414e9439..049cf5463 100644 --- a/src/alert.cpp +++ b/src/alert.cpp @@ -41,6 +41,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/error_code.hpp" #include "libtorrent/escape_string.hpp" #include "libtorrent/extensions.hpp" +#include "libtorrent/torrent.hpp" #include namespace libtorrent { @@ -49,18 +50,22 @@ namespace libtorrent { alert::~alert() {} ptime alert::timestamp() const { return m_timestamp; } + torrent_alert::torrent_alert(torrent_handle const& h) + : handle(h) + , name(h.native_handle() ? h.native_handle()->name() : "") + { + if (name.empty() && h.is_valid()) + { + char msg[41]; + to_hex((char const*)&h.native_handle()->info_hash()[0], 20, msg); + name = msg; + } + } std::string torrent_alert::message() const { if (!handle.is_valid()) return " - "; - torrent_status st = handle.status(torrent_handle::query_name); - if (st.name.empty()) - { - char msg[41]; - to_hex((char const*)&st.info_hash[0], 20, msg); - return msg; - } - return st.name; + return name; } std::string peer_alert::message() const @@ -127,6 +132,7 @@ namespace libtorrent { "too many optimistic unchoke slots", "using bittyrant unchoker with no upload rate limit set", "the disk queue limit is too high compared to the cache size. The disk queue eats into the cache size", + "outstanding AIO operations limit reached", "too few ports allowed for outgoing connections", "too few file descriptors are allowed for this process. connection limit lowered" }; @@ -297,9 +303,9 @@ namespace libtorrent { { "TCP", "TCP/SSL", "UDP", "I2P", "Socks5" }; - char ret[250]; + char ret[300]; snprintf(ret, sizeof(ret), "listening on %s failed: [%s] [%s] %s" - , print_endpoint(endpoint).c_str() + , interface.c_str() , op_str[operation] , type_str[sock_type] , convert_from_native(error.message()).c_str()); @@ -353,7 +359,8 @@ namespace libtorrent { "i2p_mixed", "privileged_ports", "utp_disabled", - "tcp_disabled" + "tcp_disabled", + "invalid_local_interface" }; snprintf(ret, sizeof(ret), "%s: blocked peer: %s [%s]" @@ -387,8 +394,16 @@ namespace libtorrent { : torrent_alert(h) , interval(in) { +#ifndef TORRENT_DISABLE_FULL_STATS for (int i = 0; i < num_channels; ++i) transferred[i] = s[i].counter(); +#else + const int len = download_protocol + 1; + for (int i = 0; i < download_protocol + 1; ++i) + transferred[i] = s[i].counter(); + for (int i = len; i < num_channels; ++i) + transferred[i] = 0; +#endif } std::string stats_alert::message() const @@ -531,6 +546,57 @@ namespace libtorrent { return msg; } + std::string mmap_cache_alert::message() const + { + char msg[600]; + snprintf(msg, sizeof(msg), "mmap cache failed: (%d) %s", error.value(), error.message().c_str()); + return msg; + } + + std::string session_stats_alert::message() const + { + char msg[100]; + snprintf(msg, sizeof(msg), "session stats (%d values)", int(values.size())); + return msg; + } + + std::string peer_error_alert::message() const + { + char msg[200]; + snprintf(msg, sizeof(msg), "%s peer error [%s] [%s]: %s", peer_alert::message().c_str() + , operation_name(operation), error.category().name(), convert_from_native(error.message()).c_str()); + return msg; + } + + char const* operation_name(int op) + { + static char const* names[] = { + "bittorrent", + "iocontrol", + "getpeername", + "getname", + "alloc_recvbuf", + "alloc_sndbuf", + "file_write", + "file_read", + "file", + "sock_write", + "sock_read", + "sock_open", + "sock_bind", + "available", + "encryption", + "connect", + "ssl_handshake", + "get_interface", + }; + + if (op < 0 || op >= sizeof(names)/sizeof(names[0])) + return "unknown operation"; + + return names[op]; + } + std::string torrent_update_alert::message() const { char msg[200]; @@ -552,8 +618,8 @@ namespace libtorrent { std::string peer_disconnected_alert::message() const { char msg[600]; - snprintf(msg, sizeof(msg), "%s disconnecting: [%s] %s", peer_alert::message().c_str() - , error.category().name(), convert_from_native(error.message()).c_str()); + snprintf(msg, sizeof(msg), "%s disconnecting [%s] [%s]: %s", peer_alert::message().c_str() + , operation_name(operation), error.category().name(), convert_from_native(error.message()).c_str()); return msg; } diff --git a/src/allocator.cpp b/src/allocator.cpp index 4697293b7..dd21b632c 100644 --- a/src/allocator.cpp +++ b/src/allocator.cpp @@ -32,13 +32,13 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/allocator.hpp" #include "libtorrent/config.hpp" -#include "libtorrent/assert.hpp" +#include "libtorrent/assert.hpp" // for print_backtrace #if defined TORRENT_BEOS #include #include // malloc/free #elif !defined TORRENT_WINDOWS -#include // valloc/free +#include // posix_memalign/free #include // _SC_PAGESIZE #endif @@ -197,6 +197,14 @@ namespace libtorrent #endif // TORRENT_WINDOWS } +#ifdef TORRENT_DEBUG_BUFFERS + bool page_aligned_allocator::in_use(char const* block) + { + int page = page_size(); + alloc_header* h = (alloc_header*)(block - page); + return h->magic == 0x1337; + } +#endif } diff --git a/src/assert.cpp b/src/assert.cpp index ed12ad367..f3f357893 100644 --- a/src/assert.cpp +++ b/src/assert.cpp @@ -33,10 +33,10 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/config.hpp" #if TORRENT_PRODUCTION_ASSERTS -#include +#include "libtorrent/atomic.hpp" #endif -#if defined TORRENT_DEBUG || defined TORRENT_ASIO_DEBUGGING || TORRENT_RELEASE_ASSERTS +#if (defined TORRENT_DEBUG && !TORRENT_NO_ASSERTS) || defined TORRENT_ASIO_DEBUGGING || defined TORRENT_PROFILE_CALLS || TORRENT_RELEASE_ASSERTS #ifdef __APPLE__ #include @@ -45,6 +45,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include // uClibc++ doesn't have cxxabi.h #if defined __GNUC__ && __GNUC__ >= 3 \ @@ -200,7 +201,8 @@ TORRENT_EXPORT void print_backtrace(char* out, int len, int max_depth) #else -TORRENT_EXPORT void print_backtrace(char* out, int len, int max_depth) {} +TORRENT_EXPORT void print_backtrace(char* out, int len, int max_depth) +{ out[0] = 0; } #endif @@ -211,20 +213,35 @@ TORRENT_EXPORT void print_backtrace(char* out, int len, int max_depth) {} #if TORRENT_PRODUCTION_ASSERTS char const* libtorrent_assert_log = "asserts.log"; // the number of asserts we've printed to the log -boost::detail::atomic_count assert_counter(0); +libtorrent::atomic_count assert_counter(0); #endif +TORRENT_EXPORT void assert_print(char const* fmt, ...) +{ +#if TORRENT_PRODUCTION_ASSERTS + if (assert_counter > 500) return; + + FILE* out = fopen(libtorrent_assert_log, "a+"); + if (out == 0) out = stderr; +#else + FILE* out = stderr; +#endif + va_list va; + va_start(va, fmt); + vfprintf(out, fmt, va); + va_end(va); + +#if TORRENT_PRODUCTION_ASSERTS + if (out != stderr) fclose(out); +#endif +} + TORRENT_EXPORT void assert_fail(char const* expr, int line, char const* file , char const* function, char const* value, int kind) { #if TORRENT_PRODUCTION_ASSERTS // no need to flood the assert log with infinite number of asserts if (++assert_counter > 500) return; - - FILE* out = fopen(libtorrent_assert_log, "a+"); - if (out == 0) out = stderr; -#else - FILE* out = stderr; #endif char stack[8192]; @@ -244,7 +261,10 @@ TORRENT_EXPORT void assert_fail(char const* expr, int line, char const* file "This indicates a bug in the client application using libtorrent\n"; } - fprintf(out, "%s\n" + assert_print("%s\n" +#if TORRENT_PRODUCTION_ASSERTS + "#: %d\n" +#endif "file: '%s'\n" "line: %d\n" "function: %s\n" @@ -252,14 +272,16 @@ TORRENT_EXPORT void assert_fail(char const* expr, int line, char const* file "%s%s\n" "stack:\n" "%s\n" - , message, file, line, function, expr + , message +#if TORRENT_PRODUCTION_ASSERTS + , int(assert_counter) +#endif + , file, line, function, expr , value ? value : "", value ? "\n" : "" , stack); // if production asserts are defined, don't abort, just print the error -#if TORRENT_PRODUCTION_ASSERTS - if (out != stderr) fclose(out); -#else +#ifndef TORRENT_PRODUCTION_ASSERTS // send SIGINT to the current process // to break into the debugger raise(SIGINT); @@ -269,6 +291,7 @@ TORRENT_EXPORT void assert_fail(char const* expr, int line, char const* file #else +TORRENT_EXPORT void assert_print(char const* fmt, ...) {} TORRENT_EXPORT void assert_fail(char const* expr, int line, char const* file , char const* function, char const* value, int kind) {} diff --git a/src/bandwidth_manager.cpp b/src/bandwidth_manager.cpp index 8ad2a7e3c..9b8cdce83 100644 --- a/src/bandwidth_manager.cpp +++ b/src/bandwidth_manager.cpp @@ -93,36 +93,20 @@ namespace libtorrent // non prioritized means that, if there's a line for bandwidth, // others will cut in front of the non-prioritized peers. // this is used by web seeds - int bandwidth_manager::request_bandwidth(boost::intrusive_ptr const& peer - , int blk, int priority - , bandwidth_channel* chan1 - , bandwidth_channel* chan2 - , bandwidth_channel* chan3 - , bandwidth_channel* chan4 - , bandwidth_channel* chan5 - ) + int bandwidth_manager::request_bandwidth(boost::shared_ptr const& peer + , int blk, int priority, bandwidth_channel** chan, int num_channels) { INVARIANT_CHECK; if (m_abort) return 0; TORRENT_ASSERT(blk > 0); TORRENT_ASSERT(priority > 0); + + // if this assert is hit, the peer is requesting more bandwidth before + // being assigned bandwidth for an already outstanding request TORRENT_ASSERT(!is_queued(peer.get())); - bw_request bwr(peer, blk, priority); - int i = 0; - if (chan1 && chan1->throttle() > 0 && chan1->need_queueing(blk)) - bwr.channel[i++] = chan1; - if (chan2 && chan2->throttle() > 0 && chan2->need_queueing(blk)) - bwr.channel[i++] = chan2; - if (chan3 && chan3->throttle() > 0 && chan3->need_queueing(blk)) - bwr.channel[i++] = chan3; - if (chan4 && chan4->throttle() > 0 && chan4->need_queueing(blk)) - bwr.channel[i++] = chan4; - if (chan5 && chan5->throttle() > 0 && chan5->need_queueing(blk)) - bwr.channel[i++] = chan5; - - if (i == 0) + if (num_channels == 0) { // the connection is not rate limited by any of its // bandwidth channels, or it doesn't belong to any @@ -131,6 +115,16 @@ namespace libtorrent return blk; } + int k = 0; + bw_request bwr(peer, blk, priority); + for (int i = 0; i < num_channels; ++i) + { + if (chan[i]->need_queueing(blk)) + bwr.channel[k++] = chan[i]; + } + + if (k == 0) return blk; + m_queued_bytes += blk; m_queue.push_back(bwr); return 0; diff --git a/src/bandwidth_queue_entry.cpp b/src/bandwidth_queue_entry.cpp index 0be7e3c0f..b6f64c6e2 100644 --- a/src/bandwidth_queue_entry.cpp +++ b/src/bandwidth_queue_entry.cpp @@ -37,7 +37,7 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { - bw_request::bw_request(boost::intrusive_ptr const& pe + bw_request::bw_request(boost::shared_ptr const& pe , int blk, int prio) : peer(pe) , priority(prio) diff --git a/src/block_cache.cpp b/src/block_cache.cpp new file mode 100644 index 000000000..06a2ee82e --- /dev/null +++ b/src/block_cache.cpp @@ -0,0 +1,1801 @@ +/* + +Copyright (c) 2010-2013, 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. + +*/ + +#include "libtorrent/block_cache.hpp" +#include "libtorrent/disk_buffer_pool.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/disk_io_job.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/io_service_fwd.hpp" +#include "libtorrent/error.hpp" +#include "libtorrent/disk_io_thread.hpp" // disk_operation_failed +#include "libtorrent/invariant_check.hpp" +#include "libtorrent/alloca.hpp" +#include "libtorrent/alert_dispatcher.hpp" +#include "libtorrent/performance_counters.hpp" + +#if TORRENT_USE_PURGABLE_CONTROL +#include +// see comments at: +// http://www.opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/vm/vm_object.c +#endif + +/* + + The disk cache mimics ARC (adaptive replacement cache). + See paper: http://dbs.uni-leipzig.de/file/ARC.pdf + See slides: http://www-vlsi.stanford.edu/smart_memories/protected/meetings/spring2004/arc-fast.pdf + + This cache has a few modifications to make it fit the bittorrent use + case better. It has a few more lists and it deferres the eviction + of pieces. + + read_lru1 + This is a plain LRU for items that have been requested once. If a piece + in this list gets accessed again, by someone other than the first + accessor, the piece is promoted into LRU2. which holds pieces that are + more frequently used, and more important to keep around as this LRU list + takes churn. + + read_lru1_ghost + This is a list of pieces that were least recently evicted from read_lru1. + These pieces don't hold any actual blocks in the cache, they are just + here to extend the reach and probability for pieces to be promoted into + read_lru2. Any piece in this list that get one more access is promoted to + read_lru2. This is technically a cache-miss, since there's no cached + blocks here, but for the purposes of promoting the piece from + infrequently used to frequently used), it's considered a cache-hit. + + read_lru2 + TODO + + read_lru2_ghost + TODO + + volatile_read_lru + TODO + + write_lru + TODO + + Cache hits + .......... + + When a piece get a cache hit, it's promoted, either to the beginning of the + lru2 or into lru2. Since this ARC implementation operates on pieces instead + of blocks, any one peer requesting blocks from one piece would essentially + always produce a "cache hit" the second block it requests. In order to make + the promotions make more sense, and be more in the spirit of the ARC + algorithm, each access contains a token, unique to each peer. If any access + has a different token than the last one, it's considered a cache hit. This + is because at least two peers requested blocks from the same piece. + + Deferred evictions + .................. + + Since pieces and blocks can be pinned in the cache, and it's not always + practical, or possible, to evict a piece at the point where a new block is + allocated (because it's not known what the block will be used for), + evictions are not done at the time of allocating blocks. Instead, whenever + an operation requires to add a new piece to the cache, it also records the + cache event leading to it, in m_last_cache_op. This is one of cache_miss + (piece did not exist in cache), lru1_ghost_hit (the piece was found in + lru1_ghost and it was promoted) or lru2_ghost_hit (the piece was found in + lru2_ghost and it was promoted). This cache operation then guides the cache + eviction algorithm to know which list to evict from. The volatile list is + always the first one to be evicted however. + + Write jobs + .......... + + When the write cache is enabled, write jobs are not issued via the normal + job queue. They are just hung on its corresponding cached piece entry, and a + flush_hashed job is issued. This job will inspect the current state of the + cached piece and determine if any of the blocks should be flushed. It also + kicks the hasher, i.e. progresses the SHA1 context, which calculates the + SHA-1 hash of the piece. This job flushed blocks that have been hashed and + also form a contiguous block run of at least the write cache line size. + + Read jobs + ......... + + The data blocks pulled in from disk by read jobs, are hung on the + corresponding cache piece (cached_piece_entry) once the operation completes. + Read operations typically pulls in an entire read cache stripe, and not just + the one block that was requested. When adjacent blocks are requested to be + read in quick succession, there is a risk that each block would pull in more + blocks (read ahead) and potentially read the same blocks several times, if + the original requests were serviced by different disk thread. This is + because all the read operation may start before any of them has completed, + hanging the resulting blocks in the cache. i.e. they would all be cache + misses, even though all but the first should be cache hits in the first's + read ahead. + + In order to solve this problem, there is only a single outstanding read job + at any given time per piece. When there is an outstanding read job on a + piece, the *outstanding_read* member is set to 1. This indicates that the + job should be hung on the piece for later processing, instead of being + issued into the main job queue. There is a tailqueue on each piece entry + called read_jobs where these jobs are added. + + At the end of every read job, this job list is inspected, any job in it is + tried against the cache to see if it's a cache hit now. If it is, complete + it right away. If it isn't, put it back in the read_jobs list except for + one, which is issued into the regular job queue. +*/ + +#define DEBUG_CACHE 0 + +#define DLOG if (DEBUG_CACHE) fprintf + +namespace libtorrent { + +#if DEBUG_CACHE +void log_refcounts(cached_piece_entry const* pe) +{ + char out[4096]; + char* ptr = out; + char* end = ptr + sizeof(out); + ptr += snprintf(ptr, end - ptr, "piece: %d [ ", int(pe->piece)); + for (int i = 0; i < pe->blocks_in_piece; ++i) + { + ptr += snprintf(ptr, end - ptr, "%d ", int(pe->blocks[i].refcount)); + } + strncpy(ptr, "]\n", end - ptr); + DLOG(stderr, out); +} +#endif + +#if TORRENT_USE_ASSERTS + + char const* piece_log_t::job_names[7] = + { + "flushing", + "flush_expired", + "try_flush_write_blocks", + "try_flush_write_blocks2", + "flush_range", + "clear_outstanding_jobs", + "set_outstanding_jobs", + }; + + void print_piece_log(std::vector const& piece_log) + { + for (int i = 0; i < int(piece_log.size()); ++i) + { + if (piece_log[i].block == -1) + { + printf("%d: %s\n", i, job_name(piece_log[i].job)); + } + else + { + printf("%d: %s %d\n", i, job_name(piece_log[i].job), piece_log[i].block); + } + } + } + +// defined in disk_io_thread.cpp + void assert_print_piece(cached_piece_entry const* pe); + +#define TORRENT_PIECE_ASSERT(cond, piece) \ + do { if (!(cond)) { assert_print_piece(piece); assert_fail(#cond, __LINE__, __FILE__, TORRENT_FUNCTION, 0); } } while(false) + +#else +#define TORRENT_PIECE_ASSERT(cond, piece) do {} while(false) +#endif + +cached_piece_entry::cached_piece_entry() + : storage() + , hash(0) + , last_requester(NULL) + , blocks() + , expire(min_time()) + , piece(0) + , num_dirty(0) + , num_blocks(0) + , blocks_in_piece(0) + , hashing(0) + , hashing_done(0) + , marked_for_deletion(false) + , need_readback(false) + , cache_state(read_lru1) + , piece_refcount(0) + , outstanding_flush(0) + , outstanding_read(0) + , pinned(0) + , refcount(0) +#if TORRENT_USE_ASSERTS + , hash_passes(0) + , in_storage(false) + , in_use(true) +#endif +{} + +cached_piece_entry::~cached_piece_entry() +{ + TORRENT_ASSERT(piece_refcount == 0); + TORRENT_ASSERT(jobs.size() == 0); + TORRENT_ASSERT(read_jobs.size() == 0); +#if TORRENT_USE_ASSERTS + for (int i = 0; i < blocks_in_piece; ++i) + { + TORRENT_ASSERT(blocks[i].buf == 0); + TORRENT_ASSERT(!blocks[i].pending); + TORRENT_ASSERT(blocks[i].refcount == 0); + TORRENT_ASSERT(blocks[i].hashing_count == 0); + TORRENT_ASSERT(blocks[i].flushing_count == 0); + } + in_use = false; +#endif + delete hash; +} + +block_cache::block_cache(int block_size, io_service& ios + , boost::function const& trigger_trim + , alert_dispatcher* alert_disp) + : disk_buffer_pool(block_size, ios, trigger_trim, alert_disp) + , m_last_cache_op(cache_miss) + , m_ghost_size(8) + , m_read_cache_size(0) + , m_write_cache_size(0) + , m_send_buffer_blocks(0) + , m_blocks_read_hit(0) + , m_pinned_blocks(0) +{} + +// returns: +// -1: not in cache +// -2: no memory +int block_cache::try_read(disk_io_job* j, bool count_stats, bool expect_no_fail) +{ + INVARIANT_CHECK; + + TORRENT_ASSERT(j->buffer == 0); + +#if TORRENT_USE_ASSERTS + // we're not allowed to add dirty blocks + // for a deleted storage! + TORRENT_ASSERT(std::find(m_deleted_storages.begin(), m_deleted_storages.end() + , std::make_pair(j->storage->files()->name(), (void const*)j->storage->files())) + == m_deleted_storages.end()); +#endif + + cached_piece_entry* p = find_piece(j); + + int ret = 0; + + // if the piece cannot be found in the cache, + // it's a cache miss + TORRENT_ASSERT(!expect_no_fail || p != NULL); + if (p == 0) return -1; + +#if TORRENT_USE_ASSERTS + p->piece_log.push_back(piece_log_t(j->action, j->d.io.offset / 0x4000)); +#endif + cache_hit(p, j->requester, j->flags & disk_io_job::volatile_read); + + ret = copy_from_piece(p, j, expect_no_fail); + if (ret < 0) return ret; + + ret = j->d.io.buffer_size; + if (count_stats) + ++m_blocks_read_hit; + return ret; +} + +void block_cache::bump_lru(cached_piece_entry* p) +{ + // move to the top of the LRU list + TORRENT_PIECE_ASSERT(p->cache_state == cached_piece_entry::write_lru, p); + linked_list* lru_list = &m_lru[p->cache_state]; + + // move to the back (MRU) of the list + lru_list->erase(p); + lru_list->push_back(p); + p->expire = time_now(); +} + +// this is called for pieces that we're reading from, when they +// are in the cache (including the ghost lists) +void block_cache::cache_hit(cached_piece_entry* p, void* requester, bool volatile_read) +{ +// this can be pretty expensive +// INVARIANT_CHECK; + + // move the piece into this queue. Whenever we have a cahe + // hit, we move the piece into the lru2 queue (i.e. the most + // frequently used piece). However, we only do that if the + // requester is different than the last one. This is to + // avoid a single requester making it look like a piece is + // frequently requested, when in fact it's only a single peer + int target_queue = cached_piece_entry::read_lru2; + + if (p->last_requester == requester || requester == NULL) + { + // if it's the same requester and the piece isn't in + // any of the ghost lists, ignore it + if (p->cache_state == cached_piece_entry::read_lru1 + || p->cache_state == cached_piece_entry::read_lru2 + || p->cache_state == cached_piece_entry::write_lru + || p->cache_state == cached_piece_entry::volatile_read_lru) + return; + + if (p->cache_state == cached_piece_entry::read_lru1_ghost) + target_queue = cached_piece_entry::read_lru1; + } + + if (p->cache_state == cached_piece_entry::volatile_read_lru) + { + // a volatile read hit on a volatile piece doesn't do anything + if (volatile_read) return; + + // however, if this is a proper read on a volatile piece + // we need to promote it to lru1 + target_queue = cached_piece_entry::read_lru1; + } + + if (requester != NULL) + p->last_requester = requester; + + // if we have this piece anywhere in L1 or L2, it's a "hit" + // and it should be bumped to the highest priority in L2 + // i.e. "frequently used" + if (p->cache_state < cached_piece_entry::read_lru1 + || p->cache_state > cached_piece_entry::read_lru2_ghost) + return; + + // if we got a cache hit in a ghost list, that indicates the proper + // list is too small. Record which ghost list we got the hit in and + // it will be used to determine which end of the cache we'll evict + // from, next time we need to reclaim blocks + if (p->cache_state == cached_piece_entry::read_lru1_ghost) + { + m_last_cache_op = ghost_hit_lru1; + p->storage->add_piece(p); + } + else if (p->cache_state == cached_piece_entry::read_lru2_ghost) + { + m_last_cache_op = ghost_hit_lru2; + p->storage->add_piece(p); + } + + // move into L2 (frequently used) + m_lru[p->cache_state].erase(p); + m_lru[target_queue].push_back(p); + p->cache_state = target_queue; + p->expire = time_now(); +#if TORRENT_USE_ASSERTS + switch (p->cache_state) + { + case cached_piece_entry::write_lru: + case cached_piece_entry::volatile_read_lru: + case cached_piece_entry::read_lru1: + case cached_piece_entry::read_lru2: + TORRENT_ASSERT(p->in_storage == true); + break; + default: + TORRENT_ASSERT(p->in_storage == false); + break; + } +#endif +} + +// this is used to move pieces primarily from the write cache +// to the read cache. Technically it can move from read to write +// cache as well, it's unclear if that ever happens though +void block_cache::update_cache_state(cached_piece_entry* p) +{ + int state = p->cache_state; + int desired_state = p->cache_state; + if (p->num_dirty > 0 || p->hash != 0) + desired_state = cached_piece_entry::write_lru; + else if (p->cache_state == cached_piece_entry::write_lru) + desired_state = cached_piece_entry::read_lru1; + + if (desired_state == state) return; + + TORRENT_PIECE_ASSERT(state < cached_piece_entry::num_lrus, p); + TORRENT_PIECE_ASSERT(desired_state < cached_piece_entry::num_lrus, p); + linked_list* src = &m_lru[state]; + linked_list* dst = &m_lru[desired_state]; + + src->erase(p); + dst->push_back(p); + p->expire = time_now(); + p->cache_state = desired_state; +#if TORRENT_USE_ASSERTS + switch (p->cache_state) + { + case cached_piece_entry::write_lru: + case cached_piece_entry::volatile_read_lru: + case cached_piece_entry::read_lru1: + case cached_piece_entry::read_lru2: + TORRENT_ASSERT(p->in_storage == true); + break; + default: + TORRENT_ASSERT(p->in_storage == false); + break; + } +#endif +} + +cached_piece_entry* block_cache::allocate_piece(disk_io_job const* j, int cache_state) +{ +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + + TORRENT_ASSERT(cache_state < cached_piece_entry::num_lrus); + + // we're assuming we're not allocating a ghost piece + // a bit further down + TORRENT_ASSERT(cache_state != cached_piece_entry::read_lru1_ghost + && cache_state != cached_piece_entry::read_lru2_ghost); + + cached_piece_entry* p = find_piece(j); + if (p == 0) + { + int piece_size = j->storage->files()->piece_size(j->piece); + int blocks_in_piece = (piece_size + block_size() - 1) / block_size(); + + cached_piece_entry pe; + pe.piece = j->piece; + pe.storage = j->storage; + pe.expire = time_now(); + pe.blocks_in_piece = blocks_in_piece; + pe.blocks.reset(new (std::nothrow) cached_block_entry[blocks_in_piece]); + pe.cache_state = cache_state; + pe.last_requester = j->requester; + TORRENT_PIECE_ASSERT(pe.blocks, &pe); + if (!pe.blocks) return 0; + p = const_cast(&*m_pieces.insert(pe).first); + + j->storage->add_piece(p); + + TORRENT_PIECE_ASSERT(p->cache_state < cached_piece_entry::num_lrus, p); + linked_list* lru_list = &m_lru[p->cache_state]; + lru_list->push_back(p); + + // this piece is part of the ARC cache (as opposed to + // the write cache). Allocating a new read piece indicates + // that we just got a cache miss. Record this to determine + // which end to evict blocks from next time we need to + // evict blocks + if (cache_state == cached_piece_entry::read_lru1) + m_last_cache_op = cache_miss; + +#if TORRENT_USE_ASSERTS + switch (p->cache_state) + { + case cached_piece_entry::write_lru: + case cached_piece_entry::volatile_read_lru: + case cached_piece_entry::read_lru1: + case cached_piece_entry::read_lru2: + TORRENT_ASSERT(p->in_storage == true); + break; + default: + TORRENT_ASSERT(p->in_storage == false); + break; + } +#endif + } + else + { + TORRENT_PIECE_ASSERT(p->in_use, p); + + // we want to retain the piece now + p->marked_for_deletion = false; + + // only allow changing the cache state downwards. i.e. turn a ghost + // piece into a non-ghost, or a read piece into a write piece + if (p->cache_state > cache_state) + { + // this can happen for instance if a piece fails the hash check + // first it's in the write cache, then it completes and is moved + // into the read cache, but fails and is cleared (into the ghost list) + // then we want to add new dirty blocks to it and we need to move + // it back into the write cache + + // it also happens when pulling a ghost piece back into the proper cache + + if (p->cache_state == cached_piece_entry::read_lru1_ghost + || p->cache_state == cached_piece_entry::read_lru2_ghost) + { + // since it used to be a ghost piece, but no more, + // we need to add it back to the storage + p->storage->add_piece(p); + } + m_lru[p->cache_state].erase(p); + p->cache_state = cache_state; + m_lru[p->cache_state].push_back(p); + p->expire = time_now(); +#if TORRENT_USE_ASSERTS + switch (p->cache_state) + { + case cached_piece_entry::write_lru: + case cached_piece_entry::volatile_read_lru: + case cached_piece_entry::read_lru1: + case cached_piece_entry::read_lru2: + TORRENT_ASSERT(p->in_storage == true); + break; + default: + TORRENT_ASSERT(p->in_storage == false); + break; + } +#endif + } + } + + return p; +} + +#if TORRENT_USE_ASSERTS +void block_cache::mark_deleted(file_storage const& fs) +{ + m_deleted_storages.push_back(std::make_pair(fs.name(), (void const*)&fs)); + if(m_deleted_storages.size() > 100) + m_deleted_storages.erase(m_deleted_storages.begin()); +} +#endif + +cached_piece_entry* block_cache::add_dirty_block(disk_io_job* j) +{ +#if !defined TORRENT_DISABLE_POOL_ALLOCATOR + TORRENT_ASSERT(is_disk_buffer(j->buffer)); +#endif +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + INVARIANT_CHECK; +#endif + +#if TORRENT_USE_ASSERTS + // we're not allowed to add dirty blocks + // for a deleted storage! + TORRENT_ASSERT(std::find(m_deleted_storages.begin(), m_deleted_storages.end() + , std::make_pair(j->storage->files()->name(), (void const*)j->storage->files())) + == m_deleted_storages.end()); +#endif + + TORRENT_ASSERT(j->buffer); + TORRENT_ASSERT(m_write_cache_size + m_read_cache_size + 1 <= in_use()); + + cached_piece_entry* pe = allocate_piece(j, cached_piece_entry::write_lru); + TORRENT_ASSERT(pe); + if (pe == 0) return pe; + + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + int block = j->d.io.offset / block_size(); + TORRENT_ASSERT((j->d.io.offset % block_size()) == 0); + + // we should never add a new dirty block on a piece + // that has checked the hash. Before we add it, the + // piece need to be cleared (with async_clear_piece) + TORRENT_PIECE_ASSERT(pe->hashing_done == 0, pe); + + // this only evicts read blocks + + int evict = num_to_evict(1); + if (evict > 0) try_evict_blocks(evict, pe); + + TORRENT_PIECE_ASSERT(block < pe->blocks_in_piece, pe); + TORRENT_PIECE_ASSERT(j->piece == pe->piece, pe); + TORRENT_PIECE_ASSERT(!pe->marked_for_deletion, pe); + + TORRENT_PIECE_ASSERT(pe->blocks[block].refcount == 0, pe); + + cached_block_entry& b = pe->blocks[block]; + + TORRENT_PIECE_ASSERT(b.buf != j->buffer, pe); + + // we might have a left-over read block from + // hash checking + // we might also have a previous dirty block which + // we're still waiting for to be written + if (b.buf != 0 && b.buf != j->buffer) + { + TORRENT_PIECE_ASSERT(b.refcount == 0 && !b.pending, pe); + free_block(pe, block); + TORRENT_PIECE_ASSERT(b.dirty == 0, pe); + } + + b.buf = j->buffer; +#ifdef TORRENT_BUFFER_STATS + rename_buffer(j->buffer, "write cache"); +#endif + + b.dirty = true; + ++pe->num_blocks; + ++pe->num_dirty; + ++m_write_cache_size; + j->buffer = 0; + TORRENT_PIECE_ASSERT(j->piece == pe->piece, pe); + TORRENT_PIECE_ASSERT(j->flags & disk_io_job::in_progress, pe); + TORRENT_PIECE_ASSERT(j->piece == pe->piece, pe); + pe->jobs.push_back(j); + + if (block == 0 && pe->hash == NULL && pe->hashing_done == false) + pe->hash = new partial_hash; + + update_cache_state(pe); + + bump_lru(pe); + + return pe; +} + +// flushed is an array of num_flushed integers. Each integer is the block index +// that was flushed. This function marks those blocks as not pending and not +// dirty. It also adjusts its understanding of the read vs. write cache size +// (since these blocks now are part of the read cache) the refcounts of the +// blocks are also decremented by this function. They are expected to have been +// incremented by the caller. +void block_cache::blocks_flushed(cached_piece_entry* pe, int const* flushed, int num_flushed) +{ + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + for (int i = 0; i < num_flushed; ++i) + { + int block = flushed[i]; + TORRENT_PIECE_ASSERT(block >= 0, pe); + TORRENT_PIECE_ASSERT(block < pe->blocks_in_piece, pe); + TORRENT_PIECE_ASSERT(pe->blocks[block].dirty, pe); + TORRENT_PIECE_ASSERT(pe->blocks[block].pending, pe); + pe->blocks[block].pending = false; + // it's important to mark it as non-dirty before decrementing the + // refcount because the buffer may be marked as discardable/volatile it + // this is the last reference to it + pe->blocks[block].dirty = false; + dec_block_refcount(pe, block, block_cache::ref_flushing); +#ifdef TORRENT_BUFFER_STATS + rename_buffer(pe->blocks[block].buf, "read cache"); +#endif + } + + m_write_cache_size -= num_flushed; + m_read_cache_size += num_flushed; + pe->num_dirty -= num_flushed; + + update_cache_state(pe); +} + +std::pair block_cache::all_pieces() const +{ + return std::make_pair(m_pieces.begin(), m_pieces.end()); +} + +void block_cache::free_block(cached_piece_entry* pe, int block) +{ + TORRENT_ASSERT(pe != 0); + TORRENT_PIECE_ASSERT(pe->in_use, pe); + TORRENT_PIECE_ASSERT(block < pe->blocks_in_piece, pe); + TORRENT_PIECE_ASSERT(block >= 0, pe); + + cached_block_entry& b = pe->blocks[block]; + + TORRENT_PIECE_ASSERT(b.refcount == 0, pe); + TORRENT_PIECE_ASSERT(!b.pending, pe); + TORRENT_PIECE_ASSERT(b.buf, pe); + + if (b.dirty) + { + --pe->num_dirty; + b.dirty = false; + TORRENT_PIECE_ASSERT(m_write_cache_size > 0, pe); + --m_write_cache_size; + } + else + { + TORRENT_PIECE_ASSERT(m_read_cache_size > 0, pe); + --m_read_cache_size; + } + TORRENT_PIECE_ASSERT(pe->num_blocks > 0, pe); + --pe->num_blocks; + free_buffer(b.buf); + b.buf = NULL; +} + +bool block_cache::evict_piece(cached_piece_entry* pe, tailqueue& jobs) +{ + INVARIANT_CHECK; + + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + char** to_delete = TORRENT_ALLOCA(char*, pe->blocks_in_piece); + int num_to_delete = 0; + for (int i = 0; i < pe->blocks_in_piece; ++i) + { + if (pe->blocks[i].buf == 0 || pe->blocks[i].refcount > 0) continue; + TORRENT_PIECE_ASSERT(!pe->blocks[i].pending, pe); + TORRENT_PIECE_ASSERT(pe->blocks[i].buf != 0, pe); + TORRENT_PIECE_ASSERT(num_to_delete < pe->blocks_in_piece, pe); + to_delete[num_to_delete++] = pe->blocks[i].buf; + pe->blocks[i].buf = NULL; + TORRENT_PIECE_ASSERT(pe->num_blocks > 0, pe); + --pe->num_blocks; + if (!pe->blocks[i].dirty) + { + TORRENT_PIECE_ASSERT(m_read_cache_size > 0, pe); + --m_read_cache_size; + } + else + { + TORRENT_PIECE_ASSERT(pe->num_dirty > 0, pe); + --pe->num_dirty; + pe->blocks[i].dirty = false; + TORRENT_PIECE_ASSERT(m_write_cache_size > 0, pe); + --m_write_cache_size; + } + if (pe->num_blocks == 0) break; + } + if (num_to_delete) free_multiple_buffers(to_delete, num_to_delete); + + if (pe->ok_to_evict(true)) + { + delete pe->hash; + pe->hash = NULL; + + // append will move the items from pe->jobs onto the end of jobs + jobs.append(pe->jobs); + TORRENT_ASSERT(pe->jobs.size() == 0); + + if (pe->cache_state == cached_piece_entry::read_lru1_ghost + || pe->cache_state == cached_piece_entry::read_lru2_ghost) + return true; + + if (pe->cache_state == cached_piece_entry::write_lru + || pe->cache_state == cached_piece_entry::volatile_read_lru) + erase_piece(pe); + else + move_to_ghost(pe); + return true; + } + + return false; +} + +void block_cache::mark_for_deletion(cached_piece_entry* p) +{ + INVARIANT_CHECK; + + DLOG(stderr, "[%p] block_cache mark-for-deletion " + "piece: %d\n", this, int(p->piece)); + + TORRENT_PIECE_ASSERT(p->jobs.empty(), p); + tailqueue jobs; + if (!evict_piece(p, jobs)) + { + p->marked_for_deletion = true; + } +} + +void block_cache::erase_piece(cached_piece_entry* pe) +{ + INVARIANT_CHECK; + + TORRENT_PIECE_ASSERT(pe->ok_to_evict(), pe); + TORRENT_PIECE_ASSERT(pe->cache_state < cached_piece_entry::num_lrus, pe); + TORRENT_PIECE_ASSERT(pe->jobs.empty(), pe); + linked_list* lru_list = &m_lru[pe->cache_state]; + if (pe->hash) + { + TORRENT_PIECE_ASSERT(pe->hash->offset == 0, pe); + delete pe->hash; + pe->hash = NULL; + } + if (pe->cache_state != cached_piece_entry::read_lru1_ghost + && pe->cache_state != cached_piece_entry::read_lru2_ghost) + pe->storage->remove_piece(pe); + lru_list->erase(pe); + m_pieces.erase(*pe); +} + +// this only evicts read blocks. For write blocks, see +// try_flush_write_blocks in disk_io_thread.cpp +int block_cache::try_evict_blocks(int num, cached_piece_entry* ignore) +{ + INVARIANT_CHECK; + + if (num <= 0) return 0; + + DLOG(stderr, "[%p] try_evict_blocks: %d\n", this, num); + + char** to_delete = TORRENT_ALLOCA(char*, num); + int num_to_delete = 0; + + // There are two ends of the ARC cache we can evict from. There's L1 and L2. + // The last cache operation determines which end we'll evict from. If we go + // through the entire list from the preferred end, and still need to evict + // more blocks, we'll go to the other end and start evicting from there. The + // lru_list is an array of two lists, these are the two ends to evict from, + // ordered by preference. + + linked_list* lru_list[3]; + + // however, before we consider any of the proper LRU lists, we evict pieces + // from the volatile list. These are low priority pieces that were + // specifically marked as to not survive long in the cache. These are the + // first pieces to go when evicting + lru_list[0] = &m_lru[cached_piece_entry::volatile_read_lru]; + + if (m_last_cache_op == cache_miss) + { + // when there was a cache miss, evict from the largest list, to tend to + // keep the lists of equal size when we don't know which one is + // performing better + if (m_lru[cached_piece_entry::read_lru2].size() + > m_lru[cached_piece_entry::read_lru1].size()) + { + lru_list[1] = &m_lru[cached_piece_entry::read_lru2]; + lru_list[2] = &m_lru[cached_piece_entry::read_lru1]; + } + else + { + lru_list[1] = &m_lru[cached_piece_entry::read_lru1]; + lru_list[2] = &m_lru[cached_piece_entry::read_lru2]; + } + } + else if (m_last_cache_op == ghost_hit_lru1) + { + // when we insert new items or move things from L1 to L2 + // evict blocks from L2 + lru_list[1] = &m_lru[cached_piece_entry::read_lru2]; + lru_list[2] = &m_lru[cached_piece_entry::read_lru1]; + } + else + { + // when we get cache hits in L2 evict from L1 + lru_list[1] = &m_lru[cached_piece_entry::read_lru1]; + lru_list[2] = &m_lru[cached_piece_entry::read_lru2]; + } + + // end refers to which end of the ARC cache we're evicting + // from. The LFU or the LRU end + for (int end = 0; num > 0 && end < 3; ++end) + { + // iterate over all blocks in order of last being used (oldest first) and + // as long as we still have blocks to evict TODO: it's somewhat expensive + // to iterate over this linked list. Presumably because of the random + // access of memory. It would be nice if pieces with no evictable blocks + // weren't in this list + for (list_iterator i = lru_list[end]->iterate(); i.get() && num > 0;) + { + cached_piece_entry* pe = reinterpret_cast(i.get()); + TORRENT_PIECE_ASSERT(pe->in_use, pe); + i.next(); + + if (pe == ignore) + continue; + + if (pe->ok_to_evict()) + { +#ifdef TORRENT_DEBUG + for (int j = 0; j < pe->blocks_in_piece; ++j) + TORRENT_PIECE_ASSERT(pe->blocks[j].buf == 0, pe); +#endif + TORRENT_PIECE_ASSERT(pe->refcount == 0, pe); + move_to_ghost(pe); + continue; + } + + TORRENT_PIECE_ASSERT(pe->num_dirty == 0, pe); + + // all blocks are pinned in this piece, skip it + if (pe->num_blocks <= pe->pinned) continue; + + // go through the blocks and evict the ones that are not dirty and not + // referenced + for (int j = 0; j < pe->blocks_in_piece && num > 0; ++j) + { + cached_block_entry& b = pe->blocks[j]; + + if (b.buf == 0 || b.refcount > 0 || b.dirty || b.pending) continue; + + to_delete[num_to_delete++] = b.buf; + b.buf = NULL; + TORRENT_PIECE_ASSERT(pe->num_blocks > 0, pe); + --pe->num_blocks; + TORRENT_PIECE_ASSERT(m_read_cache_size > 0, pe); + --m_read_cache_size; + --num; + } + + if (pe->ok_to_evict()) + { +#ifdef TORRENT_DEBUG + for (int j = 0; j < pe->blocks_in_piece; ++j) + TORRENT_PIECE_ASSERT(pe->blocks[j].buf == 0, pe); +#endif + move_to_ghost(pe); + } + } + } + + // if we can't evict enough blocks from the read cache, also look at write + // cache pieces for blocks that have already been written to disk and can be + // evicted the first pass, we only evict blocks that have been hashed, the + // second pass we flush anything this is potentially a very expensive + // operation, since we're likely to have iterate every single block in the + // cache, and we might not get to evict anything. + + // TODO: this should probably only be done every n:th time + if (num > 0 && m_read_cache_size > m_pinned_blocks) + { + for (int pass = 0; pass < 2 && num > 0; ++pass) + { + for (list_iterator i = m_lru[cached_piece_entry::write_lru].iterate(); i.get() && num > 0;) + { + cached_piece_entry* pe = reinterpret_cast(i.get()); + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + i.next(); + + if (pe == ignore) + continue; + + if (pe->ok_to_evict()) + { +#ifdef TORRENT_DEBUG + for (int j = 0; j < pe->blocks_in_piece; ++j) + TORRENT_PIECE_ASSERT(pe->blocks[j].buf == 0, pe); +#endif + TORRENT_PIECE_ASSERT(pe->refcount == 0, pe); + erase_piece(pe); + continue; + } + + // all blocks in this piece are dirty + if (pe->num_dirty == pe->num_blocks) + continue; + + int end = pe->blocks_in_piece; + + // the first pass, only evict blocks that have been + // hashed + if (pass == 0 && pe->hash) + end = pe->hash->offset / block_size(); + + // go through the blocks and evict the ones + // that are not dirty and not referenced + for (int j = 0; j < end && num > 0; ++j) + { + cached_block_entry& b = pe->blocks[j]; + + if (b.buf == 0 || b.refcount > 0 || b.dirty || b.pending) continue; + + to_delete[num_to_delete++] = b.buf; + b.buf = NULL; + TORRENT_PIECE_ASSERT(pe->num_blocks > 0, pe); + --pe->num_blocks; + TORRENT_PIECE_ASSERT(m_read_cache_size > 0, pe); + --m_read_cache_size; + --num; + } + + if (pe->ok_to_evict()) + { +#ifdef TORRENT_DEBUG + for (int j = 0; j < pe->blocks_in_piece; ++j) + TORRENT_PIECE_ASSERT(pe->blocks[j].buf == 0, pe); +#endif + erase_piece(pe); + } + } + } + } + + if (num_to_delete == 0) return num; + + DLOG(stderr, "[%p] removed %d blocks\n", this, num_to_delete); + + free_multiple_buffers(to_delete, num_to_delete); + + return num; +} + +void block_cache::clear(tailqueue& jobs) +{ + INVARIANT_CHECK; + + // this holds all the block buffers we want to free + // at the end + std::vector bufs; + + for (iterator p = m_pieces.begin() + , end(m_pieces.end()); p != end; ++p) + { + cached_piece_entry& pe = const_cast(*p); +#if TORRENT_USE_ASSERTS + for (tailqueue_iterator i = pe.jobs.iterate(); i.get(); i.next()) + TORRENT_PIECE_ASSERT(((disk_io_job*)i.get())->piece == pe.piece, &pe); + for (tailqueue_iterator i = pe.read_jobs.iterate(); i.get(); i.next()) + TORRENT_PIECE_ASSERT(((disk_io_job*)i.get())->piece == pe.piece, &pe); +#endif + // this also removes the jobs from the piece + jobs.append(pe.jobs); + jobs.append(pe.read_jobs); + + drain_piece_bufs(pe, bufs); + } + + if (!bufs.empty()) free_multiple_buffers(&bufs[0], bufs.size()); + + // clear lru lists + for (int i = 0; i < cached_piece_entry::num_lrus; ++i) + m_lru[i].get_all(); + + m_pieces.clear(); +} + +void block_cache::move_to_ghost(cached_piece_entry* pe) +{ + TORRENT_PIECE_ASSERT(pe->refcount == 0, pe); + TORRENT_PIECE_ASSERT(pe->piece_refcount == 0, pe); + TORRENT_PIECE_ASSERT(pe->num_blocks == 0, pe); + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + if (pe->cache_state == cached_piece_entry::volatile_read_lru) + { + erase_piece(pe); + return; + } + + TORRENT_PIECE_ASSERT(pe->cache_state == cached_piece_entry::read_lru1 + || pe->cache_state == cached_piece_entry::read_lru2, pe); + + // if the piece is in L1 or L2, move it into the ghost list + // i.e. recently evicted + if (pe->cache_state != cached_piece_entry::read_lru1 + && pe->cache_state != cached_piece_entry::read_lru2) + return; + + // if the ghost list is growing too big, remove the oldest entry + linked_list* ghost_list = &m_lru[pe->cache_state + 1]; + while (ghost_list->size() >= m_ghost_size) + { + cached_piece_entry* p = (cached_piece_entry*)ghost_list->front(); + TORRENT_PIECE_ASSERT(p != pe, p); + TORRENT_PIECE_ASSERT(p->num_blocks == 0, p); + TORRENT_PIECE_ASSERT(p->refcount == 0, p); + TORRENT_PIECE_ASSERT(p->piece_refcount == 0, p); + erase_piece(p); + } + + pe->storage->remove_piece(pe); + m_lru[pe->cache_state].erase(pe); + pe->cache_state += 1; + ghost_list->push_back(pe); +} + +int block_cache::pad_job(disk_io_job const* j, int blocks_in_piece + , int read_ahead) const +{ + int block_offset = j->d.io.offset & (block_size()-1); + int start = j->d.io.offset / block_size(); + int end = block_offset > 0 && (read_ahead > block_size() - block_offset) ? start + 2 : start + 1; + + // take the read-ahead into account + // make sure to not overflow in this case + if (read_ahead == INT_MAX) end = blocks_in_piece; + else end = (std::min)(blocks_in_piece, (std::max)(start + read_ahead, end)); + + return end - start; +} + +// this function allocates buffers and +// fills in the iovec array with the buffers +int block_cache::allocate_iovec(file::iovec_t* iov, int iov_len) +{ + for (int i = 0; i < iov_len; ++i) + { + iov[i].iov_base = allocate_buffer("pending read"); + iov[i].iov_len = block_size(); + if (iov[i].iov_base == NULL) + { + // uh oh. We failed to allocate the buffer! + // we need to roll back and free all the buffers + // we've already allocated + for (int j = 0; j < i; ++j) + free_buffer((char*)iov[j].iov_base); + return -1; + } + } + return 0; +} + +void block_cache::free_iovec(file::iovec_t* iov, int iov_len) +{ + for (int i = 0; i < iov_len; ++i) + free_buffer((char*)iov[i].iov_base); +} + +void block_cache::insert_blocks(cached_piece_entry* pe, int block, file::iovec_t *iov + , int iov_len, disk_io_job* j, int flags) +{ + INVARIANT_CHECK; + + TORRENT_ASSERT(pe); + TORRENT_PIECE_ASSERT(iov_len > 0, pe); + +#if TORRENT_USE_ASSERTS + // we're not allowed to add dirty blocks + // for a deleted storage! + TORRENT_ASSERT(std::find(m_deleted_storages.begin(), m_deleted_storages.end() + , std::make_pair(j->storage->files()->name(), (void const*)j->storage->files())) + == m_deleted_storages.end()); +#endif + + cache_hit(pe, j->requester, j->flags & disk_io_job::volatile_read); + + for (int i = 0; i < iov_len; ++i, ++block) + { + // each iovec buffer has to be the size of a block (or the size of the last block) + TORRENT_PIECE_ASSERT(iov[i].iov_len == (std::min)(block_size() + , pe->storage->files()->piece_size(pe->piece) - block * block_size()), pe); + + // no NULL pointers allowed + TORRENT_ASSERT(iov[i].iov_base); + +#ifdef TORRENT_DEBUG_BUFFERS + TORRENT_PIECE_ASSERT(is_disk_buffer((char*)iov[i].iov_base), pe); +#endif + + if (pe->blocks[block].buf && (flags & blocks_inc_refcount)) + { + inc_block_refcount(pe, block, ref_reading); + } + + // either free the block or insert it. Never replace a block + if (pe->blocks[block].buf) + { + free_buffer((char*)iov[i].iov_base); + } + else + { + pe->blocks[block].buf = (char*)iov[i].iov_base; + +#ifdef TORRENT_BUFFER_STATS + rename_buffer(pe->blocks[block].buf, "read cache"); +#endif + TORRENT_PIECE_ASSERT(iov[i].iov_base != NULL, pe); + TORRENT_PIECE_ASSERT(pe->blocks[block].dirty == false, pe); + ++pe->num_blocks; + ++m_read_cache_size; + + if (flags & blocks_inc_refcount) + { + bool ret = inc_block_refcount(pe, block, ref_reading); + TORRENT_ASSERT(ret); + } + else + { +#if TORRENT_USE_PURGABLE_CONTROL && TORRENT_DISABLE_POOL_ALLOCATOR + // volatile read blocks are group 0, regular reads are group 1 + int state = VM_PURGABLE_VOLATILE | ((j->flags & disk_io_job::volatile_read) ? VM_VOLATILE_GROUP_0 : VM_VOLATILE_GROUP_1); + kern_return_t ret = vm_purgable_control( + mach_task_self(), + reinterpret_cast(pe->blocks[block].buf), + VM_PURGABLE_SET_STATE, + &state); +#ifdef TORRENT_DEBUG + // if ((rand() % 200) == 0) ret = 1; +#endif + if (ret != KERN_SUCCESS || (state & VM_PURGABLE_EMPTY)) + { + fprintf(stderr, "insert_blocks(piece=%d block=%d): vm_purgable_control failed: %d state & VM_PURGABLE_EMPTY: %d\n" + , pe->piece, block, ret, state & VM_PURGABLE_EMPTY); + free_buffer(pe->blocks[block].buf); + pe->blocks[block].buf = NULL; + --pe->num_blocks; + --m_read_cache_size; + } +#endif + } + } + +#if TORRENT_USE_PURGABLE_CONTROL && TORRENT_DISABLE_POOL_ALLOCATOR + TORRENT_ASSERT(pe->blocks[block].buf != NULL + || (flags & blocks_inc_refcount) == 0); +#else + TORRENT_ASSERT(pe->blocks[block].buf != NULL); +#endif + } + + TORRENT_PIECE_ASSERT(pe->cache_state != cached_piece_entry::read_lru1_ghost, pe); + TORRENT_PIECE_ASSERT(pe->cache_state != cached_piece_entry::read_lru2_ghost, pe); +} + +// return false if the memory was purged +bool block_cache::inc_block_refcount(cached_piece_entry* pe, int block, int reason) +{ + TORRENT_PIECE_ASSERT(pe->in_use, pe); + TORRENT_PIECE_ASSERT(block < pe->blocks_in_piece, pe); + TORRENT_PIECE_ASSERT(block >= 0, pe); + if (pe->blocks[block].buf == NULL) return false; + TORRENT_PIECE_ASSERT(pe->blocks[block].refcount < cached_block_entry::max_refcount, pe); + if (pe->blocks[block].refcount == 0) + { +#if TORRENT_USE_PURGABLE_CONTROL && TORRENT_DISABLE_POOL_ALLOCATOR + // we're adding the first refcount to this block, first make sure + // its still here. It's only volatile if it's not dirty and has refcount == 0 + if (!pe->blocks[block].dirty) + { + int state = VM_PURGABLE_NONVOLATILE; + kern_return_t ret = vm_purgable_control( + mach_task_self(), + reinterpret_cast(pe->blocks[block].buf), + VM_PURGABLE_SET_STATE, + &state); +#ifdef TORRENT_DEBUG +// if ((rand() % 200) == 0) ret = 1; +#endif + if (ret != KERN_SUCCESS || (state & VM_PURGABLE_EMPTY)) + { + fprintf(stderr, "inc_block_refcount(piece=%d block=%d): vm_purgable_control failed: %d state & VM_PURGABLE_EMPTY: %d\n" + , pe->piece, block, ret, state & VM_PURGABLE_EMPTY); + + free_buffer(pe->blocks[block].buf); + pe->blocks[block].buf = NULL; + --pe->num_blocks; + --m_read_cache_size; + return false; + } + } +#endif + ++pe->pinned; + ++m_pinned_blocks; + } + ++pe->blocks[block].refcount; + ++pe->refcount; +#if TORRENT_USE_ASSERTS + switch (reason) + { + case ref_hashing: ++pe->blocks[block].hashing_count; break; + case ref_reading: ++pe->blocks[block].reading_count; break; + case ref_flushing: ++pe->blocks[block].flushing_count; break; + }; +#endif + return true; +} + +void block_cache::dec_block_refcount(cached_piece_entry* pe, int block, int reason) +{ + TORRENT_PIECE_ASSERT(pe->in_use, pe); + TORRENT_PIECE_ASSERT(block < pe->blocks_in_piece, pe); + TORRENT_PIECE_ASSERT(block >= 0, pe); + + TORRENT_PIECE_ASSERT(pe->blocks[block].buf != NULL, pe); + TORRENT_PIECE_ASSERT(pe->blocks[block].refcount > 0, pe); + --pe->blocks[block].refcount; + TORRENT_PIECE_ASSERT(pe->refcount > 0, pe); + --pe->refcount; + if (pe->blocks[block].refcount == 0) + { + TORRENT_PIECE_ASSERT(pe->pinned > 0, pe); + --pe->pinned; + TORRENT_PIECE_ASSERT(m_pinned_blocks > 0, pe); + --m_pinned_blocks; + +#if TORRENT_USE_PURGABLE_CONTROL && TORRENT_DISABLE_POOL_ALLOCATOR + // we're removing the last refcount to this block, first make sure + // its still here. It's only volatile if it's not dirty and has refcount == 0 + if (!pe->blocks[block].dirty) + { + // group 0 is the first one to be reclaimed + int state = VM_PURGABLE_VOLATILE | VM_VOLATILE_GROUP_1; + kern_return_t ret = vm_purgable_control( + mach_task_self(), + reinterpret_cast(pe->blocks[block].buf), + VM_PURGABLE_SET_STATE, + &state); +#ifdef TORRENT_DEBUG +// if ((rand() % 200) == 0) ret = 1; +#endif + if (ret != KERN_SUCCESS || (state & VM_PURGABLE_EMPTY)) + { + fprintf(stderr, "dec_block_refcount(piece=%d block=%d): vm_purgable_control failed: %d state & VM_PURGABLE_EMPTY: %d\n" + , pe->piece, block, ret, state & VM_PURGABLE_EMPTY); + free_buffer(pe->blocks[block].buf); + pe->blocks[block].buf = NULL; + --pe->num_blocks; + --m_read_cache_size; + } + } +#endif + } +#if TORRENT_USE_ASSERTS + switch (reason) + { + case ref_hashing: --pe->blocks[block].hashing_count; break; + case ref_reading: --pe->blocks[block].reading_count; break; + case ref_flushing: --pe->blocks[block].flushing_count; break; + }; +#endif +} + +void block_cache::abort_dirty(cached_piece_entry* pe) +{ + INVARIANT_CHECK; + + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + char** to_delete = TORRENT_ALLOCA(char*, pe->blocks_in_piece); + int num_to_delete = 0; + for (int i = 0; i < pe->blocks_in_piece; ++i) + { + if (!pe->blocks[i].dirty + || pe->blocks[i].refcount > 0 + || pe->blocks[i].buf == NULL) continue; + + TORRENT_PIECE_ASSERT(!pe->blocks[i].pending, pe); + TORRENT_PIECE_ASSERT(pe->blocks[i].dirty, pe); + to_delete[num_to_delete++] = pe->blocks[i].buf; + pe->blocks[i].buf = NULL; + pe->blocks[i].dirty = false; + TORRENT_PIECE_ASSERT(pe->num_blocks > 0, pe); + --pe->num_blocks; + TORRENT_PIECE_ASSERT(m_write_cache_size > 0, pe); + --m_write_cache_size; + TORRENT_PIECE_ASSERT(pe->num_dirty > 0, pe); + --pe->num_dirty; + } + if (num_to_delete) free_multiple_buffers(to_delete, num_to_delete); + + update_cache_state(pe); +} + +// frees all buffers associated with this piece. May only +// be called for pieces with a refcount of 0 +void block_cache::free_piece(cached_piece_entry* pe) +{ + INVARIANT_CHECK; + + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + TORRENT_PIECE_ASSERT(pe->refcount == 0, pe); + TORRENT_PIECE_ASSERT(pe->piece_refcount == 0, pe); + TORRENT_PIECE_ASSERT(pe->outstanding_read == 0, pe); + + // build a vector of all the buffers we need to free + // and free them all in one go + char** to_delete = TORRENT_ALLOCA(char*, pe->blocks_in_piece); + int num_to_delete = 0; + for (int i = 0; i < pe->blocks_in_piece; ++i) + { + if (pe->blocks[i].buf == 0) continue; + TORRENT_PIECE_ASSERT(pe->blocks[i].pending == false, pe); + TORRENT_PIECE_ASSERT(pe->blocks[i].refcount == 0, pe); + TORRENT_PIECE_ASSERT(num_to_delete < pe->blocks_in_piece, pe); + to_delete[num_to_delete++] = pe->blocks[i].buf; + pe->blocks[i].buf = NULL; + TORRENT_PIECE_ASSERT(pe->num_blocks > 0, pe); + --pe->num_blocks; + if (pe->blocks[i].dirty) + { + TORRENT_PIECE_ASSERT(m_write_cache_size > 0, pe); + --m_write_cache_size; + TORRENT_PIECE_ASSERT(pe->num_dirty > 0, pe); + --pe->num_dirty; + } + else + { + TORRENT_PIECE_ASSERT(m_read_cache_size > 0, pe); + --m_read_cache_size; + } + } + if (num_to_delete) free_multiple_buffers(to_delete, num_to_delete); + update_cache_state(pe); +} + +int block_cache::drain_piece_bufs(cached_piece_entry& p, std::vector& buf) +{ + int piece_size = p.storage->files()->piece_size(p.piece); + int blocks_in_piece = (piece_size + block_size() - 1) / block_size(); + int ret = 0; + + TORRENT_PIECE_ASSERT(p.in_use, &p); + + for (int i = 0; i < blocks_in_piece; ++i) + { + if (p.blocks[i].buf == 0) continue; + TORRENT_PIECE_ASSERT(p.blocks[i].refcount == 0, &p); + buf.push_back(p.blocks[i].buf); + ++ret; + p.blocks[i].buf = NULL; + TORRENT_PIECE_ASSERT(p.num_blocks > 0, &p); + --p.num_blocks; + + if (p.blocks[i].dirty) + { + TORRENT_ASSERT(m_write_cache_size > 0); + --m_write_cache_size; + TORRENT_PIECE_ASSERT(p.num_dirty > 0, &p); + --p.num_dirty; + } + else + { + TORRENT_ASSERT(m_read_cache_size > 0); + --m_read_cache_size; + } + } + update_cache_state(&p); + return ret; +} + +void block_cache::update_stats_counters(counters& c) const +{ + c.set_value(counters::num_blocks_cache_hits, m_blocks_read_hit); + c.set_value(counters::write_cache_blocks, m_write_cache_size); + c.set_value(counters::read_cache_blocks, m_read_cache_size); + c.set_value(counters::pinned_blocks, m_pinned_blocks); + + c.set_value(counters::arc_mru_size, m_lru[cached_piece_entry::read_lru1].size()); + c.set_value(counters::arc_mru_ghost_size, m_lru[cached_piece_entry::read_lru1_ghost].size()); + c.set_value(counters::arc_mfu_size, m_lru[cached_piece_entry::read_lru2].size()); + c.set_value(counters::arc_mfu_ghost_size, m_lru[cached_piece_entry::read_lru2_ghost].size()); + c.set_value(counters::arc_write_size, m_lru[cached_piece_entry::write_lru].size()); + c.set_value(counters::arc_volatile_size, m_lru[cached_piece_entry::volatile_read_lru].size()); +} + +void block_cache::get_stats(cache_status* ret) const +{ + ret->blocks_read_hit = m_blocks_read_hit; + ret->write_cache_size = m_write_cache_size; + ret->read_cache_size = m_read_cache_size; + ret->pinned_blocks = m_pinned_blocks; +#ifndef TORRENT_NO_DEPRECATE + ret->cache_size = m_read_cache_size + m_write_cache_size; +#endif + + ret->arc_mru_size = m_lru[cached_piece_entry::read_lru1].size(); + ret->arc_mru_ghost_size = m_lru[cached_piece_entry::read_lru1_ghost].size(); + ret->arc_mfu_size = m_lru[cached_piece_entry::read_lru2].size(); + ret->arc_mfu_ghost_size = m_lru[cached_piece_entry::read_lru2_ghost].size(); + ret->arc_write_size = m_lru[cached_piece_entry::write_lru].size(); + ret->arc_volatile_size = m_lru[cached_piece_entry::volatile_read_lru].size(); +} + +void block_cache::set_settings(aux::session_settings const& sett) +{ + // the ghost size is the number of pieces to keep track of + // after they are evicted. Since cache_size is blocks, the + // assumption is that there are about 128 blocks per piece, + // and there are two ghost lists, so divide by 2. + + m_ghost_size = (std::max)(8, sett.get_int(settings_pack::cache_size) + / (std::max)(sett.get_int(settings_pack::read_cache_line_size), 4) / 2); + disk_buffer_pool::set_settings(sett); +} + +#if TORRENT_USE_INVARIANT_CHECKS +void block_cache::check_invariant() const +{ + int cached_write_blocks = 0; + int cached_read_blocks = 0; + int num_pinned = 0; + + std::set storages; + + for (int i = 0; i < cached_piece_entry::num_lrus; ++i) + { + ptime timeout = min_time(); + + for (list_iterator p = m_lru[i].iterate(); p.get(); p.next()) + { + cached_piece_entry* pe = (cached_piece_entry*)p.get(); + TORRENT_PIECE_ASSERT(pe->cache_state == i, pe); + if (pe->num_dirty > 0) + TORRENT_PIECE_ASSERT(i == cached_piece_entry::write_lru, pe); + +// if (i == cached_piece_entry::write_lru) +// TORRENT_ASSERT(pe->num_dirty > 0); + for (tailqueue_iterator j = pe->jobs.iterate(); j.get(); j.next()) + { + disk_io_job* job = (disk_io_job*)j.get(); + TORRENT_PIECE_ASSERT(job->piece == pe->piece, pe); + TORRENT_PIECE_ASSERT(job->in_use, pe); + TORRENT_PIECE_ASSERT(!job->callback_called, pe); + } + + if (i != cached_piece_entry::read_lru1_ghost + && i != cached_piece_entry::read_lru2_ghost) + { + TORRENT_PIECE_ASSERT(pe->storage->has_piece(pe), pe); + TORRENT_PIECE_ASSERT(pe->expire >= timeout, pe); + timeout = pe->expire; + TORRENT_PIECE_ASSERT(pe->in_storage, pe); + TORRENT_PIECE_ASSERT(pe->storage->has_piece(pe), pe); + } + else + { + // pieces in the ghost lists should never have any blocks + TORRENT_PIECE_ASSERT(pe->num_blocks == 0, pe); + TORRENT_PIECE_ASSERT(pe->storage->has_piece(pe) == false, pe); + } + + storages.insert(pe->storage.get()); + } + } + + for (std::set::iterator i = storages.begin() + , end(storages.end()); i != end; ++i) + { + for (boost::unordered_set::iterator j = (*i)->cached_pieces().begin() + , end((*i)->cached_pieces().end()); j != end; ++j) + { + cached_piece_entry* pe = *j; + TORRENT_PIECE_ASSERT(pe->storage.get() == *i, pe); + } + } + + boost::unordered_set buffers; + for (iterator i = m_pieces.begin(), end(m_pieces.end()); i != end; ++i) + { + cached_piece_entry const& p = *i; + TORRENT_PIECE_ASSERT(p.blocks, &p); + + TORRENT_PIECE_ASSERT(p.storage, &p); + int num_blocks = 0; + int num_dirty = 0; + int num_pending = 0; + int num_refcount = 0; + + bool in_storage = p.storage->has_piece((cached_piece_entry*)&p); + switch (p.cache_state) + { + case cached_piece_entry::write_lru: + case cached_piece_entry::volatile_read_lru: + case cached_piece_entry::read_lru1: + case cached_piece_entry::read_lru2: + TORRENT_ASSERT(in_storage == true); + break; + default: + TORRENT_ASSERT(in_storage == false); + break; + } + + for (int k = 0; k < p.blocks_in_piece; ++k) + { + if (p.blocks[k].buf) + { +#if !defined TORRENT_DISABLE_POOL_ALLOCATOR && defined TORRENT_EXPENSIVE_INVARIANT_CHECKS + TORRENT_PIECE_ASSERT(is_disk_buffer(p.blocks[k].buf), &p); + + // make sure we don't have the same buffer + // in the cache twice + TORRENT_PIECE_ASSERT(buffers.count(p.blocks[k].buf) == 0, &p); + buffers.insert(p.blocks[k].buf); +#endif + ++num_blocks; + if (p.blocks[k].dirty) + { + ++num_dirty; + ++cached_write_blocks; + } + else + { + ++cached_read_blocks; + } + if (p.blocks[k].pending) ++num_pending; + if (p.blocks[k].refcount > 0) ++num_pinned; + } + else + { + TORRENT_PIECE_ASSERT(!p.blocks[k].dirty, &p); + TORRENT_PIECE_ASSERT(!p.blocks[k].pending, &p); + TORRENT_PIECE_ASSERT(p.blocks[k].refcount == 0, &p); + } + TORRENT_PIECE_ASSERT(p.blocks[k].refcount >= 0, &p); + num_refcount += p.blocks[k].refcount; + } + TORRENT_PIECE_ASSERT(num_blocks == p.num_blocks, &p); + TORRENT_PIECE_ASSERT(num_pending <= p.refcount, &p); + TORRENT_PIECE_ASSERT(num_refcount == p.refcount, &p); + TORRENT_PIECE_ASSERT(num_dirty == p.num_dirty, &p); + } + TORRENT_ASSERT(m_read_cache_size == cached_read_blocks); + TORRENT_ASSERT(m_write_cache_size == cached_write_blocks); + TORRENT_ASSERT(m_pinned_blocks == num_pinned); + TORRENT_ASSERT(m_write_cache_size + m_read_cache_size <= in_use()); + +#ifdef TORRENT_BUFFER_STATS + int read_allocs = m_categories.find(std::string("read cache"))->second; + int write_allocs = m_categories.find(std::string("write cache"))->second; + TORRENT_ASSERT(cached_read_blocks == read_allocs); + TORRENT_ASSERT(cached_write_blocks == write_allocs); +#endif +} +#endif + +// returns +// -1: block not in cache +// -2: out of memory + +int block_cache::copy_from_piece(cached_piece_entry* pe, disk_io_job* j, bool expect_no_fail) +{ + INVARIANT_CHECK; + + TORRENT_PIECE_ASSERT(j->buffer == 0, pe); + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + // copy from the cache and update the last use timestamp + int block = j->d.io.offset / block_size(); + int block_offset = j->d.io.offset & (block_size()-1); + int buffer_offset = 0; + int size = j->d.io.buffer_size; + int blocks_to_read = block_offset > 0 && (size > block_size() - block_offset) ? 2 : 1; + TORRENT_PIECE_ASSERT(size <= block_size(), pe); + const int start_block = block; + +#ifdef TORRENT_DEBUG + int piece_size = j->storage->files()->piece_size(j->piece); + int blocks_in_piece = (piece_size + block_size() - 1) / block_size(); + TORRENT_PIECE_ASSERT(start_block < blocks_in_piece, pe); +#endif + + // if there's no buffer, we don't have this block in + // the cache, and we're not currently reading it in either + // since it's not pending + + if (inc_block_refcount(pe, start_block, ref_reading) == false) + { + TORRENT_ASSERT(!expect_no_fail); + return -1; + } + + // if block_offset > 0, we need to read two blocks, and then + // copy parts of both, because it's not aligned to the block + // boundaries + if (blocks_to_read == 1 && (j->flags & disk_io_job::force_copy) == 0) + { + // special case for block aligned request + // don't actually copy the buffer, just reference + // the existing block + cached_block_entry& bl = pe->blocks[start_block]; + + // make sure it didn't wrap + TORRENT_PIECE_ASSERT(pe->refcount > 0, pe); + j->d.io.ref.storage = j->storage.get(); + j->d.io.ref.piece = pe->piece; + j->d.io.ref.block = start_block; + j->buffer = bl.buf + (j->d.io.offset & (block_size()-1)); + ++m_send_buffer_blocks; +#if TORRENT_USE_ASSERTS + ++bl.reading_count; +#endif + return j->d.io.buffer_size; + } + + // if we don't have the second block, it's a cache miss + if (blocks_to_read == 2 && inc_block_refcount(pe, start_block + 1, ref_reading) == false) + { + TORRENT_ASSERT(!expect_no_fail); + dec_block_refcount(pe, start_block, ref_reading); + return -1; + } + + j->buffer = allocate_buffer("send buffer"); + if (j->buffer == 0) return -2; + + while (size > 0) + { + TORRENT_PIECE_ASSERT(pe->blocks[block].buf, pe); + int to_copy = (std::min)(block_size() + - block_offset, size); + std::memcpy(j->buffer + buffer_offset + , pe->blocks[block].buf + block_offset + , to_copy); + ++pe->blocks[block].hitcount; + size -= to_copy; + block_offset = 0; + buffer_offset += to_copy; + ++block; + } + // we incremented the refcount for both of these blocks. + // now decrement it. + // TODO: create a holder for refcounts that automatically decrement + dec_block_refcount(pe, start_block, ref_reading); + if (blocks_to_read == 2) dec_block_refcount(pe, start_block + 1, ref_reading); + return j->d.io.buffer_size; +} + +void block_cache::reclaim_block(block_cache_reference const& ref) +{ + cached_piece_entry* pe = find_piece(ref); + TORRENT_ASSERT(pe); + if (pe == NULL) return; + + TORRENT_PIECE_ASSERT(pe->in_use, pe); + + TORRENT_PIECE_ASSERT(pe->blocks[ref.block].buf, pe); + dec_block_refcount(pe, ref.block, block_cache::ref_reading); + + TORRENT_PIECE_ASSERT(m_send_buffer_blocks > 0, pe); + --m_send_buffer_blocks; + + maybe_free_piece(pe); +} + +bool block_cache::maybe_free_piece(cached_piece_entry* pe) +{ + if (!pe->ok_to_evict() + || !pe->marked_for_deletion + || !pe->jobs.empty()) + return false; + + boost::shared_ptr s = pe->storage; + + DLOG(stderr, "[%p] block_cache maybe_free_piece " + "piece: %d refcount: %d marked_for_deletion: %d\n", this + , int(pe->piece), int(pe->refcount), int(pe->marked_for_deletion)); + + tailqueue jobs; + bool removed = evict_piece(pe, jobs); + TORRENT_PIECE_ASSERT(removed, pe); + TORRENT_PIECE_ASSERT(jobs.empty(), pe); + + return true; +} + +cached_piece_entry* block_cache::find_piece(block_cache_reference const& ref) +{ + return find_piece((piece_manager*)ref.storage, ref.piece); +} + +cached_piece_entry* block_cache::find_piece(disk_io_job const* j) +{ + return find_piece(j->storage.get(), j->piece); +} + +cached_piece_entry* block_cache::find_piece(piece_manager* st, int piece) +{ + cached_piece_entry model; + model.storage = st->shared_from_this(); + model.piece = piece; + iterator i = m_pieces.find(model); + TORRENT_ASSERT(i == m_pieces.end() || (i->storage.get() == st && i->piece == piece)); + if (i == m_pieces.end()) return 0; + TORRENT_PIECE_ASSERT(i->in_use, &*i); + +#if TORRENT_USE_ASSERTS + for (tailqueue_iterator j = i->jobs.iterate(); j.get(); j.next()) + { + disk_io_job* job = (disk_io_job*)j.get(); + TORRENT_PIECE_ASSERT(job->piece == piece, &*i); + } +#endif + + return const_cast(&*i); +} + +} + diff --git a/src/bt_peer_connection.cpp b/src/bt_peer_connection.cpp index cb15a749f..8618a3569 100644 --- a/src/bt_peer_connection.cpp +++ b/src/bt_peer_connection.cpp @@ -50,12 +50,14 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket_io.hpp" #include "libtorrent/version.hpp" #include "libtorrent/extensions.hpp" -#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/aux_/session_interface.hpp" #include "libtorrent/broadcast_socket.hpp" #include "libtorrent/escape_string.hpp" #include "libtorrent/peer_info.hpp" #include "libtorrent/random.hpp" #include "libtorrent/alloca.hpp" +#include "libtorrent/socket_type.hpp" +#include "libtorrent/performance_counters.hpp" // for counters #ifndef TORRENT_DISABLE_ENCRYPTION #include "libtorrent/pe_crypto.hpp" @@ -63,7 +65,6 @@ POSSIBILITY OF SUCH DAMAGE. #endif using boost::shared_ptr; -using libtorrent::aux::session_impl; namespace libtorrent { @@ -95,39 +96,43 @@ namespace libtorrent bt_peer_connection::bt_peer_connection( - session_impl& ses + aux::session_interface& ses + , aux::session_settings const& sett + , buffer_allocator_interface& allocator + , disk_interface& disk_thread , shared_ptr s , tcp::endpoint const& remote - , policy::peer* peerinfo + , torrent_peer* peerinfo , peer_id const& pid - , boost::weak_ptr tor - , bool outgoing) - : peer_connection(ses, tor, s, remote - , peerinfo, outgoing) + , boost::weak_ptr tor) + // if tor is set, this is an outgoing connection + : peer_connection(ses, sett, allocator, disk_thread + , ses.get_io_service() + , tor, s, remote, peerinfo, tor.lock().get()) , m_state(read_protocol_identifier) , m_supports_extensions(false) , m_supports_dht_port(false) , m_supports_fast(false) -#if TORRENT_USE_ASSERTS , m_sent_bitfield(false) - , m_in_constructor(true) , m_sent_handshake(false) -#endif #ifndef TORRENT_DISABLE_ENCRYPTION , m_encrypted(false) , m_rc4_encrypted(false) -#endif -#ifndef TORRENT_DISABLE_EXTENSIONS - , m_upload_only_id(0) - , m_holepunch_id(0) #endif , m_our_peer_id(pid) #ifndef TORRENT_DISABLE_ENCRYPTION , m_sync_bytes_read(0) #endif +#ifndef TORRENT_DISABLE_EXTENSIONS + , m_upload_only_id(0) + , m_holepunch_id(0) +#endif #ifndef TORRENT_DISABLE_EXTENSIONS , m_dont_have_id(0) , m_share_mode_id(0) +#endif +#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS + , m_in_constructor(true) #endif { #ifdef TORRENT_VERBOSE_LOGGING @@ -154,7 +159,7 @@ namespace libtorrent bt_peer_connection::~bt_peer_connection() { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); } void bt_peer_connection::on_connected() @@ -169,25 +174,29 @@ namespace libtorrent #ifdef TORRENT_VERBOSE_LOGGING peer_log("*** ON_CONNECTED [ graceful-paused ]"); #endif - disconnect(error_code(errors::torrent_paused), 0); + disconnect(error_code(errors::torrent_paused), op_bittorrent); return; } + // make sure are much as possible of the response ends up in the same + // packet, or at least back-to-back packets + cork c_(*this); + #ifndef TORRENT_DISABLE_ENCRYPTION - boost::uint8_t out_enc_policy = m_ses.get_pe_settings().out_enc_policy; + boost::uint8_t out_enc_policy = m_ses.settings().get_int(settings_pack::out_enc_policy); #ifdef TORRENT_USE_OPENSSL // never try an encrypted connection when already using SSL if (is_ssl(*get_socket())) - out_enc_policy = pe_settings::disabled; + out_enc_policy = settings_pack::pe_disabled; #endif #ifdef TORRENT_VERBOSE_LOGGING char const* policy_name[] = {"forced", "enabled", "disabled"}; peer_log("*** outgoing encryption policy: %s", policy_name[out_enc_policy]); #endif - if (out_enc_policy == pe_settings::forced) + if (out_enc_policy == settings_pack::pe_forced) { write_pe1_2_dhkey(); if (is_disconnecting()) return; @@ -196,11 +205,11 @@ namespace libtorrent reset_recv_buffer(dh_key_len); setup_receive(); } - else if (out_enc_policy == pe_settings::enabled) + else if (out_enc_policy == settings_pack::pe_enabled) { TORRENT_ASSERT(peer_info_struct()); - policy::peer* pi = peer_info_struct(); + torrent_peer* pi = peer_info_struct(); if (pi->pe_support == true) { // toggle encryption support flag, toggled back to @@ -229,7 +238,7 @@ namespace libtorrent setup_receive(); } } - else if (out_enc_policy == pe_settings::disabled) + else if (out_enc_policy == settings_pack::pe_disabled) #endif { write_handshake(); @@ -249,13 +258,15 @@ namespace libtorrent // connections that are still in the handshake // will send their bitfield when the handshake // is done - if (m_state < read_packet_size) return; + if (!m_sent_handshake) return; + if (m_sent_bitfield) return; boost::shared_ptr t = associated_torrent().lock(); TORRENT_ASSERT(t); write_bitfield(); + TORRENT_ASSERT(m_sent_bitfield); #ifndef TORRENT_DISABLE_DHT - if (m_supports_dht_port && m_ses.m_dht) - write_dht_port(m_ses.m_external_udp_port); + if (m_supports_dht_port && m_ses.has_dht()) + write_dht_port(m_ses.external_udp_port()); #endif } @@ -272,43 +283,43 @@ namespace libtorrent char* ptr = msg + 5; detail::write_uint16(listen_port, ptr); send_buffer(msg, sizeof(msg)); + + m_ses.inc_stats_counter(counters::num_outgoing_dht_port); } void bt_peer_connection::write_have_all() { INVARIANT_CHECK; - TORRENT_ASSERT(m_sent_handshake && !m_sent_bitfield); -#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_sent_handshake); m_sent_bitfield = true; -#endif #ifdef TORRENT_VERBOSE_LOGGING peer_log("==> HAVE_ALL"); #endif char msg[] = {0,0,0,1, msg_have_all}; send_buffer(msg, sizeof(msg)); + + m_ses.inc_stats_counter(counters::num_outgoing_have_all); } void bt_peer_connection::write_have_none() { INVARIANT_CHECK; - TORRENT_ASSERT(m_sent_handshake && !m_sent_bitfield); -#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_sent_handshake); m_sent_bitfield = true; -#endif #ifdef TORRENT_VERBOSE_LOGGING peer_log("==> HAVE_NONE"); #endif char msg[] = {0,0,0,1, msg_have_none}; send_buffer(msg, sizeof(msg)); + + m_ses.inc_stats_counter(counters::num_outgoing_have_none); } void bt_peer_connection::write_reject_request(peer_request const& r) { INVARIANT_CHECK; -#ifdef TORRENT_STATS - ++m_ses.m_piece_rejects; -#endif + m_ses.inc_stats_counter(counters::piece_rejects); if (!m_supports_fast) return; @@ -325,6 +336,8 @@ namespace libtorrent detail::write_int32(r.start, ptr); // begin detail::write_int32(r.length, ptr); // length send_buffer(msg, sizeof(msg)); + + m_ses.inc_stats_counter(counters::num_outgoing_reject); } void bt_peer_connection::write_allow_fast(int piece) @@ -340,6 +353,8 @@ namespace libtorrent char* ptr = msg + 5; detail::write_int32(piece, ptr); send_buffer(msg, sizeof(msg)); + + m_ses.inc_stats_counter(counters::num_outgoing_allowed_fast); } void bt_peer_connection::write_suggest(int piece) @@ -349,21 +364,22 @@ namespace libtorrent if (!m_supports_fast) return; TORRENT_ASSERT(m_sent_handshake && m_sent_bitfield); - TORRENT_ASSERT(associated_torrent().lock()->valid_metadata()); boost::shared_ptr t = associated_torrent().lock(); TORRENT_ASSERT(t); + TORRENT_ASSERT(t->valid_metadata()); - if (m_sent_suggested_pieces.empty()) - m_sent_suggested_pieces.resize(t->torrent_file().num_pieces(), false); - - if (m_sent_suggested_pieces[piece]) return; - m_sent_suggested_pieces.set_bit(piece); +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("==> SUGGEST [ piece: %d num_peers: %d ]", piece + , t->has_picker() ? t->picker().get_availability(piece) : -1); +#endif char msg[] = {0,0,0,5, msg_suggest_piece, 0, 0, 0, 0}; char* ptr = msg + 5; detail::write_int32(piece, ptr); send_buffer(msg, sizeof(msg)); + + m_ses.inc_stats_counter(counters::num_outgoing_suggest); } char random_byte() @@ -427,7 +443,7 @@ namespace libtorrent m_dh_key_exchange.reset(new (std::nothrow) dh_key_exchange); if (!m_dh_key_exchange || !m_dh_key_exchange->good()) { - disconnect(errors::no_memory); + disconnect(errors::no_memory, op_encryption); return; } @@ -505,10 +521,11 @@ namespace libtorrent // write the verification constant and crypto field int encrypt_size = sizeof(msg) - 512 + pad_size - 40; - boost::uint8_t crypto_provide = m_ses.get_pe_settings().allowed_enc_level; + boost::uint8_t crypto_provide = m_ses.settings().get_int(settings_pack::allowed_enc_level); // this is an invalid setting, but let's just make the best of the situation - if ((crypto_provide & pe_settings::both) == 0) crypto_provide = pe_settings::both; + if ((crypto_provide & settings_pack::pe_both) == 0) + crypto_provide = settings_pack::pe_both; #ifdef TORRENT_VERBOSE_LOGGING char const* level[] = {"plaintext", "rc4", "plaintext rc4"}; @@ -619,7 +636,7 @@ namespace libtorrent if (!m_enc_handler) { - disconnect(errors::no_memory); + disconnect(errors::no_memory, op_encryption); return; } @@ -673,8 +690,15 @@ namespace libtorrent return -1; } #endif // #ifndef TORRENT_DISABLE_ENCRYPTION - - void bt_peer_connection::append_const_send_buffer(char const* buffer, int size) + + void regular_c_free(char* buf, void* userdata, block_cache_reference ref) + { + ::free(buf); + } + + void bt_peer_connection::append_const_send_buffer(char const* buffer, int size + , chained_buffer::free_buffer_fun destructor, void* userdata + , block_cache_reference ref) { #ifndef TORRENT_DISABLE_ENCRYPTION if (m_encrypted && m_rc4_encrypted) @@ -683,15 +707,30 @@ namespace libtorrent // since we'll mutate it char* buf = (char*)malloc(size); memcpy(buf, buffer, size); - bt_append_send_buffer(buf, size, boost::bind(&::free, _1)); + bt_peer_connection::append_send_buffer(buf, size, ®ular_c_free, NULL); + destructor((char*)buffer, userdata, ref); } else #endif { - peer_connection::append_const_send_buffer(buffer, size); + peer_connection::append_const_send_buffer(buffer, size, destructor + , userdata, ref); } } + void bt_peer_connection::append_send_buffer(char* buffer, int size + , chained_buffer::free_buffer_fun destructor, void* userdata + , block_cache_reference ref, bool encrypted) + { + TORRENT_ASSERT(encrypted == false); +#ifndef TORRENT_DISABLE_ENCRYPTION + if (m_rc4_encrypted) + m_enc_handler->encrypt(buffer, size); +#endif + peer_connection::append_send_buffer(buffer, size, destructor + , userdata, ref, true); + } + #ifndef TORRENT_DISABLE_ENCRYPTION void encrypt(char* buf, int len, void* userdata) { @@ -721,14 +760,12 @@ namespace libtorrent peer_connection::send_buffer(buf, size, flags, fun, userdata); } - void bt_peer_connection::write_handshake() + void bt_peer_connection::write_handshake(bool plain_handshake) { INVARIANT_CHECK; TORRENT_ASSERT(!m_sent_handshake); -#if TORRENT_USE_ASSERTS m_sent_handshake = true; -#endif boost::shared_ptr t = associated_torrent().lock(); TORRENT_ASSERT(t); @@ -757,7 +794,7 @@ namespace libtorrent *(ptr + 5) |= 0x10; #endif - if (m_ses.m_settings.support_merkle_torrents) + if (m_settings.get_bool(settings_pack::support_merkle_torrents)) { // we support merkle torrents *(ptr + 5) |= 0x08; @@ -776,7 +813,7 @@ namespace libtorrent else bitmask += '0'; } } - peer_log("==> EXTENSION [ %s ]", bitmask.c_str()); + peer_log("==> EXTENSIONS [ %s ]", bitmask.c_str()); #endif ptr += 8; @@ -786,7 +823,7 @@ namespace libtorrent ptr += 20; // peer id - if (m_ses.m_settings.anonymous_mode) + if (m_settings.get_bool(settings_pack::anonymous_mode)) { // in anonymous mode, every peer connection // has a unique peer-id @@ -801,6 +838,36 @@ namespace libtorrent peer_log("==> HANDSHAKE [ ih: %s ]", to_hex(ih.to_string()).c_str()); #endif send_buffer(handshake, sizeof(handshake)); + + // for encrypted peers, just send a plain handshake. We + // don't know at this point if the rest should be + // obfuscated or not, we have to wait for the other end's + // response first. + if (plain_handshake) return; + + // we don't know how many pieces there are until we + // have the metadata + if (t->ready_for_connections()) + { + write_bitfield(); +#ifndef TORRENT_DISABLE_DHT + if (m_supports_dht_port && m_ses.has_dht()) + write_dht_port(m_ses.external_udp_port()); +#endif + + // if we don't have any pieces, don't do any preemptive + // unchoking at all. + if (t->num_have() > 0) + { + // if the peer is ignoring unchoke slots, or if we have enough + // unused slots, unchoke this peer right away, to save a round-trip + // in case it's interested. + if (ignore_unchoke_slots()) + send_unchoke(); + else if (m_ses.preemptive_unchoke()) + m_ses.unchoke_peer(*this); + } + } } boost::optional bt_peer_connection::downloading_piece_progress() const @@ -860,11 +927,11 @@ namespace libtorrent { INVARIANT_CHECK; - TORRENT_ASSERT(received > 0); - m_statistics.received_bytes(0, received); + TORRENT_ASSERT(received >= 0); + received_bytes(0, received); if (packet_size() != 1) { - disconnect(errors::invalid_choke, 2); + disconnect(errors::invalid_choke, op_bittorrent, 2); return; } if (!packet_finished()) return; @@ -910,11 +977,11 @@ namespace libtorrent { INVARIANT_CHECK; - TORRENT_ASSERT(received > 0); - m_statistics.received_bytes(0, received); + TORRENT_ASSERT(received >= 0); + received_bytes(0, received); if (packet_size() != 1) { - disconnect(errors::invalid_unchoke, 2); + disconnect(errors::invalid_unchoke, op_bittorrent, 2); return; } if (!packet_finished()) return; @@ -930,11 +997,11 @@ namespace libtorrent { INVARIANT_CHECK; - TORRENT_ASSERT(received > 0); - m_statistics.received_bytes(0, received); + TORRENT_ASSERT(received >= 0); + received_bytes(0, received); if (packet_size() != 1) { - disconnect(errors::invalid_interested, 2); + disconnect(errors::invalid_interested, op_bittorrent, 2); return; } if (!packet_finished()) return; @@ -950,11 +1017,11 @@ namespace libtorrent { INVARIANT_CHECK; - TORRENT_ASSERT(received > 0); - m_statistics.received_bytes(0, received); + TORRENT_ASSERT(received >= 0); + received_bytes(0, received); if (packet_size() != 1) { - disconnect(errors::invalid_not_interested, 2); + disconnect(errors::invalid_not_interested, op_bittorrent, 2); return; } if (!packet_finished()) return; @@ -970,11 +1037,11 @@ namespace libtorrent { INVARIANT_CHECK; - TORRENT_ASSERT(received > 0); - m_statistics.received_bytes(0, received); + TORRENT_ASSERT(received >= 0); + received_bytes(0, received); if (packet_size() != 5) { - disconnect(errors::invalid_have, 2); + disconnect(errors::invalid_have, op_bittorrent, 2); return; } if (!packet_finished()) return; @@ -995,18 +1062,18 @@ namespace libtorrent { INVARIANT_CHECK; - TORRENT_ASSERT(received > 0); + TORRENT_ASSERT(received >= 0); boost::shared_ptr t = associated_torrent().lock(); TORRENT_ASSERT(t); - m_statistics.received_bytes(0, received); + received_bytes(0, received); // if we don't have the metedata, we cannot // verify the bitfield size if (t->valid_metadata() && packet_size() - 1 != (t->torrent_file().num_pieces() + 7) / 8) { - disconnect(errors::invalid_bitfield_size, 2); + disconnect(errors::invalid_bitfield_size, op_bittorrent, 2); return; } @@ -1015,7 +1082,7 @@ namespace libtorrent buffer::const_interval recv_buffer = receive_buffer(); bitfield bits; - bits.borrow_bytes((char*)recv_buffer.begin + 1 + bits.assign((char*)recv_buffer.begin + 1 , t->valid_metadata()?get_bitfield().size():(packet_size()-1)*8); incoming_bitfield(bits); @@ -1029,11 +1096,11 @@ namespace libtorrent { INVARIANT_CHECK; - TORRENT_ASSERT(received > 0); - m_statistics.received_bytes(0, received); + TORRENT_ASSERT(received >= 0); + received_bytes(0, received); if (packet_size() != 13) { - disconnect(errors::invalid_request, 2); + disconnect(errors::invalid_request, op_bittorrent, 2); return; } if (!packet_finished()) return; @@ -1057,7 +1124,7 @@ namespace libtorrent { INVARIANT_CHECK; - TORRENT_ASSERT(received > 0); + TORRENT_ASSERT(received >= 0); buffer::const_interval recv_buffer = receive_buffer(); int recv_pos = receive_pos(); // recv_buffer.end - recv_buffer.begin; @@ -1070,12 +1137,12 @@ namespace libtorrent if (recv_pos == 1) { set_soft_packet_size(13); - m_statistics.received_bytes(0, received); + received_bytes(0, received); return; } if (recv_pos < 13) { - m_statistics.received_bytes(0, received); + received_bytes(0, received); return; } if (recv_pos == 13) @@ -1088,21 +1155,24 @@ namespace libtorrent if (list_size > packet_size() - 13) { - disconnect(errors::invalid_hash_list, 2); + disconnect(errors::invalid_hash_list, op_bittorrent, 2); return; } if (packet_size() - 13 - list_size > t->block_size()) { - disconnect(errors::packet_too_large, 2); + disconnect(errors::packet_too_large, op_bittorrent, 2); return; } TORRENT_ASSERT(!has_disk_receive_buffer()); - if (!allocate_disk_receive_buffer(packet_size() - 13 - list_size)) + if (!m_settings.get_bool(settings_pack::contiguous_recv_buffer)) { - m_statistics.received_bytes(0, received); - return; + if (!allocate_disk_receive_buffer(packet_size() - 13 - list_size)) + { + received_bytes(0, received); + return; + } } } } @@ -1114,18 +1184,21 @@ namespace libtorrent if (packet_size() - 9 > t->block_size()) { - disconnect(errors::packet_too_large, 2); + disconnect(errors::packet_too_large, op_bittorrent, 2); return; } - if (!allocate_disk_receive_buffer(packet_size() - 9)) + if (!m_settings.get_bool(settings_pack::contiguous_recv_buffer)) { - m_statistics.received_bytes(0, received); - return; + if (!allocate_disk_receive_buffer(packet_size() - 9)) + { + received_bytes(0, received); + return; + } } } } - TORRENT_ASSERT(has_disk_receive_buffer() || packet_size() == 9); + TORRENT_ASSERT(m_settings.get_bool(settings_pack::contiguous_recv_buffer) || has_disk_receive_buffer() || packet_size() == 9); // classify the received data as protocol chatter // or data payload for the statistics int piece_bytes = 0; @@ -1156,12 +1229,12 @@ namespace libtorrent if (recv_pos <= header_size) { // only received protocol data - m_statistics.received_bytes(0, received); + received_bytes(0, received); } else if (recv_pos - received >= header_size) { // only received payload data - m_statistics.received_bytes(received, 0); + received_bytes(received, 0); piece_bytes = received; } else @@ -1170,7 +1243,7 @@ namespace libtorrent TORRENT_ASSERT(recv_pos - received < header_size); TORRENT_ASSERT(recv_pos > header_size); TORRENT_ASSERT(header_size - (recv_pos - received) <= header_size); - m_statistics.received_bytes( + received_bytes( recv_pos - header_size , header_size - (recv_pos - received)); piece_bytes = recv_pos - header_size; @@ -1191,7 +1264,7 @@ namespace libtorrent if (is_disconnecting()) return; } - TORRENT_ASSERT(has_disk_receive_buffer() || packet_size() == header_size); + TORRENT_ASSERT(m_settings.get_bool(settings_pack::contiguous_recv_buffer) || has_disk_receive_buffer() || packet_size() == header_size); incoming_piece_fragment(piece_bytes); if (!packet_finished()) return; @@ -1206,7 +1279,7 @@ namespace libtorrent if (lazy_bdecode(recv_buffer.begin + 13, recv_buffer.begin+ 13 + list_size , hash_list, ec) != 0) { - disconnect(errors::invalid_hash_piece, 2); + disconnect(errors::invalid_hash_piece, op_bittorrent, 2); return; } @@ -1214,7 +1287,7 @@ namespace libtorrent // [ [node-index, hash], [node-index, hash], ... ] if (hash_list.type() != lazy_entry::list_t) { - disconnect(errors::invalid_hash_list, 2); + disconnect(errors::invalid_hash_list, op_bittorrent, 2); return; } @@ -1233,13 +1306,21 @@ namespace libtorrent } if (!nodes.empty() && !t->add_merkle_nodes(nodes, p.piece)) { - disconnect(errors::invalid_hash_piece, 2); + disconnect(errors::invalid_hash_piece, op_bittorrent, 2); return; } } - disk_buffer_holder holder(m_ses, release_disk_receive_buffer()); - incoming_piece(p, holder); + char* disk_buffer = release_disk_receive_buffer(); + if (disk_buffer) + { + disk_buffer_holder holder(m_allocator, disk_buffer); + incoming_piece(p, holder); + } + else + { + incoming_piece(p, recv_buffer.begin + header_size); + } } // ----------------------------- @@ -1250,11 +1331,11 @@ namespace libtorrent { INVARIANT_CHECK; - TORRENT_ASSERT(received > 0); - m_statistics.received_bytes(0, received); + TORRENT_ASSERT(received >= 0); + received_bytes(0, received); if (packet_size() != 13) { - disconnect(errors::invalid_cancel, 2); + disconnect(errors::invalid_cancel, op_bittorrent, 2); return; } if (!packet_finished()) return; @@ -1278,11 +1359,11 @@ namespace libtorrent { INVARIANT_CHECK; - TORRENT_ASSERT(received > 0); - m_statistics.received_bytes(0, received); + TORRENT_ASSERT(received >= 0); + received_bytes(0, received); if (packet_size() != 3) { - disconnect(errors::invalid_dht_port, 2); + disconnect(errors::invalid_dht_port, op_bittorrent, 2); return; } if (!packet_finished()) return; @@ -1298,8 +1379,8 @@ namespace libtorrent { m_supports_dht_port = true; #ifndef TORRENT_DISABLE_DHT - if (m_supports_dht_port && m_ses.m_dht) - write_dht_port(m_ses.m_external_udp_port); + if (m_supports_dht_port && m_ses.has_dht()) + write_dht_port(m_ses.external_udp_port()); #endif } } @@ -1308,10 +1389,10 @@ namespace libtorrent { INVARIANT_CHECK; - m_statistics.received_bytes(0, received); + received_bytes(0, received); if (!m_supports_fast) { - disconnect(errors::invalid_suggest, 2); + disconnect(errors::invalid_suggest, op_bittorrent, 2); return; } @@ -1328,10 +1409,10 @@ namespace libtorrent { INVARIANT_CHECK; - m_statistics.received_bytes(0, received); + received_bytes(0, received); if (!m_supports_fast) { - disconnect(errors::invalid_have_all, 2); + disconnect(errors::invalid_have_all, op_bittorrent, 2); return; } incoming_have_all(); @@ -1341,10 +1422,10 @@ namespace libtorrent { INVARIANT_CHECK; - m_statistics.received_bytes(0, received); + received_bytes(0, received); if (!m_supports_fast) { - disconnect(errors::invalid_have_none, 2); + disconnect(errors::invalid_have_none, op_bittorrent, 2); return; } incoming_have_none(); @@ -1354,10 +1435,10 @@ namespace libtorrent { INVARIANT_CHECK; - m_statistics.received_bytes(0, received); + received_bytes(0, received); if (!m_supports_fast) { - disconnect(errors::invalid_reject, 2); + disconnect(errors::invalid_reject, op_bittorrent, 2); return; } @@ -1378,10 +1459,10 @@ namespace libtorrent { INVARIANT_CHECK; - m_statistics.received_bytes(0, received); + received_bytes(0, received); if (!m_supports_fast) { - disconnect(errors::invalid_allow_fast, 2); + disconnect(errors::invalid_allow_fast, op_bittorrent, 2); return; } @@ -1490,7 +1571,7 @@ namespace libtorrent case hp_connect: { // add or find the peer with this endpoint - policy::peer* p = t->get_policy().add_peer(ep, peer_id(0), peer_info::pex, 0); + torrent_peer* p = t->add_peer(ep, peer_info::pex); if (p == 0 || p->connection) { #if defined TORRENT_VERBOSE_LOGGING @@ -1519,6 +1600,7 @@ namespace libtorrent // mark this connection to be in holepunch mode // so that it will retry faster and stick to uTP while it's // retrying + t->update_want_peers(); if (p->connection) p->connection->set_holepunch_mode(); #if defined TORRENT_VERBOSE_LOGGING @@ -1581,6 +1663,8 @@ namespace libtorrent TORRENT_ASSERT(ptr <= buf + sizeof(buf)); send_buffer(buf, ptr - buf); + + m_ses.inc_stats_counter(counters::num_outgoing_extended); } #endif // TORRENT_DISABLE_EXTENSIONS @@ -1593,17 +1677,17 @@ namespace libtorrent { INVARIANT_CHECK; - TORRENT_ASSERT(received > 0); - m_statistics.received_bytes(0, received); + TORRENT_ASSERT(received >= 0); + received_bytes(0, received); if (packet_size() < 2) { - disconnect(errors::invalid_extended, 2); + disconnect(errors::invalid_extended, op_bittorrent, 2); return; } if (associated_torrent().expired()) { - disconnect(errors::invalid_extended, 2); + disconnect(errors::invalid_extended, op_bittorrent, 2); return; } @@ -1697,7 +1781,7 @@ namespace libtorrent return; } - disconnect(errors::invalid_message, 2); + disconnect(errors::invalid_message, op_bittorrent, 2); return; } @@ -1751,8 +1835,7 @@ namespace libtorrent int listen_port = int(root.dict_find_int_value("p")); if (listen_port > 0 && peer_info_struct() != 0) { - t->get_policy().update_peer_port(listen_port - , peer_info_struct(), peer_info::incoming); + t->update_peer_port(listen_port, peer_info_struct(), peer_info::incoming); received_listen_port(); if (is_disconnecting()) return; } @@ -1772,7 +1855,7 @@ namespace libtorrent if (root.dict_find_int_value("upload_only", 0)) set_upload_only(true); - if (m_ses.m_settings.support_share_mode + if (m_settings.get_bool(settings_pack::support_share_mode) && root.dict_find_int_value("share_mode", 0)) set_share_mode(true); @@ -1784,7 +1867,7 @@ namespace libtorrent address_v4::bytes_type bytes; std::copy(myip.begin(), myip.end(), bytes.begin()); m_ses.set_external_address(address_v4(bytes) - , aux::session_impl::source_peer, remote().address()); + , aux::session_interface::source_peer, remote().address()); } #if TORRENT_USE_IPV6 else if (myip.size() == address_v6::bytes_type().size()) @@ -1794,10 +1877,10 @@ namespace libtorrent address_v6 ipv6_address(bytes); if (ipv6_address.is_v4_mapped()) m_ses.set_external_address(ipv6_address.to_v4() - , aux::session_impl::source_peer, remote().address()); + , aux::session_interface::source_peer, remote().address()); else m_ses.set_external_address(ipv6_address - , aux::session_impl::source_peer, remote().address()); + , aux::session_interface::source_peer, remote().address()); } #endif } @@ -1805,9 +1888,11 @@ namespace libtorrent // if we're finished and this peer is uploading only // disconnect it if (t->is_finished() && upload_only() - && t->settings().close_redundant_connections + && m_settings.get_bool(settings_pack::close_redundant_connections) && !t->share_mode()) - disconnect(errors::upload_upload_connection); + disconnect(errors::upload_upload_connection, op_bittorrent); + + m_ses.inc_stats_counter(counters::num_incoming_ext_handshake); } #endif // TORRENT_DISABLE_EXTENSIONS @@ -1815,12 +1900,12 @@ namespace libtorrent { INVARIANT_CHECK; - TORRENT_ASSERT(received > 0); + TORRENT_ASSERT(received >= 0); // this means the connection has been closed already if (associated_torrent().expired()) { - m_statistics.received_bytes(0, received); + received_bytes(0, received); return false; } @@ -1829,7 +1914,7 @@ namespace libtorrent TORRENT_ASSERT(recv_buffer.left() >= 1); int packet_type = (unsigned char)recv_buffer[0]; - if (m_ses.m_settings.support_merkle_torrents + if (m_settings.get_bool(settings_pack::support_merkle_torrents) && packet_type == 250) packet_type = msg_piece; if (packet_type < 0 @@ -1847,31 +1932,50 @@ namespace libtorrent } #endif - m_statistics.received_bytes(0, received); + received_bytes(0, received); // What's going on here?! // break in debug builds to allow investigation // TORRENT_ASSERT(false); - disconnect(errors::invalid_message); + disconnect(errors::invalid_message, op_bittorrent); return packet_finished(); } TORRENT_ASSERT(m_message_handler[packet_type] != 0); #ifdef TORRENT_DEBUG - size_type cur_payload_dl = m_statistics.last_payload_downloaded(); - size_type cur_protocol_dl = m_statistics.last_protocol_downloaded(); + size_type cur_payload_dl = statistics().last_payload_downloaded(); + size_type cur_protocol_dl = statistics().last_protocol_downloaded(); #endif + // call the correct handler for this packet type (this->*m_message_handler[packet_type])(received); #ifdef TORRENT_DEBUG - TORRENT_ASSERT(m_statistics.last_payload_downloaded() - cur_payload_dl >= 0); - TORRENT_ASSERT(m_statistics.last_protocol_downloaded() - cur_protocol_dl >= 0); - size_type stats_diff = m_statistics.last_payload_downloaded() - cur_payload_dl + - m_statistics.last_protocol_downloaded() - cur_protocol_dl; + TORRENT_ASSERT(statistics().last_payload_downloaded() - cur_payload_dl >= 0); + TORRENT_ASSERT(statistics().last_protocol_downloaded() - cur_protocol_dl >= 0); + size_type stats_diff = statistics().last_payload_downloaded() - cur_payload_dl + + statistics().last_protocol_downloaded() - cur_protocol_dl; TORRENT_ASSERT(stats_diff == received); #endif - return packet_finished(); + bool finished = packet_finished(); + + if (finished) + { + // count this packet in the session stats counters + int counter = counters::num_incoming_extended; + if (packet_type <= msg_dht_port) + counter = counters::num_incoming_choke + packet_type; + else if (packet_type <= msg_allowed_fast) + counter = counters::num_incoming_suggest + packet_type; + else if (packet_type <= msg_extended) + counter = counters::num_incoming_extended; + else + TORRENT_ASSERT(false); + + m_ses.inc_stats_counter(counter); + } + + return finished; } #ifndef TORRENT_DISABLE_EXTENSIONS @@ -1886,7 +1990,7 @@ namespace libtorrent // if we send upload-only, the other end is very likely to disconnect // us, at least if it's a seed. If we don't want to close redundant // connections, don't sent upload-only - if (!m_ses.settings().close_redundant_connections) return; + if (!m_settings.get_bool(settings_pack::close_redundant_connections)) return; #ifdef TORRENT_VERBOSE_LOGGING peer_log("==> UPLOAD_ONLY [ %d ]" @@ -1903,6 +2007,8 @@ namespace libtorrent // make another piece available detail::write_uint8(t->is_upload_only() && !t->super_seeding(), ptr); send_buffer(msg, sizeof(msg)); + + m_ses.inc_stats_counter(counters::num_outgoing_extended); } void bt_peer_connection::write_share_mode() @@ -1917,6 +2023,8 @@ namespace libtorrent detail::write_uint8(m_share_mode_id, ptr); detail::write_uint8(t->share_mode(), ptr); send_buffer(msg, sizeof(msg)); + + m_ses.inc_stats_counter(counters::num_outgoing_extended); } #endif @@ -1950,6 +2058,8 @@ namespace libtorrent detail::write_int32(r.length, ptr); // length send_buffer(msg, sizeof(msg)); + m_ses.inc_stats_counter(counters::num_outgoing_cancel); + if (!m_supports_fast) incoming_reject_request(r); } @@ -1968,6 +2078,8 @@ namespace libtorrent detail::write_int32(r.start, ptr); // begin detail::write_int32(r.length, ptr); // length send_buffer(msg, sizeof(msg), message_type_request); + + m_ses.inc_stats_counter(counters::num_outgoing_request); } void bt_peer_connection::write_bitfield() @@ -1976,21 +2088,19 @@ namespace libtorrent boost::shared_ptr t = associated_torrent().lock(); TORRENT_ASSERT(t); - TORRENT_ASSERT(m_sent_handshake && !m_sent_bitfield); + TORRENT_ASSERT(m_sent_handshake); TORRENT_ASSERT(t->valid_metadata()); - // in this case, have_all or have_none should be sent instead - TORRENT_ASSERT(!m_supports_fast || !t->is_seed() || t->num_have() != 0); - if (t->super_seeding()) { +#ifdef TORRENT_VERBOSE_LOGGING + peer_log(" *** NOT SENDING BITFIELD, super seeding"); +#endif if (m_supports_fast) write_have_none(); // if we are super seeding, pretend to not have any piece // and don't send a bitfield -#if TORRENT_USE_ASSERTS m_sent_bitfield = true; -#endif // bootstrap superseeding by sending two have message int piece = t->get_piece_to_super_seed(get_bitfield()); @@ -2017,9 +2127,7 @@ namespace libtorrent #ifdef TORRENT_VERBOSE_LOGGING peer_log(" *** NOT SENDING BITFIELD"); #endif -#if TORRENT_USE_ASSERTS m_sent_bitfield = true; -#endif return; } @@ -2029,7 +2137,7 @@ namespace libtorrent int num_lazy_pieces = 0; int lazy_piece = 0; - if (t->is_seed() && m_ses.settings().lazy_bitfields + if (t->is_seed() && m_settings.get_bool(settings_pack::lazy_bitfields) #ifndef TORRENT_DISABLE_ENCRYPTION && !m_encrypted #endif @@ -2081,6 +2189,12 @@ namespace libtorrent for (int c = 0; c < num_lazy_pieces; ++c) msg[5 + lazy_pieces[c] / 8] &= ~(0x80 >> (lazy_pieces[c] & 7)); + // add predictive pieces to the bitfield as well, since we won't + // announce them again + for (std::vector::const_iterator i = t->predictive_pieces().begin() + , end(t->predictive_pieces().end()); i != end; ++i) + msg[5 + *i / 8] |= (0x80 >> (*i & 7)); + #ifdef TORRENT_VERBOSE_LOGGING std::string bitfield_string; @@ -2092,12 +2206,12 @@ namespace libtorrent } peer_log("==> BITFIELD [ %s ]", bitfield_string.c_str()); #endif -#if TORRENT_USE_ASSERTS m_sent_bitfield = true; -#endif send_buffer(msg, packet_size); + m_ses.inc_stats_counter(counters::num_outgoing_bitfield); + if (num_lazy_pieces > 0) { for (int i = 0; i < num_lazy_pieces; ++i) @@ -2127,29 +2241,30 @@ namespace libtorrent // if we're using a proxy, our listen port won't be useful // anyway. - if (!m_ses.settings().force_proxy && is_outgoing()) + if (!m_settings.get_bool(settings_pack::force_proxy) && is_outgoing()) handshake["p"] = m_ses.listen_port(); // only send the port in case we bade the connection // on incoming connections the other end already knows // our listen port - if (!m_ses.m_settings.anonymous_mode) + if (!m_settings.get_bool(settings_pack::anonymous_mode)) { - handshake["v"] = m_ses.settings().handshake_client_version.empty() - ? m_ses.settings().user_agent : m_ses.settings().handshake_client_version; + handshake["v"] = m_settings.get_str(settings_pack::handshake_client_version).empty() + ? m_settings.get_str(settings_pack::user_agent) + : m_settings.get_str(settings_pack::handshake_client_version); } std::string remote_address; std::back_insert_iterator out(remote_address); detail::write_address(remote().address(), out); handshake["yourip"] = remote_address; - handshake["reqq"] = m_ses.settings().max_allowed_in_request_queue; + handshake["reqq"] = m_settings.get_int(settings_pack::max_allowed_in_request_queue); boost::shared_ptr t = associated_torrent().lock(); TORRENT_ASSERT(t); m["upload_only"] = upload_only_msg; m["ut_holepunch"] = holepunch_msg; - if (m_ses.m_settings.support_share_mode) + if (m_settings.get_bool(settings_pack::support_share_mode)) m["share_mode"] = share_mode_msg; m["lt_donthave"] = dont_have_msg; @@ -2169,18 +2284,18 @@ namespace libtorrent if (t->is_upload_only() && !t->share_mode() && !t->super_seeding() - && (!m_ses.settings().lazy_bitfields + && (!m_settings.get_bool(settings_pack::lazy_bitfields) #ifndef TORRENT_DISABLE_ENCRYPTION || m_encrypted #endif )) handshake["upload_only"] = 1; - if (m_ses.m_settings.support_share_mode + if (m_settings.get_bool(settings_pack::support_share_mode) && t->share_mode()) handshake["share_mode"] = 1; - if (!m_ses.m_settings.anonymous_mode) + if (!m_settings.get_bool(settings_pack::anonymous_mode)) { tcp::endpoint ep = m_ses.get_ipv6_interface(); if (!is_any(ep.address())) @@ -2227,6 +2342,8 @@ namespace libtorrent send_buffer(msg, sizeof(msg)); send_buffer(&dict_msg[0], dict_msg.size()); + m_ses.inc_stats_counter(counters::num_outgoing_ext_handshake); + #if defined TORRENT_VERBOSE_LOGGING peer_log("==> EXTENDED HANDSHAKE: %s", handshake.to_string().c_str()); #endif @@ -2242,6 +2359,8 @@ namespace libtorrent if (is_choked()) return; char msg[] = {0,0,0,1,msg_choke}; send_buffer(msg, sizeof(msg)); + + m_ses.inc_stats_counter(counters::num_outgoing_choke); } void bt_peer_connection::write_unchoke() @@ -2253,6 +2372,8 @@ namespace libtorrent char msg[] = {0,0,0,1,msg_unchoke}; send_buffer(msg, sizeof(msg)); + m_ses.inc_stats_counter(counters::num_outgoing_unchoke); + #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) @@ -2270,6 +2391,8 @@ namespace libtorrent char msg[] = {0,0,0,1,msg_interested}; send_buffer(msg, sizeof(msg)); + + m_ses.inc_stats_counter(counters::num_outgoing_interested); } void bt_peer_connection::write_not_interested() @@ -2280,6 +2403,8 @@ namespace libtorrent char msg[] = {0,0,0,1,msg_not_interested}; send_buffer(msg, sizeof(msg)); + + m_ses.inc_stats_counter(counters::num_outgoing_not_interested); } void bt_peer_connection::write_have(int index) @@ -2294,6 +2419,45 @@ namespace libtorrent char* ptr = msg + 5; detail::write_int32(index, ptr); send_buffer(msg, sizeof(msg)); + + m_ses.inc_stats_counter(counters::num_outgoing_have); + } + + void bt_peer_connection::write_dont_have(int index) + { +#ifndef TORRENT_DISABLE_EXTENSIONS + INVARIANT_CHECK; + TORRENT_ASSERT(associated_torrent().lock()->valid_metadata()); + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < associated_torrent().lock()->torrent_file().num_pieces()); + + if (in_handshake()) return; + + TORRENT_ASSERT(m_sent_handshake && m_sent_bitfield); + + if (!m_supports_extensions || m_dont_have_id == 0) return; + + char msg[] = {0,0,0,6,msg_extended,char(m_dont_have_id),0,0,0,0}; + char* ptr = msg + 6; + detail::write_int32(index, ptr); + send_buffer(msg, sizeof(msg)); + + m_ses.inc_stats_counter(counters::num_outgoing_extended); +#endif + } + + void buffer_reclaim_block(char* buffer, void* userdata + , block_cache_reference ref) + { + buffer_allocator_interface* buf = (buffer_allocator_interface*)userdata; + buf->reclaim_block(ref); + } + + void buffer_free_disk_buf(char* buffer, void* userdata + , block_cache_reference ref) + { + buffer_allocator_interface* buf = (buffer_allocator_interface*)userdata; + buf->free_disk_buffer(buffer); } void bt_peer_connection::write_piece(peer_request const& r, disk_buffer_holder& buffer) @@ -2317,7 +2481,7 @@ namespace libtorrent char* ptr = msg; TORRENT_ASSERT(r.length <= 16 * 1024); detail::write_int32(r.length + 1 + 4 + 4, ptr); - if (m_ses.m_settings.support_merkle_torrents && merkle) + if (m_settings.get_bool(settings_pack::support_merkle_torrents) && merkle) detail::write_uint8(250, ptr); else detail::write_uint8(msg_piece, ptr); @@ -2353,35 +2517,22 @@ namespace libtorrent send_buffer(msg, 13); } - bt_append_send_buffer(buffer.get(), r.length - , boost::bind(&session_impl::free_disk_buffer - , boost::ref(m_ses), _1)); + if (buffer.ref().storage == 0) + { + append_send_buffer(buffer.get(), r.length + , &buffer_free_disk_buf, &m_allocator); + } + else + { + append_const_send_buffer(buffer.get(), r.length + , &buffer_reclaim_block, &m_allocator, buffer.ref()); + } buffer.release(); m_payloads.push_back(range(send_buffer_size() - r.length, r.length)); setup_send(); - } - namespace - { - struct match_peer_id - { - match_peer_id(peer_id const& id, peer_connection const* pc) - : m_id(id), m_pc(pc) - { TORRENT_ASSERT(pc); } - - bool operator()(policy::peer const* p) const - { - return p->connection != m_pc - && p->connection - && p->connection->pid() == m_id - && !p->connection->pid().is_all_zeros() - && p->address() == m_pc->remote().address(); - } - - peer_id const& m_id; - peer_connection const* m_pc; - }; + m_ses.inc_stats_counter(counters::num_outgoing_piece); } // -------------------------- @@ -2395,10 +2546,14 @@ namespace libtorrent if (error) { - m_statistics.received_bytes(0, bytes_transferred); + received_bytes(0, bytes_transferred); return; } + // make sure are much as possible of the response ends up in the same + // packet, or at least back-to-back packets + cork c_(*this); + boost::shared_ptr t = associated_torrent().lock(); #ifndef TORRENT_DISABLE_ENCRYPTION @@ -2419,7 +2574,7 @@ namespace libtorrent // for outgoing if (m_state == read_pe_dhkey) { - m_statistics.received_bytes(0, bytes_transferred); + received_bytes(0, bytes_transferred); TORRENT_ASSERT(!m_encrypted); TORRENT_ASSERT(!m_rc4_encrypted); @@ -2436,7 +2591,7 @@ namespace libtorrent // read dh key, generate shared secret if (m_dh_key_exchange->compute_secret(recv_buffer.begin) == -1) { - disconnect(errors::no_memory); + disconnect(errors::no_memory, op_encryption); return; } @@ -2461,7 +2616,7 @@ namespace libtorrent // again according to peer selection. m_rc4_encrypted = true; m_encrypted = true; - write_handshake(); + write_handshake(true); m_rc4_encrypted = false; m_encrypted = false; @@ -2490,10 +2645,10 @@ namespace libtorrent if (recv_buffer.left() < 20) { - m_statistics.received_bytes(0, bytes_transferred); + received_bytes(0, bytes_transferred); if (packet_finished()) - disconnect(errors::sync_hash_not_found, 1); + disconnect(errors::sync_hash_not_found, op_bittorrent, 1); return; } @@ -2509,8 +2664,8 @@ namespace libtorrent m_sync_hash.reset(new (std::nothrow) sha1_hash(h.final())); if (!m_sync_hash) { - m_statistics.received_bytes(0, bytes_transferred); - disconnect(errors::no_memory); + received_bytes(0, bytes_transferred); + disconnect(errors::no_memory, op_encryption); return; } } @@ -2521,13 +2676,13 @@ namespace libtorrent // No sync if (syncoffset == -1) { - m_statistics.received_bytes(0, bytes_transferred); + received_bytes(0, bytes_transferred); std::size_t bytes_processed = recv_buffer.left() - 20; m_sync_bytes_read += bytes_processed; if (m_sync_bytes_read >= 512) { - disconnect(errors::sync_hash_not_found, 1); + disconnect(errors::sync_hash_not_found, op_encryption, 1); return; } @@ -2550,7 +2705,7 @@ namespace libtorrent m_sync_hash.reset(); int transferred_used = bytes_processed - recv_buffer.left() + bytes_transferred; TORRENT_ASSERT(transferred_used <= int(bytes_transferred)); - m_statistics.received_bytes(0, transferred_used); + received_bytes(0, transferred_used); bytes_transferred -= transferred_used; cut_receive_buffer(bytes_processed, 28); } @@ -2558,7 +2713,7 @@ namespace libtorrent if (m_state == read_pe_skey_vc) { - m_statistics.received_bytes(0, bytes_transferred); + received_bytes(0, bytes_transferred); bytes_transferred = 0; TORRENT_ASSERT(!m_encrypted); @@ -2567,42 +2722,37 @@ namespace libtorrent TORRENT_ASSERT(packet_size() == 28); if (!packet_finished()) return; + if (is_disconnecting()) return; + TORRENT_ASSERT(!is_disconnecting()); recv_buffer = receive_buffer(); - aux::session_impl::torrent_map::const_iterator i; + TORRENT_ASSERT(!is_disconnecting()); - for (i = m_ses.m_torrents.begin(); i != m_ses.m_torrents.end(); ++i) + sha1_hash ih(recv_buffer.begin); + torrent const* ti = m_ses.find_encrypted_torrent(ih, m_dh_key_exchange->get_hash_xor_mask()); + + if (ti) { - torrent const& ti = *i->second; - - sha1_hash const& skey_hash = ti.obfuscated_hash(); - sha1_hash obfs_hash = m_dh_key_exchange->get_hash_xor_mask(); - obfs_hash ^= skey_hash; - - if (std::equal(recv_buffer.begin, recv_buffer.begin + 20, - (char*)&obfs_hash[0])) + if (!t) { - if (!t) - { - attach_to_torrent(ti.info_hash(), false); - if (is_disconnecting()) return; + attach_to_torrent(ti->info_hash(), false); + if (is_disconnecting()) return; + TORRENT_ASSERT(!is_disconnecting()); - t = associated_torrent().lock(); - TORRENT_ASSERT(t); - } - - init_pe_rc4_handler(m_dh_key_exchange->get_secret(), ti.info_hash()); -#ifdef TORRENT_VERBOSE_LOGGING - peer_log("*** stream key found, torrent located"); -#endif - break; + t = associated_torrent().lock(); + TORRENT_ASSERT(t); } + + init_pe_rc4_handler(m_dh_key_exchange->get_secret(), ti->info_hash()); +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("*** stream key found, torrent located"); +#endif } if (!m_enc_handler.get()) { - disconnect(errors::invalid_info_hash, 1); + disconnect(errors::invalid_info_hash, op_bittorrent, 1); return; } @@ -2614,7 +2764,7 @@ namespace libtorrent const char sh_vc[] = {0,0,0,0, 0,0,0,0}; if (!std::equal(sh_vc, sh_vc+8, recv_buffer.begin + 20)) { - disconnect(errors::invalid_encryption_constant, 2); + disconnect(errors::invalid_encryption_constant, op_encryption, 2); return; } @@ -2635,9 +2785,9 @@ namespace libtorrent if (recv_buffer.left() < 8) { - m_statistics.received_bytes(0, bytes_transferred); + received_bytes(0, bytes_transferred); if (packet_finished()) - disconnect(errors::invalid_encryption_constant, 2); + disconnect(errors::invalid_encryption_constant, op_encryption, 2); return; } @@ -2649,7 +2799,7 @@ namespace libtorrent m_sync_vc.reset(new (std::nothrow) char[8]); if (!m_sync_vc) { - disconnect(errors::no_memory); + disconnect(errors::no_memory, op_encryption); return; } std::fill(m_sync_vc.get(), m_sync_vc.get() + 8, 0); @@ -2665,11 +2815,11 @@ namespace libtorrent { std::size_t bytes_processed = recv_buffer.left() - 8; m_sync_bytes_read += bytes_processed; - m_statistics.received_bytes(0, bytes_transferred); + received_bytes(0, bytes_transferred); if (m_sync_bytes_read >= 512) { - disconnect(errors::invalid_encryption_constant, 2); + disconnect(errors::invalid_encryption_constant, op_encryption, 2); return; } @@ -2688,7 +2838,7 @@ namespace libtorrent #endif int transferred_used = bytes_processed - recv_buffer.left() + bytes_transferred; TORRENT_ASSERT(transferred_used <= int(bytes_transferred)); - m_statistics.received_bytes(0, transferred_used); + received_bytes(0, transferred_used); bytes_transferred -= transferred_used; cut_receive_buffer(bytes_processed, 4 + 2); @@ -2705,7 +2855,7 @@ namespace libtorrent TORRENT_ASSERT(!m_encrypted); TORRENT_ASSERT(!m_rc4_encrypted); TORRENT_ASSERT(packet_size() == 4+2); - m_statistics.received_bytes(0, bytes_transferred); + received_bytes(0, bytes_transferred); bytes_transferred = 0; if (!packet_finished()) return; @@ -2727,12 +2877,12 @@ namespace libtorrent if (!is_outgoing()) { // select a crypto method - int allowed_encryption = m_ses.get_pe_settings().allowed_enc_level; + int allowed_encryption = m_ses.settings().get_int(settings_pack::allowed_enc_level); int crypto_select = crypto_field & allowed_encryption; // when prefer_rc4 is set, keep the most significant bit // otherwise keep the least significant one - if (m_ses.get_pe_settings().prefer_rc4) + if (m_ses.settings().get_bool(settings_pack::prefer_rc4)) { int mask = INT_MAX; while (crypto_select & (mask << 1)) @@ -2753,7 +2903,7 @@ namespace libtorrent if (crypto_select == 0) { - disconnect(errors::unsupported_encryption_mode, 1); + disconnect(errors::unsupported_encryption_mode, op_encryption, 1); return; } @@ -2763,26 +2913,26 @@ namespace libtorrent else // is_outgoing() { // check if crypto select is valid - int allowed_encryption = m_ses.get_pe_settings().allowed_enc_level; + int allowed_encryption = m_ses.settings().get_int(settings_pack::allowed_enc_level); crypto_field &= allowed_encryption; if (crypto_field == 0) { // we don't allow any of the offered encryption levels - disconnect(errors::unsupported_encryption_mode_selected, 2); + disconnect(errors::unsupported_encryption_mode_selected, op_encryption, 2); return; } - if (crypto_field == pe_settings::plaintext) + if (crypto_field == settings_pack::pe_plaintext) m_rc4_encrypted = false; - else if (crypto_field == pe_settings::rc4) + else if (crypto_field == settings_pack::pe_rc4) m_rc4_encrypted = true; } int len_pad = detail::read_int16(recv_buffer.begin); if (len_pad < 0 || len_pad > 512) { - disconnect(errors::invalid_pad_size, 2); + disconnect(errors::invalid_pad_size, op_encryption, 2); return; } @@ -2804,7 +2954,7 @@ namespace libtorrent if (m_state == read_pe_pad) { TORRENT_ASSERT(!m_encrypted); - m_statistics.received_bytes(0, bytes_transferred); + received_bytes(0, bytes_transferred); bytes_transferred = 0; if (!packet_finished()) return; @@ -2822,7 +2972,7 @@ namespace libtorrent if (len_ia < 0) { - disconnect(errors::invalid_encrypt_handshake, 2); + disconnect(errors::invalid_encrypt_handshake, op_encryption, 2); return; } @@ -2851,7 +3001,7 @@ namespace libtorrent if (m_state == read_pe_ia) { - m_statistics.received_bytes(0, bytes_transferred); + received_bytes(0, bytes_transferred); bytes_transferred = 0; TORRENT_ASSERT(!is_outgoing()); TORRENT_ASSERT(!m_encrypted); @@ -2883,10 +3033,32 @@ namespace libtorrent if (m_state == init_bt_handshake) { - m_statistics.received_bytes(0, bytes_transferred); + received_bytes(0, bytes_transferred); bytes_transferred = 0; TORRENT_ASSERT(m_encrypted); + if (is_outgoing() && t->ready_for_connections()) + { + write_bitfield(); +#ifndef TORRENT_DISABLE_DHT + if (m_supports_dht_port && m_ses.has_dht()) + write_dht_port(m_ses.external_udp_port()); +#endif + + // if we don't have any pieces, don't do any preemptive + // unchoking at all. + if (t->num_have() > 0) + { + // if the peer is ignoring unchoke slots, or if we have enough + // unused slots, unchoke this peer right away, to save a round-trip + // in case it's interested. + if (ignore_unchoke_slots()) + send_unchoke(); + else if (m_ses.preemptive_unchoke()) + m_ses.unchoke_peer(*this); + } + } + // decrypt remaining received bytes if (m_rc4_encrypted) { @@ -2912,20 +3084,22 @@ namespace libtorrent // encrypted portion of handshake completed, toggle // peer_info pe_support flag back to true if (is_outgoing() && - m_ses.get_pe_settings().out_enc_policy == pe_settings::enabled) + m_ses.settings().get_int(settings_pack::out_enc_policy) + == settings_pack::pe_enabled) { - policy::peer* pi = peer_info_struct(); + torrent_peer* pi = peer_info_struct(); TORRENT_ASSERT(pi); pi->pe_support = true; } + } #endif // #ifndef TORRENT_DISABLE_ENCRYPTION if (m_state == read_protocol_identifier) { - m_statistics.received_bytes(0, bytes_transferred); + received_bytes(0, bytes_transferred); bytes_transferred = 0; TORRENT_ASSERT(packet_size() == 20); @@ -2949,15 +3123,16 @@ namespace libtorrent #ifdef TORRENT_VERBOSE_LOGGING peer_log("*** SSL peers are not allowed to use any other encryption"); #endif - disconnect(errors::invalid_info_hash, 1); + disconnect(errors::invalid_info_hash, op_bittorrent, 1); return; } #endif // TORRENT_USE_OPENSSL if (!is_outgoing() - && m_ses.get_pe_settings().in_enc_policy == pe_settings::disabled) + && m_ses.settings().get_int(settings_pack::in_enc_policy) + == settings_pack::pe_disabled) { - disconnect(errors::no_incoming_encrypted); + disconnect(errors::no_incoming_encrypted, op_bittorrent); return; } @@ -2967,7 +3142,7 @@ namespace libtorrent // handshake by this point if (m_encrypted || is_outgoing()) { - disconnect(errors::invalid_info_hash, 1); + disconnect(errors::invalid_info_hash, op_bittorrent, 1); return; } @@ -2979,7 +3154,7 @@ namespace libtorrent TORRENT_ASSERT(!packet_finished()); return; #else - disconnect(errors::invalid_info_hash, 1); + disconnect(errors::invalid_info_hash, op_bittorrent, 1); return; #endif // TORRENT_DISABLE_ENCRYPTION } @@ -2989,11 +3164,12 @@ namespace libtorrent TORRENT_ASSERT(m_state != read_pe_dhkey); if (!is_outgoing() - && m_ses.get_pe_settings().in_enc_policy == pe_settings::forced + && m_ses.settings().get_int(settings_pack::in_enc_policy) + == settings_pack::pe_forced && !m_encrypted && !is_ssl(*get_socket())) { - disconnect(errors::no_incoming_regular); + disconnect(errors::no_incoming_regular, op_bittorrent); return; } #endif @@ -3010,7 +3186,7 @@ namespace libtorrent // fall through if (m_state == read_info_hash) { - m_statistics.received_bytes(0, bytes_transferred); + received_bytes(0, bytes_transferred); bytes_transferred = 0; TORRENT_ASSERT(packet_size() == 28); @@ -3047,6 +3223,8 @@ namespace libtorrent if (recv_buffer[7] & 0x04) m_supports_fast = true; + t = associated_torrent().lock(); + // ok, now we have got enough of the handshake. Is this connection // attached to a torrent? if (!t) @@ -3075,7 +3253,7 @@ namespace libtorrent #ifdef TORRENT_VERBOSE_LOGGING peer_log("*** received invalid info_hash"); #endif - disconnect(errors::invalid_info_hash, 1); + disconnect(errors::invalid_info_hash, op_bittorrent, 1); return; } @@ -3090,14 +3268,10 @@ namespace libtorrent // if this is a local connection, we have already // sent the handshake if (!is_outgoing()) write_handshake(); -// if (t->valid_metadata()) -// write_bitfield(); TORRENT_ASSERT(m_sent_handshake); if (is_disconnecting()) return; - TORRENT_ASSERT(t->get_policy().has_connection(this)); - m_state = read_peer_id; reset_recv_buffer(20); } @@ -3106,8 +3280,9 @@ namespace libtorrent if (m_state == read_peer_id) { TORRENT_ASSERT(m_sent_handshake); - m_statistics.received_bytes(0, bytes_transferred); + received_bytes(0, bytes_transferred); // bytes_transferred = 0; + t = associated_torrent().lock(); if (!t) { TORRENT_ASSERT(!packet_finished()); // TODO @@ -3136,17 +3311,14 @@ namespace libtorrent #endif peer_id pid; std::copy(recv_buffer.begin, recv_buffer.begin + 20, (char*)pid.begin()); - set_pid(pid); - if (t->settings().allow_multiple_connections_per_ip) + if (t->settings().get_bool(settings_pack::allow_multiple_connections_per_ip)) { // now, let's see if this connection should be closed - policy& p = t->get_policy(); - policy::iterator i = std::find_if(p.begin_peer(), p.end_peer() - , match_peer_id(pid, this)); - if (i != p.end_peer()) + peer_connection* p = t->find_peer(pid); + if (p) { - TORRENT_ASSERT((*i)->connection->pid() == pid); + TORRENT_ASSERT(p->pid() == pid); // we found another connection with the same peer-id // which connection should be closed in order to be // sure that the other end closes the same connection? @@ -3156,22 +3328,24 @@ namespace libtorrent // if not, we should close the outgoing one. if (pid < m_ses.get_peer_id() && is_outgoing()) { - (*i)->connection->disconnect(errors::duplicate_peer_id); + p->disconnect(errors::duplicate_peer_id, op_bittorrent); } else { - disconnect(errors::duplicate_peer_id); + disconnect(errors::duplicate_peer_id, op_bittorrent); return; } } } + set_pid(pid); + // disconnect if the peer has the same peer-id as ourself // since it most likely is ourself then if (pid == m_ses.get_peer_id()) { - if (peer_info_struct()) t->get_policy().ban_peer(peer_info_struct()); - disconnect(errors::self_connection, 1); + if (peer_info_struct()) t->ban_peer(peer_info_struct()); + disconnect(errors::self_connection, op_bittorrent, 1); return; } @@ -3205,15 +3379,17 @@ namespace libtorrent peer_log("<== HANDSHAKE"); #endif // consider this a successful connection, reset the failcount - if (peer_info_struct()) t->get_policy().set_failcount(peer_info_struct(), 0); + if (peer_info_struct()) + t->clear_failcount(peer_info_struct()); #ifndef TORRENT_DISABLE_ENCRYPTION // Toggle pe_support back to false if this is a // standard successful connection if (is_outgoing() && !m_encrypted && - m_ses.get_pe_settings().out_enc_policy == pe_settings::enabled) + m_ses.settings().get_int(settings_pack::out_enc_policy) + == settings_pack::pe_enabled) { - policy::peer* pi = peer_info_struct(); + torrent_peer* pi = peer_info_struct(); TORRENT_ASSERT(pi); pi->pe_support = false; @@ -3222,14 +3398,6 @@ namespace libtorrent m_state = read_packet_size; reset_recv_buffer(5); - if (t->ready_for_connections()) - { - write_bitfield(); -#ifndef TORRENT_DISABLE_DHT - if (m_supports_dht_port && m_ses.m_dht) - write_dht_port(m_ses.m_external_udp_port); -#endif - } TORRENT_ASSERT(!packet_finished()); return; @@ -3244,15 +3412,16 @@ namespace libtorrent if (!t) return; - if (recv_buffer.left() < 4) - { - m_statistics.received_bytes(0, bytes_transferred); - return; - } - int transferred_used = 4 - recv_buffer.left() + bytes_transferred; - TORRENT_ASSERT(transferred_used <= int(bytes_transferred)); - m_statistics.received_bytes(0, transferred_used); - bytes_transferred -= transferred_used; + // the 5th byte (if one) should not count as protocol + // byte here, instead it's counted in the message + // handler itself, for the specific message + TORRENT_ASSERT(bytes_transferred <= 5); + int used_bytes = recv_buffer.left() > 4 ? bytes_transferred - 1: bytes_transferred; + received_bytes(0, used_bytes); + bytes_transferred -= used_bytes; + if (recv_buffer.left() < 4) return; + + TORRENT_ASSERT(bytes_transferred <= 1); const char* ptr = recv_buffer.begin; int packet_size = detail::read_int32(ptr); @@ -3260,15 +3429,16 @@ namespace libtorrent // don't accept packets larger than 1 MB if (packet_size > 1024*1024 || packet_size < 0) { - m_statistics.received_bytes(0, bytes_transferred); // packet too large - disconnect(errors::packet_too_large, 2); + received_bytes(0, bytes_transferred); + disconnect(errors::packet_too_large, op_bittorrent, 2); return; } - + if (packet_size == 0) { - m_statistics.received_bytes(0, bytes_transferred); + TORRENT_ASSERT(bytes_transferred <= 1); + received_bytes(0, bytes_transferred); incoming_keepalive(); if (is_disconnecting()) return; // keepalive message @@ -3276,16 +3446,13 @@ namespace libtorrent cut_receive_buffer(4, 5); return; } - else - { - if (recv_buffer.left() < 5) return; + if (recv_buffer.left() < 5) return; - m_state = read_packet; - cut_receive_buffer(4, packet_size); - TORRENT_ASSERT(bytes_transferred == 1); - recv_buffer = receive_buffer(); - TORRENT_ASSERT(recv_buffer.left() == 1); - } + m_state = read_packet; + cut_receive_buffer(4, packet_size); + recv_buffer = receive_buffer(); + TORRENT_ASSERT(recv_buffer.left() == 1); + TORRENT_ASSERT(bytes_transferred == 1); } if (m_state == read_packet) @@ -3293,13 +3460,13 @@ namespace libtorrent TORRENT_ASSERT(recv_buffer == receive_buffer()); if (!t) { - m_statistics.received_bytes(0, bytes_transferred); - disconnect(errors::torrent_removed, 1); + received_bytes(0, bytes_transferred); + disconnect(errors::torrent_removed, op_bittorrent, 1); return; } #ifdef TORRENT_DEBUG - size_type cur_payload_dl = m_statistics.last_payload_downloaded(); - size_type cur_protocol_dl = m_statistics.last_protocol_downloaded(); + size_type cur_payload_dl = statistics().last_payload_downloaded(); + size_type cur_protocol_dl = statistics().last_protocol_downloaded(); #endif if (dispatch_message(bytes_transferred)) { @@ -3307,10 +3474,10 @@ namespace libtorrent reset_recv_buffer(5); } #ifdef TORRENT_DEBUG - TORRENT_ASSERT(m_statistics.last_payload_downloaded() - cur_payload_dl >= 0); - TORRENT_ASSERT(m_statistics.last_protocol_downloaded() - cur_protocol_dl >= 0); - size_type stats_diff = m_statistics.last_payload_downloaded() - cur_payload_dl + - m_statistics.last_protocol_downloaded() - cur_protocol_dl; + TORRENT_ASSERT(statistics().last_payload_downloaded() - cur_payload_dl >= 0); + TORRENT_ASSERT(statistics().last_protocol_downloaded() - cur_protocol_dl >= 0); + size_type stats_diff = statistics().last_payload_downloaded() - cur_payload_dl + + statistics().last_protocol_downloaded() - cur_protocol_dl; TORRENT_ASSERT(stats_diff == size_type(bytes_transferred)); #endif TORRENT_ASSERT(!packet_finished()); @@ -3331,7 +3498,7 @@ namespace libtorrent if (error) { - m_statistics.sent_bytes(0, bytes_transferred); + sent_bytes(0, bytes_transferred); return; } @@ -3339,6 +3506,11 @@ namespace libtorrent int amount_payload = 0; if (!m_payloads.empty()) { + // this points to the first entry to not erase. i.e. + // [begin, first_to_keep) will be erased because + // the payload ranges they represent have been sent + std::vector::iterator first_to_keep = m_payloads.begin(); + for (std::vector::iterator i = m_payloads.begin(); i != m_payloads.end(); ++i) { @@ -3348,6 +3520,8 @@ namespace libtorrent if (i->start + i->length <= 0) { amount_payload += i->length; + TORRENT_ASSERT(first_to_keep == i); + ++first_to_keep; } else { @@ -3357,16 +3531,13 @@ namespace libtorrent } } } + + // remove all payload ranges that have been sent + m_payloads.erase(m_payloads.begin(), first_to_keep); } - // TODO: move the erasing into the loop above - // remove all payload ranges that has been sent - m_payloads.erase( - std::remove_if(m_payloads.begin(), m_payloads.end(), range_below_zero) - , m_payloads.end()); - TORRENT_ASSERT(amount_payload <= (int)bytes_transferred); - m_statistics.sent_bytes(amount_payload, bytes_transferred - amount_payload); + sent_bytes(amount_payload, bytes_transferred - amount_payload); if (amount_payload > 0) { diff --git a/src/chained_buffer.cpp b/src/chained_buffer.cpp index 5fb8eb33d..c3171d745 100644 --- a/src/chained_buffer.cpp +++ b/src/chained_buffer.cpp @@ -37,6 +37,7 @@ namespace libtorrent { void chained_buffer::pop_front(int bytes_to_pop) { + TORRENT_ASSERT(is_single_thread()); TORRENT_ASSERT(bytes_to_pop <= m_bytes); while (bytes_to_pop > 0 && !m_vec.empty()) { @@ -52,7 +53,7 @@ namespace libtorrent break; } - b.free(b.buf); + b.free_fun(b.buf, b.userdata, b.ref); m_bytes -= b.used_size; m_capacity -= b.size; bytes_to_pop -= b.used_size; @@ -64,15 +65,19 @@ namespace libtorrent } void chained_buffer::append_buffer(char* buffer, int s, int used_size - , boost::function const& destructor) + , free_buffer_fun destructor, void* userdata + , block_cache_reference ref) { + TORRENT_ASSERT(is_single_thread()); TORRENT_ASSERT(s >= used_size); buffer_t b; b.buf = buffer; b.size = s; b.start = buffer; b.used_size = used_size; - b.free = destructor; + b.free_fun = destructor; + b.userdata = userdata; + b.ref = ref; m_vec.push_back(b); m_bytes += used_size; @@ -84,6 +89,7 @@ namespace libtorrent // end of the last chained buffer. int chained_buffer::space_in_last_buffer() { + TORRENT_ASSERT(is_single_thread()); if (m_vec.empty()) return 0; buffer_t& b = m_vec.back(); return b.size - b.used_size - (b.start - b.buf); @@ -94,6 +100,7 @@ namespace libtorrent // it returns false char* chained_buffer::append(char const* buf, int s) { + TORRENT_ASSERT(is_single_thread()); char* insert = allocate_appendix(s); if (insert == 0) return 0; memcpy(insert, buf, s); @@ -105,6 +112,7 @@ namespace libtorrent // enough room, returns 0 char* chained_buffer::allocate_appendix(int s) { + TORRENT_ASSERT(is_single_thread()); if (m_vec.empty()) return 0; buffer_t& b = m_vec.back(); char* insert = b.start + b.used_size; @@ -115,11 +123,12 @@ namespace libtorrent return insert; } - std::list const& chained_buffer::build_iovec(int to_send) + std::vector const& chained_buffer::build_iovec(int to_send) { + TORRENT_ASSERT(is_single_thread()); m_tmp_vec.clear(); - for (std::list::iterator i = m_vec.begin() + for (std::deque::iterator i = m_vec.begin() , end(m_vec.end()); to_send > 0 && i != end; ++i) { if (i->used_size > to_send) @@ -135,24 +144,28 @@ namespace libtorrent return m_tmp_vec; } + void chained_buffer::clear() + { + for (std::deque::iterator i = m_vec.begin() + , end(m_vec.end()); i != end; ++i) + { + i->free_fun(i->buf, i->userdata, i->ref); + } + m_bytes = 0; + m_capacity = 0; + m_vec.clear(); + } + chained_buffer::~chained_buffer() { #if TORRENT_USE_ASSERTS TORRENT_ASSERT(!m_destructed); m_destructed = true; #endif + TORRENT_ASSERT(is_single_thread()); TORRENT_ASSERT(m_bytes >= 0); TORRENT_ASSERT(m_capacity >= 0); - for (std::list::iterator i = m_vec.begin() - , end(m_vec.end()); i != end; ++i) - { - i->free(i->buf); - } -#ifdef TORRENT_DEBUG - m_bytes = -1; - m_capacity = -1; - m_vec.clear(); -#endif + clear(); } } diff --git a/src/connection_queue.cpp b/src/connection_queue.cpp index e34d72551..5161f326a 100644 --- a/src/connection_queue.cpp +++ b/src/connection_queue.cpp @@ -30,13 +30,16 @@ POSSIBILITY OF SUCH DAMAGE. */ -#include #include "libtorrent/config.hpp" #include "libtorrent/invariant_check.hpp" #include "libtorrent/connection_queue.hpp" #include "libtorrent/io_service.hpp" #include "libtorrent/error_code.hpp" #include "libtorrent/error.hpp" +#include "libtorrent/connection_interface.hpp" + +#include +#include #if defined TORRENT_ASIO_DEBUGGING #include "libtorrent/debug.hpp" @@ -46,9 +49,7 @@ namespace libtorrent { connection_queue::connection_queue(io_service& ios): m_next_ticket(0) - , m_num_connecting(0) , m_half_open_limit(0) - , m_abort(false) , m_num_timers(0) , m_timer(ios) #ifdef TORRENT_DEBUG @@ -62,68 +63,77 @@ namespace libtorrent int connection_queue::free_slots() const { - mutex_t::scoped_lock l(m_mutex); + TORRENT_ASSERT(is_single_thread()); return m_half_open_limit == 0 ? (std::numeric_limits::max)() : m_half_open_limit - m_queue.size(); } - void connection_queue::enqueue(boost::function const& on_connect - , boost::function const& on_timeout + void connection_queue::enqueue(connection_interface* conn , time_duration timeout, int priority) { - mutex_t::scoped_lock l(m_mutex); + TORRENT_ASSERT(is_single_thread()); INVARIANT_CHECK; TORRENT_ASSERT(priority >= 0); TORRENT_ASSERT(priority < 3); - entry* e = 0; + queue_entry e; + e.priority = priority; + e.conn = conn; + e.timeout = timeout; if (priority <= 0) { - m_queue.push_back(entry()); - e = &m_queue.back(); + m_queue.push_back(e); } else // priority > 0 { - m_queue.push_front(entry()); - e = &m_queue.front(); + m_queue.insert(m_queue.begin(), e); } - e->priority = priority; - e->on_connect = on_connect; - e->on_timeout = on_timeout; - e->ticket = m_next_ticket; - e->timeout = timeout; - ++m_next_ticket; - - if (m_next_ticket >= (1 << 29)) - m_next_ticket = 0; - - if (m_num_connecting < m_half_open_limit + if (num_connecting() < m_half_open_limit || m_half_open_limit == 0) m_timer.get_io_service().post(boost::bind( &connection_queue::on_try_connect, this)); + + } + + bool connection_queue::cancel(connection_interface* conn) + { + std::vector::iterator i = std::find_if( + m_queue.begin(), m_queue.end(), boost::bind(&queue_entry::conn, _1) == conn); + + if (i == m_queue.end()) + { +#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS + // assert the connection is not in the connecting list + for (std::map::iterator i = m_connecting.begin(); + i != m_connecting.end(); ++i) + { + TORRENT_ASSERT(i->second.conn != conn); + } +#endif + return false; + } + + m_queue.erase(i); + return true; } bool connection_queue::done(int ticket) { - mutex_t::scoped_lock l(m_mutex); + TORRENT_ASSERT(is_single_thread()); INVARIANT_CHECK; - std::list::iterator i = std::find_if(m_queue.begin() - , m_queue.end(), boost::bind(&entry::ticket, _1) == ticket); - if (i == m_queue.end()) - { - // this might not be here in case on_timeout calls remove - return false; - } - if (i->connecting) --m_num_connecting; - m_queue.erase(i); + std::map::iterator i = m_connecting.find(ticket); + // this might not be here in case on_timeout calls remove + if (i == m_connecting.end()) return false; - if (m_num_connecting < m_half_open_limit + m_connecting.erase(i); + + if (num_connecting() < m_half_open_limit || m_half_open_limit == 0) m_timer.get_io_service().post(boost::bind( &connection_queue::on_try_connect, this)); @@ -132,35 +142,52 @@ namespace libtorrent void connection_queue::close() { + TORRENT_ASSERT(is_single_thread()); + error_code ec; - mutex_t::scoped_lock l(m_mutex); - if (m_num_connecting == 0) m_timer.cancel(ec); - m_abort = true; + if (num_connecting() == 0) m_timer.cancel(ec); - std::list tmp; + std::vector tmp; tmp.swap(m_queue); - m_num_connecting = 0; - - // we don't want to call the timeout callback while we're locked - // since that is a recipie for dead-locks - l.unlock(); while (!tmp.empty()) { - entry& e = tmp.front(); + queue_entry& e = tmp.front(); if (e.priority > 1) { - mutex_t::scoped_lock ll(m_mutex); - if (e.connecting) ++m_num_connecting; m_queue.push_back(e); - tmp.pop_front(); + tmp.erase(tmp.begin()); continue; } TORRENT_TRY { - if (e.connecting) e.on_timeout(); - else e.on_connect(-1); + e.conn->on_allow_connect(-1); } TORRENT_CATCH(std::exception&) {} - tmp.pop_front(); + tmp.erase(tmp.begin()); + } + + std::vector > tmp2; + + for (std::map::iterator i = m_connecting.begin(); + i != m_connecting.end();) + { + if (i->second.priority <= 1) + { + tmp2.push_back(*i); + m_connecting.erase(i++); + } + else + { + ++i; + } + } + + while (!tmp2.empty()) + { + std::pair& e = tmp2.back(); + TORRENT_TRY { + e.second.conn->on_connect_timeout(); + } TORRENT_CATCH(std::exception&) {} + tmp2.erase(tmp2.end()-1); } } @@ -176,32 +203,23 @@ namespace libtorrent #if TORRENT_USE_INVARIANT_CHECKS void connection_queue::check_invariant() const { - int num_connecting = 0; - for (std::list::const_iterator i = m_queue.begin(); - i != m_queue.end(); ++i) - { - if (i->connecting) ++num_connecting; - else TORRENT_ASSERT(i->expires == max_time()); - } - TORRENT_ASSERT(num_connecting == m_num_connecting); } #endif - void connection_queue::try_connect(connection_queue::mutex_t::scoped_lock& l) + void connection_queue::try_connect() { + TORRENT_ASSERT(is_single_thread()); INVARIANT_CHECK; #ifdef TORRENT_CONNECTION_LOGGING m_log << log_time() << " " << free_slots() << std::endl; #endif - // if this is enabled, UPnP connections will be blocked when shutting down -// if (m_abort) return; - if (m_num_connecting >= m_half_open_limit + if (num_connecting() >= m_half_open_limit && m_half_open_limit > 0) return; - if (m_queue.empty()) + if (m_queue.empty() && m_connecting.empty()) { error_code ec; m_timer.cancel(ec); @@ -209,19 +227,18 @@ namespace libtorrent } // all entries are connecting, no need to look for new ones - if (int(m_queue.size()) == m_num_connecting) - return; + if (m_queue.empty()) return; - std::list::iterator i = std::find_if(m_queue.begin() - , m_queue.end(), boost::bind(&entry::connecting, _1) == false); - - std::list to_connect; - - while (i != m_queue.end()) + while (!m_queue.empty()) { - TORRENT_ASSERT(i->connecting == false); - ptime expire = time_now_hires() + i->timeout; - if (m_num_connecting == 0) + if (num_connecting() >= m_half_open_limit + && m_half_open_limit > 0) break; + + queue_entry e = m_queue.front(); + m_queue.erase(m_queue.begin()); + + ptime expire = time_now_hires() + e.timeout; + if (num_connecting() == 0) { #if defined TORRENT_ASIO_DEBUGGING add_outstanding_async("connection_queue::on_timeout"); @@ -231,35 +248,22 @@ namespace libtorrent m_timer.async_wait(boost::bind(&connection_queue::on_timeout, this, _1)); ++m_num_timers; } - i->connecting = true; - ++m_num_connecting; - i->expires = expire; + connect_entry ce; + ce.conn = e.conn; + ce.priority = e.priority; + ce.expires = time_now_hires() + e.timeout; - INVARIANT_CHECK; + int ticket = ++m_next_ticket; + m_connecting.insert(std::make_pair(ticket, ce)); - to_connect.push_back(*i); + TORRENT_TRY { + ce.conn->on_allow_connect(ticket); + } TORRENT_CATCH(std::exception&) {} #ifdef TORRENT_CONNECTION_LOGGING m_log << log_time() << " " << free_slots() << std::endl; #endif - - if (m_num_connecting >= m_half_open_limit - && m_half_open_limit > 0) break; - if (m_num_connecting == int(m_queue.size())) break; - i = std::find_if(i, m_queue.end(), boost::bind(&entry::connecting, _1) == false); } - - l.unlock(); - - while (!to_connect.empty()) - { - entry& ent = to_connect.front(); - TORRENT_TRY { - ent.on_connect(ent.ticket); - } TORRENT_CATCH(std::exception&) {} - to_connect.pop_front(); - } - } #ifdef TORRENT_DEBUG @@ -277,7 +281,6 @@ namespace libtorrent #if defined TORRENT_ASIO_DEBUGGING complete_async("connection_queue::on_timeout"); #endif - mutex_t::scoped_lock l(m_mutex); --m_num_timers; INVARIANT_CHECK; @@ -291,43 +294,31 @@ namespace libtorrent // we should just quit. However, in case there are still connections // in connecting state, and there are no other timer invocations // we need to stick around still. - if (e && (m_num_connecting == 0 || m_num_timers > 0)) return; + if (e && (num_connecting() == 0 || m_num_timers > 0)) return; ptime next_expire = max_time(); ptime now = time_now_hires() + milliseconds(100); - std::list timed_out; - for (std::list::iterator i = m_queue.begin(); - !m_queue.empty() && i != m_queue.end();) + std::vector timed_out; + for (std::map::iterator i = m_connecting.begin(); + !m_connecting.empty() && i != m_connecting.end(); ++i) { - if (i->connecting && i->expires < now) + if (i->second.expires < now) { - std::list::iterator j = i; - ++i; - timed_out.splice(timed_out.end(), m_queue, j, i); - --m_num_connecting; + timed_out.push_back(i->second); continue; } - if (i->connecting && i->expires < next_expire) - next_expire = i->expires; - ++i; + if (i->second.expires < next_expire) + next_expire = i->second.expires; } - // we don't want to call the timeout callback while we're locked - // since that is a recepie for dead-locks - l.unlock(); - - for (std::list::iterator i = timed_out.begin() + for (std::vector::iterator i = timed_out.begin() , end(timed_out.end()); i != end; ++i) { - TORRENT_ASSERT(i->connecting); - TORRENT_ASSERT(i->ticket != -1); TORRENT_TRY { - i->on_timeout(); + i->conn->on_connect_timeout(); } TORRENT_CATCH(std::exception&) {} } - l.lock(); - if (next_expire < max_time()) { #if defined TORRENT_ASIO_DEBUGGING @@ -338,13 +329,12 @@ namespace libtorrent m_timer.async_wait(boost::bind(&connection_queue::on_timeout, this, _1)); ++m_num_timers; } - try_connect(l); + try_connect(); } void connection_queue::on_try_connect() { - mutex_t::scoped_lock l(m_mutex); - try_connect(l); + try_connect(); } } diff --git a/src/crc32c.cpp b/src/crc32c.cpp new file mode 100644 index 000000000..8e6e5ccae --- /dev/null +++ b/src/crc32c.cpp @@ -0,0 +1,132 @@ +/* + +Copyright (c) 2014, 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. + +*/ + +#include "libtorrent/crc32c.hpp" +#include "libtorrent/cpuid.hpp" + +#include + +namespace libtorrent +{ + bool supports_sse42() + { +#if TORRENT_HAS_SSE + unsigned int cpui[4]; + cpuid(cpui, 1); + return cpui[2] & (1 << 20); +#else + return false; +#endif + } + + static bool sse42_support = supports_sse42(); + + boost::uint32_t crc32c_32(boost::uint32_t v) + { +#if TORRENT_HAS_SSE + if (sse42_support) + { + boost::uint32_t ret = 0xffffffff; +#ifdef __GNUC__ + // we can't use these because then we'd have to tell + // -msse4.2 to gcc on the command line +// return __builtin_ia32_crc32si(ret, v) ^ 0xffffffff; + asm ("crc32l\t" "(%1), %0" + : "=r"(ret) + : "r"(&v), "0"(ret)); + return ret ^ 0xffffffff; +#else + return _mm_crc32_u32(ret, v) ^ 0xffffffff; +#endif + } +#endif + + boost::crc_optimal<32, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, true, true> crc; + crc.process_bytes(&v, 4); + return crc.checksum(); + } + + boost::uint32_t crc32c(boost::uint64_t const* buf, int num_words) + { +#if TORRENT_HAS_SSE + if (sse42_support) + { +#if defined _M_AMD64 || defined __x86_64__ \ + || defined __x86_64 || defined _M_X64 || defined __amd64__ + boost::uint64_t ret = 0xffffffff; + for (int i = 0; i < num_words; ++i) + { +#ifdef __GNUC__ + // we can't use these because then we'd have to tell + // -msse4.2 to gcc on the command line +// ret = __builtin_ia32_crc32di(ret, buf[i]); + __asm__("crc32q\t" "(%1), %0" + : "=r"(ret) + : "r"(buf+i), "0"(ret)); +#else + ret = _mm_crc32_u64(ret, buf[i]); +#endif + } + return boost::uint32_t(ret) ^ 0xffffffff; +#else + boost::uint32_t ret = 0xffffffff; + boost::uint32_t const* buf0 = reinterpret_cast(buf); + for (int i = 0; i < num_words; ++i) + { +#ifdef __GNUC__ + // we can't use these because then we'd have to tell + // -msse4.2 to gcc on the command line +// ret = __builtin_ia32_crc32si(ret, buf0[i*2]); +// ret = __builtin_ia32_crc32si(ret, buf0[i*2+1]); + asm ("crc32l\t" "(%1), %0" + : "=r"(ret) + : "r"(buf0+i*2), "0"(ret)); + asm ("crc32l\t" "(%1), %0" + : "=r"(ret) + : "r"(buf0+i*2+1), "0"(ret)); +#else + ret = _mm_crc32_u32(ret, buf0[i*2]); + ret = _mm_crc32_u32(ret, buf0[i*2+1]); +#endif + } + return ret ^ 0xffffffff; +#endif // amd64 or x86 + } +#endif // x86 or amd64 and gcc or msvc + + boost::crc_optimal<32, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, true, true> crc; + crc.process_bytes(buf, num_words * 8); + return crc.checksum(); + } +} + + diff --git a/src/create_torrent.cpp b/src/create_torrent.cpp index c814be8db..326912bdc 100644 --- a/src/create_torrent.cpp +++ b/src/create_torrent.cpp @@ -34,10 +34,13 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/file_pool.hpp" #include "libtorrent/storage.hpp" #include "libtorrent/escape_string.hpp" +#include "libtorrent/disk_io_thread.hpp" #include "libtorrent/torrent_info.hpp" // for merkle_*() #include #include +#include +#include #include #include @@ -47,6 +50,8 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { + class alert; + namespace detail { int get_file_attributes(std::string const& p) @@ -145,55 +150,43 @@ namespace libtorrent } } } - } + } // detail namespace - struct piece_holder + void on_hash(disk_io_job const* j, create_torrent* t + , boost::shared_ptr storage, disk_io_thread* iothread + , int* piece_counter, int* completed_piece + , boost::function const* f, error_code* ec) { - piece_holder(int bytes): m_piece(page_aligned_allocator::malloc(bytes)) {} - ~piece_holder() { page_aligned_allocator::free(m_piece); } - char* bytes() { return m_piece; } - private: - char* m_piece; - }; - -#if TORRENT_USE_WSTRING - void set_piece_hashes(create_torrent& t, std::wstring const& p - , boost::function const& f, error_code& ec) - { - file_pool fp; - std::string utf8; - wchar_utf8(p, utf8); -#if TORRENT_USE_UNC_PATHS - utf8 = canonicalize_path(utf8); -#endif - boost::scoped_ptr st( - default_storage_constructor(const_cast(t.files()), 0, utf8, fp - , std::vector())); - - // calculate the hash for all pieces - int num = t.num_pieces(); - std::vector buf(t.piece_length()); - for (int i = 0; i < num; ++i) + if (j->ret != 0) { - // read hits the disk and will block. Progress should - // be updated in between reads - st->read(&buf[0], i, 0, t.piece_size(i)); - if (st->error()) - { - ec = st->error(); - return; - } - hasher h(&buf[0], t.piece_size(i)); - t.set_hash(i, h.final()); - f(i); + // on error + *ec = j->error.ec; + iothread->set_num_threads(0); + return; } + t->set_hash(j->piece, sha1_hash(j->d.piece_hash)); + (*f)(*completed_piece); + ++(*completed_piece); + if (*piece_counter < t->num_pieces()) + { + iothread->async_hash(storage.get(), *piece_counter, disk_io_job::sequential_access + , boost::bind(&on_hash, _1, t, storage, iothread + , piece_counter, completed_piece, f, ec), (void*)0); + ++(*piece_counter); + } + else + { + iothread->set_num_threads(0); + } + iothread->submit_jobs(); } -#endif void set_piece_hashes(create_torrent& t, std::string const& p - , boost::function f, error_code& ec) + , boost::function const& f, error_code& ec) { - file_pool fp; + // optimized path + io_service ios; + #if TORRENT_USE_UNC_PATHS std::string path = canonicalize_path(p); #else @@ -206,60 +199,43 @@ namespace libtorrent return; } - boost::scoped_ptr st( - default_storage_constructor(const_cast(t.files()), 0, path, fp - , std::vector())); + // dummy torrent object pointer + boost::shared_ptr dummy; + disk_io_thread disk_thread(ios, 0, 0); - // if we're calculating file hashes as well, use this hasher - hasher filehash; - int file_idx = 0; - size_type left_in_file = t.files().at(0).size; + storage_params params; + params.files = &t.files(); + params.mapped_files = NULL; + params.path = path; + params.pool = &disk_thread.files(); + params.mode = storage_mode_sparse; - // calculate the hash for all pieces - int num = t.num_pieces(); - piece_holder buf(t.piece_length()); - for (int i = 0; i < num; ++i) + storage_interface* storage_impl = default_storage_constructor(params); + + boost::shared_ptr storage = boost::make_shared( + storage_impl, dummy, (file_storage*)&t.files()); + + settings_pack sett; + sett.set_int(settings_pack::cache_size, 0); + sett.set_int(settings_pack::hashing_threads, 2); + + disk_thread.set_settings(&sett); + + int piece_counter = 0; + int completed_piece = 0; + int piece_read_ahead = 15 * 1024 * 1024 / t.piece_length(); + if (piece_read_ahead < 1) piece_read_ahead = 1; + + for (int i = 0; i < piece_read_ahead; ++i) { - // read hits the disk and will block. Progress should - // be updated in between reads - st->read(buf.bytes(), i, 0, t.piece_size(i)); - if (st->error()) - { - ec = st->error(); - return; - } - - if (t.should_add_file_hashes()) - { - int left_in_piece = t.piece_size(i); - int this_piece_size = left_in_piece; - // the number of bytes from this file we just read - while (left_in_piece > 0) - { - int to_hash_for_file = int((std::min)(size_type(left_in_piece), left_in_file)); - if (to_hash_for_file > 0) - { - int offset = this_piece_size - left_in_piece; - filehash.update(buf.bytes() + offset, to_hash_for_file); - } - left_in_file -= to_hash_for_file; - left_in_piece -= to_hash_for_file; - if (left_in_file == 0) - { - if (!t.files().at(file_idx).pad_file) - t.set_file_hash(file_idx, filehash.final()); - filehash.reset(); - file_idx++; - if (file_idx >= t.files().num_files()) break; - left_in_file = t.files().at(file_idx).size; - } - } - } - - hasher h(buf.bytes(), t.piece_size(i)); - t.set_hash(i, h.final()); - f(i); + disk_thread.async_hash(storage.get(), i, disk_io_job::sequential_access + , boost::bind(&on_hash, _1, &t, storage, &disk_thread + , &piece_counter, &completed_piece, &f, &ec), (void*)0); + ++piece_counter; + if (piece_counter >= t.num_pieces()) break; } + disk_thread.submit_jobs(); + ios.run(ec); } create_torrent::~create_torrent() {} diff --git a/src/disk_buffer_holder.cpp b/src/disk_buffer_holder.cpp index 02ac19d51..43e0c5405 100644 --- a/src/disk_buffer_holder.cpp +++ b/src/disk_buffer_holder.cpp @@ -37,34 +37,51 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { - disk_buffer_holder::disk_buffer_holder(aux::session_impl& ses, char* buf) - : m_disk_pool(ses.m_disk_thread), m_buf(buf) + disk_buffer_holder::disk_buffer_holder(buffer_allocator_interface& alloc, char* buf) + : m_allocator(alloc), m_buf(buf) { - TORRENT_ASSERT(buf == 0 || m_disk_pool.is_disk_buffer(buf)); + m_ref.storage = 0; } - disk_buffer_holder::disk_buffer_holder(disk_buffer_pool& iothread, char* buf) - : m_disk_pool(iothread), m_buf(buf) + disk_buffer_holder::disk_buffer_holder(buffer_allocator_interface& alloc, disk_io_job const& j) + : m_allocator(alloc), m_buf(j.buffer), m_ref(j.d.io.ref) { - TORRENT_ASSERT(buf == 0 || m_disk_pool.is_disk_buffer(buf)); + TORRENT_ASSERT(m_ref.storage == 0 || m_ref.piece >= 0); + TORRENT_ASSERT(m_ref.storage == 0 || m_ref.block >= 0); + TORRENT_ASSERT(m_ref.storage == 0 || m_ref.piece < ((piece_manager*)m_ref.storage)->files()->num_pieces()); + TORRENT_ASSERT(m_ref.storage == 0 || m_ref.block <= ((piece_manager*)m_ref.storage)->files()->piece_length() / 0x4000); + } + + void disk_buffer_holder::reset(disk_io_job const& j) + { + if (m_ref.storage) m_allocator.reclaim_block(m_ref); + else if (m_buf) m_allocator.free_disk_buffer(m_buf); + m_buf = j.buffer; + m_ref = j.d.io.ref; + + TORRENT_ASSERT(m_ref.piece >= 0); + TORRENT_ASSERT(m_ref.storage != 0); + TORRENT_ASSERT(m_ref.block >= 0); + TORRENT_ASSERT(m_ref.piece < ((piece_manager*)m_ref.storage)->files()->num_pieces()); + TORRENT_ASSERT(m_ref.block <= ((piece_manager*)m_ref.storage)->files()->piece_length() / 0x4000); } void disk_buffer_holder::reset(char* buf) { - if (m_buf) m_disk_pool.free_buffer(m_buf); + if (m_ref.storage) m_allocator.reclaim_block(m_ref); + else if (m_buf) m_allocator.free_disk_buffer(m_buf); m_buf = buf; + m_ref.storage = 0; } char* disk_buffer_holder::release() { char* ret = m_buf; m_buf = 0; + m_ref.storage = 0; return ret; } - disk_buffer_holder::~disk_buffer_holder() - { - if (m_buf) m_disk_pool.free_buffer(m_buf); - } + disk_buffer_holder::~disk_buffer_holder() { reset(); } } diff --git a/src/disk_buffer_pool.cpp b/src/disk_buffer_pool.cpp index 0f3efb712..fbb2499ce 100644 --- a/src/disk_buffer_pool.cpp +++ b/src/disk_buffer_pool.cpp @@ -30,60 +30,233 @@ POSSIBILITY OF SUCH DAMAGE. */ +#include "libtorrent/config.hpp" #include "libtorrent/disk_buffer_pool.hpp" #include "libtorrent/assert.hpp" +#include "libtorrent/allocator.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/alert_dispatcher.hpp" +#include "libtorrent/disk_observer.hpp" + #include +#include +#include +#include #if TORRENT_USE_MLOCK && !defined TORRENT_WINDOWS #include #endif -#ifdef TORRENT_DISK_STATS +#ifdef TORRENT_BSD +#include +#endif + +#if TORRENT_USE_RLIMIT +#include +#endif + +#ifdef TORRENT_LINUX +#include +#endif + +#if TORRENT_USE_PURGABLE_CONTROL +#include +// see comments at: +// http://www.opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/vm/vm_object.c +#endif + +#ifdef TORRENT_BUFFER_STATS #include "libtorrent/time.hpp" #endif namespace libtorrent { - disk_buffer_pool::disk_buffer_pool(int block_size) + // this is posted to the network thread + static void watermark_callback(std::vector >* cbs + , std::vector* handlers) + { + if (handlers) + { + for (std::vector::iterator i = handlers->begin() + , end(handlers->end()); i != end; ++i) + i->callback(i->buffer); + delete handlers; + } + + if (cbs != NULL) + { + for (std::vector >::iterator i = cbs->begin() + , end(cbs->end()); i != end; ++i) + (*i)->on_disk(); + delete cbs; + } + } + + // this is posted to the network thread and run from there + static void alert_callback(alert_dispatcher* disp, alert* a) + { + if (disp && disp->post_alert(a)) return; + delete a; + } + + disk_buffer_pool::disk_buffer_pool(int block_size, io_service& ios + , boost::function const& trigger_trim + , alert_dispatcher* alert_disp) : m_block_size(block_size) , m_in_use(0) + , m_max_use(64) + , m_low_watermark((std::max)(m_max_use - 32, 0)) + , m_trigger_cache_trim(trigger_trim) + , m_exceeded_max_size(false) + , m_ios(ios) + , m_cache_buffer_chunk_size(0) + , m_lock_disk_cache(false) +#if TORRENT_HAVE_MMAP + , m_cache_fd(-1) + , m_cache_pool(0) +#endif + , m_post_alert(alert_disp) #ifndef TORRENT_DISABLE_POOL_ALLOCATOR , m_using_pool_allocator(false) - , m_pool(block_size, m_settings.cache_buffer_chunk_size) + , m_want_pool_allocator(false) + , m_pool(block_size, 32) #endif { -#if defined TORRENT_DISK_STATS || defined TORRENT_STATS +#if defined TORRENT_BUFFER_STATS || defined TORRENT_STATS m_allocations = 0; #endif -#ifdef TORRENT_DISK_STATS - m_log.open("disk_buffers.log", std::ios::trunc); +#ifdef TORRENT_BUFFER_STATS + m_log = fopen("disk_buffers.log", "w+"); m_categories["read cache"] = 0; m_categories["write cache"] = 0; - - m_disk_access_log.open("disk_access.log", std::ios::trunc); #endif #if TORRENT_USE_ASSERTS m_magic = 0x1337; + m_settings_set = false; #endif } -#if TORRENT_USE_ASSERTS disk_buffer_pool::~disk_buffer_pool() { TORRENT_ASSERT(m_magic == 0x1337); +#if TORRENT_USE_ASSERTS m_magic = 0; - } #endif -#if TORRENT_USE_ASSERTS || defined TORRENT_DISK_STATS +#if TORRENT_HAVE_MMAP + if (m_cache_pool) + { + munmap(m_cache_pool, boost::uint64_t(m_max_use) * 0x4000); + m_cache_pool = 0; + // attempt to make MacOS not flush this to disk, making close() + // block for a long time + ftruncate(m_cache_fd, 0); + close(m_cache_fd); + m_cache_fd = -1; + } +#endif + } + + boost::uint32_t disk_buffer_pool::num_to_evict(int num_needed) + { + int ret = 0; + + mutex::scoped_lock l(m_pool_mutex); + + if (m_exceeded_max_size) + ret = m_in_use - (std::min)(m_low_watermark, int(m_max_use - (m_observers.size() + m_handlers.size())*2)); + + if (m_in_use + num_needed > m_max_use) + ret = (std::max)(ret, int(m_in_use + num_needed - m_max_use)); + + if (ret < 0) ret = 0; + else if (ret > m_in_use) ret = m_in_use; + + return ret; + } + + // checks to see if we're no longer exceeding the high watermark, + // and if we're in fact below the low watermark. If so, we need to + // post the notification messages to the peers that are waiting for + // more buffers to received data into + void disk_buffer_pool::check_buffer_level(mutex::scoped_lock& l) + { + if (!m_exceeded_max_size || m_in_use > m_low_watermark) return; + + m_exceeded_max_size = false; + + // if slice is non-NULL, only some of the handlers got a buffer + // back, and the slice should be posted back to the network thread + std::vector* slice = NULL; + + for (std::vector::iterator i = m_handlers.begin() + , end(m_handlers.end()); i != end; ++i) + { + i->buffer = allocate_buffer_impl(l, i->category); + if (!m_exceeded_max_size || i == end - 1) continue; + + // only some of the handlers got buffers. We need to slice the vector + slice = new std::vector(); + slice->insert(slice->end(), m_handlers.begin(), i + 1); + m_handlers.erase(m_handlers.begin(), i + 1); + break; + } + + if (slice != NULL) + { + l.unlock(); + m_ios.post(boost::bind(&watermark_callback + , (std::vector >*)NULL, slice)); + return; + } + + std::vector* handlers = new std::vector(); + handlers->swap(m_handlers); + + if (m_exceeded_max_size) + { + l.unlock(); + m_ios.post(boost::bind(&watermark_callback + , (std::vector >*)NULL, handlers)); + return; + } + + std::vector >* cbs + = new std::vector >(); + m_observers.swap(*cbs); + l.unlock(); + m_ios.post(boost::bind(&watermark_callback, cbs, handlers)); + } + +#if TORRENT_USE_ASSERTS || defined TORRENT_BUFFER_STATS bool disk_buffer_pool::is_disk_buffer(char* buffer , mutex::scoped_lock& l) const { TORRENT_ASSERT(m_magic == 0x1337); -#ifdef TORRENT_DISK_STATS + +#if TORRENT_HAVE_MMAP + if (m_cache_pool) + { + return buffer >= m_cache_pool && buffer < m_cache_pool + boost::uint64_t(m_max_use) * 0x4000; + } +#endif + +#if defined TORRENT_DEBUG + return m_buffers_in_use.count(buffer) == 1; +#endif + +#ifdef TORRENT_BUFFER_STATS if (m_buf_to_category.find(buffer) == m_buf_to_category.end()) return false; #endif + +#ifdef TORRENT_DEBUG_BUFFERS + return page_aligned_allocator::in_use(buffer); +#endif + #ifdef TORRENT_DISABLE_POOL_ALLOCATOR return true; #else @@ -101,49 +274,141 @@ namespace libtorrent } #endif + char* disk_buffer_pool::async_allocate_buffer(char const* category + , boost::function const& handler) + { + mutex::scoped_lock l(m_pool_mutex); + if (m_exceeded_max_size) + { + m_handlers.push_back(handler_t()); + handler_t& h = m_handlers.back(); + h.category = category; + h.callback = handler; + h.buffer = NULL; + return NULL; + } + + char* ret = allocate_buffer_impl(l, category); + + return ret; + } + char* disk_buffer_pool::allocate_buffer(char const* category) { mutex::scoped_lock l(m_pool_mutex); - TORRENT_ASSERT(m_magic == 0x1337); - char* ret; -#ifdef TORRENT_DISABLE_POOL_ALLOCATOR - ret = page_aligned_allocator::malloc(m_block_size); -#else - if (m_using_pool_allocator) + return allocate_buffer_impl(l, category); + } + + char* disk_buffer_pool::allocate_buffer(bool& exceeded + , boost::shared_ptr o, char const* category) + { + mutex::scoped_lock l(m_pool_mutex); + bool was_exceeded = m_exceeded_max_size; + char* ret = allocate_buffer_impl(l, category); + if (m_exceeded_max_size) { - ret = (char*)m_pool.malloc(); - m_pool.set_next_size(m_settings.cache_buffer_chunk_size); + exceeded = true; + if (o) m_observers.push_back(o); + } + return ret; + } + + char* disk_buffer_pool::allocate_buffer_impl(mutex::scoped_lock& l, char const* category) + { + TORRENT_ASSERT(m_settings_set); + TORRENT_ASSERT(m_magic == 0x1337); + + char* ret; +#if TORRENT_HAVE_MMAP + if (m_cache_pool) + { + if (m_free_list.size() <= (m_max_use - m_low_watermark) / 2 && !m_exceeded_max_size) + { + m_exceeded_max_size = true; + m_trigger_cache_trim(); + } + if (m_free_list.empty()) return 0; + boost::uint64_t slot_index = m_free_list.back(); + m_free_list.pop_back(); + ret = m_cache_pool + (slot_index * 0x4000); + TORRENT_ASSERT(is_disk_buffer(ret, l)); } else - { - ret = page_aligned_allocator::malloc(m_block_size); - } #endif + { +#if defined TORRENT_DISABLE_POOL_ALLOCATOR + +#if TORRENT_USE_PURGABLE_CONTROL + kern_return_t res = vm_allocate( + mach_task_self(), + reinterpret_cast(&ret), + 0x4000, + VM_FLAGS_PURGABLE | + VM_FLAGS_ANYWHERE); + if (res != KERN_SUCCESS) + ret = NULL; +#else + ret = page_aligned_allocator::malloc(m_block_size); +#endif // TORRENT_USE_PURGABLE_CONTROL + +#else + if (m_using_pool_allocator) + { + ret = (char*)m_pool.malloc(); + int effective_block_size = m_cache_buffer_chunk_size + ? m_cache_buffer_chunk_size + : (std::max)(m_max_use / 10, 1); + m_pool.set_next_size(effective_block_size); + } + else + { + ret = page_aligned_allocator::malloc(m_block_size); + } +#endif + if (ret == NULL) + { + m_exceeded_max_size = true; + m_trigger_cache_trim(); + return 0; + } + } + +#if defined TORRENT_DEBUG + TORRENT_ASSERT(m_buffers_in_use.count(ret) == 0); + m_buffers_in_use.insert(ret); +#endif + ++m_in_use; + if (m_in_use >= m_low_watermark + (m_max_use - m_low_watermark) / 2 && !m_exceeded_max_size) + { + m_exceeded_max_size = true; + m_trigger_cache_trim(); + } #if TORRENT_USE_MLOCK - if (m_settings.lock_disk_cache) + if (m_lock_disk_cache) { #ifdef TORRENT_WINDOWS VirtualLock(ret, m_block_size); #else mlock(ret, m_block_size); -#endif - } #endif + } +#endif // TORRENT_USE_MLOCK -#if defined TORRENT_DISK_STATS || defined TORRENT_STATS +#if defined TORRENT_BUFFER_STATS || defined TORRENT_STATS ++m_allocations; #endif -#ifdef TORRENT_DISK_STATS + +#ifdef TORRENT_BUFFER_STATS ++m_categories[category]; m_buf_to_category[ret] = category; - m_log << log_time() << " " << category << ": " << m_categories[category] << "\n"; + fprintf(m_log, "%s %s: %d\n", log_time().c_str(), category, m_categories[category]); #endif - TORRENT_ASSERT(ret == 0 || is_disk_buffer(ret, l)); + TORRENT_ASSERT(is_disk_buffer(ret, l)); return ret; } -#ifdef TORRENT_DISK_STATS +#ifdef TORRENT_BUFFER_STATS void disk_buffer_pool::rename_buffer(char* buf, char const* category) { mutex::scoped_lock l(m_pool_mutex); @@ -152,11 +417,11 @@ namespace libtorrent != m_categories.end()); std::string const& prev_category = m_buf_to_category[buf]; --m_categories[prev_category]; - m_log << log_time() << " " << prev_category << ": " << m_categories[prev_category] << "\n"; + fprintf(m_log, "%s %s: %d\n", log_time().c_str(), prev_category.c_str(), m_categories[category]); ++m_categories[category]; m_buf_to_category[buf] = category; - m_log << log_time() << " " << category << ": " << m_categories[category] << "\n"; + fprintf(m_log, "%s %s: %d\n", log_time().c_str(), category, m_categories[category]); TORRENT_ASSERT(m_categories.find(m_buf_to_category[buf]) != m_categories.end()); } @@ -173,58 +438,271 @@ namespace libtorrent { char* buf = *bufvec; TORRENT_ASSERT(buf); - free_buffer_impl(buf, l);; + free_buffer_impl(buf, l); } + + check_buffer_level(l); } void disk_buffer_pool::free_buffer(char* buf) { mutex::scoped_lock l(m_pool_mutex); free_buffer_impl(buf, l); + + check_buffer_level(l); + } + + boost::uint64_t physical_ram() + { + boost::uint64_t ret = 0; + // figure out how much physical RAM there is in + // this machine. This is used for automatically + // sizing the disk cache size when it's set to + // automatic. +#ifdef TORRENT_BSD +#ifdef HW_MEMSIZE + int mib[2] = { CTL_HW, HW_MEMSIZE }; +#else + // not entirely sure this sysctl supports 64 + // bit return values, but it's probably better + // than not building + int mib[2] = { CTL_HW, HW_PHYSMEM }; +#endif + size_t len = sizeof(ret); + if (sysctl(mib, 2, &ret, &len, NULL, 0) != 0) + ret = 0; +#elif defined TORRENT_WINDOWS + MEMORYSTATUSEX ms; + ms.dwLength = sizeof(MEMORYSTATUSEX); + if (GlobalMemoryStatusEx(&ms)) + ret = ms.ullTotalPhys; + else + ret = 0; +#elif defined TORRENT_LINUX + ret = sysconf(_SC_PHYS_PAGES); + ret *= sysconf(_SC_PAGESIZE); +#elif defined TORRENT_AMIGA + ret = AvailMem(MEMF_PUBLIC); +#endif + +#if TORRENT_USE_RLIMIT + if (ret > 0) + { + struct rlimit r; + if (getrlimit(RLIMIT_AS, &r) == 0 && r.rlim_cur != RLIM_INFINITY) + { + if (ret > r.rlim_cur) + ret = r.rlim_cur; + } + } +#endif + return ret; + } + + void disk_buffer_pool::set_settings(aux::session_settings const& sett) + { + mutex::scoped_lock l(m_pool_mutex); + + // 0 cache_buffer_chunk_size means 'automatic' (i.e. + // proportional to the total disk cache size) + m_cache_buffer_chunk_size = sett.get_int(settings_pack::cache_buffer_chunk_size); + m_lock_disk_cache = sett.get_bool(settings_pack::lock_disk_cache); +#ifndef TORRENT_DISABLE_POOL_ALLOCATOR + m_want_pool_allocator = sett.get_bool(settings_pack::use_disk_cache_pool); + // if there are no allocated blocks, it's OK to switch allocator + if (m_in_use == 0) + m_using_pool_allocator = m_want_pool_allocator; +#endif + +#if TORRENT_HAVE_MMAP + // if we've already allocated an mmap, we can't change + // anything unless there are no allocations in use + if (m_cache_pool && m_in_use > 0) return; +#endif + + // only allow changing size if we're not using mmapped + // cache, or if we're just about to turn it off + if ( +#if TORRENT_HAVE_MMAP + m_cache_pool == 0 || +#endif + sett.get_str(settings_pack::mmap_cache).empty()) + { + int cache_size = sett.get_int(settings_pack::cache_size); + if (cache_size < 0) + { + boost::uint64_t phys_ram = physical_ram(); + if (phys_ram == 0) m_max_use = 1024; + else m_max_use = phys_ram / 8 / m_block_size; + } + else + { + m_max_use = cache_size; + } + m_low_watermark = m_max_use - (std::max)(16, sett.get_int(settings_pack::max_queued_disk_bytes) / 0x4000); + if (m_low_watermark < 0) m_low_watermark = 0; + if (m_in_use >= m_max_use && !m_exceeded_max_size) + { + m_exceeded_max_size = true; + m_trigger_cache_trim(); + } + } + +#if TORRENT_USE_ASSERTS + m_settings_set = true; +#endif + +#if TORRENT_HAVE_MMAP + // #error support resizing the map + if (m_cache_pool && sett.get_str(settings_pack::mmap_cache).empty()) + { + TORRENT_ASSERT(m_in_use == 0); + munmap(m_cache_pool, boost::uint64_t(m_max_use) * 0x4000); + m_cache_pool = 0; + // attempt to make MacOS not flush this to disk, making close() + // block for a long time + ftruncate(m_cache_fd, 0); + close(m_cache_fd); + m_cache_fd = -1; + std::vector().swap(m_free_list); + } + else if (m_cache_pool == 0 && !sett.get_str(settings_pack::mmap_cache).empty()) + { + // O_TRUNC here is because we don't actually care about what's + // in the file now, there's no need to ever read that into RAM +#ifndef O_EXLOCK +#define O_EXLOCK 0 +#endif + m_cache_fd = open(sett.get_str(settings_pack::mmap_cache).c_str(), O_RDWR | O_CREAT | O_EXLOCK | O_TRUNC, 0700); + if (m_cache_fd < 0) + { + if (m_post_alert) + { + error_code ec(errno, boost::system::generic_category()); + m_ios.post(boost::bind(alert_callback, m_post_alert, new mmap_cache_alert(ec))); + } + } + else + { +#ifndef MAP_NOCACHE +#define MAP_NOCACHE 0 +#endif + ftruncate(m_cache_fd, boost::uint64_t(m_max_use) * 0x4000); + m_cache_pool = (char*)mmap(0, boost::uint64_t(m_max_use) * 0x4000, PROT_READ | PROT_WRITE + , MAP_SHARED | MAP_NOCACHE, m_cache_fd, 0); + if (intptr_t(m_cache_pool) == -1) + { + if (m_post_alert) + { + error_code ec(errno, boost::system::generic_category()); + m_ios.post(boost::bind(alert_callback, m_post_alert, new mmap_cache_alert(ec))); + } + m_cache_pool = 0; + // attempt to make MacOS not flush this to disk, making close() + // block for a long time + ftruncate(m_cache_fd, 0); + close(m_cache_fd); + m_cache_fd = -1; + } + else + { + TORRENT_ASSERT((size_t(m_cache_pool) & 0xfff) == 0); + m_free_list.reserve(m_max_use); + for (int i = 0; i < m_max_use; ++i) + m_free_list.push_back(i); + } + } + } +#endif } void disk_buffer_pool::free_buffer_impl(char* buf, mutex::scoped_lock& l) { TORRENT_ASSERT(buf); TORRENT_ASSERT(m_magic == 0x1337); + TORRENT_ASSERT(m_settings_set); TORRENT_ASSERT(is_disk_buffer(buf, l)); -#if defined TORRENT_DISK_STATS || defined TORRENT_STATS - --m_allocations; -#endif -#ifdef TORRENT_DISK_STATS - TORRENT_ASSERT(m_categories.find(m_buf_to_category[buf]) - != m_categories.end()); - std::string const& category = m_buf_to_category[buf]; - --m_categories[category]; - m_log << log_time() << " " << category << ": " << m_categories[category] << "\n"; - m_buf_to_category.erase(buf); -#endif + #if TORRENT_USE_MLOCK - if (m_settings.lock_disk_cache) + if (m_lock_disk_cache) { #ifdef TORRENT_WINDOWS VirtualUnlock(buf, m_block_size); #else munlock(buf, m_block_size); -#endif +#endif } #endif -#ifdef TORRENT_DISABLE_POOL_ALLOCATOR + +#if defined TORRENT_BUFFER_STATS || defined TORRENT_STATS + --m_allocations; +#endif +#ifdef TORRENT_BUFFER_STATS + TORRENT_ASSERT(m_categories.find(m_buf_to_category[buf]) + != m_categories.end()); + std::string const& category = m_buf_to_category[buf]; + --m_categories[category]; + fprintf(m_log, "%s %s: %d\n", log_time().c_str(), category.c_str(), m_categories[category]); + m_buf_to_category.erase(buf); +#endif + +#if TORRENT_HAVE_MMAP + if (m_cache_pool) + { + TORRENT_ASSERT(buf >= m_cache_pool); + TORRENT_ASSERT(buf < m_cache_pool + boost::uint64_t(m_max_use) * 0x4000); + int slot_index = (buf - m_cache_pool) / 0x4000; + m_free_list.push_back(slot_index); +#if defined MADV_FREE + // tell the virtual memory system that we don't actually care + // about the data in these pages anymore. If this block was + // swapped out to the SSD, it (hopefully) means it won't have + // to be read back in once we start writing our new data to it + madvise(buf, 0x4000, MADV_FREE); +#elif defined MADV_DONTNEED && defined TORRENT_LINUX + // rumor has it that MADV_DONTNEED is in fact destructive + // on linux (i.e. it won't flush it to disk or re-read from disk) + // http://kerneltrap.org/mailarchive/linux-kernel/2007/5/1/84410 + madvise(buf, 0x4000, MADV_DONTNEED); +#endif + } + else +#endif + { +#if defined TORRENT_DISABLE_POOL_ALLOCATOR + +#if TORRENT_USE_PURGABLE_CONTROL + vm_deallocate( + mach_task_self(), + reinterpret_cast(buf), + 0x4000 + ); +#else page_aligned_allocator::free(buf); +#endif // TORRENT_USE_PURGABLE_CONTROL + #else if (m_using_pool_allocator) m_pool.free(buf); else page_aligned_allocator::free(buf); +#endif // TORRENT_DISABLE_POOL_ALLOCATOR + } + +#if defined TORRENT_DEBUG + std::set::iterator i = m_buffers_in_use.find(buf); + TORRENT_ASSERT(i != m_buffers_in_use.end()); + m_buffers_in_use.erase(i); #endif + --m_in_use; #ifndef TORRENT_DISABLE_POOL_ALLOCATOR // should we switch which allocator to use? - if (m_in_use == 0 && m_settings.use_disk_cache_pool != m_using_pool_allocator) + if (m_in_use == 0 && m_want_pool_allocator != m_using_pool_allocator) { m_pool.release_memory(); - m_using_pool_allocator = m_settings.use_disk_cache_pool; + m_using_pool_allocator = m_want_pool_allocator; } #endif } @@ -238,5 +716,6 @@ namespace libtorrent m_pool.release_memory(); #endif } + } diff --git a/src/disk_io_job.cpp b/src/disk_io_job.cpp new file mode 100644 index 000000000..821b9ed41 --- /dev/null +++ b/src/disk_io_job.cpp @@ -0,0 +1,86 @@ +/* + +Copyright (c) 2011-2013, 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. + +*/ + +#include "libtorrent/disk_io_job.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/block_cache.hpp" // for cached_piece_entry +#include "libtorrent/entry.hpp" + +namespace libtorrent +{ + disk_io_job::disk_io_job() + : requester(0) + , buffer(0) + , piece(0) + , action(read) + , ret(0) + , flags(0) +#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS + , in_use(false) + , job_posted(false) + , callback_called(false) + , blocked(false) +#endif + { + d.io.offset = 0; + d.io.buffer_size = 0; + d.io.ref.storage = 0; + d.io.ref.piece = 0; + d.io.ref.block = 0; + } + + disk_io_job::~disk_io_job() + { + if (action == rename_file || action == move_storage) + free(buffer); + if (action == save_resume_data) + delete (entry*)buffer; + } + + bool disk_io_job::completed(cached_piece_entry const* pe, int block_size) + { + if (action != write) return false; + + int block_offset = d.io.offset & (block_size-1); + int size = d.io.buffer_size; + int start = d.io.offset / block_size; + int end = block_offset > 0 && (size > block_size - block_offset) ? start + 2 : start + 1; + + for (int i = start; i < end; ++i) + if (pe->blocks[i].dirty || pe->blocks[i].pending) return false; + + // if all our blocks are not pending and not dirty, it means they + // were successfully written to disk. This job is complete + return true; + } +} + diff --git a/src/disk_io_thread.cpp b/src/disk_io_thread.cpp index 6120a8a1f..9b3d83a27 100644 --- a/src/disk_io_thread.cpp +++ b/src/disk_io_thread.cpp @@ -30,10 +30,6 @@ POSSIBILITY OF SUCH DAMAGE. */ -/* - Disk queue elevator patch by Morten Husveit -*/ - #include "libtorrent/storage.hpp" #include "libtorrent/disk_io_thread.hpp" #include "libtorrent/disk_buffer_holder.hpp" @@ -44,2494 +40,3455 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/file_pool.hpp" #include #include +#include +#include +#include #include "libtorrent/time.hpp" +#include "libtorrent/disk_buffer_pool.hpp" +#include "libtorrent/disk_io_job.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/alert_dispatcher.hpp" +#include "libtorrent/uncork_interface.hpp" +#include "libtorrent/performance_counters.hpp" -#if TORRENT_USE_MLOCK && !defined TORRENT_WINDOWS -#include -#endif - -#ifdef TORRENT_BSD -#include -#endif +#include "libtorrent/debug.hpp" #if TORRENT_USE_RLIMIT #include #endif -#ifdef TORRENT_LINUX -#include -#endif +#define DEBUG_DISK_THREAD 0 + +#define DLOG if (DEBUG_DISK_THREAD) debug_log namespace libtorrent { - bool should_cancel_on_abort(disk_io_job const& j); - bool is_read_operation(disk_io_job const& j); - bool operation_has_buffer(disk_io_job const& j); + +#ifdef TORRENT_DISK_STATS + // this is defined and used in storage.cpp + extern FILE* g_access_log; +#endif + +#if TORRENT_USE_ASSERTS + char const* job_name(int j); + + void assert_print_piece(cached_piece_entry const* pe) + { + const static char* const cache_state[] = + { + "write", "volatile-read", "read-lru", "read-lru-ghost", "read-lfu", "read-lfu-ghost" + }; + + if (pe == NULL) + { + assert_print("piece: NULL\n"); + } + else + { + assert_print("piece: %d\nrefcount: %d\npiece_refcount: %d\n" + "num_blocks: %d\nhashing: %d\n\nhash: %p\nhash_offset: %d\n" + "cache_state: (%d) %s\noutstanding_flush: %d\npiece: %d\n" + "num_dirty: %d\nnum_blocks: %d\nblocks_in_piece: %d\n" + "hashing_done: %d\nmarked_for_deletion: %d\nneed_readback: %d\n" + "hash_passed: %d\nread_jobs: %d\njobs: %d\n" + "piece_log:\n" + , int(pe->piece), pe->refcount, pe->piece_refcount, pe->num_blocks + , int(pe->hashing), pe->hash, pe->hash ? pe->hash->offset : -1 + , int(pe->cache_state), pe->cache_state >= 0 && pe->cache_state + < cached_piece_entry::num_lrus ? cache_state[pe->cache_state] : "" + , int(pe->outstanding_flush), int(pe->piece), int(pe->num_dirty) + , int(pe->num_blocks), int(pe->blocks_in_piece), int(pe->hashing_done) + , int(pe->marked_for_deletion), int(pe->need_readback), pe->hash_passes + , int(pe->read_jobs.size()), int(pe->jobs.size())); + for (int i = 0; i < pe->piece_log.size(); ++i) + { + assert_print(", %s (%d)" + (i==0), job_name(pe->piece_log[i].job), pe->piece_log[i].block); + } + } + assert_print("\n"); + } + +#define TORRENT_PIECE_ASSERT(cond, piece) \ + do { if (!(cond)) { assert_print_piece(piece); assert_fail(#cond, __LINE__, __FILE__, TORRENT_FUNCTION, 0); } } while(false) + +#else +#define TORRENT_PIECE_ASSERT(cond, piece) do {} while(false) +#endif + + void debug_log(char const* fmt, ...) + { +#if DEBUG_DISK_THREAD + static mutex log_mutex; + va_list v; + va_start(v, fmt); + + char usr[2048]; + int len = vsnprintf(usr, sizeof(usr), fmt, v); + + static bool prepend_time = true; + if (!prepend_time) + { + prepend_time = (usr[len-1] == '\n'); + mutex::scoped_lock l(log_mutex); + fputs(usr, stderr); + return; + } + va_end(v); + char buf[2300]; + snprintf(buf, sizeof(buf), "%s: [%p] %s", time_now_string(), pthread_self(), usr); + prepend_time = (usr[len-1] == '\n'); + mutex::scoped_lock l(log_mutex); + fputs(buf, stderr); +#endif + } + + // this is posted to the network thread and run from there + static void alert_callback(alert_dispatcher* disp, alert* a) + { + if (disp && disp->post_alert(a)) return; + delete a; + } + + static int file_flags_for_job(disk_io_job* j) + { + int ret = 0; + + if (!(j->flags & disk_io_job::sequential_access)) ret |= file::random_access; + if (j->flags & disk_io_job::coalesce_buffers) ret |= file::coalesce_buffers; + return ret; + } // ------- disk_io_thread ------ disk_io_thread::disk_io_thread(io_service& ios - , boost::function const& queue_callback - , file_pool& fp + , alert_dispatcher* alert_disp + , void* userdata , int block_size) - : disk_buffer_pool(block_size) - , m_abort(false) - , m_waiting_to_shutdown(false) - , m_queue_buffer_size(0) + : m_num_threads(0) + , m_num_running_threads(0) + , m_num_writing_threads(0) + , m_userdata(userdata) + , m_last_cache_expiry(min_time()) , m_last_file_check(time_now_hires()) + , m_file_pool(40) + , m_disk_cache(block_size, ios, boost::bind(&disk_io_thread::trigger_cache_trim, this), alert_disp) , m_last_stats_flip(time_now()) - , m_physical_ram(0) - , m_exceeded_write_queue(false) + , m_outstanding_jobs(0) , m_ios(ios) - , m_queue_callback(queue_callback) + , m_num_blocked_jobs(0) , m_work(io_service::work(m_ios)) - , m_file_pool(fp) + , m_last_disk_aio_performance_warning(min_time()) + , m_post_alert(alert_disp) + , m_outstanding_reclaim_message(false) #if TORRENT_USE_ASSERTS , m_magic(0x1337) #endif - , m_disk_io_thread(boost::bind(&disk_io_thread::thread_fun, this)) { - // don't do anything in here. Essentially all members - // of this object are owned by the newly created thread. - // initialize stuff in thread_fun(). +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("disk_io_thread::work"); +#endif + m_disk_cache.set_settings(m_settings); + +#ifdef TORRENT_DISK_STATS + if (g_access_log == NULL) g_access_log = fopen("file_access.log", "a+"); +#endif + +#if TORRENT_USE_RLIMIT + // ---- auto-cap open files ---- + + struct rlimit rl; + if (getrlimit(RLIMIT_NOFILE, &rl) == 0) + { + // deduct some margin for epoll/kqueue, log files, + // futexes, shared objects etc. + rl.rlim_cur -= 20; + + // 80% of the available file descriptors should go to connections + // 20% goes towards regular files + m_file_pool.resize((std::min)(m_file_pool.size_limit(), int(rl.rlim_cur * 2 / 10))); + } +#endif // TORRENT_USE_RLIMIT + + set_num_threads(1); } disk_io_thread::~disk_io_thread() { + DLOG("destructing disk_io_thread\n"); + +#if TORRENT_USE_ASSERTS + // by now, all pieces should have been evicted + std::pair pieces + = m_disk_cache.all_pieces(); + TORRENT_ASSERT(pieces.first == pieces.second); +#endif + +#ifdef TORRENT_DISK_STATS + if (g_access_log) + { + FILE* f = g_access_log; + g_access_log = NULL; + fclose(f); + } +#endif + TORRENT_ASSERT(m_magic == 0x1337); #if TORRENT_USE_ASSERTS m_magic = 0xdead; #endif - TORRENT_ASSERT(m_abort == true); } - void disk_io_thread::abort() + // TODO: 3 it would be nice to have the number of threads be set dynamically + void disk_io_thread::set_num_threads(int i, bool wait) { TORRENT_ASSERT(m_magic == 0x1337); + if (i == m_num_threads) return; - mutex::scoped_lock l(m_queue_mutex); - disk_io_job j; - m_waiting_to_shutdown = true; - j.action = disk_io_job::abort_thread; - j.start_time = time_now_hires(); + if (i > m_num_threads) + { + while (m_num_threads < i) + { + int thread_id = (++m_num_threads) - 1; + thread_type_t type = generic_thread; + // the magic number 3 is also used in add_job() + // every 4:th thread is a hasher thread + if ((thread_id & 0x3) == 3) type = hasher_thread; + m_threads.push_back(boost::shared_ptr( + new thread(boost::bind(&disk_io_thread::thread_fun, this, thread_id, type)))); + } + } + else + { + while (m_num_threads > i) { --m_num_threads; } + mutex::scoped_lock l(m_job_mutex); + m_job_cond.notify_all(); + m_hash_job_cond.notify_all(); + l.unlock(); + if (wait) for (int i = m_num_threads; i < m_threads.size(); ++i) m_threads[i]->join(); + // this will detach the threads + m_threads.resize(m_num_threads); + } + } + + char* disk_io_thread::async_allocate_disk_buffer(char const* category + , boost::function const& handler) + { return m_disk_cache.async_allocate_buffer(category, handler); } + + void disk_io_thread::reclaim_block(block_cache_reference ref) + { + TORRENT_ASSERT(m_magic == 0x1337); + TORRENT_ASSERT(ref.storage); + m_blocks_to_reclaim.push_back(ref); + if (m_outstanding_reclaim_message) return; + + m_ios.post(boost::bind(&disk_io_thread::commit_reclaimed_blocks, this)); + m_outstanding_reclaim_message = true; + } + + void disk_io_thread::commit_reclaimed_blocks() + { + TORRENT_ASSERT(m_magic == 0x1337); + TORRENT_ASSERT(m_outstanding_reclaim_message); + m_outstanding_reclaim_message = false; + mutex::scoped_lock l(m_cache_mutex); + for (int i = 0; i < m_blocks_to_reclaim.size(); ++i) + m_disk_cache.reclaim_block(m_blocks_to_reclaim[i]); + m_blocks_to_reclaim.clear(); + } + + void disk_io_thread::set_settings(settings_pack* pack) + { + TORRENT_ASSERT(m_magic == 0x1337); + mutex::scoped_lock l(m_cache_mutex); + apply_pack(pack, m_settings); + m_disk_cache.set_settings(m_settings); + } + + // flush all blocks that are below p->hash.offset, since we've + // already hashed those blocks, they won't cause any read-back + int disk_io_thread::try_flush_hashed(cached_piece_entry* p, int cont_block + , tailqueue& completed_jobs, mutex::scoped_lock& l) + { + TORRENT_ASSERT(m_magic == 0x1337); TORRENT_ASSERT(l.locked()); - m_jobs.insert(m_jobs.begin(), j); - m_signal.signal(l); + TORRENT_ASSERT(cont_block > 0); + if (p->hash == 0 && !p->hashing_done) + { + DLOG("try_flush_hashed: (%d) no hash\n", int(p->piece)); + return 0; + } + + if (p->num_dirty == 0) + { + DLOG("try_flush_hashed: no dirty blocks\n"); + return 0; + } + + // end is one past the end + // round offset up to include the last block, which might + // have an odd size + int block_size = m_disk_cache.block_size(); + int end = p->hashing_done ? p->blocks_in_piece : (p->hash->offset + block_size - 1) / block_size; + + // nothing has been hashed yet, don't flush anything + if (end == 0 && !p->need_readback) return 0; + + // the number of contiguous blocks we need to be allowed to flush + int block_limit = (std::min)(cont_block, int(p->blocks_in_piece)); + + // if everything has been hashed, we might as well flush everything + // regardless of the contiguous block restriction + if (end == int(p->blocks_in_piece)) block_limit = 1; + + if (p->need_readback) + { + // if this piece needs a read-back already, don't + // try to keep it from being flushed, since we'll + // need to read it back regardless. Flushing will + // save blocks that can be used to "save" other + // pieces from being fllushed prematurely + end = int(p->blocks_in_piece); + } + + // count number of blocks that would be flushed + int num_blocks = 0; + for (int i = end-1; i >= 0; --i) + num_blocks += (p->blocks[i].dirty && !p->blocks[i].pending); + + // we did not satisfy the block_limit requirement + // i.e. too few blocks would be flushed at this point, put it off + if (block_limit > num_blocks) return 0; + + // if the cache line size is larger than a whole piece, hold + // off flushing this piece until enough adjacent pieces are + // full as well. + int cont_pieces = cont_block / p->blocks_in_piece; + + // at this point, we may enforce flushing full cache stripes even when + // they span multiple pieces. This won't necessarily work in the general + // case, because it assumes that the piece picker will have an affinity + // to download whole stripes at a time. This is why this setting is turned + // off by default, flushing only one piece at a time + + if (cont_pieces <= 1 || m_settings.get_bool(settings_pack::allow_partial_disk_writes)) + { + DLOG("try_flush_hashed: (%d) blocks_in_piece: %d end: %d\n" + , int(p->piece), int(p->blocks_in_piece), end); + + return flush_range(p, 0, end, 0, completed_jobs, l); + } + + // piece range + int range_start = (p->piece / cont_pieces) * cont_pieces; + int range_end = (std::min)(range_start + cont_pieces, p->storage->files()->num_pieces()); + + // look through all the pieces in this range to see if + // they are ready to be flushed. If so, flush them all, + // otherwise, hold off + bool range_full = true; + + cached_piece_entry* first_piece = NULL; + DLOG("try_flush_hashed: multi-piece: "); + for (int i = range_start; i < range_end; ++i) + { + if (i == p->piece) + { + if (i == range_start) first_piece = p; + DLOG("[%d self] ", i); + continue; + } + cached_piece_entry* pe = m_disk_cache.find_piece(p->storage.get(), i); + if (pe == NULL) + { + DLOG("[%d NULL] ", i); + range_full = false; + break; + } + if (i == range_start) first_piece = pe; + + // if this is a read-cache piece, it has already been flushed + if (pe->cache_state != cached_piece_entry::write_lru) + { + DLOG("[%d read-cache] ", i); + continue; + } + int hash_cursor = pe->hash ? pe->hash->offset / block_size : 0; + + // if the piece has all blocks, and they're all dirty, and they've + // all been hashed, then this piece is eligible for flushing + if (pe->num_dirty == pe->blocks_in_piece + && (pe->hashing_done + || hash_cursor == pe->blocks_in_piece + || m_settings.get_bool(settings_pack::disable_hash_checks))) + { + DLOG("[%d hash-done] ", i); + continue; + } + + if (pe->num_dirty < pe->blocks_in_piece) + { + DLOG("[%d dirty:%d] ", i, int(pe->num_dirty)); + } + else if (pe->hashing_done == 0 && hash_cursor < pe->blocks_in_piece) + { + DLOG("[%d cursor:%d] ", i, hash_cursor); + } + else + { + DLOG("[%d xx] ", i); + } + + // TOOD: in this case, the piece should probably not be flushed yet. are there + // any more cases where it should? + + range_full = false; + break; + } + + if (!range_full) + { + DLOG("not flushing\n"); + return 0; + } + DLOG("\n"); + + // now, build a iovec for all pieces that we want to flush, so that they + // can be flushed in a single atomic operation. This is especially important + // when there are more than 1 disk thread, to make sure they don't + // interleave in undesired places. + // in order to remember where each piece boundary ended up in the iovec, + // we keep the indices in the iovec_offset array + + cont_pieces = range_end - range_start; + + file::iovec_t* iov = TORRENT_ALLOCA(file::iovec_t, p->blocks_in_piece * cont_pieces); + int* flushing = TORRENT_ALLOCA(int, p->blocks_in_piece * cont_pieces); + // this is the offset into iov and flushing for each piece + int* iovec_offset = TORRENT_ALLOCA(int, cont_pieces + 1); + int iov_len = 0; + // this is the block index each piece starts at + int block_start = 0; + // keep track of the pieces that have had their refcount incremented + // so we know to decrement them later + int* refcount_pieces = TORRENT_ALLOCA(int, cont_pieces); + for (int i = 0; i < cont_pieces; ++i) + { + cached_piece_entry* pe; + if (i == p->piece) pe = p; + else pe = m_disk_cache.find_piece(p->storage.get(), range_start + i); + if (pe == NULL + || pe->cache_state != cached_piece_entry::write_lru) + { + refcount_pieces[i] = 0; + iovec_offset[i] = iov_len; + block_start += p->blocks_in_piece; + continue; + } + + iovec_offset[i] = iov_len; + refcount_pieces[i] = 1; + TORRENT_ASSERT_VAL(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(piece_log_t::flushing, -1)); +#endif + ++pe->piece_refcount; + + iov_len += build_iovec(pe, 0, p->blocks_in_piece + , iov + iov_len, flushing + iov_len, block_start); + + block_start += p->blocks_in_piece; + } + iovec_offset[cont_pieces] = iov_len; + + // ok, now we have one (or more, but hopefully one) contiguous + // iovec array. Now, flush it to disk + + TORRENT_ASSERT(first_piece != NULL); + + if (iov_len == 0) + { + // we may not exit here if we incremented any piece refcounters + TORRENT_ASSERT(cont_pieces == 0); + DLOG(" iov_len: 0 cont_pieces: %d range_start: %d range_end: %d\n" + , cont_pieces, range_start, range_end); + return 0; + } + + l.unlock(); + + storage_error error; + flush_iovec(first_piece, iov, flushing, iov_len, error); + + l.lock(); + + block_start = 0; + for (int i = 0; i < cont_pieces; ++i) + { + cached_piece_entry* pe; + if (i == p->piece) pe = p; + else pe = m_disk_cache.find_piece(p->storage.get(), range_start + i); + if (pe == NULL) + { + DLOG("iovec_flushed: piece %d gone!\n", range_start + i); + TORRENT_PIECE_ASSERT(refcount_pieces[i] == 0, pe); + block_start += p->blocks_in_piece; + continue; + } + if (refcount_pieces[i]) + { + TORRENT_PIECE_ASSERT(pe->piece_refcount > 0, pe); + --pe->piece_refcount; + m_disk_cache.maybe_free_piece(pe); + } + int num_blocks = iovec_offset[i+1] - iovec_offset[i]; + iovec_flushed(pe, flushing + iovec_offset[i], num_blocks + , block_start, error, completed_jobs); + block_start += p->blocks_in_piece; + } + + // if the cache is under high pressure, we need to evict + // the blocks we just flushed to make room for more write pieces + int evict = m_disk_cache.num_to_evict(0); + if (evict > 0) m_disk_cache.try_evict_blocks(evict); + + return iov_len; } - void disk_io_thread::join() + // iov and flushing are expected to be arrays to at least pe->blocks_in_piece + // items in them. Returns the numner of iovecs written to the iov array. + // The same number of block indices are written to the flushing array. These + // are block indices that the respecivec iovec structure refers to, since + // we might not be able to flush everything as a single contiguous block, + // the block indices indicates where the block run is broken + // the cache needs to be locked when calling this function + // block_base_index is the offset added to every block index written to + // the flushing array. This can be used when building iovecs spanning + // multiple pieces, the subsequent pieces after the first one, must have + // their block indices start where the previous one left off + int disk_io_thread::build_iovec(cached_piece_entry* pe, int start, int end + , file::iovec_t* iov, int* flushing, int block_base_index) { - TORRENT_ASSERT(m_magic == 0x1337); + INVARIANT_CHECK; - m_disk_io_thread.join(); - mutex::scoped_lock l(m_queue_mutex); - TORRENT_ASSERT(m_abort == true); - m_jobs.clear(); + DLOG("build_iovec: piece=%d [%d, %d)\n" + , int(pe->piece), start, end); + TORRENT_PIECE_ASSERT(start >= 0, pe); + TORRENT_PIECE_ASSERT(start < end, pe); + end = (std::min)(end, int(pe->blocks_in_piece)); + + int piece_size = pe->storage->files()->piece_size(pe->piece); + TORRENT_PIECE_ASSERT(piece_size > 0, pe); + + int iov_len = 0; + // the blocks we're flushing + int num_flushing = 0; + +#if DEBUG_DISK_THREAD + DLOG("build_iov: piece: %d [", int(pe->piece)); + for (int i = 0; i < start; ++i) DLOG("."); +#endif + + int block_size = m_disk_cache.block_size(); + int size_left = piece_size; + for (int i = start; i < end; ++i, size_left -= block_size) + { + TORRENT_PIECE_ASSERT(size_left > 0, pe); + // don't flush blocks that are empty (buf == 0), not dirty + // (read cache blocks), or pending (already being written) + if (pe->blocks[i].buf == NULL + || pe->blocks[i].pending + || !pe->blocks[i].dirty) + { + DLOG("-"); + continue; + } + + // if we fail to lock the block, it' no longer in the cache + bool locked = m_disk_cache.inc_block_refcount(pe, i, block_cache::ref_flushing); + + // it should always suceed, since it's a dirty block, and + // should never have been marked as volatile + TORRENT_ASSERT(locked); + + flushing[num_flushing++] = i + block_base_index; + iov[iov_len].iov_base = pe->blocks[i].buf; + iov[iov_len].iov_len = (std::min)(block_size, size_left); + ++iov_len; + pe->blocks[i].pending = true; + + DLOG("x"); + } + DLOG("]\n"); + + TORRENT_PIECE_ASSERT(iov_len == num_flushing, pe); + return iov_len; } - bool disk_io_thread::can_write() const + // does the actual writing to disk + // the cached_piece_entry is supposed to point to the + // first piece, if the iovec spans multiple pieces + void disk_io_thread::flush_iovec(cached_piece_entry* pe + , file::iovec_t const* iov, int const* flushing + , int num_blocks, storage_error& error) { - TORRENT_ASSERT(m_magic == 0x1337); - mutex::scoped_lock l(m_queue_mutex); - return !m_exceeded_write_queue; + TORRENT_PIECE_ASSERT(!error, pe); + TORRENT_PIECE_ASSERT(num_blocks > 0, pe); + ++m_num_writing_threads; + + ptime start_time = time_now_hires(); + int block_size = m_disk_cache.block_size(); + +#if DEBUG_DISK_THREAD + DLOG("flush_iovec: piece: %d [ ", int(pe->piece)); + for (int i = 0; i < num_blocks; ++i) + DLOG("%d ", flushing[i]); + DLOG("]\n"); +#endif + + // issue the actual write operation + file::iovec_t const* iov_start = iov; + int flushing_start = 0; + int piece = pe->piece; + int blocks_in_piece = pe->blocks_in_piece; + bool failed = false; + for (int i = 1; i <= num_blocks; ++i) + { + if (i < num_blocks && flushing[i] == flushing[i-1]+1) continue; + int ret = pe->storage->get_storage_impl()->writev(iov_start, i - flushing_start + , piece + flushing[flushing_start] / blocks_in_piece + , (flushing[flushing_start] % blocks_in_piece) * block_size, 0, error); + if (ret < 0 || error) failed = true; + iov_start = &iov[i]; + flushing_start = i; + } + + --m_num_writing_threads; + + if (!failed) + { + TORRENT_PIECE_ASSERT(!error, pe); + boost::uint32_t write_time = total_microseconds(time_now_hires() - start_time); + m_write_time.add_sample(write_time / num_blocks); + m_cache_stats.cumulative_write_time += write_time / 1000; + m_cache_stats.cumulative_job_time += write_time / 1000; + m_cache_stats.blocks_written += num_blocks; + ++m_cache_stats.writes; + +#if DEBUG_DISK_THREAD + DLOG("flush_iovec: %d\n", num_blocks); +#endif + } +#if DEBUG_DISK_THREAD + else + { + DLOG("flush_iovec: error: (%d) %s\n" + , error.ec.value(), error.ec.message().c_str()); + } +#endif } - void disk_io_thread::flip_stats(ptime now) + // It is necessary to call this function with the blocks produced by + // build_iovec, to reset their state to not being flushed anymore + // the cache needs to be locked when calling this function + void disk_io_thread::iovec_flushed(cached_piece_entry* pe + , int* flushing, int num_blocks, int block_offset + , storage_error const& error + , tailqueue& completed_jobs) { + for (int i = 0; i < num_blocks; ++i) + flushing[i] -= block_offset; + +#if DEBUG_DISK_THREAD + DLOG("iovec_flushed: piece: %d block_offset: %d [ " + , int(pe->piece), block_offset); + for (int i = 0; i < num_blocks; ++i) + DLOG("%d ", flushing[i]); + DLOG("]\n"); +#endif + m_disk_cache.blocks_flushed(pe, flushing, num_blocks); + + int block_size = m_disk_cache.block_size(); + + if (error) + { + fail_jobs_impl(error, pe->jobs, completed_jobs); + } + else + { + disk_io_job* j = (disk_io_job*)pe->jobs.get_all(); + while (j) + { + disk_io_job* next = (disk_io_job*)j->next; + j->next = NULL; + TORRENT_PIECE_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage, pe); + TORRENT_PIECE_ASSERT(j->piece == pe->piece, pe); + if (j->completed(pe, block_size)) + { + j->ret = j->d.io.buffer_size; + j->error = error; + completed_jobs.push_back(j); + } + else + { + pe->jobs.push_back(j); + } + j = next; + } + } + } + + // issues write operations for blocks in the given + // range on the given piece. + int disk_io_thread::flush_range(cached_piece_entry* pe, int start, int end + , int flags, tailqueue& completed_jobs, mutex::scoped_lock& l) + { + TORRENT_ASSERT(l.locked()); + INVARIANT_CHECK; + + DLOG("flush_range: piece=%d [%d, %d)\n" + , int(pe->piece), start, end); + TORRENT_PIECE_ASSERT(start >= 0, pe); + TORRENT_PIECE_ASSERT(start < end, pe); + + file::iovec_t* iov = TORRENT_ALLOCA(file::iovec_t, pe->blocks_in_piece); + int* flushing = TORRENT_ALLOCA(int, pe->blocks_in_piece); + int iov_len = build_iovec(pe, start, end, iov, flushing, 0); + if (iov_len == 0) return 0; + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(piece_log_t::flush_range, -1)); +#endif + ++pe->piece_refcount; + + l.unlock(); + + storage_error error; + flush_iovec(pe, iov, flushing, iov_len, error); + + l.lock(); + + TORRENT_PIECE_ASSERT(pe->piece_refcount > 0, pe); + --pe->piece_refcount; + iovec_flushed(pe, flushing, iov_len, 0, error, completed_jobs); + + // if the cache is under high pressure, we need to evict + // the blocks we just flushed to make room for more write pieces + int evict = m_disk_cache.num_to_evict(0); + if (evict > 0) m_disk_cache.try_evict_blocks(evict); + + m_disk_cache.maybe_free_piece(pe); + + return iov_len; + } + + void disk_io_thread::fail_jobs(storage_error const& e, tailqueue& jobs_) + { + tailqueue jobs; + fail_jobs_impl(e, jobs_, jobs); + if (jobs.size()) add_completed_jobs(jobs); + } + + void disk_io_thread::fail_jobs_impl(storage_error const& e, tailqueue& src, tailqueue& dst) + { + while (src.size()) + { + disk_io_job* j = (disk_io_job*)src.pop_front(); + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + j->ret = -1; + j->error = e; + dst.push_back(j); + } + } + + void disk_io_thread::flush_piece(cached_piece_entry* pe, int flags, tailqueue& completed_jobs, mutex::scoped_lock& l) + { + TORRENT_ASSERT(l.locked()); + if (flags & flush_delete_cache) + { + // delete dirty blocks and post handlers with + // operation_aborted error code + fail_jobs_impl(storage_error(boost::asio::error::operation_aborted), pe->jobs, completed_jobs); + fail_jobs_impl(storage_error(boost::asio::error::operation_aborted), pe->read_jobs, completed_jobs); + m_disk_cache.abort_dirty(pe); + } + else if ((flags & flush_write_cache) && pe->num_dirty > 0) + { + // issue write commands + flush_range(pe, 0, INT_MAX, 0, completed_jobs, l); + + // if we're also flushing the read cache, this piece + // should be removed as soon as all write jobs finishes + // otherwise it will turn into a read piece + } + + // mark_for_deletion may erase the piece from the cache, that's + // why we don't have the 'i' iterator referencing it at this point + if (flags & (flush_read_cache | flush_delete_cache)) + { + fail_jobs_impl(storage_error(boost::asio::error::operation_aborted), pe->jobs, completed_jobs); + m_disk_cache.mark_for_deletion(pe); + } + } + + void disk_io_thread::flush_cache(piece_manager* storage, boost::uint32_t flags + , tailqueue& completed_jobs, mutex::scoped_lock& l) + { + if (storage) + { + boost::unordered_set const& pieces = storage->cached_pieces(); + // TODO: 2 should this be allocated on the stack? + std::vector piece_index; + piece_index.reserve(pieces.size()); + for (boost::unordered_set::const_iterator i = pieces.begin() + , end(pieces.end()); i != end; ++i) + { + piece_index.push_back((*i)->piece); + } + + for (std::vector::iterator i = piece_index.begin() + , end(piece_index.end()); i != end; ++i) + { + cached_piece_entry* pe = m_disk_cache.find_piece(storage, *i); + if (pe == NULL) continue; + TORRENT_PIECE_ASSERT(pe->storage.get() == storage, pe); + flush_piece(pe, flags, completed_jobs, l); + } +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(l.locked()); + // if the user asked to delete the cache for this storage + // we really should not have any pieces left. This is only called + // from disk_io_thread::do_delete, which is a fence job and should + // have any other jobs active, i.e. there should not be any references + // keeping pieces or blocks alive + if ((flags & flush_delete_cache) && (flags & flush_expect_clear)) + { + boost::unordered_set const& storage_pieces = storage->cached_pieces(); + for (boost::unordered_set::const_iterator i = storage_pieces.begin() + , end(storage_pieces.end()); i != end; ++i) + { + cached_piece_entry* pe = m_disk_cache.find_piece(storage, (*i)->piece); + TORRENT_PIECE_ASSERT(pe->num_dirty == 0, pe); + } + } +#endif + } + else + { + std::pair range = m_disk_cache.all_pieces(); + while (range.first != range.second) + { + // TODO: 2 we're not flushing the read cache at all? + while (range.first->num_dirty == 0) + { + ++range.first; + if (range.first == range.second) return; + } + cached_piece_entry* pe = const_cast(&*range.first); + flush_piece(pe, flags, completed_jobs, l); + range = m_disk_cache.all_pieces(); + } + } + } + + // this is called if we're exceeding (or about to exceed) the cache + // size limit. This means we should not restrict ourselves to contiguous + // blocks of write cache line size, but try to flush all old blocks + // this is why we pass in 1 as cont_block to the flushing functions + void disk_io_thread::try_flush_write_blocks(int num, tailqueue& completed_jobs + , mutex::scoped_lock& l) + { + DLOG("try_flush_write_blocks: %d\n", num); + + list_iterator range = m_disk_cache.write_lru_pieces(); + std::vector > pieces; + pieces.reserve(m_disk_cache.num_write_lru_pieces()); + + for (list_iterator p = range; p.get() && num > 0; p.next()) + { + cached_piece_entry* e = (cached_piece_entry*)p.get(); + if (e->num_dirty == 0) continue; + pieces.push_back(std::make_pair(e->storage.get(), int(e->piece))); + } + + for (std::vector >::iterator i = pieces.begin() + , end(pieces.end()); i != end; ++i) + { + // TODO: instead of doing a lookup each time through the loop, save + // cached_piece_entry pointers with piece_refcount incremented to pin them + cached_piece_entry* pe = m_disk_cache.find_piece(i->first, i->second); + if (pe == NULL) continue; + + // another thread may flush this piece while we're looping and + // evict it into a read piece and then also evict it to ghost + if (pe->cache_state != cached_piece_entry::write_lru) continue; + +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(piece_log_t::try_flush_write_blocks, -1)); +#endif + ++pe->piece_refcount; + kick_hasher(pe, l); + num -= try_flush_hashed(pe, 1, completed_jobs, l); + --pe->piece_refcount; + } + + // when the write cache is under high pressure, it is likely + // counter productive to actually do this, since a piece may + // not have had its flush_hashed job run on it + // so only do it if no other thread is currently flushing + + if (num == 0 || m_num_writing_threads > 0) return; + + // if we still need to flush blocks, start over and flush + // everything in LRU order (degrade to lru cache eviction) + for (std::vector >::iterator i = pieces.begin() + , end(pieces.end()); i != end; ++i) + { + cached_piece_entry* pe = m_disk_cache.find_piece(i->first, i->second); + if (pe == NULL) continue; + if (pe->num_dirty == 0) continue; + + // another thread may flush this piece while we're looping and + // evict it into a read piece and then also evict it to ghost + if (pe->cache_state != cached_piece_entry::write_lru) continue; + + // don't flush blocks that are being hashed by another thread + if (pe->num_dirty == 0 || pe->hashing) continue; + +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(piece_log_t::try_flush_write_blocks2, -1)); +#endif + ++pe->piece_refcount; + + num -= flush_range(pe, 0, INT_MAX, 0, completed_jobs, l); + --pe->piece_refcount; + + m_disk_cache.maybe_free_piece(pe); + } + } + + void disk_io_thread::flush_expired_write_blocks(tailqueue& completed_jobs, mutex::scoped_lock& l) + { + DLOG("flush_expired_write_blocks\n"); + + ptime now = time_now(); + time_duration expiration_limit = seconds(m_settings.get_int(settings_pack::cache_expiry)); + +#if TORRENT_USE_ASSERTS + ptime timeout = min_time(); +#endif + + cached_piece_entry** to_flush = TORRENT_ALLOCA(cached_piece_entry*, 200); + int num_flush = 0; + + for (list_iterator p = m_disk_cache.write_lru_pieces(); p.get(); p.next()) + { + cached_piece_entry* e = (cached_piece_entry*)p.get(); +#if TORRENT_USE_ASSERTS + TORRENT_PIECE_ASSERT(e->expire >= timeout, e); + timeout = e->expire; +#endif + + // since we're iterating in order of last use, if this piece + // shouldn't be evicted, none of the following ones will either + if (now - e->expire < expiration_limit) break; + if (e->num_dirty == 0) continue; + + TORRENT_PIECE_ASSERT(e->cache_state <= cached_piece_entry::read_lru1 || e->cache_state == cached_piece_entry::read_lru2, e); +#if TORRENT_USE_ASSERTS + e->piece_log.push_back(piece_log_t(piece_log_t::flush_expired, -1)); +#endif + ++e->piece_refcount; + // We can rely on the piece entry not being removed by + // incrementing the piece_refcount + to_flush[num_flush++] = e; + if (num_flush == 200) break; + } + + for (int i = 0; i < num_flush; ++i) + { + flush_range(to_flush[i], 0, INT_MAX, 0, completed_jobs, l); + TORRENT_ASSERT(to_flush[i]->piece_refcount > 0); + --to_flush[i]->piece_refcount; + m_disk_cache.maybe_free_piece(to_flush[i]); + } + } + + typedef int (disk_io_thread::*disk_io_fun_t)(disk_io_job* j, tailqueue& completed_jobs); + + // this is a jump-table for disk I/O jobs + static const disk_io_fun_t job_functions[] = + { + &disk_io_thread::do_read, + &disk_io_thread::do_write, + &disk_io_thread::do_hash, + &disk_io_thread::do_move_storage, + &disk_io_thread::do_release_files, + &disk_io_thread::do_delete_files, + &disk_io_thread::do_check_fastresume, + &disk_io_thread::do_save_resume_data, + &disk_io_thread::do_rename_file, + &disk_io_thread::do_stop_torrent, + &disk_io_thread::do_cache_piece, +#ifndef TORRENT_NO_DEPRECATE + &disk_io_thread::do_finalize_file, +#endif + &disk_io_thread::do_flush_piece, + &disk_io_thread::do_flush_hashed, + &disk_io_thread::do_flush_storage, + &disk_io_thread::do_trim_cache, + &disk_io_thread::do_file_priority, + &disk_io_thread::do_load_torrent, + &disk_io_thread::do_clear_piece, + &disk_io_thread::do_tick, + }; + + const char* job_action_name[] = + { + "read", + "write", + "hash", + "move_storage", + "release_files", + "delete_files", + "check_fastresume", + "save_resume_data", + "rename_file", + "stop_torrent", + "cache_piece", + "finalize_file", + "flush_piece", + "flush_hashed", + "flush_storage", + "trim_cache", + "set_file_priority", + "load_torrent", + "clear_piece", + "tick_storage", + }; + +#if TORRENT_USE_ASSERTS || DEBUG_DISK_THREAD + char const* job_name(int j) + { + if (j < 0 || j >= piece_log_t::last_job) + return "unknown"; + + if (j < piece_log_t::flushing) + return job_action_name[j]; + return piece_log_t::job_names[j - piece_log_t::flushing]; + } + +#endif + + // evict and/or flush blocks if we're exceeding the cache size + // or used to exceed it and haven't dropped below the low watermark yet + // the low watermark is dynamic, based on the number of peers waiting + // on buffers to free up. The more waiters, the lower the low watermark + // is. Because of this, the target for flushing jobs may have dropped + // below the number of blocks we flushed by the time we're done flushing + // that's why we need to call this fairly often. Both before and after + // a disk job is executed + void disk_io_thread::check_cache_level(mutex::scoped_lock& l, tailqueue& completed_jobs) + { + int evict = m_disk_cache.num_to_evict(0); + if (evict > 0) + { + evict = m_disk_cache.try_evict_blocks(evict); + // don't evict write jobs if at least one other thread + // is flushing right now. Doing so could result in + // unnecessary flushing of the wrong pieces + if (evict > 0 && m_num_writing_threads == 0) + { + try_flush_write_blocks(evict, completed_jobs, l); + } + } + } + + void disk_io_thread::perform_job(disk_io_job* j, tailqueue& completed_jobs) + { + INVARIANT_CHECK; + TORRENT_ASSERT(j->next == 0); + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + + mutex::scoped_lock l(m_cache_mutex); + + check_cache_level(l, completed_jobs); + + DLOG("perform_job job: %s ( %s%s) piece: %d offset: %d outstanding: %d\n" + , job_action_name[j->action] + , (j->flags & disk_io_job::fence) ? "fence ": "" + , (j->flags & disk_io_job::force_copy) ? "force_copy ": "" + , j->piece, j->d.io.offset + , j->storage ? j->storage->num_outstanding_jobs() : -1); + + l.unlock(); + + boost::shared_ptr storage = j->storage; + + // TODO: instead of doing this. pass in the settings to each storage_interface + // call. Each disk thread could hold its most recent understanding of the settings + // in a shared_ptr, and update it every time it wakes up from a job. That way + // each access to the settings won't require a mutex to be held. + if (storage && storage->get_storage_impl()->m_settings == 0) + storage->get_storage_impl()->m_settings = &m_settings; + + TORRENT_ASSERT(j->action < sizeof(job_functions)/sizeof(job_functions[0])); + + // TODO: hold disk_io_thread mutex here! + if (time_now() > m_last_stats_flip + seconds(1)) flip_stats(); + + ptime start_time = time_now_hires(); + + ++m_outstanding_jobs; + + // call disk function + int ret = (this->*(job_functions[j->action]))(j, completed_jobs); + + --m_outstanding_jobs; + + if (ret == retry_job) + { + mutex::scoped_lock l(m_job_mutex); + // to avoid busy looping here, give up + // our quanta in case there aren't any other + // jobs to run in between + + // TODO: a potentially more efficient solution would be to have a special + // queue for retry jobs, that's only ever run when a job completes, in + // any thread. It would only work if m_outstanding_jobs > 0 + + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + + bool need_sleep = m_queued_jobs.empty(); + m_queued_jobs.push_back(j); + l.unlock(); + if (need_sleep) sleep(0); + return; + } + +#if TORRENT_USE_ASSERT + // TODO: it should clear the hash state even when there's an error, right? + if (j->action == disk_io_job::hash && !j->error.ec) + { + // a hash job should never return without clearing pe->hash + l.lock(); + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe != NULL) + { + TORRENT_PIECE_ASSERT(pe->hash == NULL, pe); + } + l.unlock(); + } +#endif + + if (ret == defer_handler) return; + + j->ret = ret; + + ptime now = time_now_hires(); + m_job_time.add_sample(total_microseconds(now - start_time)); + completed_jobs.push_back(j); + } + + int disk_io_thread::do_uncached_read(disk_io_job* j) + { + j->buffer = m_disk_cache.allocate_buffer("send buffer"); + if (j->buffer == 0) + { + j->error.ec = error::no_memory; + j->error.operation = storage_error::alloc_cache_piece; + return -1; + } + + ptime start_time = time_now_hires(); + + file::iovec_t b = { j->buffer, size_t(j->d.io.buffer_size) }; + + int ret = j->storage->get_storage_impl()->readv(&b, 1 + , j->piece, j->d.io.offset, j->flags, j->error); + + if (!j->error.ec) + { + boost::uint32_t read_time = total_microseconds(time_now_hires() - start_time); + m_read_time.add_sample(read_time); + m_cache_stats.cumulative_read_time += read_time / 1000; + m_cache_stats.cumulative_job_time += read_time / 1000; + ++m_cache_stats.total_read_back; + ++m_cache_stats.blocks_read; + ++m_cache_stats.reads; + } + return ret; + } + + int disk_io_thread::do_read(disk_io_job* j, tailqueue& completed_jobs) + { + if (!m_settings.get_bool(settings_pack::use_read_cache) + || m_settings.get_int(settings_pack::cache_size) == 0) + { + // we're not using a cache. This is the simple path + // just read straight from the file + int ret = do_uncached_read(j); + + mutex::scoped_lock l(m_cache_mutex); + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe) maybe_issue_queued_read_jobs(pe, completed_jobs); + return ret; + } + + int block_size = m_disk_cache.block_size(); + int piece_size = j->storage->files()->piece_size(j->piece); + int blocks_in_piece = (piece_size + block_size - 1) / block_size; + int iov_len = m_disk_cache.pad_job(j, blocks_in_piece + , m_settings.get_int(settings_pack::read_cache_line_size)); + + file::iovec_t* iov = TORRENT_ALLOCA(file::iovec_t, iov_len); + + mutex::scoped_lock l(m_cache_mutex); + + int evict = m_disk_cache.num_to_evict(iov_len); + if (evict > 0) m_disk_cache.try_evict_blocks(evict); + + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe == NULL) + { + // this isn't supposed to happen. The piece is supposed + // to be allocated when the read job is posted to the + // queue, and have 'outstanding_read' set to 1 + TORRENT_ASSERT(false); + + int cache_state = (j->flags & disk_io_job::volatile_read) + ? cached_piece_entry::volatile_read_lru + : cached_piece_entry::read_lru1; + pe = m_disk_cache.allocate_piece(j, cache_state); + if (pe == NULL) + { + j->error.ec = error::no_memory; + j->error.operation = storage_error::alloc_cache_piece; + m_disk_cache.free_iovec(iov, iov_len); + return -1; + } +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(piece_log_t::set_outstanding_jobs)); +#endif + pe->outstanding_read = 1; + } + TORRENT_PIECE_ASSERT(pe->outstanding_read == 1, pe); + + l.unlock(); + + // then we'll actually allocate the buffers + int ret = m_disk_cache.allocate_iovec(iov, iov_len); + + if (ret < 0) + { + ret = do_uncached_read(j); + + mutex::scoped_lock l(m_cache_mutex); + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe) maybe_issue_queued_read_jobs(pe, completed_jobs); + return ret; + } + + // this is the offset that's aligned to block boundaries + size_type adjusted_offset = j->d.io.offset & ~(block_size-1); + + // if this is the last piece, adjust the size of the + // last buffer to match up + iov[iov_len-1].iov_len = (std::min)(int(piece_size - adjusted_offset) + - (iov_len-1) * block_size, block_size); + TORRENT_ASSERT(iov[iov_len-1].iov_len > 0); + + // at this point, all the buffers are allocated and iov is initizalied + // and the blocks have their refcounters incremented, so no other thread + // can remove them. We can now release the cache mutex and dive into the + // disk operations. + + ptime start_time = time_now_hires(); + + ret = j->storage->get_storage_impl()->readv(iov, iov_len + , j->piece, adjusted_offset, j->flags, j->error); + + if (!j->error.ec) + { + boost::uint32_t read_time = total_microseconds(time_now_hires() - start_time); + m_read_time.add_sample(read_time / iov_len); + m_cache_stats.cumulative_read_time += read_time / 1000; + m_cache_stats.cumulative_job_time += read_time / 1000; + m_cache_stats.blocks_read += iov_len; + ++m_cache_stats.reads; + } + + l.lock(); + + if (ret < 0) + { + // read failed. free buffers and return error + m_disk_cache.free_iovec(iov, iov_len); + + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe == NULL) + { + // the piece is supposed to be allocated when the + // disk job is allocated + TORRENT_ASSERT(false); + return ret; + } + TORRENT_PIECE_ASSERT(pe->outstanding_read == 1, pe); + + if (pe->read_jobs.size() > 0) + fail_jobs_impl(j->error, pe->read_jobs, completed_jobs); + TORRENT_PIECE_ASSERT(pe->read_jobs.size() == 0, pe); + pe->outstanding_read = 0; +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(piece_log_t::clear_outstanding_jobs)); +#endif + m_disk_cache.maybe_free_piece(pe); + return ret; + } + + int block = j->d.io.offset / block_size; +#if TORRENT_USE_ASSERT + pe->piece_log.push_back(piece_log_t(j->action, block)); +#endif + // as soon we insert the blocks they may be evicted + // (if using purgeable memory). In order to prevent that + // until we can read from them, increment the refcounts + m_disk_cache.insert_blocks(pe, block, iov, iov_len, j, block_cache::blocks_inc_refcount); + + TORRENT_ASSERT(pe->blocks[block].buf); + + int tmp = m_disk_cache.try_read(j, false, true); + TORRENT_ASSERT(tmp >= 0); + + maybe_issue_queued_read_jobs(pe, completed_jobs); + + for (int i = 0; i < iov_len; ++i, ++block) + m_disk_cache.dec_block_refcount(pe, block, block_cache::ref_reading); + + return j->d.io.buffer_size; + } + + void disk_io_thread::maybe_issue_queued_read_jobs(cached_piece_entry* pe, tailqueue& completed_jobs) + { + TORRENT_PIECE_ASSERT(pe->outstanding_read == 1, pe); + + // if we're shutting down, just cancel the jobs + if (m_num_threads == 0) + { + fail_jobs_impl(storage_error(boost::asio::error::operation_aborted), pe->read_jobs, completed_jobs); + TORRENT_PIECE_ASSERT(pe->read_jobs.size() == 0, pe); + pe->outstanding_read = 0; +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(piece_log_t::clear_outstanding_jobs)); +#endif + m_disk_cache.maybe_free_piece(pe); + return; + } + + // while we were reading, there may have been a few jobs + // that got queued up also wanting to read from this piece. + // Any job that is a cache hit now, complete it immediately. + // Then, issue the first non-cache-hit job. Once it complete + // it will keep working off this list + tailqueue stalled_jobs; + pe->read_jobs.swap(stalled_jobs); + + // the next job to issue (i.e. this is a cache-miss) + disk_io_job* next_job = NULL; + + while (stalled_jobs.size() > 0) + { + disk_io_job* j = (disk_io_job*)stalled_jobs.pop_front(); + TORRENT_ASSERT(j->flags & disk_io_job::in_progress); + + int ret = m_disk_cache.try_read(j); + if (ret >= 0) + { + // cache-hit + DLOG("do_read: cache hit\n"); + j->flags |= disk_io_job::cache_hit; + j->ret = ret; + completed_jobs.push_back(j); + } + else if (ret == -2) + { + // error + j->ret = disk_io_job::operation_failed; + completed_jobs.push_back(j); + } + else + { + // cache-miss, issue the first one + // put back the rest + if (next_job == NULL) + { + next_job = j; + } + else + { + TORRENT_PIECE_ASSERT(j->piece == pe->piece, pe); + pe->read_jobs.push_back(j); + } + } + } + + if (next_job) + { + add_job(next_job); + } + else + { + TORRENT_PIECE_ASSERT(pe->read_jobs.size() == 0, pe); + pe->outstanding_read = 0; +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(piece_log_t::clear_outstanding_jobs)); +#endif + m_disk_cache.maybe_free_piece(pe); + } + } + + int disk_io_thread::do_uncached_write(disk_io_job* j) + { + ptime start_time = time_now_hires(); + + file::iovec_t b = { j->buffer, size_t(j->d.io.buffer_size) }; + + ++m_num_writing_threads; + + // the actual write operation + int ret = j->storage->get_storage_impl()->writev(&b, 1 + , j->piece, j->d.io.offset, j->flags, j->error); + + --m_num_writing_threads; + + if (!j->error.ec) + { + boost::uint32_t write_time = total_microseconds(time_now_hires() - start_time); + m_write_time.add_sample(write_time); + m_cache_stats.cumulative_write_time += write_time / 1000; + m_cache_stats.cumulative_job_time += write_time / 1000; + ++m_cache_stats.blocks_written; + ++m_cache_stats.writes; + } + + m_disk_cache.free_buffer(j->buffer); + j->buffer = NULL; + + return ret; + } + + int disk_io_thread::do_write(disk_io_job* j, tailqueue& completed_jobs) + { + INVARIANT_CHECK; + TORRENT_ASSERT(j->d.io.buffer_size <= m_disk_cache.block_size()); + int block_size = m_disk_cache.block_size(); + + // should we put this write job in the cache? + // if we don't use the cache we shouldn't. + if (m_settings.get_bool(settings_pack::use_write_cache) + && m_settings.get_int(settings_pack::cache_size) > 0) + { + mutex::scoped_lock l(m_cache_mutex); + + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe && pe->hashing_done) + { +#if TORRENT_USE_ASSERT + print_piece_log(pe->piece_log); +#endif + TORRENT_ASSERT(pe->blocks[j->d.io.offset / 16 / 1024].buf != j->buffer); + TORRENT_ASSERT(pe->blocks[j->d.io.offset / 16 / 1024].buf != NULL); + j->error.ec = error::operation_aborted; + j->error.operation = storage_error::write; + return -1; + } + + pe = m_disk_cache.add_dirty_block(j); + + if (pe) + { +#if TORRENT_USE_ASSERT + pe->piece_log.push_back(piece_log_t(j->action, j->d.io.offset / 0x4000)); +#endif + + if (!pe->hashing_done + && pe->hash == 0 + && !m_settings.get_bool(settings_pack::disable_hash_checks)) + { + pe->hash = new partial_hash; + m_disk_cache.update_cache_state(pe); + } + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); + ++pe->piece_refcount; + + // see if we can progress the hash cursor with this new block + kick_hasher(pe, l); + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); + + // flushes the piece to disk in case + // it satisfies the condition for a write + // piece to be flushed + try_flush_hashed(pe, m_settings.get_int(settings_pack::write_cache_line_size), completed_jobs, l); + + --pe->piece_refcount; + m_disk_cache.maybe_free_piece(pe); + + return defer_handler; + } + } + + // ok, we should just perform this job right now. + return do_uncached_write(j); + } + + void disk_io_thread::async_read(piece_manager* storage, peer_request const& r + , boost::function const& handler, void* requester + , int flags) + { + INVARIANT_CHECK; + +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + TORRENT_ASSERT(r.length <= m_disk_cache.block_size()); + TORRENT_ASSERT(r.length <= 16 * 1024); + + int block_size = m_disk_cache.block_size(); + DLOG("do_read piece: %d block: %d\n", r.piece, r.start / block_size); + + disk_io_job* j = allocate_job(disk_io_job::read); + j->storage = storage->shared_from_this(); + j->piece = r.piece; + j->d.io.offset = r.start; + j->d.io.buffer_size = r.length; + j->buffer = 0; + j->flags = flags; + j->requester = requester; + j->callback = handler; + + mutex::scoped_lock l(m_cache_mutex); + int ret = prep_read_job_impl(j); + l.unlock(); + + switch (ret) + { + case 0: + if (handler) handler(j); + free_job(j); + break; + case 1: + add_job(j); + break; + } + } + + // this function checks to see if a read job is a cache hit, + // and if it doesn't have a picece allocated, it allocates + // one and it sets outstanding_read flag and possibly queues + // up the job in the piece read job list + // the cache mutex must be held when calling this + // + // returns 0 if the job succeeded immediately + // 1 if it needs to be added to the job queue + // 2 if it was deferred and will be performed later (no need to + // add it to the queue) + int disk_io_thread::prep_read_job_impl(disk_io_job* j, bool check_fence) + { + TORRENT_ASSERT(j->action == disk_io_job::read); + + if (m_settings.get_bool(settings_pack::use_read_cache) + && m_settings.get_int(settings_pack::cache_size) > 0) + { + int ret = m_disk_cache.try_read(j); + if (ret >= 0) + { + DLOG("do_read: cache hit\n"); + j->flags |= disk_io_job::cache_hit; + j->ret = ret; + return 0; + } + else if (ret == -2) + { + j->error.ec = error::no_memory; + j->error.operation = storage_error::alloc_cache_piece; + j->ret = disk_io_job::operation_failed; + return 0; + } + + if (check_fence && j->storage->is_blocked(j)) + { + // this means the job was queued up inside storage + ++m_num_blocked_jobs; + DLOG("blocked job: %s (torrent: %d total: %d)\n" + , job_action_name[j->action], j->storage ? j->storage->num_blocked() : 0 + , int(m_num_blocked_jobs)); + return 2; + } + + cached_piece_entry* pe = m_disk_cache.allocate_piece(j, cached_piece_entry::read_lru1); + if (pe == NULL) + { + j->ret = -1; + j->error.ec = error::no_memory; + j->error.operation = storage_error::read; + return 0; + } + + if (pe->outstanding_read) + { + TORRENT_PIECE_ASSERT(j->piece == pe->piece, pe); + pe->read_jobs.push_back(j); + return 2; + } + +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(piece_log_t::set_outstanding_jobs)); +#endif + pe->outstanding_read = 1; + } + return 1; + } + + void disk_io_thread::async_write(piece_manager* storage, peer_request const& r + , disk_buffer_holder& buffer + , boost::function const& handler + , int flags) + { + INVARIANT_CHECK; + +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + TORRENT_ASSERT(r.length <= m_disk_cache.block_size()); + TORRENT_ASSERT(r.length <= 16 * 1024); + + disk_io_job* j = allocate_job(disk_io_job::write); + j->storage = storage->shared_from_this(); + j->piece = r.piece; + j->d.io.offset = r.start; + j->d.io.buffer_size = r.length; + j->buffer = buffer.get(); + j->callback = handler; + j->flags = flags; + +#if TORRENT_USE_ASSERT + mutex::scoped_lock l3_(m_cache_mutex); + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe) + { + // we should never add a new dirty block to a piece + // whose hash we have calculated. The piece needs + // to be cleared first, (async_clear_piece). + TORRENT_ASSERT(pe->hashing_done == 0); + + TORRENT_ASSERT(pe->blocks[r.start / 0x4000].refcount == 0 || pe->blocks[r.start / 0x4000].buf == NULL); + } + l3_.unlock(); +#endif + +#if TORRENT_USE_ASSERT && defined TORRENT_EXPENSIVE_INVARIANT_CHECKS + mutex::scoped_lock l2_(m_cache_mutex); + std::pair range = m_disk_cache.all_pieces(); + for (block_cache::iterator i = range.first; i != range.second; ++i) + { + cached_piece_entry const& p = *i; + int bs = m_disk_cache.block_size(); + int piece_size = p.storage->files()->piece_size(p.piece); + int blocks_in_piece = (piece_size + bs - 1) / bs; + for (int k = 0; k < blocks_in_piece; ++k) + TORRENT_PIECE_ASSERT(p.blocks[k].buf != j->buffer, &p); + } + l2_.unlock(); +#endif + +#if !defined TORRENT_DISABLE_POOL_ALLOCATOR && TORRENT_USE_ASSERTS + mutex::scoped_lock l_(m_cache_mutex); + TORRENT_ASSERT(m_disk_cache.is_disk_buffer(j->buffer)); + l_.unlock(); +#endif + if (m_settings.get_int(settings_pack::cache_size) > 0 + && m_settings.get_bool(settings_pack::use_write_cache)) + { + int block_size = m_disk_cache.block_size(); + + TORRENT_ASSERT((r.start % block_size) == 0); + + if (storage->is_blocked(j)) + { + // this means the job was queued up inside storage + ++m_num_blocked_jobs; + DLOG("blocked job: %s (torrent: %d total: %d)\n" + , job_action_name[j->action], j->storage ? j->storage->num_blocked() : 0 + , int(m_num_blocked_jobs)); + // make the holder give up ownership of the buffer + // since the job was successfully queued up + buffer.release(); + return; + } + + mutex::scoped_lock l(m_cache_mutex); + // if we succeed in adding the block to the cache, the job will + // be added along with it. we may not free j if so + cached_piece_entry* pe = m_disk_cache.add_dirty_block(j); + + // if the buffer was successfully added to the cache + // our holder should no longer own it + if (pe) buffer.release(); + + if (pe && pe->outstanding_flush == 0) + { + pe->outstanding_flush = 1; + l.unlock(); + + // the block and write job were successfully inserted + // into the cache. Now, see if we should trigger a flush + j = allocate_job(disk_io_job::flush_hashed); + j->storage = storage->shared_from_this(); + j->piece = r.piece; + j->flags = flags; + add_job(j); + } + // if we added the block (regardless of whether we also + // issued a flush job or not), we're done. + if (pe) return; + l.unlock(); + } + + add_job(j); + buffer.release(); + } + + void disk_io_thread::async_hash(piece_manager* storage, int piece, int flags + , boost::function const& handler, void* requester) + { +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + disk_io_job* j = allocate_job(disk_io_job::hash); + j->storage = storage->shared_from_this(); + j->piece = piece; + j->callback = handler; + j->flags = flags; + j->requester = requester; + + int piece_size = storage->files()->piece_size(piece); + + // first check to see if the hashing is already done + mutex::scoped_lock l(m_cache_mutex); + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe && !pe->hashing && pe->hash && pe->hash->offset == piece_size) + { + sha1_hash result = pe->hash->h.final(); + memcpy(j->d.piece_hash, &result[0], 20); + + delete pe->hash; + pe->hash = NULL; + + if (pe->cache_state != cached_piece_entry::volatile_read_lru) + pe->hashing_done = 1; + +#if TORRENT_USE_ASSERTS + ++pe->hash_passes; +#endif + + l.unlock(); + if (handler) handler(j); + free_job(j); + return; + } + + add_job(j); + } + + void disk_io_thread::async_move_storage(piece_manager* storage, std::string const& p, int flags + , boost::function const& handler) + { +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + disk_io_job* j = allocate_job(disk_io_job::move_storage); + j->storage = storage->shared_from_this(); + j->buffer = strdup(p.c_str()); + j->callback = handler; + j->flags = flags; + + add_fence_job(storage, j); + } + + void disk_io_thread::async_release_files(piece_manager* storage + , boost::function const& handler) + { + disk_io_job* j = allocate_job(disk_io_job::release_files); + j->storage = storage->shared_from_this(); + j->callback = handler; + + add_fence_job(storage, j); + } + + void disk_io_thread::async_delete_files(piece_manager* storage + , boost::function const& handler) + { +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + // remove cache blocks belonging to this torrent + tailqueue completed_jobs; + + // remove outstanding jobs belonging to this torrent + mutex::scoped_lock l2(m_job_mutex); + + // TODO: maybe the tailqueue_iterator should contain a pointer-pointer + // instead and have an unlink function + disk_io_job* qj = (disk_io_job*)m_queued_jobs.get_all(); + tailqueue to_abort; + + while (qj) + { + disk_io_job* next = (disk_io_job*)qj->next; +#if TORRENT_USE_ASSERTS + qj->next = NULL; +#endif + if (qj->storage.get() == storage) + to_abort.push_back(qj); + else + m_queued_jobs.push_back(qj); + qj = next; + } + l2.unlock(); + + mutex::scoped_lock l(m_cache_mutex); + flush_cache(storage, flush_delete_cache, completed_jobs, l); + l.unlock(); + + disk_io_job* j = allocate_job(disk_io_job::delete_files); + j->storage = storage->shared_from_this(); + j->callback = handler; + add_fence_job(storage, j); + + fail_jobs_impl(storage_error(boost::asio::error::operation_aborted), to_abort, completed_jobs); + + if (completed_jobs.size()) + add_completed_jobs(completed_jobs); + } + + void disk_io_thread::async_check_fastresume(piece_manager* storage + , lazy_entry const* resume_data + , boost::function const& handler) + { +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + disk_io_job* j = allocate_job(disk_io_job::check_fastresume); + j->storage = storage->shared_from_this(); + j->buffer = (char*)resume_data; + j->callback = handler; + + add_fence_job(storage, j); + } + + void disk_io_thread::async_save_resume_data(piece_manager* storage + , boost::function const& handler) + { +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + disk_io_job* j = allocate_job(disk_io_job::save_resume_data); + j->storage = storage->shared_from_this(); + j->buffer = NULL; + j->callback = handler; + + add_fence_job(storage, j); + } + + void disk_io_thread::async_rename_file(piece_manager* storage, int index, std::string const& name + , boost::function const& handler) + { +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + disk_io_job* j = allocate_job(disk_io_job::rename_file); + j->storage = storage->shared_from_this(); + j->piece = index; + j->buffer = strdup(name.c_str()); + j->callback = handler; + add_fence_job(storage, j); + } + + void disk_io_thread::async_stop_torrent(piece_manager* storage + , boost::function const& handler) + { + // remove outstanding hash jobs belonging to this torrent + mutex::scoped_lock l2(m_job_mutex); + + disk_io_job* qj = (disk_io_job*)m_queued_hash_jobs.get_all(); + tailqueue to_abort; + + while (qj) + { + disk_io_job* next = (disk_io_job*)qj->next; +#if TORRENT_USE_ASSERTS + qj->next = NULL; +#endif + if (qj->storage.get() == storage) + to_abort.push_back(qj); + else + m_queued_hash_jobs.push_back(qj); + qj = next; + } + l2.unlock(); + + disk_io_job* j = allocate_job(disk_io_job::stop_torrent); + j->storage = storage->shared_from_this(); + j->callback = handler; + add_fence_job(storage, j); + + tailqueue completed_jobs; + fail_jobs_impl(storage_error(boost::asio::error::operation_aborted), to_abort, completed_jobs); + if (completed_jobs.size()) + add_completed_jobs(completed_jobs); + } + + void disk_io_thread::async_cache_piece(piece_manager* storage, int piece + , boost::function const& handler) + { +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + disk_io_job* j = allocate_job(disk_io_job::cache_piece); + j->storage = storage->shared_from_this(); + j->piece = piece; + j->callback = handler; + + add_job(j); + } + +#ifndef TORRENT_NO_DEPRECATE + void disk_io_thread::async_finalize_file(piece_manager* storage, int file + , boost::function const& handler) + { +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + disk_io_job* j = allocate_job(disk_io_job::finalize_file); + j->storage = storage->shared_from_this(); + j->piece = file; + j->callback = handler; + + add_job(j); + } +#endif + + void disk_io_thread::async_flush_piece(piece_manager* storage, int piece + , boost::function const& handler) + { +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + disk_io_job* j = allocate_job(disk_io_job::flush_piece); + j->storage = storage->shared_from_this(); + j->piece = piece; + j->callback = handler; + + if (m_num_threads == 0) + { + j->error.ec = asio::error::operation_aborted; + if (handler) handler(j); + free_job(j); + return; + } + + add_job(j); + } + + void disk_io_thread::async_set_file_priority(piece_manager* storage + , std::vector const& prios + , boost::function const& handler) + { +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + std::vector* p = new std::vector(prios); + + disk_io_job* j = allocate_job(disk_io_job::file_priority); + j->storage = storage->shared_from_this(); + j->buffer = (char*)p; + j->callback = handler; + + add_fence_job(storage, j); + } + + void disk_io_thread::async_load_torrent(add_torrent_params* params + , boost::function const& handler) + { + disk_io_job* j = allocate_job(disk_io_job::load_torrent); + j->requester = (char*)params; + j->callback = handler; + + add_job(j); + } + + void disk_io_thread::async_tick_torrent(piece_manager* storage + , boost::function const& handler) + { + disk_io_job* j = allocate_job(disk_io_job::tick_storage); + j->storage = storage->shared_from_this(); + j->callback = handler; + + add_job(j); + } + + void disk_io_thread::clear_read_cache(piece_manager* storage) + { + mutex::scoped_lock l(m_cache_mutex); + + tailqueue jobs; + boost::unordered_set const& cache = storage->cached_pieces(); + for (boost::unordered_set::const_iterator i = cache.begin() + , end(cache.end()); i != end; ++i) + { + tailqueue temp; + m_disk_cache.evict_piece(*(i++), temp); + jobs.append(temp); + } + fail_jobs(storage_error(boost::asio::error::operation_aborted), jobs); + } + + void disk_io_thread::async_clear_piece(piece_manager* storage, int index + , boost::function const& handler) + { +#ifdef TORRENT_DEBUG + // the caller must increment the torrent refcount before + // issuing an async disk request + storage->assert_torrent_refcount(); +#endif + + disk_io_job* j = allocate_job(disk_io_job::clear_piece); + j->storage = storage->shared_from_this(); + j->piece = index; + j->callback = handler; + + // regular jobs are not guaranteed to be executed in-order + // since clear piece must guarantee that all write jobs that + // have been issued finish before the clear piece job completes + + // TODO: this is potentially very expensive. One way to solve + // it would be to have a fence for just this one piece. + add_fence_job(storage, j); + } + + void disk_io_thread::clear_piece(piece_manager* storage, int index) + { + mutex::scoped_lock l(m_cache_mutex); + + cached_piece_entry* pe = m_disk_cache.find_piece(storage, index); + if (pe == 0) return; + TORRENT_PIECE_ASSERT(pe->hashing == false, pe); + pe->hashing_done = 0; + delete pe->hash; + pe->hash = NULL; + + // evict_piece returns true if the piece was in fact + // evicted. A piece may fail to be evicted if there + // are still outstanding operations on it, which should + // never be the case when this function is used + // in fact, no jobs should really be hung on this piece + // at this point + tailqueue jobs; + bool ok = m_disk_cache.evict_piece(pe, jobs); + TORRENT_PIECE_ASSERT(ok, pe); + fail_jobs(storage_error(boost::asio::error::operation_aborted), jobs); + } + + void disk_io_thread::kick_hasher(cached_piece_entry* pe, mutex::scoped_lock& l) + { + if (!pe->hash) return; + if (pe->hashing) return; + + int piece_size = pe->storage.get()->files()->piece_size(pe->piece); + partial_hash* ph = pe->hash; + + // are we already done? + if (ph->offset >= piece_size) return; + + int block_size = m_disk_cache.block_size(); + int cursor = ph->offset / block_size; + int end = cursor; + TORRENT_PIECE_ASSERT(ph->offset % block_size == 0, pe); + + for (int i = cursor; i < pe->blocks_in_piece; ++i) + { + cached_block_entry& bl = pe->blocks[i]; + if (bl.buf == 0) break; + + // if we fail to lock the block, it' no longer in the cache + if (m_disk_cache.inc_block_refcount(pe, i, block_cache::ref_hashing) == false) + break; + + ++end; + } + + // no blocks to hash? + if (end == cursor) return; + + pe->hashing = 1; + + DLOG("kick_hasher: %d - %d (piece: %d offset: %d)\n" + , cursor, end, int(pe->piece), ph->offset); + + l.unlock(); + + ptime start_time = time_now_hires(); + + for (int i = cursor; i < end; ++i) + { + cached_block_entry& bl = pe->blocks[i]; + int size = (std::min)(block_size, piece_size - ph->offset); + ph->h.update(bl.buf, size); + ph->offset += size; + } + + boost::uint64_t hash_time = total_microseconds(time_now_hires() - start_time); + + l.lock(); + + TORRENT_PIECE_ASSERT(pe->hashing, pe); + TORRENT_PIECE_ASSERT(pe->hash, pe); + + m_hash_time.add_sample(hash_time / (end - cursor)); + m_cache_stats.cumulative_hash_time += hash_time / 1000; + m_cache_stats.cumulative_job_time += hash_time / 1000; + + pe->hashing = 0; + + // decrement the block refcounters + for (int i = cursor; i < end; ++i) + m_disk_cache.dec_block_refcount(pe, i, block_cache::ref_hashing); + + // did we complete the hash? + if (pe->hash->offset != piece_size) return; + + // if there are any hash-jobs hanging off of this piece + // we should post them now + disk_io_job* j = (disk_io_job*)pe->jobs.get_all(); + tailqueue hash_jobs; + while (j) + { + TORRENT_PIECE_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage, pe); + disk_io_job* next = (disk_io_job*)j->next; + j->next = NULL; + TORRENT_PIECE_ASSERT(j->piece == pe->piece, pe); + if (j->action == disk_io_job::hash) hash_jobs.push_back(j); + else pe->jobs.push_back(j); + j = next; + } + if (hash_jobs.size()) + { + sha1_hash result = pe->hash->h.final(); + + for (tailqueue_iterator i = hash_jobs.iterate(); i.get(); i.next()) + { + disk_io_job* j = (disk_io_job*)i.get(); + memcpy(j->d.piece_hash, &result[0], 20); + j->ret = 0; + } + + delete pe->hash; + pe->hash = NULL; + if (pe->cache_state != cached_piece_entry::volatile_read_lru) + pe->hashing_done = 1; +#if TORRENT_USE_ASSERTS + ++pe->hash_passes; +#endif + add_completed_jobs(hash_jobs); + } + } + + int disk_io_thread::do_uncached_hash(disk_io_job* j) + { + // we're not using a cache. This is the simple path + // just read straight from the file TORRENT_ASSERT(m_magic == 0x1337); + int piece_size = j->storage->files()->piece_size(j->piece); + int block_size = m_disk_cache.block_size(); + int blocks_in_piece = (piece_size + block_size - 1) / block_size; + int file_flags = file_flags_for_job(j); + + file::iovec_t iov; + iov.iov_base = m_disk_cache.allocate_buffer("hashing"); + hasher h; + int ret = 0; + int offset = 0; + for (int i = 0; i < blocks_in_piece; ++i) + { + DLOG("do_hash: (uncached) reading (piece: %d block: %d)\n" + , int(j->piece), i); + + ptime start_time = time_now_hires(); + + iov.iov_len = (std::min)(block_size, piece_size - offset); + ret = j->storage->get_storage_impl()->readv(&iov, 1, j->piece + , offset, file_flags, j->error); + if (ret < 0) break; + + if (!j->error.ec) + { + boost::uint32_t read_time = total_microseconds(time_now_hires() - start_time); + m_read_time.add_sample(read_time); + m_cache_stats.cumulative_read_time += read_time / 1000; + m_cache_stats.cumulative_job_time += read_time / 1000; + ++m_cache_stats.blocks_read; + ++m_cache_stats.reads; + } + + offset += block_size; + h.update((char const*)iov.iov_base, iov.iov_len); + } + + m_disk_cache.free_buffer((char*)iov.iov_base); + + sha1_hash piece_hash = h.final(); + memcpy(j->d.piece_hash, &piece_hash[0], 20); + return ret >= 0 ? 0 : -1; + } + + int disk_io_thread::do_hash(disk_io_job* j, tailqueue& completed_jobs) + { + INVARIANT_CHECK; + + if (m_settings.get_int(settings_pack::cache_size) == 0) + return do_uncached_hash(j); + + int piece_size = j->storage->files()->piece_size(j->piece); + int file_flags = file_flags_for_job(j); + + mutex::scoped_lock l(m_cache_mutex); + + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe) + { +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(j->action)); +#endif + m_disk_cache.cache_hit(pe, j->requester, j->flags & disk_io_job::volatile_read); + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); + ++pe->piece_refcount; + kick_hasher(pe, l); + --pe->piece_refcount; + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); + + // are we already done hashing? + if (pe->hash && !pe->hashing && pe->hash->offset == piece_size) + { + DLOG("do_hash: (%d) (already done)\n", int(pe->piece)); + sha1_hash piece_hash = pe->hash->h.final(); + memcpy(j->d.piece_hash, &piece_hash[0], 20); + delete pe->hash; + pe->hash = NULL; + if (pe->cache_state != cached_piece_entry::volatile_read_lru) + pe->hashing_done = 1; +#if TORRENT_USE_ASSERTS + ++pe->hash_passes; +#endif + m_disk_cache.update_cache_state(pe); + m_disk_cache.maybe_free_piece(pe); + return 0; + } + } + + if (pe == NULL && !m_settings.get_bool(settings_pack::use_read_cache)) + { + l.unlock(); + // if there's no piece in the cache, and the read cache is disabled + // it means it's already been flushed to disk, and there's no point + // in reading it into the cache, since we're not using read cache + // so just use the uncached version + return do_uncached_hash(j); + } + + if (pe == NULL) + { + int cache_state = (j->flags & disk_io_job::volatile_read) + ? cached_piece_entry::volatile_read_lru + : cached_piece_entry::read_lru1; + pe = m_disk_cache.allocate_piece(j, cache_state); + } + if (pe == NULL) + { + j->error.ec = error::no_memory; + j->error.operation = storage_error::alloc_cache_piece; + return -1; + } + + if (pe->hashing) + { + TORRENT_PIECE_ASSERT(pe->hash, pe); + // another thread is hashing this piece right now + // try again in a little bit + DLOG("do_hash: retry\n"); + // TODO: we should probably just hang the job on the piece and make sure the hasher gets kicked + return retry_job; + } + + pe->hashing = 1; + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); + ++pe->piece_refcount; + + if (pe->hash == NULL) + { + pe->hashing_done = 0; + pe->hash = new partial_hash; + } + partial_hash* ph = pe->hash; + + int block_size = m_disk_cache.block_size(); + int blocks_in_piece = (piece_size + block_size - 1) / block_size; + + file::iovec_t iov; + int ret = 0; + + // keep track of which blocks we have locked by incrementing + // their refcounts. This is used to decrement only these blocks + // later. + int* locked_blocks = TORRENT_ALLOCA(int, blocks_in_piece); + memset(locked_blocks, 0, blocks_in_piece * sizeof(int)); + int num_locked_blocks = 0; + + // increment the refcounts of all + // blocks up front, and then hash them without holding the lock + TORRENT_PIECE_ASSERT(ph->offset % block_size == 0, pe); + for (int i = ph->offset / block_size; i < blocks_in_piece; ++i) + { + iov.iov_len = (std::min)(block_size, piece_size - ph->offset); + + // is the block not in the cache? + if (pe->blocks[i].buf == NULL) continue; + + // if we fail to lock the block, it' no longer in the cache + if (m_disk_cache.inc_block_refcount(pe, i, block_cache::ref_hashing) == false) + continue; + + locked_blocks[num_locked_blocks++] = i; + } + + l.unlock(); + + int next_locked_block = 0; + for (int i = ph->offset / block_size; i < blocks_in_piece; ++i) + { + iov.iov_len = (std::min)(block_size, piece_size - ph->offset); + + if (next_locked_block < num_locked_blocks + && locked_blocks[next_locked_block] == i) + { + ++next_locked_block; + TORRENT_PIECE_ASSERT(pe->blocks[i].buf, pe); + TORRENT_PIECE_ASSERT(ph->offset == i * block_size, pe); + ph->offset += iov.iov_len; + ph->h.update(pe->blocks[i].buf, iov.iov_len); + } + else + { + iov.iov_base = m_disk_cache.allocate_buffer("hashing"); + + if (iov.iov_base == NULL) + { + l.lock(); + // TODO: introduce a holder class that automatically increments + // and decrements the piece_refcount + + // decrement the refcounts of the blocks we just hashed + for (int i = 0; i < num_locked_blocks; ++i) + m_disk_cache.dec_block_refcount(pe, locked_blocks[i], block_cache::ref_hashing); + + --pe->piece_refcount; + pe->hashing = false; + delete pe->hash; + pe->hash = NULL; + + m_disk_cache.maybe_free_piece(pe); + + j->error.ec = errors::no_memory; + j->error.operation = storage_error::alloc_cache_piece; + return -1; + } + + DLOG("do_hash: reading (piece: %d block: %d)\n", int(pe->piece), i); + + ptime start_time = time_now_hires(); + + TORRENT_PIECE_ASSERT(ph->offset == i * block_size, pe); + ret = j->storage->get_storage_impl()->readv(&iov, 1, j->piece + , ph->offset, file_flags, j->error); + + if (ret < 0) + { + m_disk_cache.free_buffer((char*)iov.iov_base); + l.lock(); + break; + } + + if (!j->error.ec) + { + boost::uint32_t read_time = total_microseconds(time_now_hires() - start_time); + m_read_time.add_sample(read_time); + m_cache_stats.cumulative_read_time += read_time / 1000; + m_cache_stats.cumulative_job_time += read_time / 1000; + ++m_cache_stats.total_read_back; + ++m_cache_stats.blocks_read; + ++m_cache_stats.reads; + } + + TORRENT_PIECE_ASSERT(ph->offset == i * block_size, pe); + ph->offset += iov.iov_len; + ph->h.update((char const*)iov.iov_base, iov.iov_len); + + l.lock(); + m_disk_cache.insert_blocks(pe, i, &iov, 1, j); + l.unlock(); + } + } + + l.lock(); + + // decrement the refcounts of the blocks we just hashed + for (int i = 0; i < num_locked_blocks; ++i) + m_disk_cache.dec_block_refcount(pe, locked_blocks[i], block_cache::ref_hashing); + + --pe->piece_refcount; + + pe->hashing = 0; + + if (ret >= 0) + { + sha1_hash piece_hash = ph->h.final(); + memcpy(j->d.piece_hash, &piece_hash[0], 20); + + delete pe->hash; + pe->hash = NULL; + if (pe->cache_state != cached_piece_entry::volatile_read_lru) + pe->hashing_done = 1; +#if TORRENT_USE_ASSERTS + ++pe->hash_passes; +#endif + m_disk_cache.update_cache_state(pe); + } + + m_disk_cache.maybe_free_piece(pe); + + return ret < 0 ? ret : 0; + } + + int disk_io_thread::do_move_storage(disk_io_job* j, tailqueue& completed_jobs) + { + // if this assert fails, something's wrong with the fence logic + TORRENT_ASSERT(j->storage->num_outstanding_jobs() == 1); + + // if files have to be closed, that's the storage's responsibility + return j->storage->get_storage_impl()->move_storage(j->buffer, j->flags, j->error); + } + + int disk_io_thread::do_release_files(disk_io_job* j, tailqueue& completed_jobs) + { + INVARIANT_CHECK; + + // if this assert fails, something's wrong with the fence logic + TORRENT_ASSERT(j->storage->num_outstanding_jobs() == 1); + + mutex::scoped_lock l(m_cache_mutex); + flush_cache(j->storage.get(), flush_write_cache, completed_jobs, l); + l.unlock(); + + j->storage->get_storage_impl()->release_files(j->error); + return j->error ? -1 : 0; + } + + int disk_io_thread::do_delete_files(disk_io_job* j, tailqueue& completed_jobs) + { + TORRENT_ASSERT(j->buffer == 0); + INVARIANT_CHECK; + + // if this assert fails, something's wrong with the fence logic + TORRENT_ASSERT(j->storage->num_outstanding_jobs() == 1); + + mutex::scoped_lock l(m_cache_mutex); +#if TORRENT_USE_ASSERTS + m_disk_cache.mark_deleted(*j->storage->files()); +#endif + + flush_cache(j->storage.get(), flush_delete_cache | flush_expect_clear, completed_jobs, l); + l.unlock(); + + j->storage->get_storage_impl()->delete_files(j->error); + return j->error ? -1 : 0; + } + + int disk_io_thread::do_check_fastresume(disk_io_job* j, tailqueue& completed_jobs) + { + // if this assert fails, something's wrong with the fence logic + TORRENT_ASSERT(j->storage->num_outstanding_jobs() == 1); + + lazy_entry const* rd = (lazy_entry const*)j->buffer; + lazy_entry tmp; + if (rd == NULL) rd = &tmp; + + return j->storage->check_fastresume(*rd, j->error); + } + + int disk_io_thread::do_save_resume_data(disk_io_job* j, tailqueue& completed_jobs) + { + // if this assert fails, something's wrong with the fence logic + TORRENT_ASSERT(j->storage->num_outstanding_jobs() == 1); + + mutex::scoped_lock l(m_cache_mutex); + flush_cache(j->storage.get(), flush_write_cache, completed_jobs, l); + l.unlock(); + + entry* resume_data = new entry(entry::dictionary_t); + j->storage->get_storage_impl()->write_resume_data(*resume_data, j->error); + TORRENT_ASSERT(j->buffer == 0); + j->buffer = (char*)resume_data; + return j->error ? -1 : 0; + } + + int disk_io_thread::do_rename_file(disk_io_job* j, tailqueue& completed_jobs) + { + // if this assert fails, something's wrong with the fence logic + TORRENT_ASSERT(j->storage->num_outstanding_jobs() == 1); + + // if files need to be closed, that's the storage's responsibility + j->storage->get_storage_impl()->rename_file(j->piece, j->buffer, j->error); + return j->error ? -1 : 0; + } + + int disk_io_thread::do_stop_torrent(disk_io_job* j, tailqueue& completed_jobs) + { + // if this assert fails, something's wrong with the fence logic + TORRENT_ASSERT(j->storage->num_outstanding_jobs() == 1); + + // issue write commands for all dirty blocks + // and clear all read jobs + mutex::scoped_lock l(m_cache_mutex); + flush_cache(j->storage.get(), flush_read_cache | flush_write_cache, completed_jobs, l); + l.unlock(); + + m_disk_cache.release_memory(); + + return 0; + } + + int disk_io_thread::do_cache_piece(disk_io_job* j, tailqueue& completed_jobs) + { + INVARIANT_CHECK; + TORRENT_ASSERT(j->buffer == 0); + + if (m_settings.get_int(settings_pack::cache_size) == 0 + || m_settings.get_bool(settings_pack::use_read_cache) == false) + return 0; + + int file_flags = file_flags_for_job(j); + + mutex::scoped_lock l(m_cache_mutex); + + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe == NULL) + { + int cache_state = (j->flags & disk_io_job::volatile_read) + ? cached_piece_entry::volatile_read_lru + : cached_piece_entry::read_lru1; + pe = m_disk_cache.allocate_piece(j, cache_state); + } + if (pe == NULL) + { + j->error.ec = error::no_memory; + j->error.operation = storage_error::alloc_cache_piece; + return -1; + } + +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(j->action)); +#endif + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); + ++pe->piece_refcount; + + int block_size = m_disk_cache.block_size(); + int piece_size = j->storage->files()->piece_size(j->piece); + int blocks_in_piece = (piece_size + block_size - 1) / block_size; + + file::iovec_t iov; + int ret = 0; + int offset = 0; + + // TODO: it would be nice to not have to lock the mutex every + // turn through this loop + for (int i = 0; i < blocks_in_piece; ++i) + { + iov.iov_len = (std::min)(block_size, piece_size - offset); + + // is the block already in the cache? + if (pe->blocks[i].buf) continue; + l.unlock(); + + iov.iov_base = m_disk_cache.allocate_buffer("read cache"); + + if (iov.iov_base == NULL) + { + //#error introduce a holder class that automatically increments and decrements the piece_refcount + --pe->piece_refcount; + m_disk_cache.maybe_free_piece(pe); + j->error.ec = errors::no_memory; + j->error.operation = storage_error::alloc_cache_piece; + return -1; + } + + DLOG("do_cache_piece: reading (piece: %d block: %d)\n" + , int(pe->piece), i); + + ptime start_time = time_now_hires(); + + ret = j->storage->get_storage_impl()->readv(&iov, 1, j->piece + , offset, file_flags, j->error); + + if (ret < 0) + { + l.lock(); + break; + } + + if (!j->error.ec) + { + boost::uint32_t read_time = total_microseconds(time_now_hires() - start_time); + m_read_time.add_sample(read_time); + m_cache_stats.cumulative_read_time += read_time / 1000; + m_cache_stats.cumulative_job_time += read_time / 1000; + ++m_cache_stats.blocks_read; + ++m_cache_stats.reads; + } + + offset += block_size; + + l.lock(); + m_disk_cache.insert_blocks(pe, i, &iov, 1, j); + } + + --pe->piece_refcount; + m_disk_cache.maybe_free_piece(pe); + return 0; + } + +#ifndef TORRENT_NO_DEPRECATE + int disk_io_thread::do_finalize_file(disk_io_job* j, tailqueue& completed_jobs) + { + j->storage->get_storage_impl()->finalize_file(j->piece, j->error); + return j->error ? -1 : 0; + } +#endif + + void disk_io_thread::flip_stats() + { // calling mean() will actually reset the accumulators - m_cache_stats.average_queue_time = m_queue_time.mean(); m_cache_stats.average_read_time = m_read_time.mean(); m_cache_stats.average_write_time = m_write_time.mean(); m_cache_stats.average_hash_time = m_hash_time.mean(); m_cache_stats.average_job_time = m_job_time.mean(); - m_cache_stats.average_sort_time = m_sort_time.mean(); - - m_last_stats_flip = now; + m_last_stats_flip = time_now(); } - void disk_io_thread::get_cache_info(sha1_hash const& ih, std::vector& ret) const + void get_cache_info_impl(cached_piece_info& info, cached_piece_entry const* i, int block_size) { - TORRENT_ASSERT(m_magic == 0x1337); - - mutex::scoped_lock l(m_piece_mutex); - ret.clear(); - ret.reserve(m_pieces.size()); - for (cache_t::const_iterator i = m_pieces.begin() - , end(m_pieces.end()); i != end; ++i) - { - torrent_info const& ti = *i->storage->info(); - if (ti.info_hash() != ih) continue; - cached_piece_info info; - info.next_to_hash = i->next_block_to_hash; - info.piece = i->piece; - info.last_use = i->expire; - info.kind = cached_piece_info::write_cache; - int blocks_in_piece = (ti.piece_size(i->piece) + (m_block_size) - 1) / m_block_size; - info.blocks.resize(blocks_in_piece); - for (int b = 0; b < blocks_in_piece; ++b) - if (i->blocks[b].buf) info.blocks[b] = true; - ret.push_back(info); - } - for (cache_t::const_iterator i = m_read_pieces.begin() - , end(m_read_pieces.end()); i != end; ++i) - { - torrent_info const& ti = *i->storage->info(); - if (ti.info_hash() != ih) continue; - cached_piece_info info; - info.next_to_hash = i->next_block_to_hash; - info.piece = i->piece; - info.last_use = i->expire; - info.kind = cached_piece_info::read_cache; - int blocks_in_piece = (ti.piece_size(i->piece) + (m_block_size) - 1) / m_block_size; - info.blocks.resize(blocks_in_piece); - for (int b = 0; b < blocks_in_piece; ++b) - if (i->blocks[b].buf) info.blocks[b] = true; - ret.push_back(info); - } - } - - cache_status disk_io_thread::status() const - { - mutex::scoped_lock l(m_piece_mutex); - m_cache_stats.total_used_buffers = in_use(); - m_cache_stats.queued_bytes = m_queue_buffer_size; - - cache_status ret = m_cache_stats; - - ret.job_queue_length = m_jobs.size() + m_sorted_read_jobs.size(); - ret.read_queue_size = m_sorted_read_jobs.size(); - - return ret; + info.piece = i->piece; + info.storage = i->storage.get(); + info.last_use = i->expire; + info.need_readback = i->need_readback; + info.next_to_hash = i->hash == 0 ? -1 : (i->hash->offset + block_size - 1) / block_size; + info.kind = i->cache_state == cached_piece_entry::write_lru + ? cached_piece_info::write_cache + : i->cache_state == cached_piece_entry::volatile_read_lru + ? cached_piece_info::volatile_read_cache + : cached_piece_info::read_cache; + int blocks_in_piece = i->blocks_in_piece; + info.blocks.resize(blocks_in_piece); + for (int b = 0; b < blocks_in_piece; ++b) + info.blocks[b] = i->blocks[b].buf != 0; } - // aborts read operations - void disk_io_thread::stop(boost::intrusive_ptr s) + void disk_io_thread::update_stats_counters(counters& c) const { - mutex::scoped_lock l(m_queue_mutex); - // read jobs are aborted, write and move jobs are syncronized - for (std::deque::iterator i = m_jobs.begin(); - i != m_jobs.end();) + // These are atomic_counts, so it's safe to access them from + // a different thread + c.set_value(counters::disk_read_time, m_cache_stats.cumulative_read_time); + c.set_value(counters::disk_write_time, m_cache_stats.cumulative_write_time); + c.set_value(counters::disk_hash_time, m_cache_stats.cumulative_hash_time); + c.set_value(counters::disk_job_time, m_cache_stats.cumulative_job_time); + + c.set_value(counters::num_writing_threads, m_num_writing_threads); + c.set_value(counters::num_running_threads, m_num_running_threads); + c.set_value(counters::blocked_disk_jobs, m_num_blocked_jobs); + + // counters + c.set_value(counters::num_blocks_written, m_cache_stats.blocks_written); + c.set_value(counters::num_blocks_read, m_cache_stats.blocks_read); + c.set_value(counters::num_write_ops, m_cache_stats.writes); + c.set_value(counters::num_read_ops, m_cache_stats.reads); + + mutex::scoped_lock jl(m_job_mutex); + + c.set_value(counters::queued_disk_jobs, m_num_blocked_jobs + + m_queued_jobs.size() + m_queued_hash_jobs.size()); + c.set_value(counters::num_read_jobs, read_jobs_in_use()); + c.set_value(counters::num_write_jobs, write_jobs_in_use()); + c.set_value(counters::num_jobs, jobs_in_use()); + + jl.unlock(); + + mutex::scoped_lock l(m_cache_mutex); + + // gauges + c.set_value(counters::disk_blocks_in_use, m_disk_cache.in_use()); + + m_disk_cache.update_stats_counters(c); + } + + void disk_io_thread::get_cache_info(cache_status* ret, bool no_pieces + , piece_manager const* storage) const + { + mutex::scoped_lock jl(m_job_mutex); + ret->queued_jobs = m_queued_jobs.size() + m_queued_hash_jobs.size(); + jl.unlock(); + + mutex::scoped_lock l(m_cache_mutex); + *ret = m_cache_stats; + ret->total_used_buffers = m_disk_cache.in_use(); + ret->blocked_jobs = m_num_blocked_jobs; + + ret->pending_jobs = m_outstanding_jobs; + ret->num_jobs = jobs_in_use(); + ret->num_read_jobs = read_jobs_in_use(); + ret->num_write_jobs = write_jobs_in_use(); + ret->num_writing_threads = m_num_writing_threads; + + m_disk_cache.get_stats(ret); + + ret->pieces.clear(); + + if (no_pieces) return; + + int block_size = m_disk_cache.block_size(); + + if (storage) { - if (i->storage != s) + ret->pieces.reserve(storage->num_pieces()); + + for (boost::unordered_set::iterator i + = storage->cached_pieces().begin(), end(storage->cached_pieces().end()); + i != end; ++i) { - ++i; - continue; - } - if (should_cancel_on_abort(*i)) - { - if (i->action == disk_io_job::write) - { - TORRENT_ASSERT(m_queue_buffer_size >= i->buffer_size); - m_queue_buffer_size -= i->buffer_size; - } - post_callback(*i, -3); - i = m_jobs.erase(i); - continue; - } - ++i; - } - disk_io_job j; - j.action = disk_io_job::abort_torrent; - j.storage = s; - add_job(j, l); - } + TORRENT_ASSERT((*i)->storage.get() == storage); - struct update_last_use - { - update_last_use(int exp): expire(exp) {} - void operator()(disk_io_thread::cached_piece_entry& p) - { - TORRENT_ASSERT(p.storage); - p.expire = time_now() + seconds(expire); - } - int expire; - }; - - disk_io_thread::cache_piece_index_t::iterator disk_io_thread::find_cached_piece( - disk_io_thread::cache_t& cache - , disk_io_job const& j, mutex::scoped_lock& l) - { - cache_piece_index_t& idx = cache.get<0>(); - cache_piece_index_t::iterator i - = idx.find(std::pair(j.storage.get(), j.piece)); - TORRENT_ASSERT(i == idx.end() || (i->storage == j.storage && i->piece == j.piece)); - return i; - } - - void disk_io_thread::flush_expired_pieces() - { - ptime now = time_now(); - - mutex::scoped_lock l(m_piece_mutex); - - INVARIANT_CHECK; - // flush write cache - cache_lru_index_t& widx = m_pieces.get<1>(); - cache_lru_index_t::iterator i = widx.begin(); - time_duration cut_off = seconds(m_settings.cache_expiry); - while (i != widx.end() && now - i->expire > cut_off) - { - TORRENT_ASSERT(i->storage); - flush_range(const_cast(*i), 0, INT_MAX, l); - TORRENT_ASSERT(i->num_blocks == 0); - - // we want to keep the piece in here to have an accurate - // number for next_block_to_hash, if we're in avoid_readback mode - - bool erase = m_settings.disk_cache_algorithm != session_settings::avoid_readback; - if (!erase) - { - // however, if we've already hashed the whole piece, in-order - // there's no need to keep it around - int piece_size = i->storage->info()->piece_size(i->piece); - int blocks_in_piece = (piece_size + m_block_size - 1) / m_block_size; - erase = i->next_block_to_hash == blocks_in_piece; - } - - if (erase) widx.erase(i++); - else ++i; - } - - if (m_settings.explicit_read_cache) return; - - // flush read cache - std::vector bufs; - cache_lru_index_t& ridx = m_read_pieces.get<1>(); - i = ridx.begin(); - while (i != ridx.end() && now - i->expire > cut_off) - { - drain_piece_bufs(const_cast(*i), bufs, l); - ridx.erase(i++); - } - if (!bufs.empty()) free_multiple_buffers(&bufs[0], bufs.size()); - } - - int disk_io_thread::drain_piece_bufs(cached_piece_entry& p, std::vector& buf - , mutex::scoped_lock& l) - { - int piece_size = p.storage->info()->piece_size(p.piece); - int blocks_in_piece = (piece_size + m_block_size - 1) / m_block_size; - int ret = 0; - - for (int i = 0; i < blocks_in_piece; ++i) - { - if (p.blocks[i].buf == 0) continue; - buf.push_back(p.blocks[i].buf); - ++ret; - p.blocks[i].buf = 0; - --p.num_blocks; - --m_cache_stats.cache_size; - --m_cache_stats.read_cache_size; - } - return ret; - } - - // returns the number of blocks that were freed - int disk_io_thread::free_piece(cached_piece_entry& p, mutex::scoped_lock& l) - { - int piece_size = p.storage->info()->piece_size(p.piece); - int blocks_in_piece = (piece_size + m_block_size - 1) / m_block_size; - int ret = 0; - - // build a vector of all the buffers we need to free - // and free them all in one go - std::vector buffers; - for (int i = 0; i < blocks_in_piece; ++i) - { - if (p.blocks[i].buf == 0) continue; - buffers.push_back(p.blocks[i].buf); - ++ret; - p.blocks[i].buf = 0; - --p.num_blocks; - --m_cache_stats.cache_size; - --m_cache_stats.read_cache_size; - } - if (!buffers.empty()) free_multiple_buffers(&buffers[0], buffers.size()); - return ret; - } - - // returns the number of blocks that were freed - int disk_io_thread::clear_oldest_read_piece( - int num_blocks, ignore_t ignore, mutex::scoped_lock& l) - { - INVARIANT_CHECK; - - cache_lru_index_t& idx = m_read_pieces.get<1>(); - if (idx.empty()) return 0; - - cache_lru_index_t::iterator i = idx.begin(); - if (i->piece == ignore.piece && i->storage == ignore.storage) - { - ++i; - if (i == idx.end()) return 0; - } - - // don't replace an entry that hasn't expired yet - if (time_now() < i->expire) return 0; - int blocks = 0; - - // build a vector of all the buffers we need to free - // and free them all in one go - std::vector buffers; - if (num_blocks >= i->num_blocks) - { - blocks = drain_piece_bufs(const_cast(*i), buffers, l); - } - else - { - // delete blocks from the start and from the end - // until num_blocks have been freed - int end = (i->storage->info()->piece_size(i->piece) + m_block_size - 1) / m_block_size - 1; - int start = 0; - - while (num_blocks) - { - // if we have a volatile read cache, only clear - // from the end, since we're already clearing - // from the start as blocks are read - if (!m_settings.volatile_read_cache) - { - while (i->blocks[start].buf == 0 && start <= end) ++start; - if (start > end) break; - buffers.push_back(i->blocks[start].buf); - i->blocks[start].buf = 0; - ++blocks; - --const_cast(*i).num_blocks; - --m_cache_stats.cache_size; - --m_cache_stats.read_cache_size; - --num_blocks; - if (!num_blocks) break; - } - - while (i->blocks[end].buf == 0 && start <= end) --end; - if (start > end) break; - buffers.push_back(i->blocks[end].buf); - i->blocks[end].buf = 0; - ++blocks; - --const_cast(*i).num_blocks; - --m_cache_stats.cache_size; - --m_cache_stats.read_cache_size; - --num_blocks; - } - } - if (i->num_blocks == 0) idx.erase(i); - - if (!buffers.empty()) free_multiple_buffers(&buffers[0], buffers.size()); - return blocks; - } - - int contiguous_blocks(disk_io_thread::cached_piece_entry const& b) - { - int ret = 0; - int current = 0; - int blocks_in_piece = (b.storage->info()->piece_size(b.piece) + 16 * 1024 - 1) / (16 * 1024); - for (int i = 0; i < blocks_in_piece; ++i) - { - if (b.blocks[i].buf) ++current; - else - { - if (current > ret) ret = current; - current = 0; - } - } - if (current > ret) ret = current; - return ret; - } - - int disk_io_thread::flush_contiguous_blocks(cached_piece_entry& p - , mutex::scoped_lock& l, int lower_limit, bool avoid_readback) - { - // first find the largest range of contiguous blocks - int len = 0; - int current = 0; - int pos = 0; - int start = 0; - int blocks_in_piece = (p.storage->info()->piece_size(p.piece) - + m_block_size - 1) / m_block_size; - - if (avoid_readback) - { - start = p.next_block_to_hash; - for (int i = p.next_block_to_hash; i < blocks_in_piece; ++i) - { - if (p.blocks[i].buf) ++current; - else break; + if ((*i)->cache_state == cached_piece_entry::read_lru2_ghost + || (*i)->cache_state == cached_piece_entry::read_lru1_ghost) + continue; + ret->pieces.push_back(cached_piece_info()); + get_cache_info_impl(ret->pieces.back(), *i, block_size); } } else { - for (int i = 0; i < blocks_in_piece; ++i) + ret->pieces.reserve(m_disk_cache.num_pieces()); + + std::pair range + = m_disk_cache.all_pieces(); + + for (block_cache::iterator i = range.first; i != range.second; ++i) { - if (p.blocks[i].buf) ++current; - else - { - if (current > len) - { - len = current; - pos = start; - } - current = 0; - start = i + 1; - } + if (i->cache_state == cached_piece_entry::read_lru2_ghost + || i->cache_state == cached_piece_entry::read_lru1_ghost) + continue; + ret->pieces.push_back(cached_piece_info()); + get_cache_info_impl(ret->pieces.back(), &*i, block_size); } } - if (current > len) - { - len = current; - pos = start; - } - - if (len < lower_limit || len <= 0) return 0; - len = flush_range(p, pos, pos + len, l); - return len; } - bool cmp_contiguous(disk_io_thread::cached_piece_entry const& lhs - , disk_io_thread::cached_piece_entry const& rhs) + int disk_io_thread::do_flush_piece(disk_io_job* j, tailqueue& completed_jobs) { - return lhs.num_contiguous_blocks < rhs.num_contiguous_blocks; - } + mutex::scoped_lock l(m_cache_mutex); - // flushes 'blocks' blocks from the cache - int disk_io_thread::flush_cache_blocks(mutex::scoped_lock& l - , int blocks, ignore_t ignore, int options) - { - // first look if there are any read cache entries that can - // be cleared - int ret = 0; - int tmp = 0; - do { - tmp = clear_oldest_read_piece(blocks, ignore, l); - blocks -= tmp; - ret += tmp; - } while (tmp > 0 && blocks > 0); + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe == NULL) return 0; - if (blocks == 0) return ret; - - if (options & dont_flush_write_blocks) return ret; - - // if we don't have any blocks in the cache, no need to go look for any - if (m_cache_stats.cache_size == 0) return ret; - - if (m_settings.disk_cache_algorithm == session_settings::lru) - { - cache_lru_index_t& idx = m_pieces.get<1>(); - while (blocks > 0) - { - cache_lru_index_t::iterator i = idx.begin(); - if (i == idx.end()) return ret; - tmp = flush_range(const_cast(*i), 0, INT_MAX, l); - idx.erase(i); - blocks -= tmp; - ret += tmp; - } - } - else if (m_settings.disk_cache_algorithm == session_settings::largest_contiguous) - { - cache_lru_index_t& idx = m_pieces.get<1>(); - while (blocks > 0) - { - cache_lru_index_t::iterator i = std::max_element(idx.begin(), idx.end(), &cmp_contiguous); - if (i == idx.end()) return ret; - tmp = flush_contiguous_blocks(const_cast(*i), l); - if (i->num_blocks == 0) idx.erase(i); - blocks -= tmp; - ret += tmp; - } - } - else if (m_settings.disk_cache_algorithm == session_settings::avoid_readback) - { - cache_lru_index_t& idx = m_pieces.get<1>(); - for (cache_lru_index_t::iterator i = idx.begin(); i != idx.end();) - { - cached_piece_entry& p = const_cast(*i); - cache_lru_index_t::iterator piece = i; - ++i; - - if (!piece->blocks[p.next_block_to_hash].buf) continue; - int piece_size = p.storage->info()->piece_size(p.piece); - int blocks_in_piece = (piece_size + m_block_size - 1) / m_block_size; - int start = p.next_block_to_hash; - int end = start + 1; - while (end < blocks_in_piece && p.blocks[end].buf) ++end; - tmp = flush_range(p, start, end, l); - p.num_contiguous_blocks = contiguous_blocks(p); - if (p.num_blocks == 0 && p.next_block_to_hash == blocks_in_piece) - idx.erase(piece); - blocks -= tmp; - ret += tmp; - if (blocks <= 0) break; - } - - // if we still need to flush blocks, flush the largest contiguous blocks - // regardless of if we'll have to read them back later - while (blocks > 0) - { - cache_lru_index_t::iterator i = std::max_element(idx.begin(), idx.end(), &cmp_contiguous); - if (i == idx.end() || i->num_blocks == 0) return ret; - tmp = flush_contiguous_blocks(const_cast(*i), l); - // at this point, we will for sure need a read-back for - // this piece anyway. We might as well save some time looping - // over the disk cache by deleting the entry - if (i->num_blocks == 0) idx.erase(i); - blocks -= tmp; - ret += tmp; - } - } - return ret; - } - - int disk_io_thread::flush_range(cached_piece_entry& p - , int start, int end, mutex::scoped_lock& l) - { - INVARIANT_CHECK; - - TORRENT_ASSERT(start < end); - - int piece_size = p.storage->info()->piece_size(p.piece); -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " flushing " << piece_size << std::endl; +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(j->action)); #endif - TORRENT_ASSERT(piece_size > 0); - - int blocks_in_piece = (piece_size + m_block_size - 1) / m_block_size; - int buffer_size = 0; - int offset = 0; + try_flush_hashed(pe, m_settings.get_int(settings_pack::write_cache_line_size), completed_jobs, l); - boost::scoped_array buf; - file::iovec_t* iov = 0; - int iov_counter = 0; - if (m_settings.coalesce_writes) buf.reset(new (std::nothrow) char[piece_size]); - else iov = TORRENT_ALLOCA(file::iovec_t, blocks_in_piece); - - end = (std::min)(end, blocks_in_piece); - int num_write_calls = 0; - ptime write_start = time_now_hires(); - for (int i = start; i <= end; ++i) - { - if (i == end || p.blocks[i].buf == 0) - { - if (buffer_size == 0) continue; - - TORRENT_ASSERT(buffer_size <= i * m_block_size); - l.unlock(); - if (iov) - { - int ret = p.storage->write_impl(iov, p.piece, (std::min)( - i * m_block_size, piece_size) - buffer_size, iov_counter); - iov_counter = 0; - if (ret > 0) ++num_write_calls; - } - else - { - TORRENT_ASSERT(buf); - file::iovec_t b = { buf.get(), size_t(buffer_size) }; - int ret = p.storage->write_impl(&b, p.piece, (std::min)( - i * m_block_size, piece_size) - buffer_size, 1); - if (ret > 0) ++num_write_calls; - } - l.lock(); - ++m_cache_stats.writes; -// std::cerr << " flushing p: " << p.piece << " bytes: " << buffer_size << std::endl; - buffer_size = 0; - offset = 0; - continue; - } - int block_size = (std::min)(piece_size - i * m_block_size, m_block_size); - TORRENT_ASSERT(offset + block_size <= piece_size); - TORRENT_ASSERT(offset + block_size > 0); - if (iov) - { - TORRENT_ASSERT(!buf); - iov[iov_counter].iov_base = p.blocks[i].buf; - iov[iov_counter].iov_len = block_size; - ++iov_counter; - } - else - { - TORRENT_ASSERT(buf); - TORRENT_ASSERT(iov == 0); - std::memcpy(buf.get() + offset, p.blocks[i].buf, block_size); - offset += m_block_size; - } - buffer_size += block_size; - TORRENT_ASSERT(p.num_blocks > 0); - --p.num_blocks; - ++m_cache_stats.blocks_written; - --m_cache_stats.cache_size; - if (i == p.next_block_to_hash) ++p.next_block_to_hash; - } - - ptime done = time_now_hires(); - - int ret = 0; - disk_io_job j; - j.storage = p.storage; - j.action = disk_io_job::write; - j.buffer = 0; - j.piece = p.piece; - test_error(j); - std::vector buffers; - for (int i = start; i < end; ++i) - { - if (p.blocks[i].buf == 0) continue; - j.buffer_size = (std::min)(piece_size - i * m_block_size, m_block_size); - int result = j.error ? -1 : j.buffer_size; - j.offset = i * m_block_size; - j.callback = p.blocks[i].callback; - buffers.push_back(p.blocks[i].buf); - post_callback(j, result); - p.blocks[i].callback.clear(); - p.blocks[i].buf = 0; - ++ret; - } - if (!buffers.empty()) free_multiple_buffers(&buffers[0], buffers.size()); - - if (num_write_calls > 0) - { - m_write_time.add_sample(total_microseconds(done - write_start) / num_write_calls); - m_cache_stats.cumulative_write_time += total_milliseconds(done - write_start); - } - if (ret > 0) - p.num_contiguous_blocks = contiguous_blocks(p); - - TORRENT_ASSERT(buffer_size == 0); -// std::cerr << " flushing p: " << p.piece << " cached_blocks: " << m_cache_stats.cache_size << std::endl; -#ifdef TORRENT_DEBUG - for (int i = start; i < end; ++i) - TORRENT_ASSERT(p.blocks[i].buf == 0); -#endif - return ret; - } - - // returns -1 on failure - int disk_io_thread::cache_block(disk_io_job& j - , boost::function& handler - , int cache_expire - , mutex::scoped_lock& l) - { - INVARIANT_CHECK; - TORRENT_ASSERT(find_cached_piece(m_pieces, j, l) == m_pieces.end()); - TORRENT_ASSERT((j.offset & (m_block_size-1)) == 0); - TORRENT_ASSERT(j.cache_min_time >= 0); - cached_piece_entry p; - - int piece_size = j.storage->info()->piece_size(j.piece); - int blocks_in_piece = (piece_size + m_block_size - 1) / m_block_size; - // there's no point in caching the piece if - // there's only one block in it - if (blocks_in_piece <= 1) return -1; - -#ifdef TORRENT_DISK_STATS - rename_buffer(j.buffer, "write cache"); -#endif - - p.piece = j.piece; - p.storage = j.storage; - p.expire = time_now() + seconds(j.cache_min_time); - p.num_blocks = 1; - p.num_contiguous_blocks = 1; - p.next_block_to_hash = 0; - p.blocks.reset(new (std::nothrow) cached_block_entry[blocks_in_piece]); - if (!p.blocks) return -1; - int block = j.offset / m_block_size; -// std::cerr << " adding cache entry for p: " << j.piece << " block: " << block << " cached_blocks: " << m_cache_stats.cache_size << std::endl; - p.blocks[block].buf = j.buffer; - p.blocks[block].callback.swap(handler); - ++m_cache_stats.cache_size; - cache_lru_index_t& idx = m_pieces.get<1>(); - TORRENT_ASSERT(p.storage); - idx.insert(p); return 0; } - // fills a piece with data from disk, returns the total number of bytes - // read or -1 if there was an error - int disk_io_thread::read_into_piece(cached_piece_entry& p, int start_block - , int options, int num_blocks, mutex::scoped_lock& l) + // this is triggered every time we insert a new dirty block in a piece + // by the time this gets executed, the block may already have been flushed + // triggered by another mechanism. + int disk_io_thread::do_flush_hashed(disk_io_job* j, tailqueue& completed_jobs) { - TORRENT_ASSERT(num_blocks > 0); - int piece_size = p.storage->info()->piece_size(p.piece); - int blocks_in_piece = (piece_size + m_block_size - 1) / m_block_size; + mutex::scoped_lock l(m_cache_mutex); - int end_block = start_block; - int num_read = 0; + cached_piece_entry* pe = m_disk_cache.find_piece(j); - int iov_counter = 0; - file::iovec_t* iov = TORRENT_ALLOCA(file::iovec_t, (std::min)(blocks_in_piece - start_block, num_blocks)); + if (pe == NULL) return 0; + if (pe->num_dirty == 0) return 0; - int piece_offset = start_block * m_block_size; + // if multiple threads are flushing this piece, this assert may fire + // this happens if the cache is running full and pieces are started to + // get flushed +// TORRENT_PIECE_ASSERT(pe->outstanding_flush == 1, pe); - int ret = 0; +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(j->action)); +#endif + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); + ++pe->piece_refcount; - boost::scoped_array buf; - for (int i = start_block; i < blocks_in_piece - && ((options & ignore_cache_size) - || in_use() < m_settings.cache_size); ++i) + if (!pe->hashing_done) { - int block_size = (std::min)(piece_size - piece_offset, m_block_size); - TORRENT_ASSERT(piece_offset <= piece_size); - - // this is a block that is already allocated - // free it and allocate a new one - if (p.blocks[i].buf) + if (pe->hash == 0 && !m_settings.get_bool(settings_pack::disable_hash_checks)) { - free_buffer(p.blocks[i].buf); - --p.num_blocks; - --m_cache_stats.cache_size; - --m_cache_stats.read_cache_size; + pe->hash = new partial_hash; + m_disk_cache.update_cache_state(pe); } - p.blocks[i].buf = allocate_buffer("read cache"); - // the allocation failed, break - if (p.blocks[i].buf == 0) - { - free_piece(p, l); - return -1; - } - ++p.num_blocks; - ++m_cache_stats.cache_size; - ++m_cache_stats.read_cache_size; - ++end_block; - ++num_read; - iov[iov_counter].iov_base = p.blocks[i].buf; - iov[iov_counter].iov_len = block_size; - ++iov_counter; - piece_offset += m_block_size; - if (num_read >= num_blocks) break; + // see if we can progress the hash cursor with this new block + kick_hasher(pe, l); + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); } - if (end_block == start_block) + // flushes the piece to disk in case + // it satisfies the condition for a write + // piece to be flushed + // #error if hash checks are disabled, always just flush + try_flush_hashed(pe, m_settings.get_int(settings_pack::write_cache_line_size), completed_jobs, l); + + TORRENT_ASSERT(l.locked()); + +// TORRENT_PIECE_ASSERT(pe->outstanding_flush == 1, pe); + pe->outstanding_flush = 0; + --pe->piece_refcount; + + m_disk_cache.maybe_free_piece(pe); + + return 0; + } + + int disk_io_thread::do_flush_storage(disk_io_job* j, tailqueue& completed_jobs) + { + mutex::scoped_lock l(m_cache_mutex); + flush_cache(j->storage.get(), flush_write_cache, completed_jobs, l); + return 0; + } + + int disk_io_thread::do_trim_cache(disk_io_job* j, tailqueue& completed_jobs) + { +//#error implement + return 0; + } + + int disk_io_thread::do_file_priority(disk_io_job* j, tailqueue& completed_jobs) + { + std::vector* p = reinterpret_cast*>(j->buffer); + j->storage->get_storage_impl()->set_file_priority(*p, j->error); + delete p; + return 0; + } + + int disk_io_thread::do_load_torrent(disk_io_job* j, tailqueue& completed_jobs) + { + add_torrent_params* params = (add_torrent_params*)j->requester; + + std::string filename = resolve_file_url(params->url); + torrent_info* t = new torrent_info(filename, j->error.ec); + if (j->error.ec) { - // something failed. Free all buffers - // we just allocated - free_piece(p, l); - return -2; - } - - TORRENT_ASSERT(iov_counter <= (std::min)(blocks_in_piece - start_block, num_blocks)); - - // the buffer_size is the size of the buffer we need to read - // all these blocks. - const int buffer_size = (std::min)((end_block - start_block) * m_block_size - , piece_size - start_block * m_block_size); - TORRENT_ASSERT(buffer_size > 0); - TORRENT_ASSERT(buffer_size <= piece_size); - TORRENT_ASSERT(buffer_size + start_block * m_block_size <= piece_size); - - if (m_settings.coalesce_reads) - buf.reset(new (std::nothrow) char[buffer_size]); - - if (buf) - { - l.unlock(); - file::iovec_t b = { buf.get(), size_t(buffer_size) }; - ret = p.storage->read_impl(&b, p.piece, start_block * m_block_size, 1); - l.lock(); - ++m_cache_stats.reads; - if (p.storage->error()) - { - free_piece(p, l); - return -1; - } - - if (ret != buffer_size) - { - // this means the file wasn't big enough for this read - char msg[70]; - snprintf(msg, sizeof(msg), "reading p: %d b: %d s: %d (read: %d)", p.piece, start_block, buffer_size, ret); - p.storage->get_storage_impl()->set_error(msg, errors::file_too_short); - free_piece(p, l); - return -1; - } - - int offset = 0; - for (int i = 0; i < iov_counter; ++i) - { - TORRENT_ASSERT(iov[i].iov_base); - TORRENT_ASSERT(iov[i].iov_len > 0); - TORRENT_ASSERT(int(offset + iov[i].iov_len) <= buffer_size); - std::memcpy(iov[i].iov_base, buf.get() + offset, iov[i].iov_len); - offset += iov[i].iov_len; - } + j->buffer = NULL; + delete t; } else { - l.unlock(); - ret = p.storage->read_impl(iov, p.piece, start_block * m_block_size, iov_counter); - l.lock(); - ++m_cache_stats.reads; - if (p.storage->error()) - { - free_piece(p, l); - return -1; - } - - if (ret != buffer_size) - { - // this means the file wasn't big enough for this read - char msg[70]; - snprintf(msg, sizeof(msg), "reading p: %d b: %d s: %d (read: %d)", p.piece, start_block, buffer_size, ret); - p.storage->get_storage_impl()->set_error(msg, errors::file_too_short); - free_piece(p, l); - return -1; - } + j->buffer = (char*)t; } - TORRENT_ASSERT(ret == buffer_size); + return 0; + } + + // this job won't return until all outstanding jobs on this + // piece are completed or cancelled and the buffers for it + // have been evicted + int disk_io_thread::do_clear_piece(disk_io_job* j, tailqueue& completed_jobs) + { + mutex::scoped_lock l(m_cache_mutex); + + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe == 0) return 0; + TORRENT_PIECE_ASSERT(pe->hashing == false, pe); + pe->hashing_done = 0; + delete pe->hash; + pe->hash = NULL; + pe->hashing_done = false; + +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(j->action)); +#endif + + // evict_piece returns true if the piece was in fact + // evicted. A piece may fail to be evicted if there + // are still outstanding operations on it, in which case + // try again later + tailqueue jobs; + if (m_disk_cache.evict_piece(pe, jobs)) + { + fail_jobs_impl(storage_error(boost::asio::error::operation_aborted), jobs, completed_jobs); + return 0; + } + + m_disk_cache.mark_for_deletion(pe); + if (pe->num_blocks == 0) return 0; + + // we should always be able to evict the piece, since + // this is a fence job + TORRENT_PIECE_ASSERT(false, pe); + return retry_job; + } + + int disk_io_thread::do_tick(disk_io_job* j, tailqueue& completed_jobs) + { + // true means this storage wants more ticks, false + // disables ticking (until it's enabled again) + return j->storage->get_storage_impl()->tick(); + } + + void disk_io_thread::add_fence_job(piece_manager* storage, disk_io_job* j) + { + // if this happens, it means we started to shut down + // the disk threads too early. We have to post all jobs + // before the disk threads are shut down + TORRENT_ASSERT(m_num_threads > 0); + + DLOG("add_fence:job: %s (outstanding: %d)\n" + , job_action_name[j->action] + , j->storage->num_outstanding_jobs()); + + ++m_cache_stats.num_fence_jobs[j->action]; + + disk_io_job* fj = allocate_job(disk_io_job::flush_storage); + fj->storage = j->storage; + + int ret = storage->raise_fence(j, fj, &m_num_blocked_jobs); + if (ret == disk_job_fence::fence_post_fence) + { + mutex::scoped_lock l(m_job_mutex); + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + // prioritize fence jobs since they're blocking other jobs + m_queued_jobs.push_front(j); + l.unlock(); + + // discard the flush job + free_job(fj); + return; + } + + // in this case, we can't run the fence job right now, because there + // are other jobs outstanding on this storage. We need to trigger a + // flush of all those jobs now. Only write jobs linger, those are the + // jobs that needs to be kicked + TORRENT_ASSERT(j->blocked); + + if (ret == disk_job_fence::fence_post_flush) + { + // now, we have to make sure that all outstanding jobs on this + // storage actually get flushed, in order for the fence job to + // be executed + mutex::scoped_lock l(m_job_mutex); + TORRENT_ASSERT((fj->flags & disk_io_job::in_progress) || !fj->storage); + + m_queued_jobs.push_front(fj); + } + else + { + TORRENT_ASSERT((fj->flags & disk_io_job::in_progress) == 0); + TORRENT_ASSERT(fj->blocked); + } + } + + void disk_io_thread::add_job(disk_io_job* j) + { + TORRENT_ASSERT(m_magic == 0x1337); + + TORRENT_ASSERT(!j->storage || j->storage->files()->is_valid()); + TORRENT_ASSERT(j->next == NULL); + // if this happens, it means we started to shut down + // the disk threads too early. We have to post all jobs + // before the disk threads are shut down + TORRENT_ASSERT(m_num_threads > 0 + || j->action == disk_io_job::flush_piece + || j->action == disk_io_job::trim_cache); + + // this happens for read jobs that get hung on pieces in the + // block cache, and then get issued + if (j->flags & disk_io_job::in_progress) + { + mutex::scoped_lock l(m_job_mutex); + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + m_queued_jobs.push_back(j); + return; + } + + DLOG("add_job: %s (outstanding: %d)\n" + , job_action_name[j->action] + , j->storage ? j->storage->num_outstanding_jobs() : 0); + + // is the fence up for this storage? + // jobs that are instantaneous are not affected by the fence, is_blocked() + // will take ownership of the job and queue it up, in case the fence is up + // if the fence flag is set, this job just raised the fence on the storage + // and should be scheduled + if (j->storage && j->storage->is_blocked(j)) + { + ++m_num_blocked_jobs; + DLOG("blocked job: %s (torrent: %d total: %d)\n" + , job_action_name[j->action], j->storage ? j->storage->num_blocked() : 0 + , int(m_num_blocked_jobs)); + return; + } + + mutex::scoped_lock l(m_job_mutex); + + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + + // if there are at least 3 threads, there's a hasher thread + // and the hash jobs go into a separate queue + // see set_num_threads() + if (m_num_threads > 3 && j->action == disk_io_job::hash) + m_queued_hash_jobs.push_back(j); + else + m_queued_jobs.push_back(j); + } + + void disk_io_thread::submit_jobs() + { + mutex::scoped_lock l(m_job_mutex); + if (!m_queued_jobs.empty()) + m_job_cond.notify_all(); + if (!m_queued_hash_jobs.empty()) + m_hash_job_cond.notify_all(); + } + + void disk_io_thread::thread_fun(int thread_id, thread_type_t type) + { + DLOG("started disk thread %d\n", int(thread_id)); + + ++m_num_running_threads; + + mutex::scoped_lock l(m_job_mutex); + for (;;) + { + disk_io_job* j = 0; + if (type == generic_thread) + { + TORRENT_ASSERT(l.locked()); + while (m_queued_jobs.empty() && thread_id < m_num_threads) m_job_cond.wait(l); + + // if the number of wanted threads is decreased, + // we may stop this thread + // when we're terminating the last thread (id=0), make sure + // we finish up all queued jobs first + if (thread_id >= m_num_threads && !(thread_id == 0 && m_queued_jobs.size() > 0)) + { + // time to exit this thread. + break; + } + + j = (disk_io_job*)m_queued_jobs.pop_front(); + } + else if (type == hasher_thread) + { + TORRENT_ASSERT(l.locked()); + while (m_queued_hash_jobs.empty() && thread_id < m_num_threads) m_hash_job_cond.wait(l); + if (m_queued_hash_jobs.empty() && thread_id >= m_num_threads) break; + j = (disk_io_job*)m_queued_hash_jobs.pop_front(); + } + + l.unlock(); + + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + + if (thread_id == 0) + { + // there's no need for all threads to be doing this + ptime now = time_now_hires(); + if (now > m_last_cache_expiry + seconds(5)) + { + mutex::scoped_lock l2(m_cache_mutex); + DLOG("blocked_jobs: %d queued_jobs: %d num_threads %d\n" + , int(m_num_blocked_jobs), m_queued_jobs.size(), int(m_num_threads)); + m_last_cache_expiry = now; + tailqueue completed_jobs; + flush_expired_write_blocks(completed_jobs, l2); + l2.unlock(); + if (completed_jobs.size()) + add_completed_jobs(completed_jobs); + } + } + + tailqueue completed_jobs; + perform_job(j, completed_jobs); + + mutex::scoped_lock l2(m_cache_mutex); + check_cache_level(l2, completed_jobs); + l2.unlock(); + + if (completed_jobs.size()) + add_completed_jobs(completed_jobs); + + l.lock(); + } + l.unlock(); + + // do cleanup in the last running thread + if (--m_num_running_threads > 0) + { + DLOG("exiting disk thread %d. num_threads: %d\n", thread_id, int(m_num_threads)); + TORRENT_ASSERT(m_magic == 0x1337); + return; + } + + // at this point, there are no queued jobs left. However, main + // thread is still running and may still have peer_connections + // that haven't fully destructed yet, reclaiming their references + // to read blocks in the disk cache. We need to wait until all + // references are removed from other threads before we can go + // ahead with the cleanup. + mutex::scoped_lock l2(m_cache_mutex); + while (m_disk_cache.pinned_blocks() > 0) + { + l2.unlock(); + sleep(100); + l2.lock(); + } + l2.unlock(); + + DLOG("disk thread %d is the last one alive. cleaning up\n", thread_id); + + tailqueue jobs; + + m_disk_cache.clear(jobs); + fail_jobs(storage_error(boost::asio::error::operation_aborted), jobs); + + // close all files. This may take a long + // time on certain OSes (i.e. Mac OS) + // that's why it's important to do this in + // the disk thread in parallel with stopping + // trackers. + m_file_pool.release(); + +#if TORRENT_USE_ASSERTS + // by now, all pieces should have been evicted + std::pair pieces + = m_disk_cache.all_pieces(); + TORRENT_ASSERT(pieces.first == pieces.second); +#endif + // release the io_service to allow the run() call to return + // we do this once we stop posting new callbacks to it. +#if defined TORRENT_ASIO_DEBUGGING + complete_async("disk_io_thread::work"); +#endif + m_work.reset(); + TORRENT_ASSERT(m_magic == 0x1337); + } + + // this is a callback called by the block_cache when + // it's exceeding the disk cache size. + void disk_io_thread::trigger_cache_trim() + { + // we just exceeded the cache size limit. Trigger a trim job + disk_io_job* j = allocate_job(disk_io_job::trim_cache); + add_job(j); + submit_jobs(); + } + + char* disk_io_thread::allocate_disk_buffer(bool& exceeded + , boost::shared_ptr o + , char const* category) + { + char* ret = m_disk_cache.allocate_buffer(exceeded, o, category); return ret; } - // returns -1 on read error, -2 if there isn't any space in the cache - // or the number of bytes read - int disk_io_thread::cache_read_block(disk_io_job const& j, mutex::scoped_lock& l) + void disk_io_thread::add_completed_job(disk_io_job* j) { - INVARIANT_CHECK; + tailqueue tmp; + tmp.push_back(j); + add_completed_jobs(tmp); + } - TORRENT_ASSERT(j.cache_min_time >= 0); - - // this function will create a new cached_piece_entry - // and requires that it doesn't already exist - cache_piece_index_t& idx = m_read_pieces.get<0>(); - TORRENT_ASSERT(find_cached_piece(m_read_pieces, j, l) == idx.end()); - - int piece_size = j.storage->info()->piece_size(j.piece); - int blocks_in_piece = (piece_size + m_block_size - 1) / m_block_size; - - int start_block = j.offset / m_block_size; - - int blocks_to_read = blocks_in_piece - start_block; - blocks_to_read = (std::min)(blocks_to_read, (std::max)((m_settings.cache_size - + m_cache_stats.read_cache_size - in_use())/2, 3)); - blocks_to_read = (std::min)(blocks_to_read, m_settings.read_cache_line_size); - if (j.max_cache_line > 0) blocks_to_read = (std::min)(blocks_to_read, j.max_cache_line); - - if (in_use() + blocks_to_read > m_settings.cache_size) + void disk_io_thread::add_completed_jobs(tailqueue& jobs) + { + tailqueue new_completed_jobs; + do { - int clear = in_use() + blocks_to_read - m_settings.cache_size; - if (flush_cache_blocks(l, clear, ignore_t(j.piece, j.storage.get()) - , dont_flush_write_blocks) < clear) - return -2; + // when a job completes, it's possible for it to cause + // a fence to be lowered, issuing the jobs queued up + // behind the fence. It's also possible for some of these + // jobs to be cache-hits, completing immediately. Those + // jobs are added to the new_completed_jobs queue and + // we need to re-issue those + add_completed_jobs_impl(jobs, new_completed_jobs); + TORRENT_ASSERT(jobs.size() == 0); + jobs.swap(new_completed_jobs); + } while (jobs.size() > 0); + } + + void disk_io_thread::add_completed_jobs_impl(tailqueue& jobs + , tailqueue& completed_jobs) + { + tailqueue new_jobs; + int ret = 0; + for (tailqueue_iterator i = jobs.iterate(); i.get(); i.next()) + { + disk_io_job* j = (disk_io_job*)i.get(); + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); + +// DLOG("job_complete %s outstanding: %d\n" +// , job_action_name[j->action], j->storage ? j->storage->num_outstanding_jobs() : 0); + + if (j->storage) + { + if (j->flags & disk_io_job::fence) + --m_cache_stats.num_fence_jobs[j->action]; + ret += j->storage->job_complete(j, new_jobs); + } + TORRENT_ASSERT(ret == new_jobs.size()); + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) == 0); +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(j->job_posted == false); + j->job_posted = true; +#endif } - cached_piece_entry p; - p.piece = j.piece; - p.storage = j.storage; - p.expire = time_now() + seconds(j.cache_min_time); - p.num_blocks = 0; - p.num_contiguous_blocks = 0; - p.next_block_to_hash = 0; - p.blocks.reset(new (std::nothrow) cached_block_entry[blocks_in_piece]); - if (!p.blocks) return -1; +#if DEBUG_DISK_THREAD + if (ret) DLOG("unblocked %d jobs (%d left)\n", ret + , int(m_num_blocked_jobs) - ret); +#endif - int ret = read_into_piece(p, start_block, 0, blocks_to_read, l); + m_num_blocked_jobs -= ret; + TORRENT_ASSERT(m_num_blocked_jobs >= 0); - TORRENT_ASSERT(p.storage); - if (ret >= 0) idx.insert(p); + if (new_jobs.size() > 0) + { +#if TORRENT_USE_ASSERTS + for (tailqueue_iterator i = new_jobs.iterate(); i.get(); i.next()) + { + disk_io_job const* j = static_cast(i.get()); + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) || !j->storage); - return ret; + if (j->action == disk_io_job::write) + { + mutex::scoped_lock l(m_cache_mutex); + cached_piece_entry* pe = m_disk_cache.find_piece(j); + if (pe) + { + TORRENT_ASSERT(pe->blocks[j->d.io.offset / 16 / 1024].buf != j->buffer); + TORRENT_ASSERT(pe->blocks[j->d.io.offset / 16 / 1024].buf == NULL); + TORRENT_ASSERT(!pe->hashing_done); + } + } + } +#endif + tailqueue other_jobs; + tailqueue flush_jobs; + mutex::scoped_lock l_(m_cache_mutex); + while (new_jobs.size() > 0) + { + disk_io_job* j = (disk_io_job*)new_jobs.pop_front(); + + if (j->action == disk_io_job::read + && m_settings.get_bool(settings_pack::use_read_cache) + && m_settings.get_int(settings_pack::cache_size) > 0) + { + int ret = prep_read_job_impl(j, false); + switch (ret) + { + case 0: + completed_jobs.push_back(j); + break; + case 1: + other_jobs.push_back(j); + break; + } + continue; + } + + // write jobs should be put straight into the cache + if (j->action != disk_io_job::write) + { + other_jobs.push_back(j); + continue; + } + + cached_piece_entry* pe = m_disk_cache.find_piece(j); + pe = m_disk_cache.add_dirty_block(j); + + if (pe == NULL) + { + // this isn't correct, since jobs in the jobs + // queue aren't ordered + other_jobs.push_back(j); + continue; + } + +#if TORRENT_USE_ASSERTS + pe->piece_log.push_back(piece_log_t(j->action, j->d.io.offset / 0x4000)); +#endif + + if (!pe->hashing_done + && pe->hash == 0 + && !m_settings.get_bool(settings_pack::disable_hash_checks)) + { + pe->hash = new partial_hash; + m_disk_cache.update_cache_state(pe); + } + + TORRENT_PIECE_ASSERT(pe->cache_state <= cached_piece_entry::read_lru1 || pe->cache_state == cached_piece_entry::read_lru2, pe); + + if (pe->outstanding_flush == 0) + { + pe->outstanding_flush = 1; + + // the block and write job were successfully inserted + // into the cache. Now, see if we should trigger a flush + disk_io_job* fj = allocate_job(disk_io_job::flush_hashed); + fj->storage = j->storage; + fj->piece = j->piece; + flush_jobs.push_back(fj); + } + } + l_.unlock(); + + mutex::scoped_lock l(m_job_mutex); + m_queued_jobs.append(other_jobs); + l.unlock(); + + while (flush_jobs.size() > 0) + { + disk_io_job* j = (disk_io_job*)flush_jobs.pop_front(); + add_job(j); + } + + m_job_cond.notify_all(); + } + + mutex::scoped_lock l(m_completed_jobs_mutex); + + bool need_post = m_completed_jobs.size() == 0; + m_completed_jobs.append(jobs); + l.unlock(); + + if (need_post) + { +#if DEBUG_DISK_THREAD + // we take this lock just to make the logging prettier (non-interleaved) + DLOG("posting job handlers (%d)\n", m_completed_jobs.size()); +#endif + m_ios.post(boost::bind(&disk_io_thread::call_job_handlers, this, m_userdata)); + } + } + + // This is run in the network thread + void disk_io_thread::call_job_handlers(void* userdata) + { + mutex::scoped_lock l(m_completed_jobs_mutex); + +#if DEBUG_DISK_THREAD + DLOG("call_job_handlers (%d)\n", m_completed_jobs.size()); +#endif + + int num_jobs = m_completed_jobs.size(); + disk_io_job* j = (disk_io_job*)m_completed_jobs.get_all(); + l.unlock(); + + uncork_interface* uncork = (uncork_interface*)userdata; + std::vector to_delete; + to_delete.reserve(num_jobs); + + while (j) + { + TORRENT_ASSERT(j->job_posted == true); + TORRENT_ASSERT(j->callback_called == false); +// DLOG(" callback: %s\n", job_action_name[j->action]); + disk_io_job* next = (disk_io_job*)j->next; + +#if TORRENT_USE_ASSERTS + j->callback_called = true; +#endif + if (j->callback) j->callback(j); + to_delete.push_back(j); + j = next; + } + + if (!to_delete.empty()) + free_jobs(&to_delete[0], to_delete.size()); + + // uncork all peers who received a disk event. This is + // to coalesce all the socket writes caused by the events. + if (uncork) uncork->do_delayed_uncork(); } #if TORRENT_USE_INVARIANT_CHECKS void disk_io_thread::check_invariant() const { - int cached_write_blocks = 0; - cache_piece_index_t const& idx = m_pieces.get<0>(); - for (cache_piece_index_t::const_iterator i = idx.begin() - , end(idx.end()); i != end; ++i) - { - cached_piece_entry const& p = *i; - TORRENT_ASSERT(p.blocks); -// TORRENT_ASSERT(p.num_contiguous_blocks == contiguous_blocks(p)); - - TORRENT_ASSERT(p.storage); - int piece_size = p.storage->info()->piece_size(p.piece); - int blocks_in_piece = (piece_size + m_block_size - 1) / m_block_size; - int blocks = 0; - for (int k = 0; k < blocks_in_piece; ++k) - { - if (p.blocks[k].buf) - { -#if !defined TORRENT_DISABLE_POOL_ALLOCATOR && defined TORRENT_EXPENSIVE_INVARIANT_CHECKS - TORRENT_ASSERT(is_disk_buffer(p.blocks[k].buf)); -#endif - ++blocks; - } - } -// TORRENT_ASSERT(blocks == p.num_blocks); - cached_write_blocks += blocks; - } - - int cached_read_blocks = 0; - for (cache_t::const_iterator i = m_read_pieces.begin() - , end(m_read_pieces.end()); i != end; ++i) - { - cached_piece_entry const& p = *i; - TORRENT_ASSERT(p.blocks); - - int piece_size = p.storage->info()->piece_size(p.piece); - int blocks_in_piece = (piece_size + m_block_size - 1) / m_block_size; - int blocks = 0; - for (int k = 0; k < blocks_in_piece; ++k) - { - if (p.blocks[k].buf) - { -#if !defined TORRENT_DISABLE_POOL_ALLOCATOR && defined TORRENT_EXPENSIVE_INVARIANT_CHECKS - TORRENT_ASSERT(is_disk_buffer(p.blocks[k].buf)); -#endif - ++blocks; - } - } -// TORRENT_ASSERT(blocks == p.num_blocks); - cached_read_blocks += blocks; - } - - TORRENT_ASSERT(cached_read_blocks == m_cache_stats.read_cache_size); - TORRENT_ASSERT(cached_read_blocks + cached_write_blocks == m_cache_stats.cache_size); - -#ifdef TORRENT_DISK_STATS - int read_allocs = m_categories.find(std::string("read cache"))->second; - int write_allocs = m_categories.find(std::string("write cache"))->second; - TORRENT_ASSERT(cached_read_blocks == read_allocs); - TORRENT_ASSERT(cached_write_blocks == write_allocs); -#endif - - // when writing, there may be a one block difference, right before an old piece - // is flushed - TORRENT_ASSERT(m_cache_stats.cache_size <= m_settings.cache_size + 1); } #endif - - // reads the full piece specified by j into the read cache - // returns the iterator to it and whether or not it already - // was in the cache (hit). - int disk_io_thread::cache_piece(disk_io_job const& j, cache_piece_index_t::iterator& p - , bool& hit, int options, mutex::scoped_lock& l) - { - INVARIANT_CHECK; - - TORRENT_ASSERT(j.cache_min_time >= 0); - - cache_piece_index_t& idx = m_read_pieces.get<0>(); - p = find_cached_piece(m_read_pieces, j, l); - - hit = true; - int ret = 0; - - int piece_size = j.storage->info()->piece_size(j.piece); - int blocks_in_piece = (piece_size + m_block_size - 1) / m_block_size; - - if (p != m_read_pieces.end() && p->num_blocks != blocks_in_piece) - { - INVARIANT_CHECK; - // we have the piece in the cache, but not all of the blocks - ret = read_into_piece(const_cast(*p), 0 - , options, blocks_in_piece, l); - hit = false; - if (ret < 0) return ret; - idx.modify(p, update_last_use(j.cache_min_time)); - } - else if (p == m_read_pieces.end()) - { - INVARIANT_CHECK; - // if the piece cannot be found in the cache, - // read the whole piece starting at the block - // we got a request for. - - cached_piece_entry pe; - pe.piece = j.piece; - pe.storage = j.storage; - pe.expire = time_now() + seconds(j.cache_min_time); - pe.num_blocks = 0; - pe.num_contiguous_blocks = 0; - pe.next_block_to_hash = 0; - pe.blocks.reset(new (std::nothrow) cached_block_entry[blocks_in_piece]); - if (!pe.blocks) return -1; - ret = read_into_piece(pe, 0, options, INT_MAX, l); - - hit = false; - if (ret < 0) return ret; - TORRENT_ASSERT(pe.storage); - p = idx.insert(pe).first; - } - else - { - idx.modify(p, update_last_use(j.cache_min_time)); - } - TORRENT_ASSERT(!m_read_pieces.empty()); - TORRENT_ASSERT(p->piece == j.piece); - TORRENT_ASSERT(p->storage == j.storage); - return ret; - } - - // cache the entire piece and hash it - int disk_io_thread::read_piece_from_cache_and_hash(disk_io_job const& j, sha1_hash& h) - { - TORRENT_ASSERT(j.buffer); - - TORRENT_ASSERT(j.cache_min_time >= 0); - - mutex::scoped_lock l(m_piece_mutex); - - int piece_size = j.storage->info()->piece_size(j.piece); - int blocks_in_piece = (piece_size + m_block_size - 1) / m_block_size; - - if (in_use() + blocks_in_piece >= m_settings.cache_size) - { - flush_cache_blocks(l, in_use() - m_settings.cache_size + blocks_in_piece); - } - - cache_piece_index_t::iterator p; - bool hit; - int ret = cache_piece(j, p, hit, ignore_cache_size, l); - if (ret < 0) return ret; - - if (!m_settings.disable_hash_checks) - { - hasher ctx; - - for (int i = 0; i < blocks_in_piece; ++i) - { - TORRENT_ASSERT(p->blocks[i].buf); - ctx.update((char const*)p->blocks[i].buf, (std::min)(piece_size, m_block_size)); - piece_size -= m_block_size; - } - h = ctx.final(); - } - - ret = copy_from_piece(const_cast(*p), hit, j, l); - TORRENT_ASSERT(ret > 0); - if (ret < 0) return ret; - cache_piece_index_t& idx = m_read_pieces.get<0>(); - if (p->num_blocks == 0) idx.erase(p); - else idx.modify(p, update_last_use(j.cache_min_time)); - - // if read cache is disabled or we exceeded the - // limit, remove this piece from the cache - // also, if the piece wasn't in the cache when - // the function was called, and we're using an - // explicit read cache, remove it again - if (in_use() >= m_settings.cache_size - || !m_settings.use_read_cache - || (m_settings.explicit_read_cache && !hit)) - { - TORRENT_ASSERT(!m_read_pieces.empty()); - TORRENT_ASSERT(p->piece == j.piece); - TORRENT_ASSERT(p->storage == j.storage); - if (p != m_read_pieces.end()) - { - free_piece(const_cast(*p), l); - m_read_pieces.erase(p); - } - } - - ret = j.buffer_size; - ++m_cache_stats.blocks_read; - if (hit) ++m_cache_stats.blocks_read_hit; - return ret; - } - - // this doesn't modify the read cache, it only - // checks to see if the given read request can - // be fully satisfied from the given cached piece - // this is similar to copy_from_piece() but it - // doesn't do anything but determining if it's a - // cache hit or not - bool disk_io_thread::is_cache_hit(cached_piece_entry& p - , disk_io_job const& j, mutex::scoped_lock& l) - { - int block = j.offset / m_block_size; - int block_offset = j.offset & (m_block_size-1); - int size = j.buffer_size; - int min_blocks_to_read = block_offset > 0 && (size > m_block_size - block_offset) ? 2 : 1; - TORRENT_ASSERT(size <= m_block_size); - int start_block = block; - // if we have to read more than one block, and - // the first block is there, make sure we test - // for the second block - if (p.blocks[start_block].buf != 0 && min_blocks_to_read > 1) - ++start_block; - -#ifdef TORRENT_DEBUG - int piece_size = j.storage->info()->piece_size(j.piece); - int blocks_in_piece = (piece_size + m_block_size - 1) / m_block_size; - TORRENT_ASSERT(start_block < blocks_in_piece); -#endif - - return p.blocks[start_block].buf != 0; - } - - int disk_io_thread::copy_from_piece(cached_piece_entry& p, bool& hit - , disk_io_job const& j, mutex::scoped_lock& l) - { - TORRENT_ASSERT(j.buffer); - - // copy from the cache and update the last use timestamp - int block = j.offset / m_block_size; - int block_offset = j.offset & (m_block_size-1); - int buffer_offset = 0; - int size = j.buffer_size; - int min_blocks_to_read = block_offset > 0 && (size > m_block_size - block_offset) ? 2 : 1; - TORRENT_ASSERT(size <= m_block_size); - int start_block = block; - if (p.blocks[start_block].buf != 0 && min_blocks_to_read > 1) - ++start_block; - - int piece_size = j.storage->info()->piece_size(j.piece); - int blocks_in_piece = (piece_size + m_block_size - 1) / m_block_size; - TORRENT_ASSERT(start_block < blocks_in_piece); - - // if block_offset > 0, we need to read two blocks, and then - // copy parts of both, because it's not aligned to the block - // boundaries - if (p.blocks[start_block].buf == 0) - { - // if we use an explicit read cache, pretend there's no - // space to force hitting disk without caching anything - if (m_settings.explicit_read_cache) return -2; - - int end_block = start_block; - while (end_block < blocks_in_piece && p.blocks[end_block].buf == 0) ++end_block; - - int blocks_to_read = end_block - block; - blocks_to_read = (std::min)(blocks_to_read, (std::max)((m_settings.cache_size - + m_cache_stats.read_cache_size - in_use())/2, 3)); - blocks_to_read = (std::min)(blocks_to_read, m_settings.read_cache_line_size); - blocks_to_read = (std::max)(blocks_to_read, min_blocks_to_read); - if (j.max_cache_line > 0) blocks_to_read = (std::min)(blocks_to_read, j.max_cache_line); - - // if we don't have enough space for the new piece, try flushing something else - if (in_use() + blocks_to_read > m_settings.cache_size) - { - int clear = in_use() + blocks_to_read - m_settings.cache_size; - if (flush_cache_blocks(l, clear, ignore_t(p.piece, p.storage.get()) - , dont_flush_write_blocks) < clear) - return -2; - } - - int ret = read_into_piece(p, block, 0, blocks_to_read, l); - hit = false; - if (ret < 0) return ret; - if (ret < size + block_offset) return -2; - TORRENT_ASSERT(p.blocks[block].buf); - } - - // build a vector of all the buffers we need to free - // and free them all in one go - std::vector buffers; - while (size > 0) - { - TORRENT_ASSERT(p.blocks[block].buf); - int to_copy = (std::min)(m_block_size - - block_offset, size); - std::memcpy(j.buffer + buffer_offset - , p.blocks[block].buf + block_offset - , to_copy); - size -= to_copy; - block_offset = 0; - buffer_offset += to_copy; - if (m_settings.volatile_read_cache) - { - // if volatile read cache is set, the assumption is - // that no other peer is likely to request the same - // piece. Therefore, for each request out of the cache - // we clear the block that was requested and any blocks - // the peer skipped - for (int i = block; i >= 0 && p.blocks[i].buf; --i) - { - buffers.push_back(p.blocks[i].buf); - p.blocks[i].buf = 0; - --p.num_blocks; - --m_cache_stats.cache_size; - --m_cache_stats.read_cache_size; - } - } - ++block; - } - if (!buffers.empty()) free_multiple_buffers(&buffers[0], buffers.size()); - return j.buffer_size; - } - - int disk_io_thread::try_read_from_cache(disk_io_job const& j, bool& hit, int flags) - { - TORRENT_ASSERT(m_magic == 0x1337); - - TORRENT_ASSERT(j.buffer); - TORRENT_ASSERT(j.cache_min_time >= 0); - - mutex::scoped_lock l(m_piece_mutex); - if (!m_settings.use_read_cache) - { - hit = false; - return -2; - } - - cache_piece_index_t& idx = m_read_pieces.get<0>(); - cache_piece_index_t::iterator p - = find_cached_piece(m_read_pieces, j, l); - - hit = true; - int ret = 0; - - // if the piece cannot be found in the cache, - // read the whole piece starting at the block - // we got a request for. - if (p == idx.end()) - { - if (flags & cache_only) return -2; - // if we use an explicit read cache and we - // couldn't find the block in the cache, - // pretend that there's not enough space - // to cache it, to force the read operation - // go go straight to disk - if (m_settings.explicit_read_cache) return -2; - - ret = cache_read_block(j, l); - hit = false; - if (ret < 0) return ret; - - p = find_cached_piece(m_read_pieces, j, l); - TORRENT_ASSERT(!m_read_pieces.empty()); - TORRENT_ASSERT(p->piece == j.piece); - TORRENT_ASSERT(p->storage == j.storage); - } - - TORRENT_ASSERT(p != idx.end()); - - ret = copy_from_piece(const_cast(*p), hit, j, l); - if (ret < 0) return ret; - if (p->num_blocks == 0) idx.erase(p); - else idx.modify(p, update_last_use(j.cache_min_time)); - - ret = j.buffer_size; - ++m_cache_stats.blocks_read; - if (hit) ++m_cache_stats.blocks_read_hit; - return ret; - } - - size_type disk_io_thread::queue_buffer_size() const - { - TORRENT_ASSERT(m_magic == 0x1337); - - mutex::scoped_lock l(m_queue_mutex); - return m_queue_buffer_size; - } - - typedef std::list > job_queue_t; - void completion_queue_handler(job_queue_t* completed_jobs) - { - boost::shared_ptr holder(completed_jobs); - - for (job_queue_t::iterator i = completed_jobs->begin() - , end(completed_jobs->end()); i != end; ++i) - { - TORRENT_TRY - { - i->first.callback(i->second, i->first); - } - TORRENT_CATCH(std::exception& e) - {} - } - } - - int disk_io_thread::add_job(disk_io_job const& j - , mutex::scoped_lock& l - , boost::function const& f) - { - TORRENT_ASSERT(m_magic == 0x1337); - - const_cast(j).start_time = time_now_hires(); - - if (j.action == disk_io_job::write) - { - m_queue_buffer_size += j.buffer_size; - if (m_queue_buffer_size >= m_settings.max_queued_disk_bytes - && m_settings.max_queued_disk_bytes > 0) - m_exceeded_write_queue = true; - } -/* - else if (j.action == disk_io_job::read) - { - // if this is a cache hit, return it right away! - // this is OK because the cache is actually protected by - // the m_piece_mutex - bool hit = false; - if (j.buffer == 0) - { - // this is OK because the disk_buffer pool has its - // own mutex to protect the pool allocator - const_cast(j).buffer = allocate_buffer("send buffer"); - } - int ret = try_read_from_cache(j, hit, cache_only); - if (hit && ret >= 0) - { - TORRENT_ASSERT(f); - const_cast(j).callback.swap( - const_cast&>(f)); - job_queue_t* q = new job_queue_t; - q->push_back(std::make_pair(j, ret)); - m_ios.post(boost::bind(completion_queue_handler, q)); - return m_queue_buffer_size; - } - free_buffer(j.buffer); - const_cast(j).buffer = 0; - } -*/ - TORRENT_ASSERT(l.locked()); - m_jobs.push_back(j); - m_jobs.back().callback.swap(const_cast&>(f)); - - m_signal.signal(l); - return m_queue_buffer_size; - } - - int disk_io_thread::add_job(disk_io_job const& j - , boost::function const& f) - { - TORRENT_ASSERT(m_magic == 0x1337); - TORRENT_ASSERT(!m_abort); - TORRENT_ASSERT(j.storage - || j.action == disk_io_job::abort_thread - || j.action == disk_io_job::update_settings); - TORRENT_ASSERT(j.buffer_size <= m_block_size); - mutex::scoped_lock l(m_queue_mutex); - TORRENT_ASSERT(m_magic == 0x1337); - return add_job(j, l, f); - } - - bool disk_io_thread::test_error(disk_io_job& j) - { - TORRENT_ASSERT(m_magic == 0x1337); - - TORRENT_ASSERT(j.storage); - error_code const& ec = j.storage->error(); - if (ec) - { - j.buffer = 0; - j.str.clear(); - j.error = ec; - j.error_file = j.storage->error_file(); - j.storage->clear_error(); - return true; - } - return false; - } - - void disk_io_thread::post_callback(disk_io_job const& j, int ret) - { - if (!j.callback) return; - m_queued_completions.push_back(std::make_pair(j, ret)); - } - - enum action_flags_t - { - read_operation = 1 - , buffer_operation = 2 - , cancel_on_abort = 4 - }; - - static const boost::uint8_t action_flags[] = - { - read_operation + buffer_operation + cancel_on_abort // read - , buffer_operation // write - , 0 // hash - , 0 // move_storage - , 0 // release_files - , 0 // delete_files - , 0 // check_fastresume - , cancel_on_abort // check_files - , 0 // save_resume_data - , 0 // rename_file - , 0 // abort_thread - , 0 // clear_read_cache - , 0 // abort_torrent - , cancel_on_abort // update_settings - , read_operation + cancel_on_abort // read_and_hash - , read_operation + cancel_on_abort // cache_piece - , 0 // file_priority -#ifndef TORRENT_NO_DEPRECATE - , 0 // finalize_file -#endif - }; - - bool should_cancel_on_abort(disk_io_job const& j) - { - TORRENT_ASSERT(j.action >= 0 && j.action < int(sizeof(action_flags))); - return action_flags[j.action] & cancel_on_abort; - } - - bool is_read_operation(disk_io_job const& j) - { - TORRENT_ASSERT(j.action >= 0 && j.action < int(sizeof(action_flags))); - return action_flags[j.action] & read_operation; - } - - bool operation_has_buffer(disk_io_job const& j) - { - TORRENT_ASSERT(j.action >= 0 && j.action < int(sizeof(action_flags))); - return action_flags[j.action] & buffer_operation; - } - - void disk_io_thread::thread_fun() - { -#ifdef TORRENT_DISK_STATS - m_log.open("disk_io_thread.log", std::ios::trunc); -#endif - - // figure out how much physical RAM there is in - // this machine. This is used for automatically - // sizing the disk cache size when it's set to - // automatic. -#ifdef TORRENT_BSD -#ifdef HW_MEMSIZE - int mib[2] = { CTL_HW, HW_MEMSIZE }; -#else - // not entirely sure this sysctl supports 64 - // bit return values, but it's probably better - // than not building - int mib[2] = { CTL_HW, HW_PHYSMEM }; -#endif - size_t len = sizeof(m_physical_ram); - if (sysctl(mib, 2, &m_physical_ram, &len, NULL, 0) != 0) - m_physical_ram = 0; -#elif defined TORRENT_WINDOWS - MEMORYSTATUSEX ms; - ms.dwLength = sizeof(MEMORYSTATUSEX); - if (GlobalMemoryStatusEx(&ms)) - m_physical_ram = ms.ullTotalPhys; - else - m_physical_ram = 0; -#elif defined TORRENT_LINUX - m_physical_ram = sysconf(_SC_PHYS_PAGES); - m_physical_ram *= sysconf(_SC_PAGESIZE); -#elif defined TORRENT_AMIGA - m_physical_ram = AvailMem(MEMF_PUBLIC); -#endif - -#if TORRENT_USE_RLIMIT - if (m_physical_ram > 0) - { - struct rlimit r; - if (getrlimit(RLIMIT_AS, &r) == 0 && r.rlim_cur != RLIM_INFINITY) - { - if (m_physical_ram > r.rlim_cur) - m_physical_ram = r.rlim_cur; - } - } -#endif - // 1 = forward in list, -1 = backwards in list - int elevator_direction = 1; - - read_jobs_t::iterator elevator_job_pos = m_sorted_read_jobs.begin(); - size_type last_elevator_pos = 0; - bool need_update_elevator_pos = false; - int immediate_jobs_in_row = 0; - - for (;;) - { -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " idle" << std::endl; -#endif - - TORRENT_ASSERT(m_magic == 0x1337); - - mutex::scoped_lock jl(m_queue_mutex); - - if (m_queued_completions.size() >= 30 || (m_jobs.empty() && !m_queued_completions.empty())) - { - job_queue_t* q = new job_queue_t; - q->swap(m_queued_completions); - m_ios.post(boost::bind(completion_queue_handler, q)); - } - - - ptime job_start; - while (m_jobs.empty() && m_sorted_read_jobs.empty() && !m_abort) - { - // if there hasn't been an event in one second - // see if we should flush the cache -// if (!m_signal.timed_wait(jl, boost::posix_time::seconds(1))) -// flush_expired_pieces(); - m_signal.wait(jl); - m_signal.clear(jl); - - job_start = time_now(); - if (job_start >= m_last_stats_flip + seconds(1)) flip_stats(job_start); - } - - if (m_abort && m_jobs.empty()) - { - jl.unlock(); - - mutex::scoped_lock l(m_piece_mutex); - // flush all disk caches - cache_piece_index_t& widx = m_pieces.get<0>(); - for (cache_piece_index_t::iterator i = widx.begin() - , end(widx.end()); i != end; ++i) - flush_range(const_cast(*i), 0, INT_MAX, l); - -#ifdef TORRENT_DISABLE_POOL_ALLOCATOR - // since we're aborting the thread, we don't actually - // need to free all the blocks individually. We can just - // clear the piece list and the memory will be freed when we - // destruct the m_pool. If we're not using a pool, we actually - // have to free everything individually though - cache_piece_index_t& idx = m_read_pieces.get<0>(); - for (cache_piece_index_t::iterator i = idx.begin() - , end(idx.end()); i != end; ++i) - free_piece(const_cast(*i), l); -#endif - - m_pieces.clear(); - m_read_pieces.clear(); - // release the io_service to allow the run() call to return - // we do this once we stop posting new callbacks to it. - m_work.reset(); - - TORRENT_ASSERT(m_magic == 0x1337); - - return; - } - - disk_io_job j; - - ptime now = time_now_hires(); - ptime operation_start = now; - - // make sure we don't starve out the read queue by just issuing - // write jobs constantly, mix in a read job every now and then - // with a configurable ratio - // this rate must increase to every other jobs if the queued - // up read jobs increases too far. - int read_job_every = m_settings.read_job_every; - - int unchoke_limit = m_settings.unchoke_slots_limit; - if (unchoke_limit < 0) unchoke_limit = 100; - - if (int(m_sorted_read_jobs.size()) > unchoke_limit * 2) - { - int range = unchoke_limit; - int exceed = m_sorted_read_jobs.size() - range * 2; - read_job_every = (exceed * 1 + (range - exceed) * read_job_every) / 2; - if (read_job_every < 1) read_job_every = 1; - } - - bool pick_read_job = m_jobs.empty() - || (immediate_jobs_in_row >= read_job_every - && !m_sorted_read_jobs.empty()); - - if (!pick_read_job) - { - // we have a job in the job queue. If it's - // a read operation and we are allowed to - // reorder jobs, sort it into the read job - // list and continue, otherwise just pop it - // and use it later - j = m_jobs.front(); - m_jobs.pop_front(); - if (j.action == disk_io_job::write) - { - TORRENT_ASSERT(m_queue_buffer_size >= j.buffer_size); - m_queue_buffer_size -= j.buffer_size; - - if (m_exceeded_write_queue) - { - int low_watermark = m_settings.max_queued_disk_bytes_low_watermark == 0 - || m_settings.max_queued_disk_bytes_low_watermark >= m_settings.max_queued_disk_bytes - ? size_type(m_settings.max_queued_disk_bytes) * 7 / 8 - : m_settings.max_queued_disk_bytes_low_watermark; - - if (m_queue_buffer_size < low_watermark - || m_settings.max_queued_disk_bytes == 0) - { - m_exceeded_write_queue = false; - // we just dropped below the high watermark of number of bytes - // queued for writing to the disk. Notify the session so that it - // can trigger all the connections waiting for this event - if (m_queue_callback) m_ios.post(m_queue_callback); - } - } - } - - jl.unlock(); - - bool defer = false; - - if (is_read_operation(j)) - { - defer = true; - - // at this point the operation we're looking - // at is a read operation. If this read operation - // can be fully satisfied by the read cache, handle - // it immediately - if (m_settings.use_read_cache) - { -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " check_cache_hit" << std::endl; -#endif - // unfortunately we need to lock the cache - // if the cache querying function would be - // made asyncronous, this would not be - // necessary anymore - mutex::scoped_lock l(m_piece_mutex); - cache_piece_index_t::iterator p - = find_cached_piece(m_read_pieces, j, l); - - cache_piece_index_t& idx = m_read_pieces.get<0>(); - // if it's a cache hit, process the job immediately - if (p != idx.end() && is_cache_hit(const_cast(*p), j, l)) - defer = false; - } - } - - if (m_settings.use_disk_read_ahead && defer) - { - j.storage->hint_read_impl(j.piece, j.offset, j.buffer_size); - } - - TORRENT_ASSERT(j.offset >= 0); - if (m_settings.allow_reordered_disk_operations && defer) - { -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " sorting_job" << std::endl; -#endif - ptime sort_start = time_now_hires(); - - size_type phys_off = j.storage->physical_offset(j.piece, j.offset); - need_update_elevator_pos = need_update_elevator_pos || m_sorted_read_jobs.empty(); - m_sorted_read_jobs.insert(std::pair(phys_off, j)); - - ptime now = time_now_hires(); - m_sort_time.add_sample(total_microseconds(now - sort_start)); - m_job_time.add_sample(total_microseconds(now - operation_start)); - m_cache_stats.cumulative_sort_time += total_milliseconds(now - sort_start); - m_cache_stats.cumulative_job_time += total_milliseconds(now - operation_start); - continue; - } - - ++immediate_jobs_in_row; - } - else - { - // the job queue is empty, pick the next read job - // from the sorted job list. So we don't need the - // job queue lock anymore - jl.unlock(); - - immediate_jobs_in_row = 0; - - TORRENT_ASSERT(!m_sorted_read_jobs.empty()); - - // if m_sorted_read_jobs used to be empty, - // we need to update the elevator position - if (need_update_elevator_pos) - { - elevator_job_pos = m_sorted_read_jobs.lower_bound(last_elevator_pos); - need_update_elevator_pos = false; - } - - // if we've reached the end, change the elevator direction - if (elevator_job_pos == m_sorted_read_jobs.end()) - { - elevator_direction = -1; - --elevator_job_pos; - } - TORRENT_ASSERT(!m_sorted_read_jobs.empty()); - - TORRENT_ASSERT(elevator_job_pos != m_sorted_read_jobs.end()); - j = elevator_job_pos->second; - read_jobs_t::iterator to_erase = elevator_job_pos; - - // if we've reached the begining of the sorted list, - // change the elvator direction - if (elevator_job_pos == m_sorted_read_jobs.begin()) - elevator_direction = 1; - - // move the elevator before erasing the job we're processing - // to keep the iterator valid - if (elevator_direction > 0) ++elevator_job_pos; - else --elevator_job_pos; - - TORRENT_ASSERT(to_erase != elevator_job_pos); - last_elevator_pos = to_erase->first; - m_sorted_read_jobs.erase(to_erase); - } - - m_queue_time.add_sample(total_microseconds(now - j.start_time)); - - // if there's a buffer in this job, it will be freed - // when this holder is destructed, unless it has been - // released. - disk_buffer_holder holder(*this - , operation_has_buffer(j) ? j.buffer : 0); - - flush_expired_pieces(); - - int ret = 0; - - TORRENT_ASSERT(j.storage - || j.action == disk_io_job::abort_thread - || j.action == disk_io_job::update_settings); -#ifdef TORRENT_DISK_STATS - ptime start = time_now(); -#endif - - if (j.cache_min_time < 0) - j.cache_min_time = j.cache_min_time == 0 ? m_settings.default_cache_min_age - : (std::max)(m_settings.default_cache_min_age, j.cache_min_time); - - TORRENT_TRY - { - - if (j.storage && j.storage->get_storage_impl()->m_settings == 0) - j.storage->get_storage_impl()->m_settings = &m_settings; - - switch (j.action) - { - case disk_io_job::update_settings: - { -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " update_settings " << std::endl; -#endif - TORRENT_ASSERT(j.buffer); - session_settings const* s = ((session_settings*)j.buffer); - TORRENT_ASSERT(s->cache_size >= -1); - TORRENT_ASSERT(s->cache_expiry > 0); - -#if defined TORRENT_WINDOWS - if (m_settings.low_prio_disk != s->low_prio_disk) - { - m_file_pool.set_low_prio_io(s->low_prio_disk); - // we need to close all files, since the prio - // only takes affect when files are opened - m_file_pool.release(0); - } -#endif - m_settings = *s; - delete s; - - m_file_pool.resize(m_settings.file_pool_size); -#if defined __APPLE__ && defined __MACH__ && MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 - setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD - , m_settings.low_prio_disk ? IOPOL_THROTTLE : IOPOL_DEFAULT); -#elif defined IOPRIO_WHO_PROCESS - syscall(ioprio_set, IOPRIO_WHO_PROCESS, getpid(), IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE - , m_settings.get_bool(settings_pack::low_prio_disk) ? 7: 0)); -#endif - if (m_settings.cache_size == -1) - { - // the cache size is set to automatic. Make it - // depend on the amount of physical RAM - // if we don't know how much RAM we have, just set the - // cache size to 16 MiB (1024 blocks) - if (m_physical_ram == 0) - m_settings.cache_size = 1024; - else - m_settings.cache_size = m_physical_ram / 8 / m_block_size; - } - break; - } - case disk_io_job::abort_torrent: - { -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " abort_torrent " << std::endl; -#endif - mutex::scoped_lock jl(m_queue_mutex); - for (std::deque::iterator i = m_jobs.begin(); - i != m_jobs.end();) - { - if (i->storage != j.storage) - { - ++i; - continue; - } - if (should_cancel_on_abort(*i)) - { - if (i->action == disk_io_job::write) - { - TORRENT_ASSERT(m_queue_buffer_size >= i->buffer_size); - m_queue_buffer_size -= i->buffer_size; - } - post_callback(*i, -3); - i = m_jobs.erase(i); - continue; - } - ++i; - } - // now clear all the read jobs - for (read_jobs_t::iterator i = m_sorted_read_jobs.begin(); - i != m_sorted_read_jobs.end();) - { - if (i->second.storage != j.storage) - { - ++i; - continue; - } - post_callback(i->second, -3); - if (elevator_job_pos == i) ++elevator_job_pos; - m_sorted_read_jobs.erase(i++); - } - jl.unlock(); - - mutex::scoped_lock l(m_piece_mutex); - - // build a vector of all the buffers we need to free - // and free them all in one go - std::vector buffers; - for (cache_t::iterator i = m_read_pieces.begin(); - i != m_read_pieces.end();) - { - if (i->storage == j.storage) - { - drain_piece_bufs(const_cast(*i), buffers, l); - i = m_read_pieces.erase(i); - } - else - { - ++i; - } - } - l.unlock(); - if (!buffers.empty()) free_multiple_buffers(&buffers[0], buffers.size()); - release_memory(); - break; - } - case disk_io_job::abort_thread: - { -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " abort_thread " << std::endl; -#endif - // clear all read jobs - mutex::scoped_lock jl(m_queue_mutex); - - for (std::deque::iterator i = m_jobs.begin(); - i != m_jobs.end();) - { - if (should_cancel_on_abort(*i)) - { - if (i->action == disk_io_job::write) - { - TORRENT_ASSERT(m_queue_buffer_size >= i->buffer_size); - m_queue_buffer_size -= i->buffer_size; - } - post_callback(*i, -3); - i = m_jobs.erase(i); - continue; - } - ++i; - } - jl.unlock(); - - for (read_jobs_t::iterator i = m_sorted_read_jobs.begin(); - i != m_sorted_read_jobs.end();) - { - if (i->second.storage != j.storage) - { - ++i; - continue; - } - post_callback(i->second, -3); - if (elevator_job_pos == i) ++elevator_job_pos; - m_sorted_read_jobs.erase(i++); - } - - m_abort = true; - break; - } - case disk_io_job::read_and_hash: - { -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " read_and_hash " << j.buffer_size << std::endl; -#endif - INVARIANT_CHECK; - TORRENT_ASSERT(j.buffer == 0); - j.buffer = allocate_buffer("send buffer"); - TORRENT_ASSERT(j.buffer_size <= m_block_size); - if (j.buffer == 0) - { - ret = -1; -#if BOOST_VERSION == 103500 - j.error = error_code(boost::system::posix_error::not_enough_memory - , get_posix_category()); -#elif BOOST_VERSION > 103500 - j.error = error_code(boost::system::errc::not_enough_memory - , get_posix_category()); -#else - j.error = error::no_memory; -#endif - j.str.clear(); - break; - } - - disk_buffer_holder read_holder(*this, j.buffer); - - // read the entire piece and verify the piece hash - // since we need to check the hash, this function - // will ignore the cache size limit (at least for - // reading and hashing, not for keeping it around) - sha1_hash h; - ret = read_piece_from_cache_and_hash(j, h); - - // -2 means there's no space in the read cache - // or that the read cache is disabled - if (ret == -1) - { - test_error(j); - break; - } - if (!m_settings.disable_hash_checks) - ret = (j.storage->info()->hash_for_piece(j.piece) == h)?ret:-3; - if (ret == -3) - { - j.storage->mark_failed(j.piece); - j.error = errors::failed_hash_check; - j.str.clear(); - j.buffer = 0; - break; - } - - TORRENT_ASSERT(j.buffer == read_holder.get()); - read_holder.release(); -#if TORRENT_DISK_STATS - rename_buffer(j.buffer, "released send buffer"); -#endif - break; - } -#ifndef TORRENT_NO_DEPRECATE - case disk_io_job::finalize_file: - break; -#endif - case disk_io_job::read: - { - if (test_error(j)) - { - ret = -1; - break; - } -#ifdef TORRENT_DISK_STATS - m_log << log_time(); -#endif - INVARIANT_CHECK; - if (j.buffer == 0) j.buffer = allocate_buffer("send buffer"); - TORRENT_ASSERT(j.buffer_size <= m_block_size); - if (j.buffer == 0) - { -#ifdef TORRENT_DISK_STATS - m_log << " read 0" << std::endl; -#endif - ret = -1; -#if BOOST_VERSION == 103500 - j.error = error_code(boost::system::posix_error::not_enough_memory - , get_posix_category()); -#elif BOOST_VERSION > 103500 - j.error = error_code(boost::system::errc::not_enough_memory - , get_posix_category()); -#else - j.error = error::no_memory; -#endif - j.str.clear(); - break; - } - - disk_buffer_holder read_holder(*this, j.buffer); - - bool hit; - ret = try_read_from_cache(j, hit); - -#ifdef TORRENT_DISK_STATS - m_log << (hit?" read-cache-hit ":" read ") << j.buffer_size << std::endl; -#endif - // -2 means there's no space in the read cache - // or that the read cache is disabled - if (ret == -1) - { - j.buffer = 0; - test_error(j); - break; - } - else if (ret == -2) - { - file::iovec_t b = { j.buffer, size_t(j.buffer_size) }; - ret = j.storage->read_impl(&b, j.piece, j.offset, 1); - if (ret < 0) - { - test_error(j); - break; - } - if (ret != j.buffer_size) - { - char msg[70]; - snprintf(msg, sizeof(msg), "reading p: %d o: %d s: %d (read: %d)", j.piece, j.offset, j.buffer_size, ret); - - // this means the file wasn't big enough for this read - j.buffer = 0; - j.error = errors::file_too_short; - j.error_file = msg; - j.str.clear(); - ret = -1; - break; - } - ++m_cache_stats.blocks_read; - hit = false; - } - if (!hit) - { - ptime now = time_now_hires(); - m_read_time.add_sample(total_microseconds(now - operation_start)); - m_cache_stats.cumulative_read_time += total_milliseconds(now - operation_start); - } - TORRENT_ASSERT(j.buffer == read_holder.get()); - read_holder.release(); -#if TORRENT_DISK_STATS - rename_buffer(j.buffer, "released send buffer"); -#endif - break; - } - case disk_io_job::write: - { -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " write " << j.buffer_size << std::endl; -#endif - mutex::scoped_lock l(m_piece_mutex); - INVARIANT_CHECK; - - TORRENT_ASSERT(!j.storage->error()); - TORRENT_ASSERT(j.cache_min_time >= 0); - - if (in_use() >= m_settings.cache_size) - { - flush_cache_blocks(l, in_use() - m_settings.cache_size + 1); - if (test_error(j)) break; - } - TORRENT_ASSERT(!j.storage->error()); - - cache_piece_index_t& idx = m_pieces.get<0>(); - cache_piece_index_t::iterator p = find_cached_piece(m_pieces, j, l); - int block = j.offset / m_block_size; - TORRENT_ASSERT(j.buffer); - TORRENT_ASSERT(j.buffer_size <= m_block_size); - int piece_size = j.storage->info()->piece_size(j.piece); - int blocks_in_piece = (piece_size + m_block_size - 1) / m_block_size; - if (p != idx.end()) - { - bool recalc_contiguous = false; - TORRENT_ASSERT(p->blocks[block].buf == 0); - if (p->blocks[block].buf) - { - free_buffer(p->blocks[block].buf); - --m_cache_stats.cache_size; - --const_cast(*p).num_blocks; - } - else if ((block > 0 && p->blocks[block-1].buf) - || (block < blocks_in_piece-1 && p->blocks[block+1].buf) - || p->num_blocks == 0) - { - // update the contiguous blocks counter for this piece. Only if it has - // an adjacent block. If it doesn't, we already know it couldn't have - // increased the largest contiguous block span in this piece - recalc_contiguous = true; - } - p->blocks[block].buf = j.buffer; - p->blocks[block].callback.swap(j.callback); -#ifdef TORRENT_DISK_STATS - rename_buffer(j.buffer, "write cache"); -#endif - ++m_cache_stats.cache_size; - ++const_cast(*p).num_blocks; - if (recalc_contiguous) - { - const_cast(*p).num_contiguous_blocks = contiguous_blocks(*p); - } - idx.modify(p, update_last_use(j.cache_min_time)); - // we might just have created a contiguous range - // that meets the requirement to be flushed. try it - // if we're in avoid_readback mode, don't do this. Only flush - // pieces when we need more space in the cache (which will avoid - // flushing blocks out-of-order) or when we issue a hash job, - // wich indicates the piece is completely downloaded - flush_contiguous_blocks(const_cast(*p) - , l, m_settings.write_cache_line_size - , m_settings.disk_cache_algorithm == session_settings::avoid_readback); - - if (p->num_blocks == 0 && p->next_block_to_hash == 0) idx.erase(p); - test_error(j); - TORRENT_ASSERT(!j.storage->error()); - } - else - { - TORRENT_ASSERT(!j.storage->error()); - if (cache_block(j, j.callback, j.cache_min_time, l) < 0) - { - l.unlock(); - ptime start = time_now_hires(); - file::iovec_t iov = {j.buffer, size_t(j.buffer_size) }; - ret = j.storage->write_impl(&iov, j.piece, j.offset, 1); - l.lock(); - if (ret < 0) - { - test_error(j); - break; - } - ptime done = time_now_hires(); - m_write_time.add_sample(total_microseconds(done - start)); - m_cache_stats.cumulative_write_time += total_milliseconds(done - start); - // we successfully wrote the block. Ignore previous errors - j.storage->clear_error(); - break; - } - TORRENT_ASSERT(!j.storage->error()); - } - // we've now inserted the buffer - // in the cache, we should not - // free it at the end - holder.release(); - - if (in_use() > m_settings.cache_size) - { - flush_cache_blocks(l, in_use() - m_settings.cache_size); - test_error(j); - } - TORRENT_ASSERT(!j.storage->error()); - - break; - } - case disk_io_job::cache_piece: - { - mutex::scoped_lock l(m_piece_mutex); - - if (test_error(j)) - { - ret = -1; - break; - } -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " cache " << j.piece << std::endl; -#endif - INVARIANT_CHECK; - TORRENT_ASSERT(j.buffer == 0); - - cache_piece_index_t::iterator p; - bool hit; - ret = cache_piece(j, p, hit, 0, l); - if (ret == -2) ret = -1; - - if (ret < 0) test_error(j); - break; - } - case disk_io_job::hash: - { -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " hash" << std::endl; -#endif - TORRENT_ASSERT(!j.storage->error()); - mutex::scoped_lock l(m_piece_mutex); - INVARIANT_CHECK; - - cache_piece_index_t& idx = m_pieces.get<0>(); - cache_piece_index_t::iterator i = find_cached_piece(m_pieces, j, l); - if (i != idx.end()) - { - TORRENT_ASSERT(i->storage); - ret = flush_range(const_cast(*i), 0, INT_MAX, l); - idx.erase(i); - if (test_error(j)) - { - ret = -1; - j.storage->mark_failed(j.piece); - break; - } - } - l.unlock(); - if (m_settings.disable_hash_checks) - { - ret = 0; - break; - } - - ptime hash_start = time_now_hires(); - - int readback = 0; - sha1_hash h = j.storage->hash_for_piece_impl(j.piece, &readback); - if (test_error(j)) - { - ret = -1; - j.storage->mark_failed(j.piece); - break; - } - - m_cache_stats.total_read_back += readback / m_block_size; - - ret = (j.storage->info()->hash_for_piece(j.piece) == h)?0:-2; - if (ret == -2) j.storage->mark_failed(j.piece); - - ptime done = time_now_hires(); - m_hash_time.add_sample(total_microseconds(done - hash_start)); - m_cache_stats.cumulative_hash_time += total_milliseconds(done - hash_start); - break; - } - case disk_io_job::move_storage: - { -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " move" << std::endl; -#endif - TORRENT_ASSERT(j.buffer == 0); - ret = j.storage->move_storage_impl(j.str, j.piece); - if (ret == piece_manager::file_exist) - { - j.error = error_code(boost::system::errc::file_exists, get_system_category()); - j.error_file = -1; - j.buffer = NULL; - break; - } - if (ret != piece_manager::no_error && ret != piece_manager::need_full_check) - { - test_error(j); - break; - } - j.str = j.storage->save_path(); - break; - } - case disk_io_job::release_files: - { -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " release" << std::endl; -#endif - TORRENT_ASSERT(j.buffer == 0); - - mutex::scoped_lock l(m_piece_mutex); - INVARIANT_CHECK; - - for (cache_t::iterator i = m_pieces.begin(); i != m_pieces.end();) - { - if (i->storage == j.storage) - { - flush_range(const_cast(*i), 0, INT_MAX, l); - i = m_pieces.erase(i); - } - else - { - ++i; - } - } - l.unlock(); - release_memory(); - - ret = j.storage->release_files_impl(); - if (ret != 0) test_error(j); - break; - } - case disk_io_job::clear_read_cache: - { -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " clear-cache" << std::endl; -#endif - TORRENT_ASSERT(j.buffer == 0); - - mutex::scoped_lock l(m_piece_mutex); - INVARIANT_CHECK; - - for (cache_t::iterator i = m_read_pieces.begin(); - i != m_read_pieces.end();) - { - if (i->storage == j.storage) - { - free_piece(const_cast(*i), l); - i = m_read_pieces.erase(i); - } - else - { - ++i; - } - } - l.unlock(); - release_memory(); - ret = 0; - break; - } - case disk_io_job::delete_files: - { -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " delete" << std::endl; -#endif - TORRENT_ASSERT(j.buffer == 0); - - mutex::scoped_lock l(m_piece_mutex); - INVARIANT_CHECK; - - // delete all write cache entries for this storage - cache_piece_index_t& idx = m_pieces.get<0>(); - cache_piece_index_t::iterator start = idx.lower_bound(std::pair(j.storage.get(), 0)); - cache_piece_index_t::iterator end = idx.upper_bound(std::pair(j.storage.get(), INT_MAX)); - - // build a vector of all the buffers we need to free - // and free them all in one go - std::vector buffers; - torrent_info const& ti = *j.storage->info(); - for (cache_piece_index_t::iterator i = start; i != end; ++i) - { - int blocks_in_piece = (ti.piece_size(i->piece) + m_block_size - 1) / m_block_size; - cached_piece_entry& e = const_cast(*i); - for (int j = 0; j < blocks_in_piece; ++j) - { - if (i->blocks[j].buf == 0) continue; - buffers.push_back(i->blocks[j].buf); - i->blocks[j].buf = 0; - --m_cache_stats.cache_size; - TORRENT_ASSERT(e.num_blocks > 0); - --e.num_blocks; - } - TORRENT_ASSERT(i->num_blocks == 0); - } - idx.erase(start, end); - l.unlock(); - if (!buffers.empty()) free_multiple_buffers(&buffers[0], buffers.size()); - release_memory(); - - ret = j.storage->delete_files_impl(); - if (ret != 0) test_error(j); - break; - } - case disk_io_job::check_fastresume: - { -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " check_fastresume" << std::endl; -#endif - lazy_entry const* rd = (lazy_entry const*)j.buffer; - TORRENT_ASSERT(rd != 0); - ret = j.storage->check_fastresume(*rd, j.error); - test_error(j); - break; - } - case disk_io_job::check_files: - { -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " check_files" << std::endl; -#endif - int piece_size = j.storage->info()->piece_length(); - for (int processed = 0; processed < 4 * 1024 * 1024; processed += piece_size) - { - ptime now = time_now_hires(); - TORRENT_ASSERT(now >= m_last_file_check); - // this happens sometimes on windows for some reason - if (now < m_last_file_check) now = m_last_file_check; - -#if BOOST_VERSION > 103600 - if (now - m_last_file_check < milliseconds(m_settings.file_checks_delay_per_block)) - { - int sleep_time = m_settings.file_checks_delay_per_block - * (piece_size / (16 * 1024)) - - total_milliseconds(now - m_last_file_check); - if (sleep_time < 0) sleep_time = 0; - TORRENT_ASSERT(sleep_time < 5 * 1000); - - sleep(sleep_time); - } - m_last_file_check = time_now_hires(); -#endif - - ptime hash_start = time_now_hires(); - if (m_waiting_to_shutdown) break; - - ret = j.storage->check_files(j.piece, j.offset, j.error); - - ptime done = time_now_hires(); - m_hash_time.add_sample(total_microseconds(done - hash_start)); - m_cache_stats.cumulative_hash_time += total_milliseconds(done - hash_start); - - TORRENT_TRY { - TORRENT_ASSERT(j.callback); - if (j.callback && ret == piece_manager::need_full_check) - post_callback(j, ret); - } TORRENT_CATCH(std::exception&) {} - if (ret != piece_manager::need_full_check) break; - } - if (test_error(j)) - { - ret = piece_manager::fatal_disk_error; - break; - } - TORRENT_ASSERT(ret != -2 || j.error); - - // if the check is not done, add it at the end of the job queue - if (ret == piece_manager::need_full_check) - { - // offset needs to be reset to 0 so that the disk - // job sorting can be done correctly - j.offset = 0; - add_job(j, j.callback); - continue; - } - break; - } - case disk_io_job::save_resume_data: - { -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " save_resume_data" << std::endl; -#endif - j.resume_data.reset(new entry(entry::dictionary_t)); - j.storage->write_resume_data(*j.resume_data); - ret = 0; - break; - } - case disk_io_job::rename_file: - { -#ifdef TORRENT_DISK_STATS - m_log << log_time() << " rename_file" << std::endl; -#endif - ret = j.storage->rename_file_impl(j.piece, j.str); - if (ret != 0) - { - test_error(j); - } - break; - } - case disk_io_job::file_priority: - { - std::vector* p - = reinterpret_cast*>(j.buffer); - j.storage->set_file_priority_impl(*p); - delete p; - ret = 0; - break; - } - } - } - TORRENT_CATCH(std::exception& e) - { - TORRENT_DECLARE_DUMMY(std::exception, e); - ret = -1; - TORRENT_TRY { - j.str = e.what(); - } TORRENT_CATCH(std::exception&) {} - } - - TORRENT_ASSERT(!j.storage || !j.storage->error()); - - ptime done = time_now_hires(); - m_job_time.add_sample(total_microseconds(done - operation_start)); - m_cache_stats.cumulative_job_time += total_milliseconds(done - operation_start); - -// if (!j.callback) std::cerr << "DISK THREAD: no callback specified" << std::endl; -// else std::cerr << "DISK THREAD: invoking callback" << std::endl; - TORRENT_TRY { - TORRENT_ASSERT(ret != -2 || j.error - || j.action == disk_io_job::hash); -#if TORRENT_DISK_STATS - if ((j.action == disk_io_job::read || j.action == disk_io_job::read_and_hash) - && j.buffer != 0) - rename_buffer(j.buffer, "posted send buffer"); -#endif - post_callback(j, ret); - } TORRENT_CATCH(std::exception&) { - TORRENT_ASSERT(false); - } - } - TORRENT_ASSERT(false); - } + } diff --git a/src/disk_job_pool.cpp b/src/disk_job_pool.cpp new file mode 100644 index 000000000..7c21fac73 --- /dev/null +++ b/src/disk_job_pool.cpp @@ -0,0 +1,110 @@ +/* + +Copyright (c) 2012-2013, 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. + +*/ + +#include "libtorrent/disk_job_pool.hpp" +#include "libtorrent/disk_io_job.hpp" + +namespace libtorrent +{ + disk_job_pool::disk_job_pool() + : m_jobs_in_use(0) + , m_read_jobs(0) + , m_write_jobs(0) + , m_job_pool(sizeof(disk_io_job)) + {} + + disk_job_pool::~disk_job_pool() + { +// #error this should be fixed! +// TORRENT_ASSERT(m_jobs_in_use == 0); + } + + disk_io_job* disk_job_pool::allocate_job(int type) + { + mutex::scoped_lock l(m_job_mutex); + disk_io_job* ptr = (disk_io_job*)m_job_pool.malloc(); + m_job_pool.set_next_size(100); + if (ptr == 0) return 0; + ++m_jobs_in_use; + if (type == disk_io_job::read) ++m_read_jobs; + else if (type == disk_io_job::write) ++m_write_jobs; + l.unlock(); + TORRENT_ASSERT(ptr); + + new (ptr) disk_io_job; + ptr->action = (disk_io_job::action_t)type; +#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS + ptr->in_use = true; +#endif + return ptr; + } + + void disk_job_pool::free_job(disk_io_job* j) + { + TORRENT_ASSERT(j); + if (j == 0) return; +#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS + TORRENT_ASSERT(j->in_use); + j->in_use = false; +#endif + int type = j->action; + j->~disk_io_job(); + mutex::scoped_lock l(m_job_mutex); + if (type == disk_io_job::read) --m_read_jobs; + else if (type == disk_io_job::write) --m_write_jobs; + --m_jobs_in_use; + m_job_pool.free(j); + } + + void disk_job_pool::free_jobs(disk_io_job** j, int num) + { + if (num == 0) return; + + int read_jobs = 0; + int write_jobs = 0; + for (int i = 0; i < num; ++i) + { + int type = j[i]->action; + j[i]->~disk_io_job(); + if (type == disk_io_job::read) ++read_jobs; + else if (type == disk_io_job::write) ++write_jobs; + } + + mutex::scoped_lock l(m_job_mutex); + m_read_jobs -= read_jobs; + m_write_jobs -= write_jobs; + m_jobs_in_use -= num; + for (int i = 0; i < num; ++i) + m_job_pool.free(j[i]); + } +} + diff --git a/src/enum_net.cpp b/src/enum_net.cpp index 8986339ef..4373318ac 100644 --- a/src/enum_net.cpp +++ b/src/enum_net.cpp @@ -38,6 +38,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/broadcast_socket.hpp" #include "libtorrent/error_code.hpp" #include "libtorrent/assert.hpp" +#include "libtorrent/socket_type.hpp" #if BOOST_VERSION < 103500 #include #else @@ -343,7 +344,12 @@ namespace libtorrent { std::vector net = enum_net_interfaces(ios, ec); if (ec) return false; - for (std::vector::iterator i = net.begin() + return in_local_network(net, addr); + } + + bool in_local_network(std::vector const& net, address const& addr) + { + for (std::vector::const_iterator i = net.begin() , end(net.end()); i != end; ++i) { if (match_addr_mask(addr, i->interface_address, i->netmask)) @@ -1055,6 +1061,28 @@ namespace libtorrent return ret; } + // returns true if the given device exists + bool has_interface(char const* name, io_service& ios, error_code& ec) + { + std::vector ifs = enum_net_interfaces(ios, ec); + if (ec) return false; + + for (int i = 0; i < int(ifs.size()); ++i) + if (ifs[i].name == name) return true; + return false; + } + + // returns the device name whose local address is ``addr``. If + // no such device is found, an empty string is returned. + std::string device_for_address(address addr, io_service& ios, error_code& ec) + { + std::vector ifs = enum_net_interfaces(ios, ec); + if (ec) return std::string(); + + for (int i = 0; i < int(ifs.size()); ++i) + if (ifs[i].interface_address == addr) return ifs[i].name; + return std::string(); + } } diff --git a/src/error_code.cpp b/src/error_code.cpp index b9091ef3f..6db477745 100644 --- a/src/error_code.cpp +++ b/src/error_code.cpp @@ -50,7 +50,7 @@ namespace libtorrent const char* libtorrent_error_category::name() const BOOST_SYSTEM_NOEXCEPT { - return "libtorrent error"; + return "libtorrent"; } std::string libtorrent_error_category::message(int ev) const BOOST_SYSTEM_NOEXCEPT @@ -171,7 +171,7 @@ namespace libtorrent "SSL connection required", "invalid SSL certificate", "not an SSL torrent", - "", + "banned by port filter", "", "", "", @@ -204,7 +204,7 @@ namespace libtorrent "invalid entry type in slot list", "invalid piece index in slot list", "pieces needs to be reordered", - "", + "fastresume not modified since last save", "", "", "", diff --git a/src/escape_string.cpp b/src/escape_string.cpp index e6400314d..83ec59e9e 100644 --- a/src/escape_string.cpp +++ b/src/escape_string.cpp @@ -208,6 +208,15 @@ namespace libtorrent if (*i == '\\') *i = '/'; } +#ifdef TORRENT_WINDOWS + void convert_path_to_windows(std::string& path) + { + for (std::string::iterator i = path.begin() + , end(path.end()); i != end; ++i) + if (*i == '/') *i = '\\'; + } +#endif + std::string read_until(char const*& str, char delim, char const* end) { TORRENT_ASSERT(str <= end); @@ -244,6 +253,32 @@ namespace libtorrent return msg; } + std::string resolve_file_url(std::string const& url) + { + TORRENT_ASSERT(url.substr(0, 7) == "file://"); + // first, strip the file:// part. + // On windows, we have + // to strip the first / as well + int num_to_strip = 7; +#ifdef TORRENT_WINDOWS + if (url[7] == '/' || url[7] == '\\') ++num_to_strip; +#endif + std::string ret = url.substr(num_to_strip); + + // we also need to URL-decode it + error_code ec; + std::string unescaped = unescape_string(ret, ec); + if (ec) unescaped = ret; + + // on windows, we need to convert forward slashes + // to backslashes +#ifdef TORRENT_WINDOWS + convert_path_to_windows(unescaped); +#endif + + return unescaped; + } + std::string base64encode(const std::string& s) { static const char base64_table[] = diff --git a/src/file.cpp b/src/file.cpp index 3f5efad56..3200878ce 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -48,10 +48,26 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/alloca.hpp" #include "libtorrent/allocator.hpp" // page_size #include "libtorrent/escape_string.hpp" // for string conversion +#include "libtorrent/file.hpp" +#include +#include +#if TORRENT_DEBUG_FILE_LEAKS +#include +#include "libtorrent/thread.hpp" +#endif + +// for convert_to_wstring and convert_to_native +#include "libtorrent/escape_string.hpp" +#include +#include "libtorrent/assert.hpp" #include #include +#ifdef TORRENT_DISK_STATS +#include "libtorrent/io.hpp" +#endif + #include #ifdef TORRENT_WINDOWS @@ -78,7 +94,6 @@ POSSIBILITY OF SUCH DAMAGE. // posix part #include -#include // for F_LOG2PHYS #include #include #include @@ -95,12 +110,6 @@ POSSIBILITY OF SUCH DAMAGE. #endif #include -#include -#ifdef HAVE_LINUX_FIEMAP_H -#include // FIEMAP_* -#include // FS_IOC_FIEMAP -#endif - #ifdef TORRENT_ANDROID #include #define lseek lseek64 @@ -141,19 +150,147 @@ BOOST_STATIC_ASSERT(sizeof(lseek(0, 0, 0)) >= 8); #endif // posix part -#include "libtorrent/file.hpp" -#include -#include +#if TORRENT_USE_PREADV +# if defined TORRENT_WINDOWS +namespace +{ + // wrap the windows function in something that looks + // like preadv() and pwritev() -// for convert_to_wstring and convert_to_native -#include "libtorrent/escape_string.hpp" -#include -#include "libtorrent/assert.hpp" + int preadv(HANDLE fd, libtorrent::file::iovec_t const* bufs, int num_bufs, libtorrent::size_type file_offset) + { + OVERLAPPED* ol = TORRENT_ALLOCA(OVERLAPPED, num_bufs); + memset(ol, 0, sizeof(OVERLAPPED) * num_bufs); + + HANDLE* h = TORRENT_ALLOCA(HANDLE, num_bufs); + + for (int i = 0; i < num_bufs; ++i) + { + ol[i].OffsetHigh = file_offset >> 32; + ol[i].Offset = file_offset & 0xffffffff; + ol[i].hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + h[i] = ol[i].hEvent; + if (h[i] == NULL) + { + // we failed to create the event, roll-back and return an error + for (int j = 0; j < i; ++j) CloseHandle(h[i]); + return -1; + } + file_offset += bufs[i].iov_len; + } + + int ret = 0; + for (int i = 0; i < num_bufs; ++i) + { + DWORD num_read; + if (ReadFile(fd, bufs[i].iov_base, bufs[i].iov_len, &num_read, &ol[i]) == FALSE + && GetLastError() != ERROR_IO_PENDING +#ifdef ERROR_CANT_WAIT + && GetLastError() != ERROR_CANT_WAIT +#endif + ) + { + ret = -1; + goto done; + } + } + + WaitForMultipleObjects(num_bufs, h, TRUE, INFINITE); + + for (int i = 0; i < num_bufs; ++i) + { + WaitForSingleObject(ol[i].hEvent, INFINITE); + DWORD num_read; + if (GetOverlappedResult(fd, &ol[i], &num_read, FALSE) == FALSE) + { +#ifdef ERROR_CANT_WAIT + TORRENT_ASSERT(GetLastError() != ERROR_CANT_WAIT); +#endif + ret = -1; + break; + } + ret += num_read; + } +done: + + for (int i = 0; i < num_bufs; ++i) + CloseHandle(h[i]); + + return ret; + } + + int pwritev(HANDLE fd, libtorrent::file::iovec_t const* bufs, int num_bufs, libtorrent::size_type file_offset) + { + OVERLAPPED* ol = TORRENT_ALLOCA(OVERLAPPED, num_bufs); + memset(ol, 0, sizeof(OVERLAPPED) * num_bufs); + + HANDLE* h = TORRENT_ALLOCA(HANDLE, num_bufs); + + for (int i = 0; i < num_bufs; ++i) + { + ol[i].OffsetHigh = file_offset >> 32; + ol[i].Offset = file_offset & 0xffffffff; + ol[i].hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + h[i] = ol[i].hEvent; + if (h[i] == NULL) + { + // we failed to create the event, roll-back and return an error + for (int j = 0; j < i; ++j) CloseHandle(h[i]); + return -1; + } + file_offset += bufs[i].iov_len; + } + + int ret = 0; + for (int i = 0; i < num_bufs; ++i) + { + DWORD num_written; + if (WriteFile(fd, bufs[i].iov_base, bufs[i].iov_len, &num_written, &ol[i]) == FALSE + && GetLastError() != ERROR_IO_PENDING +#ifdef ERROR_CANT_WAIT + && GetLastError() != ERROR_CANT_WAIT +#endif + ) + { + ret = -1; + goto done; + } + } + + WaitForMultipleObjects(num_bufs, h, TRUE, INFINITE); + + for (int i = 0; i < num_bufs; ++i) + { + WaitForSingleObject(ol[i].hEvent, INFINITE); + DWORD num_written; + if (GetOverlappedResult(fd, &ol[i], &num_written, FALSE) == FALSE) + { +#ifdef ERROR_CANT_WAIT + TORRENT_ASSERT(GetLastError() != ERROR_CANT_WAIT); +#endif + ret = -1; + break; + } + ret += num_written; + } +done: + + for (int i = 0; i < num_bufs; ++i) + CloseHandle(h[i]); + + return ret; + } +} +# else +# define _BSD_SOURCE +# include +# endif +#endif #ifdef TORRENT_DEBUG -BOOST_STATIC_ASSERT((libtorrent::file::rw_mask & libtorrent::file::no_buffer) == 0); +BOOST_STATIC_ASSERT((libtorrent::file::rw_mask & libtorrent::file::sparse) == 0); BOOST_STATIC_ASSERT((libtorrent::file::rw_mask & libtorrent::file::attribute_mask) == 0); -BOOST_STATIC_ASSERT((libtorrent::file::no_buffer & libtorrent::file::attribute_mask) == 0); +BOOST_STATIC_ASSERT((libtorrent::file::sparse & libtorrent::file::attribute_mask) == 0); #endif #ifdef TORRENT_WINDOWS @@ -164,7 +301,6 @@ BOOST_STATIC_ASSERT((libtorrent::file::no_buffer & libtorrent::file::attribute_m namespace libtorrent { - #ifdef TORRENT_WINDOWS std::string convert_separators(std::string p) { @@ -206,7 +342,7 @@ namespace libtorrent WIN32_FILE_ATTRIBUTE_DATA data; if (!GetFileAttributesEx(f.c_str(), GetFileExInfoStandard, &data)) { - ec.assign(GetLastError(), boost::system::get_system_category()); + ec.assign(GetLastError(), boost::system::system_category()); return; } @@ -304,7 +440,7 @@ namespace libtorrent #ifdef TORRENT_WINDOWS if (CreateDirectory_(n.c_str(), 0) == 0 && GetLastError() != ERROR_ALREADY_EXISTS) - ec.assign(GetLastError(), boost::system::get_system_category()); + ec.assign(GetLastError(), boost::system::system_category()); #else int ret = mkdir(n.c_str(), 0777); if (ret < 0 && errno != EEXIST) @@ -359,7 +495,7 @@ namespace libtorrent #ifdef TORRENT_WINDOWS if (CopyFile_(f1.c_str(), f2.c_str(), false) == 0) - ec.assign(GetLastError(), boost::system::get_system_category()); + ec.assign(GetLastError(), boost::system::system_category()); #elif defined __APPLE__ && defined __MACH__ && MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 // this only works on 10.5 copyfile_state_t state = copyfile_state_alloc(); @@ -564,6 +700,19 @@ namespace libtorrent return std::string(f.c_str(), len); } + char const* filename_cstr(char const* f) + { + if (f == 0) return f; + + char const* sep = strrchr(f, '/'); +#ifdef TORRENT_WINDOWS + char const* altsep = strrchr(f, '\\'); + if (sep == 0 || altsep > sep) sep = altsep; +#endif + if (sep == 0) return f; + return sep+1; + } + std::string filename(std::string const& f) { if (f.empty()) return ""; @@ -750,7 +899,7 @@ namespace libtorrent if (RemoveDirectory_(f.c_str()) != 0) return; } - ec.assign(GetLastError(), boost::system::get_system_category()); + ec.assign(GetLastError(), boost::system::system_category()); return; } #else // TORRENT_WINDOWS @@ -832,7 +981,7 @@ namespace libtorrent m_handle = FindFirstFile_(p.c_str(), &m_fd); if (m_handle == INVALID_HANDLE_VALUE) { - ec.assign(GetLastError(), boost::system::get_system_category()); + ec.assign(GetLastError(), boost::system::system_category()); m_done = true; return; } @@ -906,7 +1055,7 @@ namespace libtorrent m_done = true; int err = GetLastError(); if (err != ERROR_NO_MORE_FILES) - ec.assign(err, boost::system::get_system_category()); + ec.assign(err, boost::system::system_category()); } ++m_inode; #else @@ -920,6 +1069,10 @@ namespace libtorrent #endif } +#ifndef INVALID_HANDLE_VALUE +#define INVALID_HANDLE_VALUE -1 +#endif + #ifdef TORRENT_WINDOWS struct overlapped_t { @@ -938,7 +1091,7 @@ namespace libtorrent if (ol.hEvent != INVALID_HANDLE_VALUE && WaitForSingleObject(ol.hEvent, INFINITE) == WAIT_FAILED) { - ec.assign(GetLastError(), get_system_category()); + ec.assign(GetLastError(), system_category()); return -1; } @@ -951,7 +1104,7 @@ namespace libtorrent #ifdef ERROR_CANT_WAIT TORRENT_ASSERT(last_error != ERROR_CANT_WAIT); #endif - ec.assign(last_error, get_system_category()); + ec.assign(last_error, system_category()); return -1; } } @@ -971,25 +1124,27 @@ namespace libtorrent #endif file::file() -#ifdef TORRENT_WINDOWS : m_file_handle(INVALID_HANDLE_VALUE) -#else - : m_fd(-1) -#endif , m_open_mode(0) #if defined TORRENT_WINDOWS || defined TORRENT_LINUX , m_sector_size(0) #endif - {} + { +#ifdef TORRENT_DISK_STATS + m_file_id = 0; +#endif + } file::file(std::string const& path, int mode, error_code& ec) -#ifdef TORRENT_WINDOWS : m_file_handle(INVALID_HANDLE_VALUE) -#else - : m_fd(-1) -#endif , m_open_mode(0) +#if defined TORRENT_WINDOWS || defined TORRENT_LINUX + , m_sector_size(0) +#endif { +#ifdef TORRENT_DISK_STATS + m_file_id = 0; +#endif // the return value is not important, since the // error code contains the same information open(path, mode, ec); @@ -1000,9 +1155,31 @@ namespace libtorrent close(); } +#ifdef TORRENT_DISK_STATS + boost::uint32_t silly_hash(std::string const& str) + { + boost::uint32_t ret = 1; + for (int i = 0; i < str.size(); ++i) + { + if (str[i] == 0) continue; + ret *= int(str[i]); + } + return ret; + } +#endif + bool file::open(std::string const& path, int mode, error_code& ec) { close(); + +#if TORRENT_DEBUG_FILE_LEAKS + m_file_path = path; +#endif + +#ifdef TORRENT_DISK_STATS + m_file_id = silly_hash(path); +#endif + #ifdef TORRENT_WINDOWS struct open_mode_t @@ -1063,34 +1240,36 @@ namespace libtorrent // turns out that it isn't. That flag will break your operating system: // http://support.microsoft.com/kb/2549369 - DWORD flags - = ((mode & random_access) ? 0 : FILE_FLAG_SEQUENTIAL_SCAN) + DWORD flags = ((mode & random_access) ? 0 : FILE_FLAG_SEQUENTIAL_SCAN) | (a ? a : FILE_ATTRIBUTE_NORMAL) - | ((mode & no_buffer) ? FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING : 0); + | FILE_FLAG_OVERLAPPED + | ((mode & direct_io) ? FILE_FLAG_NO_BUFFERING : 0) + | ((mode & no_cache) ? FILE_FLAG_WRITE_THROUGH : 0); - m_file_handle = CreateFile_(m_path.c_str(), m.rw_mode + handle_type handle = CreateFile_(m_path.c_str(), m.rw_mode , (mode & lock_file) ? 0 : share_array[mode & rw_mask] , 0, m.create_mode, flags, 0); - if (m_file_handle == INVALID_HANDLE_VALUE) + if (handle == INVALID_HANDLE_VALUE) { - ec.assign(GetLastError(), get_system_category()); + ec.assign(GetLastError(), system_category()); TORRENT_ASSERT(ec); return false; } + m_file_handle = handle; + // try to make the file sparse if supported // only set this flag if the file is opened for writing if ((mode & file::sparse) && (mode & rw_mask) != read_only) { DWORD temp; - bool use_overlapped = (m_open_mode & no_buffer) != 0; overlapped_t ol; - BOOL ret = ::DeviceIoControl(m_file_handle, FSCTL_SET_SPARSE, 0, 0 - , 0, 0, &temp, use_overlapped ? &ol.ol : NULL); + BOOL ret = ::DeviceIoControl(native_handle(), FSCTL_SET_SPARSE, 0, 0 + , 0, 0, &temp, &ol.ol); error_code error; - if (use_overlapped && ret == FALSE && GetLastError() == ERROR_IO_PENDING) - ol.wait(m_file_handle, error); + if (ret == FALSE && GetLastError() == ERROR_IO_PENDING) + ol.wait(native_handle(), error); } #else // TORRENT_WINDOWS @@ -1107,60 +1286,40 @@ namespace libtorrent #else static const int mode_array[] = {O_RDONLY, O_WRONLY | O_CREAT, O_RDWR | O_CREAT}; #endif -#ifdef O_DIRECT - static const int no_buffer_flag[] = {0, O_DIRECT}; -#else - static const int no_buffer_flag[] = {0, 0}; -#endif #ifdef O_NOATIME static const int no_atime_flag[] = {0, O_NOATIME}; #endif - m_fd = ::open(convert_to_native(path).c_str() + handle_type handle = ::open(convert_to_native(path).c_str() , mode_array[mode & rw_mask] - | no_buffer_flag[(mode & no_buffer) >> 2] #ifdef O_NOATIME - | no_atime_flag[(mode & no_atime) >> 4] + | ((mode & no_atime) ? O_NOATIME : 0) +#endif +#ifdef O_DIRECT + | ((mode & direct_io) ? O_DIRECT : 0) #endif , permissions); -#ifdef TORRENT_LINUX - // workaround for linux bug - // https://bugs.launchpad.net/ubuntu/+source/linux/+bug/269946 - if (m_fd == -1 && (mode & no_buffer) && errno == EINVAL) - { - mode &= ~no_buffer; - m_fd = ::open(path.c_str() - , mode_array[mode & rw_mask] -#ifdef O_NOATIME - | no_atime_flag[(mode & no_atime) >> 4] -#endif - - , permissions); - } -#endif - #ifdef O_NOATIME // O_NOATIME is not allowed for files we don't own // so, if we get EPERM when we try to open with it // try again without O_NOATIME - if (m_fd == -1 && (mode & no_atime) && errno == EPERM) + if (handle == -1 && (mode & no_atime) && errno == EPERM) { mode &= ~no_atime; - m_fd = ::open(path.c_str() - , mode_array[mode & rw_mask] - | no_buffer_flag[(mode & no_buffer) >> 2] - , permissions); + handle = ::open(path.c_str(), mode_array[mode & rw_mask], permissions); } #endif - if (m_fd == -1) + if (handle == -1) { ec.assign(errno, get_posix_category()); TORRENT_ASSERT(ec); return false; } + m_file_handle = handle; + // The purpose of the lock_file flag is primarily to prevent other // processes from corrupting files that are being used by libtorrent. // the posix file locking mechanism does not prevent others from @@ -1169,19 +1328,24 @@ namespace libtorrent #ifdef DIRECTIO_ON // for solaris - if (mode & no_buffer) + if (mode & no_cache) { int yes = 1; - directio(m_fd, DIRECTIO_ON); + directio(native_handle(), DIRECTIO_ON); } #endif #ifdef F_NOCACHE // for BSD/Mac - if (mode & no_buffer) + if (mode & no_cache) { int yes = 1; - fcntl(m_fd, F_NOCACHE, &yes); + fcntl(native_handle(), F_NOCACHE, &yes); + +#ifdef F_NODIRECT + // it's OK to temporarily cache written pages + fcntl(native_handle(), F_NODIRECT, &yes); +#endif } #endif @@ -1189,7 +1353,7 @@ namespace libtorrent if (mode & random_access) { // disable read-ahead - posix_fadvise(m_fd, 0, 0, POSIX_FADV_RANDOM); + posix_fadvise(native_handle(), 0, 0, POSIX_FADV_RANDOM); } #endif @@ -1200,85 +1364,23 @@ namespace libtorrent return true; } +#if TORRENT_DEBUG_FILE_LEAKS + void file::print_info(FILE* out) const + { + if (!is_open()) return; + fprintf(out, "\n===> FILE: %s\n", m_file_path.c_str()); + } +#endif + bool file::is_open() const { -#ifdef TORRENT_WINDOWS return m_file_handle != INVALID_HANDLE_VALUE; -#else - return m_fd != -1; -#endif - } - - int file::pos_alignment() const - { - // on linux and windows, file offsets needs - // to be aligned to the disk sector size -#if defined TORRENT_LINUX - if (m_sector_size == 0) - { - struct statvfs fs; - if (fstatvfs(m_fd, &fs) == 0) - m_sector_size = fs.f_bsize; - else - m_sector_size = 4096; - } - return m_sector_size; -#elif defined TORRENT_WINDOWS - if (m_sector_size == 0) - { - DWORD sectors_per_cluster; - DWORD bytes_per_sector; - DWORD free_clusters; - DWORD total_clusters; -#if TORRENT_USE_WSTRING -#define GetDiskFreeSpace_ GetDiskFreeSpaceW - wchar_t backslash = L'\\'; -#else -#define GetDiskFreeSpace_ GetDiskFreeSpaceA - char backslash = '\\'; -#endif - if (GetDiskFreeSpace_(m_path.substr(0, m_path.find_first_of(backslash)+1).c_str() - , §ors_per_cluster, &bytes_per_sector - , &free_clusters, &total_clusters)) - { - m_sector_size = bytes_per_sector; - m_cluster_size = sectors_per_cluster * bytes_per_sector; - } - else - { - // make a conservative guess - m_sector_size = 512; - m_cluster_size = 4096; - } - } - return m_sector_size; -#else - return 1; -#endif - } - - int file::buf_alignment() const - { -#if defined TORRENT_WINDOWS - init_file(); - return m_page_size; -#else - return pos_alignment(); -#endif - } - - int file::size_alignment() const - { -#if defined TORRENT_WINDOWS - init_file(); - return m_page_size; -#else - return pos_alignment(); -#endif } #ifdef TORRENT_WINDOWS - bool is_sparse(HANDLE file, bool overlapped) + // returns true if the given file has any regions that are + // sparse, i.e. not allocated. + bool is_sparse(HANDLE file) { LARGE_INTEGER file_size; if (!GetFileSizeEx(file, &file_size)) @@ -1302,9 +1404,9 @@ typedef struct _FILE_ALLOCATED_RANGE_BUFFER { DWORD returned_bytes = 0; BOOL ret = DeviceIoControl(file, FSCTL_QUERY_ALLOCATED_RANGES, (void*)&in, sizeof(in) - , out, sizeof(out), &returned_bytes, overlapped ? &ol.ol : NULL); + , out, sizeof(out), &returned_bytes, &ol.ol); - if (overlapped && ret == FALSE && GetLastError() == ERROR_IO_PENDING) + if (ret == FALSE && GetLastError() == ERROR_IO_PENDING) { error_code ec; returned_bytes = ol.wait(file, ec); @@ -1327,657 +1429,315 @@ typedef struct _FILE_ALLOCATED_RANGE_BUFFER { void file::close() { +#ifdef TORRENT_DISK_STATS + m_file_id = 0; +#endif #if defined TORRENT_WINDOWS || defined TORRENT_LINUX m_sector_size = 0; #endif + if (!is_open()) return; + #ifdef TORRENT_WINDOWS - if (m_file_handle == INVALID_HANDLE_VALUE) return; // if this file is open for writing, has the sparse // flag set, but there are no sparse regions, unset // the flag int rw_mode = m_open_mode & rw_mask; - bool use_overlapped = (m_open_mode & no_buffer) != 0; if ((rw_mode != read_only) && (m_open_mode & sparse) - && !is_sparse(m_file_handle, use_overlapped)) + && !is_sparse(native_handle())) { overlapped_t ol; // according to MSDN, clearing the sparse flag of a file only // works on windows vista and later #ifdef TORRENT_MINGW - typedef struct _FILE_SET_SPARSE_BUFFER { - BOOLEAN SetSparse; - } FILE_SET_SPARSE_BUFFER, *PFILE_SET_SPARSE_BUFFER; + typedef struct _FILE_SET_SPARSE_BUFFER { + BOOLEAN SetSparse; + } FILE_SET_SPARSE_BUFFER, *PFILE_SET_SPARSE_BUFFER; #endif DWORD temp; FILE_SET_SPARSE_BUFFER b; b.SetSparse = FALSE; - BOOL ret = ::DeviceIoControl(m_file_handle, FSCTL_SET_SPARSE, &b, sizeof(b) - , 0, 0, &temp, use_overlapped ? &ol.ol : NULL); + BOOL ret = ::DeviceIoControl(native_handle(), FSCTL_SET_SPARSE, &b, sizeof(b) + , 0, 0, &temp, &ol.ol); error_code ec; - if (use_overlapped && ret == FALSE && GetLastError() == ERROR_IO_PENDING) + if (ret == FALSE && GetLastError() == ERROR_IO_PENDING) { - ol.wait(m_file_handle, ec); + ol.wait(native_handle(), ec); } } - CloseHandle(m_file_handle); - m_file_handle = INVALID_HANDLE_VALUE; + CloseHandle(native_handle()); m_path.clear(); #else - if (m_fd == -1) return; - ::close(m_fd); - m_fd = -1; + if (m_file_handle != INVALID_HANDLE_VALUE) + ::close(m_file_handle); #endif + + m_file_handle = INVALID_HANDLE_VALUE; + m_open_mode = 0; } // defined in storage.cpp int bufs_size(file::iovec_t const* bufs, int num_bufs); -#if defined TORRENT_WINDOWS || defined TORRENT_LINUX || defined TORRENT_DEBUG - - int file::m_page_size = 0; - - void file::init_file() + void gather_copy(file::iovec_t const* bufs, int num_bufs, char* dst) { - if (m_page_size != 0) return; - - m_page_size = page_size(); + int offset = 0; + for (int i = 0; i < num_bufs; ++i) + { + // TODO: 2 use vm_copy here, if available, and if buffers are aligned + memcpy(dst + offset, bufs[i].iov_base, bufs[i].iov_len); + offset += bufs[i].iov_len; + } } -#endif - - void file::hint_read(size_type file_offset, int len) + void scatter_copy(file::iovec_t const* bufs, int num_bufs, char const* src) { -#if defined POSIX_FADV_WILLNEED - posix_fadvise(m_fd, file_offset, len, POSIX_FADV_WILLNEED); -#elif defined F_RDADVISE - radvisory r; - r.ra_offset = file_offset; - r.ra_count = len; - fcntl(m_fd, F_RDADVISE, &r); -#else - // TODO: is there any way to pre-fetch data from a file on windows? -#endif + int offset = 0; + for (int i = 0; i < num_bufs; ++i) + { + // TODO: 2 use vm_copy here, if available, and if buffers are aligned + memcpy(bufs[i].iov_base, src + offset, bufs[i].iov_len); + offset += bufs[i].iov_len; + } } - size_type file::readv(size_type file_offset, iovec_t const* bufs, int num_bufs, error_code& ec) + bool coalesce_read_buffers(file::iovec_t const*& bufs, int& num_bufs, file::iovec_t* tmp) { + int buf_size = bufs_size(bufs, num_bufs); + // this is page aligned since it's used in APIs which + // are likely to require that (depending on OS) + char* buf = (char*)page_aligned_allocator::malloc(buf_size); + if (!buf) return false; + tmp->iov_base = buf; + tmp->iov_len = buf_size; + bufs = tmp; + num_bufs = 1; + return true; + } + + void coalesce_read_buffers_end(file::iovec_t const* bufs, int num_bufs, char* buf, bool copy) + { + if (copy) scatter_copy(bufs, num_bufs, buf); + page_aligned_allocator::free(buf); + } + + bool coalesce_write_buffers(file::iovec_t const*& bufs, int& num_bufs, file::iovec_t* tmp) + { + // coalesce buffers means allocate a temporary buffer and + // issue a single write operation instead of using a vector + // operation + int buf_size = 0; + for (int i = 0; i < num_bufs; ++i) buf_size += bufs[i].iov_len; + char* buf = (char*)page_aligned_allocator::malloc(buf_size); + if (!buf) return false; + gather_copy(bufs, num_bufs, buf); + tmp->iov_base = buf; + tmp->iov_len = buf_size; + bufs = tmp; + num_bufs = 1; + return true; + } + + template + size_type iov(Fun f, handle_type fd, size_type file_offset, file::iovec_t const* bufs_in + , int num_bufs_in, error_code& ec) + { + file::iovec_t const* bufs = bufs_in; + int num_bufs = num_bufs_in; + +#if TORRENT_USE_PREADV + + int ret = 0; + while (num_bufs > 0) + { + int nbufs = (std::min)(num_bufs, TORRENT_IOV_MAX); + int tmp_ret = 0; + tmp_ret = f(fd, bufs, nbufs, file_offset); + if (tmp_ret < 0) + { #ifdef TORRENT_WINDOWS + ec.assign(GetLastError(), system_category()); +#else + ec.assign(errno, get_posix_category()); +#endif + return -1; + } + file_offset += tmp_ret; + ret += tmp_ret; + + num_bufs -= nbufs; + bufs += nbufs; + } + return ret; + +#elif TORRENT_USE_PREAD + + int ret = 0; + for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i < end; ++i) + { + int tmp_ret = f(fd, i->iov_base, i->iov_len, file_offset); + if (tmp_ret < 0) + { +#ifdef TORRENT_WINDOWS + ec.assign(last_error, system_category()); +#else + ec.assign(errno, get_posix_category()); +#endif + return -1; + } + file_offset += tmp_ret; + ret += tmp_ret; + if (tmp_ret < int(i->iov_len)) break; + } + + return ret; + +#else + + int ret = 0; + +#ifdef TORRENT_WINDOWS + if (SetFilePointerEx(fd, offs, &offs, FILE_BEGIN) == FALSE) + { + ec.assign(GetLastError(), system_category()); + return -1; + } +#else + if (lseek(fd, file_offset, SEEK_SET) < 0) + { + ec.assign(errno, get_posix_category()); + return -1; + } +#endif + + for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i < end; ++i) + { + int tmp_ret = f(fd, i->iov_base, i->iov_len); + if (tmp_ret < 0) + { +#ifdef TORRENT_WINDOWS + ec.assign(GetLastError(), system_category()); +#else + ec.assign(errno, get_posix_category()); +#endif + return -1; + } + file_offset += tmp_ret; + ret += tmp_ret; + if (tmp_ret < int(i->iov_len)) break; + } + + return ret; + +#endif + } + + // this has to be thread safe and atomic. i.e. on posix systems it has to be + // turned into a series of pread() calls + size_type file::readv(size_type file_offset, iovec_t const* bufs, int num_bufs + , error_code& ec, int flags) + { if (m_file_handle == INVALID_HANDLE_VALUE) { - ec = error_code(ERROR_INVALID_HANDLE, get_system_category()); - return -1; - } +#ifdef TORRENT_WINDOWS + ec = error_code(ERROR_INVALID_HANDLE, system_category()); #else - if (m_fd == -1) - { - ec = error_code(EBADF, get_system_category()); + ec = error_code(EBADF, system_category()); +#endif return -1; } -#endif TORRENT_ASSERT((m_open_mode & rw_mask) == read_only || (m_open_mode & rw_mask) == read_write); TORRENT_ASSERT(bufs); TORRENT_ASSERT(num_bufs > 0); TORRENT_ASSERT(is_open()); -#if defined TORRENT_WINDOWS || defined TORRENT_LINUX || defined TORRENT_DEBUG - // make sure m_page_size is initialized - init_file(); -#endif +#if TORRENT_USE_PREADV + int ret = iov(&::preadv, native_handle(), file_offset, bufs, num_bufs, ec); +#else -#ifdef TORRENT_DEBUG - if (m_open_mode & no_buffer) + file::iovec_t tmp; + if (flags & file::coalesce_buffers) { - bool eof = false; - int size = 0; - // when opened in no_buffer mode, the file_offset must - // be aligned to pos_alignment() - TORRENT_ASSERT((file_offset & (pos_alignment()-1)) == 0); - for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i < end; ++i) - { - TORRENT_ASSERT((uintptr_t(i->iov_base) & (buf_alignment()-1)) == 0); - // every buffer must be a multiple of the page size - // except for the last one - TORRENT_ASSERT((i->iov_len & (size_alignment()-1)) == 0 || i == end-1); - if ((i->iov_len & (size_alignment()-1)) != 0) eof = true; - size += i->iov_len; - } - error_code code; - if (eof) - { - size_type fsize = get_size(code); - if (code) printf("get_size: %s\n", code.message().c_str()); - if (file_offset + size < fsize) - { - printf("offset: %d size: %d get_size: %d\n", int(file_offset), int(size), int(fsize)); - TORRENT_ASSERT(false); - } - } - } -#endif - -#ifdef TORRENT_WINDOWS - - DWORD ret = 0; - - // since the ReadFileScatter requires the file to be opened - // with no buffering, and no buffering requires page aligned - // buffers, open the file in non-buffered mode in case the - // buffer is not aligned. Most of the times the buffer should - // be aligned though - - if ((m_open_mode & no_buffer) == 0) - { - // this means the buffer base or the buffer size is not aligned - // to the page size. Use a regular file for this operation. - - LARGE_INTEGER offs; - offs.QuadPart = file_offset; - if (SetFilePointerEx(m_file_handle, offs, &offs, FILE_BEGIN) == FALSE) - { - ec.assign(GetLastError(), get_system_category()); - return -1; - } - - for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i < end; ++i) - { - DWORD intermediate = 0; - if (ReadFile(m_file_handle, (char*)i->iov_base - , (DWORD)i->iov_len, &intermediate, 0) == FALSE) - { - ec.assign(GetLastError(), get_system_category()); - return -1; - } - ret += intermediate; - } - return ret; + if (!coalesce_read_buffers(bufs, num_bufs, &tmp)) + // ok, that failed, don't coalesce this read + flags &= ~file::coalesce_buffers; } - int size = bufs_size(bufs, num_bufs); - // number of pages for the read. round up - int num_pages = (size + m_page_size - 1) / m_page_size; - // allocate array of FILE_SEGMENT_ELEMENT for ReadFileScatter - FILE_SEGMENT_ELEMENT* segment_array = TORRENT_ALLOCA(FILE_SEGMENT_ELEMENT, num_pages + 1); -#ifdef __GNUC__ - // MingW seems to have issues with 64 bit wide pointers - // (PVOID64) and only assign the low 32 bits. Therefore, make - // sure the other 32 bits are cleared out - memset(segment_array, 0, (num_pages + 1) * sizeof(FILE_SEGMENT_ELEMENT)); +#if TORRENT_USE_PREAD + int ret = iov(&::pread, native_handle(), file_offset, bufs, num_bufs, ec); +#else + int ret = iov(&::read, native_handle(), file_offset, bufs, num_bufs, ec); #endif - FILE_SEGMENT_ELEMENT* cur_seg = segment_array; - for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i < end; ++i) - { - for (int k = 0; k < int(i->iov_len); k += m_page_size) - { - cur_seg->Buffer = PtrToPtr64((((char*)i->iov_base) + k)); - ++cur_seg; - } - } - // terminate the array - cur_seg->Buffer = 0; + if (flags & file::coalesce_buffers) + coalesce_read_buffers_end(bufs, num_bufs, (char*)tmp.iov_base, !ec); - OVERLAPPED ol; - memset(&ol, 0, sizeof(ol)); - ol.Internal = 0; - ol.InternalHigh = 0; - ol.OffsetHigh = DWORD(file_offset >> 32); - ol.Offset = DWORD(file_offset & 0xffffffff); - ol.hEvent = CreateEvent(0, true, false, 0); - if (ol.hEvent == NULL) - { - ec.assign(GetLastError(), get_system_category()); - return -1; - } - - ret += size; - size = num_pages * m_page_size; - if (ReadFileScatter(m_file_handle, segment_array, size, 0, &ol) == 0) - { - DWORD last_error = GetLastError(); - if (last_error != ERROR_IO_PENDING -#ifdef ERROR_CANT_WAIT - && last_error != ERROR_CANT_WAIT #endif -) - { - ec.assign(last_error, get_system_category()); - CloseHandle(ol.hEvent); - return -1; - } - if (WaitForSingleObject(ol.hEvent, INFINITE) == WAIT_FAILED) - { - ec.assign(GetLastError(), get_system_category()); - CloseHandle(ol.hEvent); - return -1; - } - DWORD num_read; - if (GetOverlappedResult(m_file_handle, &ol, &num_read, false) == 0) - { - DWORD last_error = GetLastError(); - if (last_error != ERROR_HANDLE_EOF) - { -#ifdef ERROR_CANT_WAIT - TORRENT_ASSERT(last_error != ERROR_CANT_WAIT); -#endif - ec.assign(last_error, get_system_category()); - CloseHandle(ol.hEvent); - return -1; - } - } - if (num_read < ret) ret = num_read; - } - CloseHandle(ol.hEvent); return ret; - -#else // TORRENT_WINDOWS - - size_type ret = lseek(m_fd, file_offset, SEEK_SET); - if (ret < 0) - { - ec.assign(errno, get_posix_category()); - return -1; - } -#if TORRENT_USE_READV - - ret = 0; - while (num_bufs > 0) - { - int nbufs = (std::min)(num_bufs, TORRENT_IOV_MAX); - int tmp_ret = 0; -#ifdef TORRENT_LINUX - bool aligned = false; - int size = 0; - // if we're not opened in no-buffer mode, we don't need alignment - if ((m_open_mode & no_buffer) == 0) aligned = true; - if (!aligned) - { - size = bufs_size(bufs, nbufs); - if ((size & (size_alignment()-1)) == 0) aligned = true; - } - if (aligned) -#endif // TORRENT_LINUX - { - tmp_ret = ::readv(m_fd, bufs, nbufs); - if (tmp_ret < 0) - { - ec.assign(errno, get_posix_category()); - return -1; - } - ret += tmp_ret; - } -#ifdef TORRENT_LINUX - else - { - file::iovec_t* temp_bufs = TORRENT_ALLOCA(file::iovec_t, nbufs); - memcpy(temp_bufs, bufs, sizeof(file::iovec_t) * nbufs); - iovec_t& last = temp_bufs[nbufs-1]; - last.iov_len = (last.iov_len & ~(size_alignment()-1)) + m_page_size; - tmp_ret = ::readv(m_fd, temp_bufs, nbufs); - if (tmp_ret < 0) - { - ec.assign(errno, get_posix_category()); - return -1; - } - ret += (std::min)(tmp_ret, size); - } -#endif // TORRENT_LINUX - - num_bufs -= nbufs; - bufs += nbufs; - } - - return ret; - -#else // TORRENT_USE_READV - - ret = 0; - for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i < end; ++i) - { - int tmp = read(m_fd, i->iov_base, i->iov_len); - if (tmp < 0) - { - ec.assign(errno, get_posix_category()); - return -1; - } - ret += tmp; - if (tmp < i->iov_len) break; - } - return ret; - -#endif // TORRENT_USE_READV - -#endif // TORRENT_WINDOWS } - size_type file::writev(size_type file_offset, iovec_t const* bufs, int num_bufs, error_code& ec) + // This has to be thread safe, i.e. atomic. + // that means, on posix this has to be turned into a series of + // pwrite() calls + size_type file::writev(size_type file_offset, iovec_t const* bufs, int num_bufs + , error_code& ec, int flags) { -#ifdef TORRENT_WINDOWS if (m_file_handle == INVALID_HANDLE_VALUE) { - ec = error_code(ERROR_INVALID_HANDLE, get_system_category()); - return -1; - } +#ifdef TORRENT_WINDOWS + ec = error_code(ERROR_INVALID_HANDLE, system_category()); #else - if (m_fd == -1) - { - ec = error_code(EBADF, get_system_category()); + ec = error_code(EBADF, system_category()); +#endif return -1; } -#endif TORRENT_ASSERT((m_open_mode & rw_mask) == write_only || (m_open_mode & rw_mask) == read_write); TORRENT_ASSERT(bufs); TORRENT_ASSERT(num_bufs > 0); TORRENT_ASSERT(is_open()); -#if defined TORRENT_WINDOWS || defined TORRENT_LINUX || defined TORRENT_DEBUG - // make sure m_page_size is initialized - init_file(); -#endif + ec.clear(); -#ifdef TORRENT_DEBUG - if (m_open_mode & no_buffer) - { - bool eof = false; - int size = 0; - // when opened in no_buffer mode, the file_offset must - // be aligned to pos_alignment() - TORRENT_ASSERT((file_offset & (pos_alignment()-1)) == 0); - for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i < end; ++i) - { - TORRENT_ASSERT((uintptr_t(i->iov_base) & (buf_alignment()-1)) == 0); - // every buffer must be a multiple of the page size - // except for the last one - TORRENT_ASSERT((i->iov_len & (size_alignment()-1)) == 0 || i == end-1); - if ((i->iov_len & (size_alignment()-1)) != 0) eof = true; - size += i->iov_len; - } - error_code code; - if (eof) TORRENT_ASSERT(file_offset + size >= get_size(code)); - } -#endif - -#ifdef TORRENT_WINDOWS - - DWORD ret = 0; - - // since the ReadFileScatter requires the file to be opened - // with no buffering, and no buffering requires page aligned - // buffers, open the file in non-buffered mode in case the - // buffer is not aligned. Most of the times the buffer should - // be aligned though - - if ((m_open_mode & no_buffer) == 0) - { - // this means the buffer base or the buffer size is not aligned - // to the page size. Use a regular file for this operation. - - LARGE_INTEGER offs; - offs.QuadPart = file_offset; - if (SetFilePointerEx(m_file_handle, offs, &offs, FILE_BEGIN) == FALSE) - { - ec.assign(GetLastError(), get_system_category()); - return -1; - } - - for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i < end; ++i) - { - DWORD intermediate = 0; - if (WriteFile(m_file_handle, (char const*)i->iov_base - , (DWORD)i->iov_len, &intermediate, 0) == FALSE) - { - ec.assign(GetLastError(), get_system_category()); - return -1; - } - ret += intermediate; - } - return ret; - } - - int size = bufs_size(bufs, num_bufs); - // number of pages for the write. round up - int num_pages = (size + m_page_size - 1) / m_page_size; - // allocate array of FILE_SEGMENT_ELEMENT for WriteFileGather - FILE_SEGMENT_ELEMENT* segment_array = TORRENT_ALLOCA(FILE_SEGMENT_ELEMENT, num_pages + 1); -#ifdef __GNUC__ - // MingW seems to have issues with 64 bit wide pointers - // (PVOID64) and only assign the low 32 bits. Therefore, make - // sure the other 32 bits are cleared out - memset(segment_array, 0, (num_pages + 1) * sizeof(FILE_SEGMENT_ELEMENT)); -#endif - FILE_SEGMENT_ELEMENT* cur_seg = segment_array; - - for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i < end; ++i) - { - for (int k = 0; k < int(i->iov_len); k += m_page_size) - { - cur_seg->Buffer = PtrToPtr64((((char*)i->iov_base) + k)); - ++cur_seg; - } - } - // terminate the array - cur_seg->Buffer = 0; - - OVERLAPPED ol; - memset(&ol, 0, sizeof(ol)); - ol.Internal = 0; - ol.InternalHigh = 0; - ol.OffsetHigh = DWORD(file_offset >> 32); - ol.Offset = DWORD(file_offset & 0xffffffff); - ol.hEvent = CreateEvent(0, true, false, 0); - if (ol.hEvent == NULL) - { - ec.assign(GetLastError(), get_system_category()); - return -1; - } - - ret += size; - size_type file_size = 0; - - if ((size & (m_page_size-1)) != 0) - { - // if size is not an even multiple, this must be the tail - // of the file. - - file_size = file_offset + size; - size = num_pages * m_page_size; - } - - if (WriteFileGather(m_file_handle, segment_array, size, 0, &ol) == 0) - { - DWORD last_error = GetLastError(); - if (last_error != ERROR_IO_PENDING -#ifdef ERROR_CANT_WAIT - && last_error != ERROR_CANT_WAIT -#endif - ) - { - TORRENT_ASSERT(last_error != ERROR_BAD_ARGUMENTS); - ec.assign(last_error, get_system_category()); - CloseHandle(ol.hEvent); - return -1; - } - if (WaitForSingleObject(ol.hEvent, INFINITE) == WAIT_FAILED) - { - ec.assign(GetLastError(), get_system_category()); - CloseHandle(ol.hEvent); - return -1; - } - DWORD num_written; - if (GetOverlappedResult(m_file_handle, &ol, &num_written, false) == 0) - { - DWORD last_error = GetLastError(); -#ifdef ERROR_CANT_WAIT - TORRENT_ASSERT(last_error != ERROR_CANT_WAIT); -#endif - ec.assign(last_error, get_system_category()); - CloseHandle(ol.hEvent); - return -1; - } - if (num_written < ret) ret = num_written; - } - CloseHandle(ol.hEvent); - if (file_size > 0) set_size(file_size, ec); - return ret; +#if TORRENT_USE_PREADV + int ret = iov(&::pwritev, native_handle(), file_offset, bufs, num_bufs, ec); #else - size_type ret = lseek(m_fd, file_offset, SEEK_SET); - if (ret < 0) + + file::iovec_t tmp; + if (flags & file::coalesce_buffers) { - ec.assign(errno, get_posix_category()); - return -1; + if (!coalesce_write_buffers(bufs, num_bufs, &tmp)) + // ok, that failed, don't coalesce writes + flags &= ~file::coalesce_buffers; } -#if TORRENT_USE_WRITEV - - ret = 0; - while (num_bufs > 0) - { - int nbufs = (std::min)(num_bufs, TORRENT_IOV_MAX); - int tmp_ret = 0; -#ifdef TORRENT_LINUX - bool aligned = false; - int size = 0; - // if we're not opened in no-buffer mode, we don't need alignment - if ((m_open_mode & no_buffer) == 0) aligned = true; - if (!aligned) - { - size = bufs_size(bufs, nbufs); - if ((size & (size_alignment()-1)) == 0) aligned = true; - } - if (aligned) +#if TORRENT_USE_PREAD + int ret = iov(&::pwrite, native_handle(), file_offset, bufs, num_bufs, ec); +#else + int ret = iov(&::write, native_handle(), file_offset, bufs, num_bufs, ec); #endif - { - tmp_ret = ::writev(m_fd, bufs, nbufs); - if (tmp_ret < 0) - { - ec.assign(errno, get_posix_category()); - return -1; - } - ret += tmp_ret; - } -#ifdef TORRENT_LINUX - else - { - file::iovec_t* temp_bufs = TORRENT_ALLOCA(file::iovec_t, nbufs); - memcpy(temp_bufs, bufs, sizeof(file::iovec_t) * nbufs); - iovec_t& last = temp_bufs[nbufs-1]; - last.iov_len = (last.iov_len & ~(size_alignment()-1)) + size_alignment(); - tmp_ret = ::writev(m_fd, temp_bufs, nbufs); - if (tmp_ret < 0) - { - ec.assign(errno, get_posix_category()); - return -1; - } - if (ftruncate(m_fd, file_offset + size) < 0) - { - ec.assign(errno, get_posix_category()); - return -1; - } - ret += (std::min)(tmp_ret, size); - } -#endif // TORRENT_LINUX - num_bufs -= nbufs; - bufs += nbufs; - } + if (flags & file::coalesce_buffers) + free(tmp.iov_base); - return ret; - -#else // TORRENT_USE_WRITEV - - ret = 0; - for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i < end; ++i) +#endif +#if TORRENT_HAVE_FDATASYNC \ + && !defined F_NOCACHE && \ + !defined DIRECTIO_ON + if (m_open_mode & no_cache) { - int tmp = write(m_fd, i->iov_base, i->iov_len); - if (tmp < 0) + if (fdatasync(native_handle()) != 0 + && errno != EINVAL + && errno != ENOSYS) { ec.assign(errno, get_posix_category()); - return -1; } - ret += tmp; - if (tmp < i->iov_len) break; } - return ret; - -#endif // TORRENT_USE_WRITEV - -#endif // TORRENT_WINDOWS - } - - size_type file::phys_offset(size_type offset) - { -#ifdef FIEMAP_EXTENT_UNKNOWN - // for documentation of this feature - // http://lwn.net/Articles/297696/ - struct - { - struct fiemap fiemap; - struct fiemap_extent extent; - } fm; - - memset(&fm, 0, sizeof(fm)); - fm.fiemap.fm_start = offset; - fm.fiemap.fm_length = size_alignment(); - // this sounds expensive - fm.fiemap.fm_flags = FIEMAP_FLAG_SYNC; - fm.fiemap.fm_extent_count = 1; - - if (ioctl(m_fd, FS_IOC_FIEMAP, &fm) == -1) - return 0; - - if (fm.fiemap.fm_extents[0].fe_flags & FIEMAP_EXTENT_UNKNOWN) - return 0; - - // the returned extent is not guaranteed to start - // at the requested offset, adjust for that in - // case they differ - TORRENT_ASSERT(offset >= fm.fiemap.fm_extents[0].fe_logical); - return fm.fiemap.fm_extents[0].fe_physical + (offset - fm.fiemap.fm_extents[0].fe_logical); - -#elif defined F_LOG2PHYS - // for documentation of this feature - // http://developer.apple.com/mac/library/documentation/Darwin/Reference/ManPages/man2/fcntl.2.html - - log2phys l; - size_type ret = lseek(m_fd, offset, SEEK_SET); - if (ret < 0) return 0; - if (fcntl(m_fd, F_LOG2PHYS, &l) == -1) return 0; - return l.l2p_devoffset; -#elif defined TORRENT_WINDOWS - // for documentation of this feature - // http://msdn.microsoft.com/en-us/library/aa364572(VS.85).aspx - STARTING_VCN_INPUT_BUFFER in; - RETRIEVAL_POINTERS_BUFFER out; - DWORD out_bytes; - - // query cluster size - pos_alignment(); - in.StartingVcn.QuadPart = offset / m_cluster_size; - int cluster_offset = int(in.StartingVcn.QuadPart % m_cluster_size); - - if (DeviceIoControl(m_file_handle, FSCTL_GET_RETRIEVAL_POINTERS, &in - , sizeof(in), &out, sizeof(out), &out_bytes, 0) == 0) - { - DWORD error = GetLastError(); - TORRENT_ASSERT(error != ERROR_INVALID_PARAMETER); - - // insufficient buffer error is expected, but we're - // only interested in the first extent anyway - if (error != ERROR_MORE_DATA) return 0; - } - if (out_bytes < sizeof(out)) return 0; - if (out.ExtentCount == 0) return 0; - if (out.Extents[0].Lcn.QuadPart == (LONGLONG)-1) return 0; - TORRENT_ASSERT(in.StartingVcn.QuadPart >= out.StartingVcn.QuadPart); - return (out.Extents[0].Lcn.QuadPart - + (in.StartingVcn.QuadPart - out.StartingVcn.QuadPart)) - * m_cluster_size + cluster_offset; #endif - return 0; + return ret; } #ifdef TORRENT_WINDOWS @@ -2087,67 +1847,11 @@ typedef struct _FILE_ALLOCATED_RANGE_BUFFER { #ifdef TORRENT_WINDOWS - if ((m_open_mode & no_buffer) && (s & (size_alignment()-1)) != 0) - { - // the file is opened in unbuffered mode, and the size is not - // aligned to the required cluster size. Use NtSetInformationFile - -#define FileEndOfFileInformation 20 -#ifndef NT_SUCCESS -#define NT_SUCCESS(x) (!((x) & 0x80000000)) -#endif - - // for NtSetInformationFile, see: - // http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/File/NtSetInformationFile.html - - typedef DWORD _NTSTATUS; - typedef _NTSTATUS (NTAPI * NtSetInformationFile_t)(HANDLE file, PULONG_PTR iosb, PVOID data, ULONG len, ULONG file_info_class); - - static NtSetInformationFile_t NtSetInformationFile = 0; - static bool failed_ntdll = false; - - if (NtSetInformationFile == 0 && !failed_ntdll) - { - HMODULE nt = LoadLibraryA("ntdll"); - if (nt) - { - NtSetInformationFile = (NtSetInformationFile_t)GetProcAddress(nt, "NtSetInformationFile"); - if (NtSetInformationFile == 0) failed_ntdll = true; - } - else failed_ntdll = true; - } - - if (!failed_ntdll && NtSetInformationFile) - { - ULONG_PTR Iosb[2]; - LARGE_INTEGER fsize; - fsize.QuadPart = s; - _NTSTATUS st = NtSetInformationFile(m_file_handle - , Iosb, &fsize, sizeof(fsize), FileEndOfFileInformation); - if (!NT_SUCCESS(st)) - { - ec.assign(INVALID_SET_FILE_POINTER, get_system_category()); - return false; - } - - if ((m_open_mode & sparse) == 0) - set_file_valid_data(m_file_handle, s); - - return true; - } - - // couldn't find ntdll or NtSetFileInformation function - // and the file is opened in unbuffered mode! There's - // nothing we can do! (short of re-opening the file, but - // that introduces all sorts of nasty race conditions) - return false; - } - LARGE_INTEGER offs; LARGE_INTEGER cur_size; - if (GetFileSizeEx(m_file_handle, &cur_size) == FALSE) + if (GetFileSizeEx(native_handle(), &cur_size) == FALSE) { - ec.assign(GetLastError(), get_system_category()); + ec.assign(GetLastError(), system_category()); return false; } offs.QuadPart = s; @@ -2156,14 +1860,14 @@ typedef struct _FILE_ALLOCATED_RANGE_BUFFER { // modification time if we don't have to if (cur_size.QuadPart != s) { - if (SetFilePointerEx(m_file_handle, offs, &offs, FILE_BEGIN) == FALSE) + if (SetFilePointerEx(native_handle(), offs, &offs, FILE_BEGIN) == FALSE) { - ec.assign(GetLastError(), get_system_category()); + ec.assign(GetLastError(), system_category()); return false; } - if (::SetEndOfFile(m_file_handle) == FALSE) + if (::SetEndOfFile(native_handle()) == FALSE) { - ec.assign(GetLastError(), get_system_category()); + ec.assign(GetLastError(), system_category()); return false; } } @@ -2207,7 +1911,7 @@ typedef struct _FILE_ALLOCATED_RANGE_BUFFER { offs.HighPart = high_dword; if (offs.LowPart == INVALID_FILE_SIZE) { - ec.assign(GetLastError(), get_system_category()); + ec.assign(GetLastError(), system_category()); if (ec) return false; } } @@ -2222,7 +1926,7 @@ typedef struct _FILE_ALLOCATED_RANGE_BUFFER { } #else // NON-WINDOWS struct stat st; - if (fstat(m_fd, &st) != 0) + if (fstat(native_handle(), &st) != 0) { ec.assign(errno, get_posix_category()); return false; @@ -2230,7 +1934,7 @@ typedef struct _FILE_ALLOCATED_RANGE_BUFFER { // only truncate the file if it doesn't already // have the right size. We don't want to update - if (st.st_size != s && ftruncate(m_fd, s) < 0) + if (st.st_size != s && ftruncate(native_handle(), s) < 0) { ec.assign(errno, get_posix_category()); return false; @@ -2250,7 +1954,7 @@ typedef struct _FILE_ALLOCATED_RANGE_BUFFER { // but if we don't do anything if the file size is #ifdef F_PREALLOCATE fstore_t f = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, s, 0}; - if (fcntl(m_fd, F_PREALLOCATE, &f) < 0) + if (fcntl(native_handle(), F_PREALLOCATE, &f) < 0) { if (errno != ENOSPC) { @@ -2259,7 +1963,7 @@ typedef struct _FILE_ALLOCATED_RANGE_BUFFER { } // ok, let's try to allocate non contiguous space then fstore_t f = {F_ALLOCATEALL, F_PEOFPOSMODE, 0, s, 0}; - if (fcntl(m_fd, F_PREALLOCATE, &f) < 0) + if (fcntl(native_handle(), F_PREALLOCATE, &f) < 0) { ec.assign(errno, get_posix_category()); return false; @@ -2285,7 +1989,7 @@ typedef struct _FILE_ALLOCATED_RANGE_BUFFER { #endif #if defined TORRENT_LINUX - ret = my_fallocate(m_fd, 0, 0, s); + ret = my_fallocate(native_handle(), 0, 0, s); // if we return 0, everything went fine // the fallocate call succeeded if (ret == 0) return true; @@ -2306,7 +2010,7 @@ typedef struct _FILE_ALLOCATED_RANGE_BUFFER { // which can be painfully slow // if you get a compile error here, you might want to // define TORRENT_HAS_FALLOCATE to 0. - ret = posix_fallocate(m_fd, 0, s); + ret = posix_fallocate(native_handle(), 0, s); // posix_allocate fails with EINVAL in case the underlying // filesystem does bot support this operation if (ret != 0 && ret != EINVAL) @@ -2324,15 +2028,15 @@ typedef struct _FILE_ALLOCATED_RANGE_BUFFER { { #ifdef TORRENT_WINDOWS LARGE_INTEGER file_size; - if (!GetFileSizeEx(m_file_handle, &file_size)) + if (!GetFileSizeEx(native_handle(), &file_size)) { - ec.assign(GetLastError(), get_system_category()); + ec.assign(GetLastError(), system_category()); return -1; } return file_size.QuadPart; #else struct stat fs; - if (fstat(m_fd, &fs) != 0) + if (fstat(native_handle(), &fs) != 0) { ec.assign(errno, get_posix_category()); return -1; @@ -2344,13 +2048,15 @@ typedef struct _FILE_ALLOCATED_RANGE_BUFFER { size_type file::sparse_end(size_type start) const { #ifdef TORRENT_WINDOWS + #ifdef TORRENT_MINGW typedef struct _FILE_ALLOCATED_RANGE_BUFFER { LARGE_INTEGER FileOffset; LARGE_INTEGER Length; } FILE_ALLOCATED_RANGE_BUFFER, *PFILE_ALLOCATED_RANGE_BUFFER; #define FSCTL_QUERY_ALLOCATED_RANGES ((0x9 << 16) | (1 << 14) | (51 << 2) | 3) -#endif +#endif // TORRENT_MINGW + FILE_ALLOCATED_RANGE_BUFFER buffer; DWORD bytes_returned = 0; FILE_ALLOCATED_RANGE_BUFFER in; @@ -2358,20 +2064,10 @@ typedef struct _FILE_ALLOCATED_RANGE_BUFFER { size_type file_size = get_size(ec); if (ec) return start; - if (m_open_mode & no_buffer) - { - boost::uint64_t mask = size_alignment()-1; - in.FileOffset.QuadPart = start & (~mask); - in.Length.QuadPart = ((file_size + mask) & ~mask) - in.FileOffset.QuadPart; - TORRENT_ASSERT((in.Length.QuadPart & mask) == 0); - } - else - { - in.FileOffset.QuadPart = start; - in.Length.QuadPart = file_size - start; - } + in.FileOffset.QuadPart = start; + in.Length.QuadPart = file_size - start; - if (!DeviceIoControl(m_file_handle, FSCTL_QUERY_ALLOCATED_RANGES + if (!DeviceIoControl(native_handle(), FSCTL_QUERY_ALLOCATED_RANGES , &in, sizeof(FILE_ALLOCATED_RANGE_BUFFER) , &buffer, sizeof(FILE_ALLOCATED_RANGE_BUFFER), &bytes_returned, 0)) { @@ -2392,7 +2088,7 @@ typedef struct _FILE_ALLOCATED_RANGE_BUFFER { #elif defined SEEK_DATA // this is supported on solaris - size_type ret = lseek(m_fd, start, SEEK_DATA); + size_type ret = lseek(native_handle(), start, SEEK_DATA); if (ret < 0) return start; return start; #else @@ -2400,5 +2096,73 @@ typedef struct _FILE_ALLOCATED_RANGE_BUFFER { #endif } +#if TORRENT_DEBUG_FILE_LEAKS + std::set global_file_handles; + mutex file_handle_mutex; + + file_handle::file_handle() + { + mutex::scoped_lock l(file_handle_mutex); + global_file_handles.insert(this); + stack[0] = 0; + } + file_handle::file_handle(file* f): m_file(f) + { + mutex::scoped_lock l(file_handle_mutex); + global_file_handles.insert(this); + if (f) print_backtrace(stack, sizeof(stack), 10); + else stack[0] = 0; + } + file_handle::file_handle(file_handle const& fh) + { + mutex::scoped_lock l(file_handle_mutex); + global_file_handles.insert(this); + m_file = fh.m_file; + if (m_file) print_backtrace(stack, sizeof(stack), 10); + else stack[0] = 0; + } + file_handle::~file_handle() + { + mutex::scoped_lock l(file_handle_mutex); + global_file_handles.erase(this); + stack[0] = 0; + } + file* file_handle::operator->() { return m_file.get(); } + file const* file_handle::operator->() const { return m_file.get(); } + file& file_handle::operator*() { return *m_file.get(); } + file const& file_handle::operator*() const { return *m_file.get(); } + file* file_handle::get() { return m_file.get(); } + file const* file_handle::get() const { return m_file.get(); } + file_handle::operator bool() const { return m_file.get(); } + file_handle& file_handle::reset(file* f) + { + mutex::scoped_lock l(file_handle_mutex); + if (f) print_backtrace(stack, sizeof(stack), 10); + else stack[0] = 0; + l.unlock(); + m_file.reset(f); + return *this; + } + + void print_open_files(char const* event, char const* name) + { + FILE* out = fopen("open_files.log", "a+"); + mutex::scoped_lock l(file_handle_mutex); + fprintf(out, "\n\nEVENT: %s TORRENT: %s\n\n", event, name); + for (std::set::iterator i = global_file_handles.begin() + , end(global_file_handles.end()); i != end; ++i) + { + TORRENT_ASSERT(*i != NULL); + if (!*i) continue; + file_handle const& h = **i; + if (!h) continue; + + if (!h->is_open()) continue; + h->print_info(out); + fprintf(out, "\n%s\n\n", h.stack); + } + fclose(out); + } +#endif } diff --git a/src/file_pool.cpp b/src/file_pool.cpp index a53f2e937..2bf74db99 100644 --- a/src/file_pool.cpp +++ b/src/file_pool.cpp @@ -39,70 +39,18 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { - file_pool::file_pool(int size) : m_size(size) , m_low_prio_io(true) -#if TORRENT_CLOSE_MAY_BLOCK - , m_stop_thread(false) - , m_closer_thread(boost::bind(&file_pool::closer_thread_fun, this)) -#endif { } file_pool::~file_pool() { -#if TORRENT_CLOSE_MAY_BLOCK - mutex::scoped_lock l(m_closer_mutex); - m_stop_thread = true; - l.unlock(); - // wait for hte closer thread to finish closing all files - m_closer_thread.join(); -#endif } -#if TORRENT_CLOSE_MAY_BLOCK - void file_pool::closer_thread_fun() - { - for (;;) - { - mutex::scoped_lock l(m_closer_mutex); - if (m_stop_thread) - { - l.unlock(); - m_queued_for_close.clear(); - return; - } - - // find a file that doesn't have any other threads referencing - // it. Since only those files can be closed in this thead - std::vector >::iterator i = std::find_if( - m_queued_for_close.begin(), m_queued_for_close.end() - , boost::bind(&file::refcount, boost::bind(&boost::intrusive_ptr::get, _1)) == 1); - - if (i == m_queued_for_close.end()) - { - l.unlock(); - // none of the files are ready to be closed yet - // because they're still in use by other threads - // hold off for a while - sleep(100); - } - else - { - // ok, first pull the file out of the queue, release the mutex - // (since closing the file may block) and then close it. - boost::intrusive_ptr f = *i; - m_queued_for_close.erase(i); - l.unlock(); - f->close(); - } - } - } -#endif - #ifdef TORRENT_WINDOWS - void set_low_priority(boost::intrusive_ptr const& f) + void set_low_priority(file_handle const& f) { // file prio is only supported on vista and up // so load the functions dynamically @@ -168,14 +116,22 @@ namespace libtorrent } #endif // TORRENT_WINDOWS - boost::intrusive_ptr file_pool::open_file(void* st, std::string const& p + file_handle file_pool::open_file(void* st, std::string const& p , int file_index, file_storage const& fs, int m, error_code& ec) { + mutex::scoped_lock l(m_mutex); + +#if TORRENT_USE_ASSERTS + // we're not allowed to open a file + // from a deleted storage! + TORRENT_ASSERT(std::find(m_deleted_storages.begin(), m_deleted_storages.end(), std::make_pair(fs.name(), (void const*)&fs)) + == m_deleted_storages.end()); +#endif + TORRENT_ASSERT(st != 0); TORRENT_ASSERT(is_complete(p)); TORRENT_ASSERT((m & file::rw_mask) == file::read_only || (m & file::rw_mask) == file::read_write); - mutex::scoped_lock l(m_mutex); file_set::iterator i = m_files.find(std::make_pair(st, file_index)); if (i != m_files.end()) { @@ -190,7 +146,7 @@ namespace libtorrent #if BOOST_VERSION >= 103500 ec = errors::file_collision; #endif - return boost::intrusive_ptr(); + return file_handle(); } e.key = st; @@ -199,44 +155,33 @@ namespace libtorrent // write mode, re-open it if ((((e.mode & file::rw_mask) != file::read_write) && ((m & file::rw_mask) == file::read_write)) - || (e.mode & file::no_buffer) != (m & file::no_buffer) || (e.mode & file::random_access) != (m & file::random_access)) { // close the file before we open it with - // the new read/write privilages - TORRENT_ASSERT(e.file_ptr->refcount() == 1); + // the new read/write privilages, since windows may + // file opening a file twice. However, since there may + // be outstanding operations on it, we can't close the + // file, we can only delete our reference to it. + // if this is the only reference to the file, it will be closed + e.file_ptr.reset(new (std::nothrow)file); -#if TORRENT_CLOSE_MAY_BLOCK - mutex::scoped_lock l(m_closer_mutex); - m_queued_for_close.push_back(e.file_ptr); - l.unlock(); - e.file_ptr = new file; -#else - e.file_ptr->close(); -#endif std::string full_path = fs.file_path(file_index, p); if (!e.file_ptr->open(full_path, m, ec)) { m_files.erase(i); - return boost::intrusive_ptr(); + return file_handle(); } #ifdef TORRENT_WINDOWS if (m_low_prio_io) set_low_priority(e.file_ptr); #endif + TORRENT_ASSERT(e.file_ptr->is_open()); e.mode = m; } - TORRENT_ASSERT((e.mode & file::no_buffer) == (m & file::no_buffer)); return e.file_ptr; } - // the file is not in our cache - if ((int)m_files.size() >= m_size) - { - // the file cache is at its maximum size, close - // the least recently used (lru) file from it - remove_oldest(); - } + lru_file_entry e; e.file_ptr.reset(new (std::nothrow)file); if (!e.file_ptr) @@ -246,7 +191,7 @@ namespace libtorrent } std::string full_path = fs.file_path(file_index, p); if (!e.file_ptr->open(full_path, m, ec)) - return boost::intrusive_ptr(); + return file_handle(); #ifdef TORRENT_WINDOWS if (m_low_prio_io) set_low_priority(e.file_ptr); @@ -255,36 +200,65 @@ namespace libtorrent e.key = st; m_files.insert(std::make_pair(std::make_pair(st, file_index), e)); TORRENT_ASSERT(e.file_ptr->is_open()); - return e.file_ptr; + + file_handle file_ptr = e.file_ptr; + + // the file is not in our cache + if ((int)m_files.size() >= m_size) + { + // the file cache is at its maximum size, close + // the least recently used (lru) file from it + remove_oldest(l); + } + return file_ptr; } - void file_pool::remove_oldest() + void file_pool::get_status(std::vector* files, void* st) const + { + mutex::scoped_lock l(m_mutex); + + file_set::const_iterator start = m_files.lower_bound(std::make_pair(st, 0)); + file_set::const_iterator end = m_files.upper_bound(std::make_pair(st, INT_MAX)); + + for (file_set::const_iterator i = start; i != end; ++i) + { + pool_file_status s; + s.file_index = i->first.second; + s.open_mode = i->second.mode; + s.last_use = i->second.last_use; + files->push_back(s); + } + } + + void file_pool::remove_oldest(mutex::scoped_lock& l) { file_set::iterator i = std::min_element(m_files.begin(), m_files.end() , boost::bind(&lru_file_entry::last_use, boost::bind(&file_set::value_type::second, _1)) < boost::bind(&lru_file_entry::last_use, boost::bind(&file_set::value_type::second, _2))); if (i == m_files.end()) return; -#if TORRENT_CLOSE_MAY_BLOCK - mutex::scoped_lock l_(m_closer_mutex); - m_queued_for_close.push_back(i->second.file_ptr); - l_.unlock(); -#endif + file_handle file_ptr = i->second.file_ptr; m_files.erase(i); + + // closing a file may be long running operation (mac os x) + l.unlock(); + file_ptr.reset(); + l.lock(); } void file_pool::release(void* st, int file_index) { mutex::scoped_lock l(m_mutex); + file_set::iterator i = m_files.find(std::make_pair(st, file_index)); if (i == m_files.end()) return; -#if TORRENT_CLOSE_MAY_BLOCK - mutex::scoped_lock l2(m_closer_mutex); - m_queued_for_close.push_back(i->second.file_ptr); - l2.unlock(); -#endif + file_handle file_ptr = i->second.file_ptr; m_files.erase(i); + + // closing a file may be long running operation (mac os x) + l.unlock(); + file_ptr.reset(); } // closes files belonging to the specified @@ -292,34 +266,67 @@ namespace libtorrent void file_pool::release(void* st) { mutex::scoped_lock l(m_mutex); + if (st == 0) { - m_files.clear(); + file_set tmp; + tmp.swap(m_files); + l.unlock(); return; } + std::vector to_close; for (file_set::iterator i = m_files.begin(); i != m_files.end();) { if (i->second.key == st) + { + to_close.push_back(i->second.file_ptr); m_files.erase(i++); + } else ++i; } + l.unlock(); + // the files are closed here } +#if TORRENT_USE_ASSERTS + void file_pool::mark_deleted(file_storage const& fs) + { + mutex::scoped_lock l(m_mutex); + m_deleted_storages.push_back(std::make_pair(fs.name(), (void const*)&fs)); + if(m_deleted_storages.size() > 100) + m_deleted_storages.erase(m_deleted_storages.begin()); + } + + bool file_pool::assert_idle_files(void* st) const + { + mutex::scoped_lock l(m_mutex); + + for (file_set::const_iterator i = m_files.begin(); + i != m_files.end(); ++i) + { + if (i->second.key == st && i->second.file_ptr->refcount() > 1) + return false; + } + return true; + } +#endif + void file_pool::resize(int size) { + mutex::scoped_lock l(m_mutex); + TORRENT_ASSERT(size > 0); if (size == m_size) return; - mutex::scoped_lock l(m_mutex); m_size = size; if (int(m_files.size()) <= m_size) return; // close the least recently used files while (int(m_files.size()) > m_size) - remove_oldest(); + remove_oldest(l); } } diff --git a/src/file_storage.cpp b/src/file_storage.cpp index b0a01c74d..15cd10e22 100644 --- a/src/file_storage.cpp +++ b/src/file_storage.cpp @@ -41,9 +41,10 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { file_storage::file_storage() - : m_total_size(0) + : m_piece_length(0) , m_num_pieces(0) - , m_piece_length(0) + , m_total_size(0) + , m_num_files(0) {} void file_storage::reserve(int num_files) @@ -69,31 +70,41 @@ namespace libtorrent void file_storage::update_path_index(internal_file_entry& e) { - std::string fname = e.filename(); - if (is_complete(fname)) + std::string fn = e.filename(); + if (is_complete(fn)) { e.path_index = -2; return; } - std::string parent = parent_path(fname); - - if (parent.empty()) + // sorry about this messy string handling, but I did + // profile it, and it was expensive + char const* leaf = filename_cstr(fn.c_str()); + char const* branch_path = ""; + if (leaf > fn.c_str()) + { + // split the string into the leaf filename + // and the branch path + branch_path = fn.c_str(); + ((char*)leaf)[-1] = 0; + } + if (branch_path[0] == 0) { e.path_index = -1; return; } - if (parent.size() >= m_name.size() - && parent.compare(0, m_name.size(), m_name) == 0 - && (parent.size() == m_name.size() + int branch_len = strlen(branch_path); + if (branch_len >= m_name.size() + && std::memcmp(branch_path, m_name.c_str(), m_name.size()) == 0 + && (branch_len == m_name.size() #ifdef TORRENT_WINDOWS - || parent[m_name.size()] == '\\' + || branch_path[m_name.size()] == '\\' #endif - || parent[m_name.size()] == '/' + || branch_path[m_name.size()] == '/' )) { - parent.erase(parent.begin(), parent.begin() + m_name.size() - + (m_name.size() == parent.size()?0:1)); + branch_path += m_name.size() + + (m_name.size() == branch_len?0:1); e.no_root_dir = false; } else @@ -103,20 +114,20 @@ namespace libtorrent // do we already have this path in the path list? std::vector::reverse_iterator p - = std::find(m_paths.rbegin(), m_paths.rend(), parent); + = std::find(m_paths.rbegin(), m_paths.rend(), branch_path); if (p == m_paths.rend()) { // no, we don't. add it e.path_index = m_paths.size(); - m_paths.push_back(parent); + m_paths.push_back(branch_path); } else { // yes we do. use it e.path_index = p.base() - m_paths.begin() - 1; } - e.set_name(filename(e.filename()).c_str()); + e.set_name(leaf); } file_entry::file_entry(): offset(0), size(0), file_base(0) @@ -421,6 +432,7 @@ namespace libtorrent } TORRENT_ASSERT_PRECOND(m_name == split_path(file).c_str()); m_files.push_back(internal_file_entry()); + ++m_num_files; internal_file_entry& e = m_files.back(); e.set_name(file.c_str()); e.size = size; @@ -467,6 +479,7 @@ namespace libtorrent internal_file_entry ife(ent); int file_index = m_files.size(); m_files.push_back(ife); + ++m_num_files; internal_file_entry& e = m_files.back(); e.offset = m_total_size; m_total_size += e.size; @@ -762,6 +775,7 @@ namespace libtorrent int cur_index = i - m_files.begin(); int index = m_files.size(); m_files.push_back(internal_file_entry()); + ++m_num_files; internal_file_entry& e = m_files.back(); // i may have been invalidated, refresh it i = m_files.begin() + cur_index; @@ -789,5 +803,15 @@ namespace libtorrent } m_total_size = off; } + + void file_storage::unload() + { + std::vector().swap(m_files); + std::vector().swap(m_file_hashes); + std::vector().swap(m_symlinks); + std::vector().swap(m_mtime); + std::vector().swap(m_file_base); + std::vector().swap(m_paths); + } } diff --git a/src/hasher.cpp b/src/hasher.cpp index 35d016760..a14f342b5 100644 --- a/src/hasher.cpp +++ b/src/hasher.cpp @@ -125,12 +125,12 @@ namespace libtorrent #endif } -#ifdef TORRENT_USE_GCRYPT hasher::~hasher() { +#ifdef TORRENT_USE_GCRYPT gcry_md_close(m_context); - } #endif + } } diff --git a/src/http_connection.cpp b/src/http_connection.cpp index 6f104590c..a1bcf0c36 100644 --- a/src/http_connection.cpp +++ b/src/http_connection.cpp @@ -38,6 +38,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket.hpp" #include "libtorrent/connection_queue.hpp" #include "libtorrent/socket_type.hpp" // for async_shutdown +#include "libtorrent/resolver_interface.hpp" +#include "libtorrent/settings_pack.hpp" #if defined TORRENT_ASIO_DEBUGGING #include "libtorrent/debug.hpp" @@ -49,7 +51,9 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { -http_connection::http_connection(io_service& ios, connection_queue& cc +http_connection::http_connection(io_service& ios + , connection_queue& cc + , resolver_interface& resolver , http_handler const& handler , bool bottled , int max_bottled_buffer_size @@ -59,34 +63,37 @@ http_connection::http_connection(io_service& ios, connection_queue& cc , boost::asio::ssl::context* ssl_ctx #endif ) - : m_sock(ios) -#if TORRENT_USE_I2P - , m_i2p_conn(0) -#endif - , m_read_pos(0) - , m_resolver(ios) - , m_handler(handler) - , m_connect_handler(ch) - , m_filter_handler(fh) - , m_timer(ios) - , m_last_receive(time_now()) - , m_start_time(time_now()) - , m_bottled(bottled) - , m_max_bottled_buffer_size(max_bottled_buffer_size) - , m_called(false) + : m_cc(cc) #ifdef TORRENT_USE_OPENSSL , m_ssl_ctx(ssl_ctx) , m_own_ssl_context(false) #endif - , m_rate_limit(0) - , m_download_quota(0) - , m_limiter_timer_active(false) + , m_sock(ios) +#if TORRENT_USE_I2P + , m_i2p_conn(0) +#endif + , m_resolver(resolver) + , m_handler(handler) + , m_connect_handler(ch) + , m_filter_handler(fh) + , m_timer(ios) , m_limiter_timer(ios) + , m_last_receive(time_now()) + , m_start_time(time_now()) + , m_read_pos(0) , m_redirects(5) , m_connection_ticket(-1) - , m_cc(cc) - , m_ssl(false) + , m_max_bottled_buffer_size(max_bottled_buffer_size) + , m_rate_limit(0) + , m_download_quota(0) , m_priority(0) + , m_resolve_flags(0) + , m_port(0) + , m_bottled(bottled) + , m_called(false) + , m_limiter_timer_active(false) + , m_queued_for_connection(false) + , m_ssl(false) , m_abort(false) { TORRENT_ASSERT(!m_handler.empty()); @@ -102,13 +109,14 @@ http_connection::~http_connection() void http_connection::get(std::string const& url, time_duration timeout, int prio , proxy_settings const* ps, int handle_redirects, std::string const& user_agent - , address const& bind_addr + , address const& bind_addr, int resolve_flags #if TORRENT_USE_I2P , i2p_connection* i2p_conn #endif ) { m_user_agent = user_agent; + m_resolve_flags = resolve_flags; std::string protocol; std::string auth; @@ -127,6 +135,13 @@ void http_connection::get(std::string const& url, time_duration timeout, int pri // deletes this object boost::shared_ptr me(shared_from_this()); + if (ec) + { + m_timer.get_io_service().post(boost::bind(&http_connection::callback + , me, ec, (char*)0, 0)); + return; + } + if (protocol != "http" #ifdef TORRENT_USE_OPENSSL && protocol != "https" @@ -134,14 +149,7 @@ void http_connection::get(std::string const& url, time_duration timeout, int pri ) { error_code ec(errors::unsupported_url_protocol); - m_resolver.get_io_service().post(boost::bind(&http_connection::callback - , me, ec, (char*)0, 0)); - return; - } - - if (ec) - { - m_resolver.get_io_service().post(boost::bind(&http_connection::callback + m_timer.get_io_service().post(boost::bind(&http_connection::callback , me, ec, (char*)0, 0)); return; } @@ -161,14 +169,14 @@ void http_connection::get(std::string const& url, time_duration timeout, int pri // exclude ssl here, because SSL assumes CONNECT support in the // proxy and is handled at the lower layer - if (ps && (ps->type == proxy_settings::http - || ps->type == proxy_settings::http_pw) + if (ps && (ps->type == settings_pack::http + || ps->type == settings_pack::http_pw) && !ssl) { // if we're using an http proxy and not an ssl // connection, just do a regular http proxy request APPEND_FMT1("GET %s HTTP/1.1\r\n", url.c_str()); - if (ps->type == proxy_settings::http_pw) + if (ps->type == settings_pack::http_pw) APPEND_FMT1("Proxy-Authorization: Basic %s\r\n", base64encode( ps->username + ":" + ps->password).c_str()); @@ -202,17 +210,19 @@ void http_connection::get(std::string const& url, time_duration timeout, int pri sendbuffer.assign(request); m_url = url; - start(hostname, to_string(port).elems, timeout, prio - , ps, ssl, handle_redirects, bind_addr + start(hostname, port, timeout, prio + , ps, ssl, handle_redirects, bind_addr, m_resolve_flags #if TORRENT_USE_I2P , i2p_conn #endif ); } -void http_connection::start(std::string const& hostname, std::string const& port - , time_duration timeout, int prio, proxy_settings const* ps, bool ssl, int handle_redirects +void http_connection::start(std::string const& hostname, int port + , time_duration timeout, int prio, proxy_settings const* ps, bool ssl + , int handle_redirects , address const& bind_addr + , int resolve_flags #if TORRENT_USE_I2P , i2p_connection* i2p_conn #endif @@ -221,6 +231,7 @@ void http_connection::start(std::string const& hostname, std::string const& port TORRENT_ASSERT(prio >= 0 && prio < 3); m_redirects = handle_redirects; + m_resolve_flags = resolve_flags; if (ps) m_proxy = *ps; // keep ourselves alive even if the callback function @@ -228,7 +239,8 @@ void http_connection::start(std::string const& hostname, std::string const& port boost::shared_ptr me(shared_from_this()); m_completion_timeout = timeout; - m_read_timeout = (std::max)(seconds(5), timeout / 5); + m_read_timeout = seconds(5); + if (m_read_timeout < timeout / 5) m_read_timeout = timeout / 5; error_code ec; m_timer.expires_from_now(m_completion_timeout, ec); #if defined TORRENT_ASIO_DEBUGGING @@ -244,7 +256,7 @@ void http_connection::start(std::string const& hostname, std::string const& port if (ec) { - m_resolver.get_io_service().post(boost::bind(&http_connection::callback + m_timer.get_io_service().post(boost::bind(&http_connection::callback , me, ec, (char*)0, 0)); return; } @@ -282,9 +294,9 @@ void http_connection::start(std::string const& hostname, std::string const& port #endif #if TORRENT_USE_I2P - if (is_i2p && i2p_conn->proxy().type != proxy_settings::i2p_proxy) + if (is_i2p && i2p_conn->proxy().type != settings_pack::i2p_proxy) { - m_resolver.get_io_service().post(boost::bind(&http_connection::callback + m_timer.get_io_service().post(boost::bind(&http_connection::callback , me, error_code(errors::no_i2p_router, get_libtorrent_category()), (char*)0, 0)); return; } @@ -292,14 +304,19 @@ void http_connection::start(std::string const& hostname, std::string const& port proxy_settings const* proxy = ps; #if TORRENT_USE_I2P - if (is_i2p) proxy = &i2p_conn->proxy(); + proxy_settings i2p_proxy; + if (is_i2p) + { + i2p_proxy = i2p_conn->proxy(); + proxy = &i2p_proxy; + } #endif // in this case, the upper layer is assumed to have taken // care of the proxying already. Don't instantiate the socket // with this proxy - if (proxy && (proxy->type == proxy_settings::http - || proxy->type == proxy_settings::http_pw) + if (proxy && (proxy->type == settings_pack::http + || proxy->type == settings_pack::http_pw) && !ssl) { proxy = 0; @@ -313,7 +330,7 @@ void http_connection::start(std::string const& hostname, std::string const& port if (m_ssl_ctx == 0) { m_ssl_ctx = new (std::nothrow) boost::asio::ssl::context( - m_resolver.get_io_service(), asio::ssl::context::sslv23_client); + m_timer.get_io_service(), asio::ssl::context::sslv23_client); if (m_ssl_ctx) { m_own_ssl_context = true; @@ -325,7 +342,7 @@ void http_connection::start(std::string const& hostname, std::string const& port userdata = m_ssl_ctx; } #endif - instantiate_connection(m_resolver.get_io_service() + instantiate_connection(m_timer.get_io_service() , proxy ? *proxy : null_proxy, m_sock, userdata); if (m_bind_addr != address_v4::any()) @@ -335,7 +352,7 @@ void http_connection::start(std::string const& hostname, std::string const& port m_sock.bind(tcp::endpoint(m_bind_addr, 0), ec); if (ec) { - m_resolver.get_io_service().post(boost::bind(&http_connection::callback + m_timer.get_io_service().post(boost::bind(&http_connection::callback , me, ec, (char*)0, 0)); return; } @@ -344,7 +361,7 @@ void http_connection::start(std::string const& hostname, std::string const& port setup_ssl_hostname(m_sock, hostname, ec); if (ec) { - m_resolver.get_io_service().post(boost::bind(&http_connection::callback + m_timer.get_io_service().post(boost::bind(&http_connection::callback , me, ec, (char*)0, 0)); return; } @@ -361,12 +378,12 @@ void http_connection::start(std::string const& hostname, std::string const& port else #endif if (ps && ps->proxy_hostnames - && (ps->type == proxy_settings::socks5 - || ps->type == proxy_settings::socks5_pw)) + && (ps->type == settings_pack::socks5 + || ps->type == settings_pack::socks5_pw)) { m_hostname = hostname; m_port = port; - m_endpoints.push_back(tcp::endpoint(address(), atoi(port.c_str()))); + m_endpoints.push_back(tcp::endpoint(address(), port)); queue_connect(); } else @@ -374,9 +391,10 @@ void http_connection::start(std::string const& hostname, std::string const& port #if defined TORRENT_ASIO_DEBUGGING add_outstanding_async("http_connection::on_resolve"); #endif + TORRENT_ASSERT(!m_self_reference); m_endpoints.clear(); - tcp::resolver::query query(hostname, port); - m_resolver.async_resolve(query, boost::bind(&http_connection::on_resolve + m_resolver.async_resolve(hostname, m_resolve_flags + , boost::bind(&http_connection::on_resolve , me, _1, _2)); } m_hostname = hostname; @@ -387,6 +405,7 @@ void http_connection::start(std::string const& hostname, std::string const& port void http_connection::on_connect_timeout() { TORRENT_ASSERT(m_connection_ticket > -1); + TORRENT_ASSERT(!m_queued_for_connection); // keep ourselves alive even if the callback function // deletes this object @@ -394,6 +413,8 @@ void http_connection::on_connect_timeout() error_code ec; m_sock.close(ec); + + m_self_reference.reset(); } void http_connection::on_timeout(boost::weak_ptr p @@ -448,17 +469,25 @@ void http_connection::close(bool force) if (m_abort) return; error_code ec; - m_timer.cancel(ec); - m_resolver.cancel(); - m_limiter_timer.cancel(ec); - if (force) m_sock.close(ec); else async_shutdown(m_sock, shared_from_this()); + if (m_queued_for_connection) + m_cc.cancel(this); + + if (m_connection_ticket > -1) + { + m_cc.done(m_connection_ticket); + m_connection_ticket = -1; + } + + m_timer.cancel(ec); + m_limiter_timer.cancel(ec); + m_hostname.clear(); - m_port.clear(); + m_port = 0; m_handler.clear(); m_abort = true; } @@ -498,7 +527,7 @@ void http_connection::on_i2p_resolve(error_code const& e #endif void http_connection::on_resolve(error_code const& e - , tcp::resolver::iterator i) + , std::vector
const& addresses) { #if defined TORRENT_ASIO_DEBUGGING complete_async("http_connection::on_resolve"); @@ -511,10 +540,11 @@ void http_connection::on_resolve(error_code const& e close(); return; } - TORRENT_ASSERT(i != tcp::resolver::iterator()); + TORRENT_ASSERT(!addresses.empty()); - std::transform(i, tcp::resolver::iterator(), std::back_inserter(m_endpoints) - , boost::bind(&tcp::resolver::iterator::value_type::endpoint, _1)); + for (std::vector
::const_iterator i = addresses.begin() + , end(addresses.end()); i != end; ++i) + m_endpoints.push_back(tcp::endpoint(*i, m_port)); if (m_filter_handler) m_filter_handler(*this, m_endpoints); if (m_endpoints.empty()) @@ -523,6 +553,8 @@ void http_connection::on_resolve(error_code const& e return; } + std::random_shuffle(m_endpoints.begin(), m_endpoints.end()); + // The following statement causes msvc to crash (ICE). Since it's not // necessary in the vast majority of cases, just ignore the endpoint // order for windows @@ -542,26 +574,42 @@ void http_connection::on_resolve(error_code const& e void http_connection::queue_connect() { TORRENT_ASSERT(!m_endpoints.empty()); - tcp::endpoint target = m_endpoints.front(); - m_endpoints.pop_front(); - - m_cc.enqueue(boost::bind(&http_connection::connect, shared_from_this(), _1, target) - , boost::bind(&http_connection::on_connect_timeout, shared_from_this()) - , m_read_timeout, m_priority); + m_self_reference = shared_from_this(); + m_cc.enqueue(this, m_read_timeout, m_priority); + m_queued_for_connection = true; } -void http_connection::connect(int ticket, tcp::endpoint target_address) +void http_connection::on_allow_connect(int ticket) { + TORRENT_ASSERT(m_queued_for_connection); + m_queued_for_connection = false; + + boost::shared_ptr me(shared_from_this()); + m_self_reference.reset(); +#if defined TORRENT_ASIO_DEBUGGING + TORRENT_ASSERT(has_outstanding_async("connection_queue::on_timeout")); +#endif + if (ticket == -1) { close(); return; } + TORRENT_ASSERT(!m_endpoints.empty()); + if (m_endpoints.empty()) + { + m_cc.done(ticket); + return; + } + + tcp::endpoint target_address = m_endpoints.front(); + m_endpoints.erase(m_endpoints.begin()); + m_connection_ticket = ticket; if (m_proxy.proxy_hostnames - && (m_proxy.type == proxy_settings::socks5 - || m_proxy.type == proxy_settings::socks5_pw)) + && (m_proxy.type == settings_pack::socks5 + || m_proxy.type == settings_pack::socks5_pw)) { // we're using a socks proxy and we're resolving // hostnames through it @@ -795,7 +843,7 @@ void http_connection::on_read(error_code const& e if (!ec) { get(location, m_completion_timeout, m_priority, &m_proxy, m_redirects - 1 - , m_user_agent, m_bind_addr + , m_user_agent, m_bind_addr, m_resolve_flags #if TORRENT_USE_I2P , m_i2p_conn #endif @@ -816,7 +864,7 @@ void http_connection::on_read(error_code const& e url += location; get(url, m_completion_timeout, m_priority, &m_proxy, m_redirects - 1 - , m_user_agent, m_bind_addr + , m_user_agent, m_bind_addr, m_resolve_flags #if TORRENT_USE_I2P , m_i2p_conn #endif diff --git a/src/http_parser.cpp b/src/http_parser.cpp index 3d19858cd..e2c5c04c6 100644 --- a/src/http_parser.cpp +++ b/src/http_parser.cpp @@ -62,20 +62,20 @@ namespace libtorrent http_parser::http_parser(int flags) : m_recv_pos(0) - , m_status_code(-1) , m_content_length(-1) , m_range_start(-1) , m_range_end(-1) - , m_state(read_status) , m_recv_buffer(0, 0) - , m_body_start_pos(0) - , m_connection_close(false) - , m_chunked_encoding(false) - , m_finished(false) , m_cur_chunk_end(-1) + , m_status_code(-1) , m_chunk_header_size(0) , m_partial_chunk_header(0) , m_flags(flags) + , m_body_start_pos(0) + , m_state(read_status) + , m_connection_close(false) + , m_chunked_encoding(false) + , m_finished(false) {} boost::tuple http_parser::incoming( diff --git a/src/http_seed_connection.cpp b/src/http_seed_connection.cpp index a39fbedd7..cc54f0f43 100644 --- a/src/http_seed_connection.cpp +++ b/src/http_seed_connection.cpp @@ -53,12 +53,15 @@ using libtorrent::aux::session_impl; namespace libtorrent { http_seed_connection::http_seed_connection( - session_impl& ses + aux::session_interface& ses + , aux::session_settings const& sett + , buffer_allocator_interface& allocator + , disk_interface& disk_thread , boost::weak_ptr t , boost::shared_ptr s - , tcp::endpoint const& remote , web_seed_entry& web) - : web_connection_base(ses, t, s, remote, web) + : web_connection_base(ses, sett, allocator, disk_thread + , t, s, web) , m_url(web.url) , m_response_left(0) , m_chunk_pos(0) @@ -66,7 +69,7 @@ namespace libtorrent { INVARIANT_CHECK; - if (!ses.settings().report_web_seed_downloads) + if (!m_settings.get_bool(settings_pack::report_web_seed_downloads)) ignore_stats(true); shared_ptr tor = t.lock(); @@ -75,7 +78,7 @@ namespace libtorrent // multiply with the blocks per piece since that many requests are // merged into one http request - max_out_request_queue(ses.settings().urlseed_pipeline_size + max_out_request_queue(m_settings.get_int(settings_pack::urlseed_pipeline_size) * blocks_per_piece); prefer_whole_pieces(1); @@ -85,10 +88,10 @@ namespace libtorrent #endif } - void http_seed_connection::disconnect(error_code const& ec, int error) + void http_seed_connection::disconnect(error_code const& ec, peer_connection_interface::operation_t op, int error) { boost::shared_ptr t = associated_torrent().lock(); - peer_connection::disconnect(ec, error); + peer_connection::disconnect(ec, op, error); if (t) t->disconnect_web_seed(this); } @@ -159,9 +162,9 @@ namespace libtorrent size -= pr.length; } - proxy_settings const& ps = m_ses.proxy(); - bool using_proxy = (ps.type == proxy_settings::http - || ps.type == proxy_settings::http_pw) && !m_ssl; + int proxy_type = m_ses.settings().get_int(settings_pack::proxy_type); + bool using_proxy = (proxy_type == settings_pack::http + || proxy_type == settings_pack::http_pw) && !m_ssl; request += "GET "; request += using_proxy ? m_url : m_path; @@ -182,7 +185,7 @@ namespace libtorrent } request += " HTTP/1.1\r\n"; - add_headers(request, ps, using_proxy); + add_headers(request, m_ses.settings(), using_proxy); request += "\r\n\r\n"; m_first_request = false; @@ -204,7 +207,7 @@ namespace libtorrent if (error) { - m_statistics.received_bytes(0, bytes_transferred); + received_bytes(0, bytes_transferred); #ifdef TORRENT_VERBOSE_LOGGING peer_log("*** http_seed_connection error: %s", error.message().c_str()); #endif @@ -224,8 +227,8 @@ namespace libtorrent TORRENT_ASSERT(!m_requests.empty()); if (m_requests.empty()) { - m_statistics.received_bytes(0, bytes_transferred); - disconnect(errors::http_error, 2); + received_bytes(0, bytes_transferred); + disconnect(errors::http_error, op_bittorrent, 2); return; } @@ -238,7 +241,7 @@ namespace libtorrent int protocol = 0; int payload = 0; boost::tie(payload, protocol) = m_parser.incoming(recv_buffer, parse_error); - m_statistics.received_bytes(0, protocol); + received_bytes(0, protocol); bytes_transferred -= protocol; #if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS if (payload > front_request.length) payload = front_request.length; @@ -246,8 +249,8 @@ namespace libtorrent if (parse_error) { - m_statistics.received_bytes(0, bytes_transferred); - disconnect(errors::http_parse_error, 2); + received_bytes(0, bytes_transferred); + disconnect(errors::http_parse_error, op_bittorrent, 2); return; } @@ -273,13 +276,13 @@ namespace libtorrent std::string error_msg = to_string(m_parser.status_code()).elems + (" " + m_parser.message()); - if (m_ses.m_alerts.should_post()) + if (t->alerts().should_post()) { - m_ses.m_alerts.post_alert(url_seed_alert(t->get_handle(), url() + t->alerts().post_alert(url_seed_alert(t->get_handle(), url() , error_msg)); } - m_statistics.received_bytes(0, bytes_transferred); - disconnect(error_code(m_parser.status_code(), get_http_category()), 1); + received_bytes(0, bytes_transferred); + disconnect(error_code(m_parser.status_code(), get_http_category()), op_bittorrent, 1); return; } if (!m_parser.header_finished()) @@ -298,20 +301,18 @@ namespace libtorrent // this means we got a redirection request // look for the location header std::string location = m_parser.header("location"); - m_statistics.received_bytes(0, bytes_transferred); + received_bytes(0, bytes_transferred); if (location.empty()) { // we should not try this server again. - t->remove_web_seed(this); - disconnect(errors::missing_location, 2); + t->remove_web_seed(this, errors::missing_location, op_bittorrent, 2); return; } // add the redirected url and remove the current one t->add_web_seed(location, web_seed_entry::http_seed); - t->remove_web_seed(this); - disconnect(errors::redirecting, 2); + t->remove_web_seed(this, errors::redirecting, op_bittorrent, 2); return; } @@ -328,18 +329,16 @@ namespace libtorrent m_response_left = atol(m_parser.header("content-length").c_str()); if (m_response_left == -1) { - m_statistics.received_bytes(0, bytes_transferred); + received_bytes(0, bytes_transferred); // we should not try this server again. - t->remove_web_seed(this); - disconnect(errors::no_content_length, 2); + t->remove_web_seed(this, errors::no_content_length, op_bittorrent, 2); return; } if (m_response_left != front_request.length) { - m_statistics.received_bytes(0, bytes_transferred); + received_bytes(0, bytes_transferred); // we should not try this server again. - t->remove_web_seed(this); - disconnect(errors::invalid_range, 2); + t->remove_web_seed(this, errors::invalid_range, op_bittorrent, 2); return; } m_body_start = m_parser.body_start(); @@ -364,7 +363,7 @@ namespace libtorrent { TORRENT_ASSERT(bytes_transferred >= size_t(chunk_start.left() - m_partial_chunk_header)); bytes_transferred -= chunk_start.left() - m_partial_chunk_header; - m_statistics.received_bytes(0, chunk_start.left() - m_partial_chunk_header); + received_bytes(0, chunk_start.left() - m_partial_chunk_header); m_partial_chunk_header = chunk_start.left(); if (bytes_transferred == 0) return; break; @@ -377,7 +376,7 @@ namespace libtorrent TORRENT_ASSERT(bytes_transferred >= size_t(header_size - m_partial_chunk_header)); bytes_transferred -= header_size - m_partial_chunk_header; - m_statistics.received_bytes(0, header_size - m_partial_chunk_header); + received_bytes(0, header_size - m_partial_chunk_header); m_partial_chunk_header = 0; TORRENT_ASSERT(chunk_size != 0 || chunk_start.left() <= header_size || chunk_start.begin[header_size] == 'H'); // cut out the chunk header from the receive buffer @@ -399,7 +398,7 @@ namespace libtorrent int payload = bytes_transferred; if (payload > m_response_left) payload = int(m_response_left); if (payload > front_request.length) payload = front_request.length; - m_statistics.received_bytes(payload, 0); + received_bytes(payload, 0); incoming_piece_fragment(payload); m_response_left -= payload; @@ -413,10 +412,10 @@ namespace libtorrent peer_log("*** retrying in %d seconds", retry_time); #endif - m_statistics.received_bytes(0, bytes_transferred); + received_bytes(0, bytes_transferred); // temporarily unavailable, retry later t->retry_web_seed(this, retry_time); - disconnect(error_code(m_parser.status_code(), get_http_category()), 1); + disconnect(error_code(m_parser.status_code(), get_http_category()), op_bittorrent, 1); return; } diff --git a/src/http_stream.cpp b/src/http_stream.cpp index 0a84d84e1..ec56514c0 100644 --- a/src/http_stream.cpp +++ b/src/http_stream.cpp @@ -40,13 +40,7 @@ namespace libtorrent void http_stream::name_lookup(error_code const& e, tcp::resolver::iterator i , boost::shared_ptr h) { - if (e || i == tcp::resolver::iterator()) - { - (*h)(e); - error_code ec; - close(ec); - return; - } + if (handle_error(e, h)) return; m_sock.async_connect(i->endpoint(), boost::bind( &http_stream::connected, this, _1, h)); @@ -54,13 +48,7 @@ namespace libtorrent void http_stream::connected(error_code const& e, boost::shared_ptr h) { - if (e) - { - (*h)(e); - error_code ec; - close(ec); - return; - } + if (handle_error(e, h)) return; using namespace libtorrent::detail; @@ -95,13 +83,7 @@ namespace libtorrent void http_stream::handshake1(error_code const& e, boost::shared_ptr h) { - if (e) - { - (*h)(e); - error_code ec; - close(ec); - return; - } + if (handle_error(e, h)) return; // read one byte from the socket m_buffer.resize(1); @@ -111,13 +93,7 @@ namespace libtorrent void http_stream::handshake2(error_code const& e, boost::shared_ptr h) { - if (e) - { - (*h)(e); - error_code ec; - close(ec); - return; - } + if (handle_error(e, h)) return; int read_pos = m_buffer.size(); // look for \n\n and \r\n\r\n diff --git a/src/http_tracker_connection.cpp b/src/http_tracker_connection.cpp index 40dc34c57..784bb35c8 100644 --- a/src/http_tracker_connection.cpp +++ b/src/http_tracker_connection.cpp @@ -31,6 +31,7 @@ POSSIBILITY OF SUCH DAMAGE. */ #include +#include #include #include @@ -74,8 +75,7 @@ namespace libtorrent , tracker_manager& man , tracker_request const& req , boost::weak_ptr c - , aux::session_impl const& ses - , proxy_settings const& ps + , aux::session_impl& ses , std::string const& auth #if TORRENT_USE_I2P , i2p_connection* i2p_conn @@ -84,7 +84,6 @@ namespace libtorrent : tracker_connection(man, req, ios, c) , m_man(man) , m_ses(ses) - , m_ps(ps) , m_cc(cc) , m_ios(ios) #if TORRENT_USE_I2P @@ -117,7 +116,7 @@ namespace libtorrent static const bool i2p = false; #endif - session_settings const& settings = m_ses.settings(); + aux::session_settings const& settings = m_ses.settings(); // if request-string already contains // some parameters, append an ampersand instead @@ -142,7 +141,7 @@ namespace libtorrent "&downloaded=%" PRId64 "&left=%" PRId64 "&corrupt=%" PRId64 - "&key=%X" + "&key=%08X" "%s%s" // event "&numwant=%d" "&compact=1" @@ -162,10 +161,11 @@ namespace libtorrent , tracker_req().num_want); url += str; #ifndef TORRENT_DISABLE_ENCRYPTION - if (m_ses.get_pe_settings().in_enc_policy != pe_settings::disabled) + if (m_ses.settings().get_int(settings_pack::in_enc_policy) != settings_pack::pe_disabled + && m_ses.settings().get_bool(settings_pack::announce_crypto_support)) url += "&supportcrypto=1"; #endif - if (stats && m_ses.settings().report_redundant_bytes) + if (stats && m_ses.settings().get_bool(settings_pack::report_redundant_bytes)) { url += "&redundant="; url += to_string(tracker_req().redundant).elems; @@ -187,14 +187,14 @@ namespace libtorrent } else #endif - if (!m_ses.settings().anonymous_mode) + if (!m_ses.settings().get_bool(settings_pack::anonymous_mode)) { - if (!settings.announce_ip.empty()) + std::string announce_ip = settings.get_str(settings_pack::announce_ip); + if (!announce_ip.empty()) { - url += "&ip=" + escape_string( - settings.announce_ip.c_str(), settings.announce_ip.size()); + url += "&ip=" + escape_string(announce_ip.c_str(), announce_ip.size()); } - else if (m_ses.settings().announce_double_nat + else if (m_ses.settings().get_bool(settings_pack::announce_double_nat) && is_local(m_ses.listen_address())) { // only use the global external listen address here @@ -206,9 +206,9 @@ namespace libtorrent } } - m_tracker_connection.reset(new http_connection(m_ios, m_cc + m_tracker_connection.reset(new http_connection(m_ios, m_cc, m_ses.m_host_resolver , boost::bind(&http_tracker_connection::on_response, self(), _1, _2, _3, _4) - , true, settings.max_http_recv_buffer_size + , true, settings.get_int(settings_pack::max_http_recv_buffer_size) , boost::bind(&http_tracker_connection::on_connect, self(), _1) , boost::bind(&http_tracker_connection::on_filter, self(), _1, _2) #ifdef TORRENT_USE_OPENSSL @@ -217,13 +217,22 @@ namespace libtorrent )); int timeout = tracker_req().event==tracker_request::stopped - ?settings.stop_tracker_timeout - :settings.tracker_completion_timeout; + ?settings.get_int(settings_pack::stop_tracker_timeout) + :settings.get_int(settings_pack::tracker_completion_timeout); + // when sending stopped requests, prefer the cached DNS entry + // to avoid being blocked for slow or failing responses. Chances + // are that we're shutting down, and this should be a best-effort + // attempt. It's not worth stalling shutdown. + proxy_settings ps = m_ses.proxy(); m_tracker_connection->get(url, seconds(timeout) , tracker_req().event == tracker_request::stopped ? 2 : 1 - , &m_ps, 5, settings.anonymous_mode ? "" : settings.user_agent + , &ps, 5, settings.get_bool(settings_pack::anonymous_mode) + ? "" : settings.get_str(settings_pack::user_agent) , bind_interface() + , tracker_req().event == tracker_request::stopped + ? resolver_interface::prefer_cache + : 0 #if TORRENT_USE_I2P , m_i2p_conn #endif @@ -252,12 +261,12 @@ namespace libtorrent tracker_connection::close(); } - void http_tracker_connection::on_filter(http_connection& c, std::list& endpoints) + void http_tracker_connection::on_filter(http_connection& c, std::vector& endpoints) { if (tracker_req().apply_ip_filter == false) return; // remove endpoints that are filtered by the IP filter - for (std::list::iterator i = endpoints.begin(); + for (std::vector::iterator i = endpoints.begin(); i != endpoints.end();) { if (m_ses.m_ip_filter.access(i->address()) == ip_filter::blocked) @@ -283,7 +292,6 @@ namespace libtorrent tcp::endpoint ep = c.socket().remote_endpoint(ec); m_tracker_ip = ep.address(); boost::shared_ptr cb = requester(); - if (cb) cb->m_tracker_address = ep; } void http_tracker_connection::on_response(error_code const& ec @@ -528,8 +536,8 @@ namespace libtorrent { error_code ec; ip_list.push_back(m_tracker_connection->socket().remote_endpoint(ec).address()); - std::list const& epts = m_tracker_connection->endpoints(); - for (std::list::const_iterator i = epts.begin() + std::vector const& epts = m_tracker_connection->endpoints(); + for (std::vector::const_iterator i = epts.begin() , end(epts.end()); i != end; ++i) { ip_list.push_back(i->address()); diff --git a/src/i2p_stream.cpp b/src/i2p_stream.cpp index af72b66a4..f60a35064 100644 --- a/src/i2p_stream.cpp +++ b/src/i2p_stream.cpp @@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/assert.hpp" #include "libtorrent/error_code.hpp" #include "libtorrent/string_util.hpp" +#include "libtorrent/settings_pack.hpp" #if TORRENT_USE_I2P @@ -93,18 +94,28 @@ namespace libtorrent if (m_sam_socket) m_sam_socket->close(e); } - void i2p_connection::open(proxy_settings const& s, i2p_stream::handler_type const& handler) + proxy_settings i2p_connection::proxy() const + { + proxy_settings ret; + ret.hostname = m_hostname; + ret.port = m_port; + ret.type = settings_pack::i2p_proxy; + return ret; + } + + void i2p_connection::open(std::string const& s, int port + , i2p_stream::handler_type const& handler) { // we already seem to have a session to this SAM router - if (m_sam_router.hostname == s.hostname - && m_sam_router.port == s.port + if (m_hostname == s + && m_port == port && m_sam_socket && (is_open() || m_state == sam_connecting)) return; - m_sam_router = s; - m_sam_router.type = proxy_settings::i2p_proxy; + m_hostname = s; + m_port = port; - if (m_sam_router.hostname.empty()) return; + if (m_hostname.empty()) return; m_state = sam_connecting; @@ -114,7 +125,7 @@ namespace libtorrent to_hex(tmp, 20, &m_session_id[0]); m_sam_socket.reset(new i2p_stream(m_io_service)); - m_sam_socket->set_proxy(m_sam_router.hostname, m_sam_router.port); + m_sam_socket->set_proxy(m_hostname, m_port); m_sam_socket->set_command(i2p_stream::cmd_create_session); m_sam_socket->set_session_id(m_session_id.c_str()); @@ -207,16 +218,6 @@ namespace libtorrent #endif } -// TODO: move this to proxy_base and use it in all proxies - bool i2p_stream::handle_error(error_code const& e, boost::shared_ptr const& h) - { - TORRENT_ASSERT(m_magic == 0x1337); - if (!e) return false; -// fprintf(stderr, "i2p error \"%s\"\n", e.message().c_str()); - (*h)(e); - return true; - } - void i2p_stream::do_connect(error_code const& e, tcp::resolver::iterator i , boost::shared_ptr h) { diff --git a/src/instantiate_connection.cpp b/src/instantiate_connection.cpp index 18619e34e..b3289186d 100644 --- a/src/instantiate_connection.cpp +++ b/src/instantiate_connection.cpp @@ -63,7 +63,7 @@ namespace libtorrent str->set_impl(sm->new_utp_socket(str)); } #if TORRENT_USE_I2P - else if (ps.type == proxy_settings::i2p_proxy) + else if (ps.type == settings_pack::i2p_proxy) { // it doesn't make any sense to try ssl over i2p TORRENT_ASSERT(ssl_context == 0); @@ -71,7 +71,7 @@ namespace libtorrent s.get()->set_proxy(ps.hostname, ps.port); } #endif - else if (ps.type == proxy_settings::none + else if (ps.type == settings_pack::none || (peer_connection && !ps.proxy_peer_connections)) { #ifdef TORRENT_USE_OPENSSL @@ -85,8 +85,8 @@ namespace libtorrent s.instantiate(ios); } } - else if (ps.type == proxy_settings::http - || ps.type == proxy_settings::http_pw) + else if (ps.type == settings_pack::http + || ps.type == settings_pack::http_pw) { http_stream* str; #ifdef TORRENT_USE_OPENSSL @@ -103,12 +103,12 @@ namespace libtorrent } str->set_proxy(ps.hostname, ps.port); - if (ps.type == proxy_settings::http_pw) + if (ps.type == settings_pack::http_pw) str->set_username(ps.username, ps.password); } - else if (ps.type == proxy_settings::socks5 - || ps.type == proxy_settings::socks5_pw - || ps.type == proxy_settings::socks4) + else if (ps.type == settings_pack::socks5 + || ps.type == settings_pack::socks5_pw + || ps.type == settings_pack::socks4) { socks5_stream* str; #ifdef TORRENT_USE_OPENSSL @@ -124,9 +124,9 @@ namespace libtorrent str = s.get(); } str->set_proxy(ps.hostname, ps.port); - if (ps.type == proxy_settings::socks5_pw) + if (ps.type == settings_pack::socks5_pw) str->set_username(ps.username, ps.password); - if (ps.type == proxy_settings::socks4) + if (ps.type == settings_pack::socks4) str->set_version(4); } else diff --git a/src/ip_filter.cpp b/src/ip_filter.cpp index c481fea9c..22d1a75f8 100644 --- a/src/ip_filter.cpp +++ b/src/ip_filter.cpp @@ -36,7 +36,7 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { - void ip_filter::add_rule(address first, address last, int flags) + void ip_filter::add_rule(address first, address last, boost::uint32_t flags) { if (first.is_v4()) { @@ -76,7 +76,7 @@ namespace libtorrent #endif } - void port_filter::add_rule(boost::uint16_t first, boost::uint16_t last, int flags) + void port_filter::add_rule(boost::uint16_t first, boost::uint16_t last, boost::uint32_t flags) { m_filter.add_rule(first, last, flags); } diff --git a/src/kademlia/dht_tracker.cpp b/src/kademlia/dht_tracker.cpp index a4bd47f92..7cbb6ddb3 100644 --- a/src/kademlia/dht_tracker.cpp +++ b/src/kademlia/dht_tracker.cpp @@ -52,6 +52,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/io.hpp" #include "libtorrent/version.hpp" #include "libtorrent/escape_string.hpp" +#include "libtorrent/performance_counters.hpp" // for counters using boost::ref; using libtorrent::dht::node_impl; @@ -176,11 +177,12 @@ namespace libtorrent { namespace dht // class that puts the networking and the kademlia node in a single // unit and connecting them together. dht_tracker::dht_tracker(libtorrent::aux::session_impl& ses, rate_limited_udp_socket& sock - , dht_settings const& settings, entry const* state) - : m_dht(&ses, this, settings, extract_node_id(state) - , ses.external_address().external_address(address_v4()), &ses) + , dht_settings const& settings, counters& cnt, entry const* state) + : m_counters(cnt) + , m_dht(&ses, this, settings, extract_node_id(state) + , ses.external_address().external_address(address_v4()), &ses, cnt) , m_sock(sock) - , m_last_new_key(time_now() - minutes(key_refresh)) + , m_last_new_key(time_now() - minutes(int(key_refresh))) , m_timer(sock.get_io_service()) , m_connection_timer(sock.get_io_service()) , m_refresh_timer(sock.get_io_service()) @@ -300,7 +302,7 @@ namespace libtorrent { namespace dht m_timer.async_wait(boost::bind(&dht_tracker::tick, self(), _1)); ptime now = time_now(); - if (now - m_last_new_key > minutes(key_refresh)) + if (now - minutes(int(key_refresh)) > m_last_new_key) { m_last_new_key = now; m_dht.new_write_key(); @@ -480,10 +482,10 @@ namespace libtorrent { namespace dht || ec == asio::error::connection_reset || ec == asio::error::connection_aborted #ifdef WIN32 - || ec == error_code(ERROR_HOST_UNREACHABLE, get_system_category()) - || ec == error_code(ERROR_PORT_UNREACHABLE, get_system_category()) - || ec == error_code(ERROR_CONNECTION_REFUSED, get_system_category()) - || ec == error_code(ERROR_CONNECTION_ABORTED, get_system_category()) + || ec == error_code(ERROR_HOST_UNREACHABLE, system_category()) + || ec == error_code(ERROR_PORT_UNREACHABLE, system_category()) + || ec == error_code(ERROR_CONNECTION_REFUSED, system_category()) + || ec == error_code(ERROR_CONNECTION_ABORTED, system_category()) #endif ) { @@ -511,57 +513,15 @@ namespace libtorrent { namespace dht return true; } - node_ban_entry* match = 0; - node_ban_entry* min = m_ban_nodes; - ptime now = time_now(); - for (node_ban_entry* i = m_ban_nodes; i < m_ban_nodes + num_ban_nodes; ++i) - { - if (i->src == ep.address()) - { - match = i; - break; - } - if (i->count < min->count) min = i; - } - - if (match) - { - ++match->count; - if (match->count >= 20) - { - if (now < match->limit) - { -#ifdef TORRENT_DHT_VERBOSE_LOGGING - if (match->count == 20) - { - TORRENT_LOG(dht_tracker) << " BANNING PEER [ ip: " - << ep << " time: " << total_milliseconds((now - match->limit) + seconds(5)) / 1000.f - << " count: " << match->count << " ]"; - } -#endif - // we've received 20 messages in less than 5 seconds from - // this node. Ignore it until it's silent for 5 minutes - match->limit = now + minutes(5); - return true; - } - - // we got 50 messages from this peer, but it was in - // more than 5 seconds. Reset the counter and the timer - match->count = 0; - match->limit = now + seconds(5); - } - } - else - { - min->count = 1; - min->limit = now + seconds(5); - min->src = ep.address(); - } + if (!m_blocker.incoming(ep.address(), time_now())) + return true; #ifdef TORRENT_DHT_VERBOSE_LOGGING ++m_total_message_input; m_total_in_bytes += size; #endif + m_counters.inc_stats_counter(counters::dht_bytes_in, size); + m_counters.inc_stats_counter(counters::dht_messages_in); using libtorrent::entry; using libtorrent::bdecode; @@ -680,6 +640,11 @@ namespace libtorrent { namespace dht m_dht.add_router_node(node); } + bool dht_tracker::has_quota() + { + return m_sock.has_quota(); + } + bool dht_tracker::send_packet(libtorrent::entry& e, udp::endpoint const& addr, int send_flags) { using libtorrent::bencode; @@ -705,6 +670,7 @@ namespace libtorrent { namespace dht { if (ec) { + m_counters.inc_stats_counter(counters::dht_messages_out_dropped); #ifdef TORRENT_DHT_VERBOSE_LOGGING TORRENT_LOG(dht_tracker) << "==> " << addr << " DROPPED (" << ec.message() << ") " << log_line.str(); #endif @@ -714,6 +680,8 @@ namespace libtorrent { namespace dht // account for IP and UDP overhead m_sent_bytes += m_send_buf.size() + (addr.address().is_v6() ? 48 : 28); + m_counters.inc_stats_counter(counters::dht_bytes_out, m_send_buf.size()); + m_counters.inc_stats_counter(counters::dht_messages_out); #ifdef TORRENT_DHT_VERBOSE_LOGGING m_total_out_bytes += m_send_buf.size(); @@ -745,6 +713,8 @@ namespace libtorrent { namespace dht } else { + m_counters.inc_stats_counter(counters::dht_messages_out_dropped); + #ifdef TORRENT_DHT_VERBOSE_LOGGING TORRENT_LOG(dht_tracker) << "==> " << addr << " DROPPED " << log_line.str(); #endif diff --git a/src/kademlia/dos_blocker.cpp b/src/kademlia/dos_blocker.cpp new file mode 100644 index 000000000..93f2b71ba --- /dev/null +++ b/src/kademlia/dos_blocker.cpp @@ -0,0 +1,100 @@ +/* + +Copyright (c) 2006-2013, 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. + +*/ + +#include "libtorrent/kademlia/dos_blocker.hpp" + +#ifdef TORRENT_DHT_VERBOSE_LOGGING +#include "libtorrent/kademlia/logging.hpp" +#endif + +namespace libtorrent { namespace dht +{ + +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_DECLARE_LOG(dht_tracker); +#endif + + dos_blocker::dos_blocker() {} + + bool dos_blocker::incoming(address addr, ptime now) + { + node_ban_entry* match = 0; + node_ban_entry* min = m_ban_nodes; + for (node_ban_entry* i = m_ban_nodes; i < m_ban_nodes + num_ban_nodes; ++i) + { + if (i->src == addr) + { + match = i; + break; + } + if (i->count < min->count) min = i; + } + + if (match) + { + ++match->count; + if (match->count >= 20) + { + if (now < match->limit) + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + if (match->count == 20) + { + TORRENT_LOG(dht_tracker) << " BANNING PEER [ ip: " + << addr << " time: " << total_milliseconds((now - match->limit) + seconds(5)) / 1000.f + << " count: " << match->count << " ]"; + } +#endif + + // we've received 20 messages in less than 5 seconds from + // this node. Ignore it until it's silent for 5 minutes + match->limit = now + minutes(5); + return false; + } + + // we got 50 messages from this peer, but it was in + // more than 5 seconds. Reset the counter and the timer + match->count = 0; + match->limit = now + seconds(5); + } + } + else + { + min->count = 1; + min->limit = now + seconds(5); + min->src = addr; + } + return true; + } +} +} + diff --git a/src/kademlia/get_peers.cpp b/src/kademlia/get_peers.cpp index 98064bec7..9e7821a50 100644 --- a/src/kademlia/get_peers.cpp +++ b/src/kademlia/get_peers.cpp @@ -33,6 +33,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include namespace libtorrent { namespace dht { @@ -142,6 +143,8 @@ bool get_peers::invoke(observer_ptr o) a["info_hash"] = m_target.to_string(); if (m_noseeds) a["noseed"] = 1; + m_node.stats_counters().inc_stats_counter(counters::dht_get_peers_out); + return m_node.m_rpc.invoke(e, o->target_ep(), o); } diff --git a/src/kademlia/node.cpp b/src/kademlia/node.cpp index bd8a82b72..c8de239dd 100644 --- a/src/kademlia/node.cpp +++ b/src/kademlia/node.cpp @@ -51,6 +51,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/kademlia/refresh.hpp" #include "libtorrent/kademlia/get_peers.hpp" #include "libtorrent/kademlia/get_item.hpp" +#include "libtorrent/performance_counters.hpp" // for counters #ifdef TORRENT_USE_VALGRIND #include @@ -98,7 +99,8 @@ void nop() {} node_impl::node_impl(alert_dispatcher* alert_disp , udp_socket_interface* sock , dht_settings const& settings, node_id nid, address const& external_address - , dht_observer* observer) + , dht_observer* observer + , struct counters& cnt) : m_settings(settings) , m_id(nid == (node_id::min)() || !verify_id(nid, external_address) ? generate_id(external_address) : nid) , m_table(m_id, 8, settings) @@ -107,6 +109,7 @@ node_impl::node_impl(alert_dispatcher* alert_disp , m_last_tracker_tick(time_now()) , m_post_alert(alert_disp) , m_sock(sock) + , m_counters(cnt) { m_secret[0] = random(); m_secret[1] = std::rand(); @@ -327,6 +330,7 @@ namespace a["token"] = i->second; a["seed"] = (flags & node_impl::flag_seed) ? 1 : 0; if (flags & node_impl::flag_implied_port) a["implied_port"] = 1; + node.stats_counters().inc_stats_counter(counters::dht_announce_peer_in); node.m_rpc.invoke(e, i->first.ep(), o); } } @@ -359,6 +363,7 @@ void node_impl::add_node(udp::endpoint node) entry e; e["y"] = "q"; e["q"] = "ping"; + m_counters.inc_stats_counter(counters::dht_get_peers_out); m_rpc.invoke(e, node, o); } @@ -425,7 +430,7 @@ time_duration node_impl::connection_timeout() { time_duration d = m_rpc.tick(); ptime now(time_now()); - if (now - m_last_tracker_tick < minutes(2)) return d; + if (now - minutes(2) < m_last_tracker_tick) return d; m_last_tracker_tick = now; for (dht_immutable_table_t::iterator i = m_immutable_table.begin(); @@ -438,6 +443,7 @@ time_duration node_impl::connection_timeout() } free(i->second.value); m_immutable_table.erase(i++); + m_counters.inc_stats_counter(counters::dht_immutable_data, -1); } // look through all peers and see if any have timed out @@ -452,7 +458,11 @@ time_duration node_impl::connection_timeout() if (t.peers.empty()) { table_t::iterator i = m_map.find(key); - if (i != m_map.end()) m_map.erase(i); + if (i != m_map.end()) + { + m_map.erase(i); + m_counters.inc_stats_counter(counters::dht_torrents, -1); + } } } @@ -704,6 +714,9 @@ struct immutable_item_comparator // build response void node_impl::incoming_request(msg const& m, entry& e) { + if (!m_sock->has_quota()) + return; + e = entry(entry::dictionary_t); e["y"] = "r"; e["t"] = m.message.dict_find_string_value("t"); @@ -749,6 +762,7 @@ void node_impl::incoming_request(msg const& m, entry& e) if (strcmp(query, "ping") == 0) { + m_counters.inc_stats_counter(counters::dht_ping_in); // we already have 't' and 'id' in the response // no more left to add } @@ -770,6 +784,8 @@ void node_impl::incoming_request(msg const& m, entry& e) reply["token"] = generate_token(m.addr, msg_keys[0]->string_ptr()); + m_counters.inc_stats_counter(counters::dht_get_peers_in); + sha1_hash info_hash(msg_keys[0]->string_ptr()); nodes_t n; // always return nodes as well as peers @@ -805,6 +821,7 @@ void node_impl::incoming_request(msg const& m, entry& e) return; } + m_counters.inc_stats_counter(counters::dht_find_node_in); sha1_hash target(msg_keys[0]->string_ptr()); // TODO: 1 find_node should write directly to the response entry @@ -866,45 +883,60 @@ void node_impl::incoming_request(msg const& m, entry& e) return; } + m_counters.inc_stats_counter(counters::dht_announce_peer_in); + // the token was correct. That means this // node is not spoofing its address. So, let // the table get a chance to add it. m_table.node_seen(id, m.addr, 0xffff); - if (!m_map.empty() && int(m_map.size()) >= m_settings.max_torrents) + table_t::iterator ti = m_map.find(info_hash); + torrent_entry* v; + if (ti == m_map.end()) { - // we need to remove some. Remove the ones with the - // fewest peers - int num_peers = m_map.begin()->second.peers.size(); - table_t::iterator candidate = m_map.begin(); - for (table_t::iterator i = m_map.begin() - , end(m_map.end()); i != end; ++i) + // we don't have this torrent, add it + // do we need to remove another one first? + if (!m_map.empty() && int(m_map.size()) >= m_settings.max_torrents) { - if (int(i->second.peers.size()) > num_peers) continue; - if (i->first == info_hash) continue; - num_peers = i->second.peers.size(); - candidate = i; + // we need to remove some. Remove the ones with the + // fewest peers + int num_peers = m_map.begin()->second.peers.size(); + table_t::iterator candidate = m_map.begin(); + for (table_t::iterator i = m_map.begin() + , end(m_map.end()); i != end; ++i) + { + if (int(i->second.peers.size()) > num_peers) continue; + if (i->first == info_hash) continue; + num_peers = i->second.peers.size(); + candidate = i; + } + m_map.erase(candidate); + m_counters.inc_stats_counter(counters::dht_torrents, -1); } - m_map.erase(candidate); + m_counters.inc_stats_counter(counters::dht_torrents); + v = &m_map[info_hash]; + } + else + { + v = &ti->second; } - torrent_entry& v = m_map[info_hash]; // the peer announces a torrent name, and we don't have a name // for this torrent. Store it. - if (msg_keys[3] && v.name.empty()) + if (msg_keys[3] && v->name.empty()) { std::string name = msg_keys[3]->string_value(); if (name.size() > 50) name.resize(50); - v.name = name; + v->name = name; } peer_entry peer; peer.addr = tcp::endpoint(m.addr.address(), port); peer.added = time_now(); peer.seed = msg_keys[4] && msg_keys[4]->int_value(); - std::set::iterator i = v.peers.find(peer); - if (i != v.peers.end()) v.peers.erase(i++); - v.peers.insert(i, peer); + std::set::iterator i = v->peers.find(peer); + if (i != v->peers.end()) v->peers.erase(i++); + v->peers.insert(i, peer); #ifdef TORRENT_DHT_VERBOSE_LOGGING ++g_announces; #endif @@ -932,6 +964,8 @@ void node_impl::incoming_request(msg const& m, entry& e) return; } + m_counters.inc_stats_counter(counters::dht_put_in); + // is this a mutable put? bool mutable_put = (msg_keys[2] && msg_keys[3] && msg_keys[4]); @@ -1001,6 +1035,7 @@ void node_impl::incoming_request(msg const& m, entry& e) TORRENT_ASSERT(j != m_immutable_table.end()); free(j->second.value); m_immutable_table.erase(j); + m_counters.inc_stats_counter(counters::dht_immutable_data, -1); } dht_immutable_item to_add; to_add.value = (char*)malloc(buf.second); @@ -1009,6 +1044,7 @@ void node_impl::incoming_request(msg const& m, entry& e) boost::tie(i, boost::tuples::ignore) = m_immutable_table.insert( std::make_pair(target, to_add)); + m_counters.inc_stats_counter(counters::dht_immutable_data); } // fprintf(stderr, "added immutable item (%d)\n", int(m_immutable_table.size())); @@ -1048,6 +1084,7 @@ void node_impl::incoming_request(msg const& m, entry& e) free(j->second.value); free(j->second.salt); m_mutable_table.erase(j); + m_counters.inc_stats_counter(counters::dht_mutable_data, -1); } dht_mutable_item to_add; to_add.value = (char*)malloc(buf.second); @@ -1068,6 +1105,7 @@ void node_impl::incoming_request(msg const& m, entry& e) boost::tie(i, boost::tuples::ignore) = m_mutable_table.insert( std::make_pair(target, to_add)); + m_counters.inc_stats_counter(counters::dht_mutable_data); // fprintf(stderr, "added mutable item (%d)\n", int(m_mutable_table.size())); } @@ -1146,6 +1184,7 @@ void node_impl::incoming_request(msg const& m, entry& e) return; } + m_counters.inc_stats_counter(counters::dht_get_in); sha1_hash target(msg_keys[0]->string_ptr()); // fprintf(stderr, "%s GET target: %s\n" diff --git a/src/kademlia/refresh.cpp b/src/kademlia/refresh.cpp index 6ac6c072c..7fc10761b 100644 --- a/src/kademlia/refresh.cpp +++ b/src/kademlia/refresh.cpp @@ -33,6 +33,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include @@ -73,6 +74,7 @@ bool refresh::invoke(observer_ptr o) e["q"] = "find_node"; entry& a = e["a"]; a["target"] = target().to_string(); + m_node.stats_counters().inc_stats_counter(counters::dht_find_node_out); return m_node.m_rpc.invoke(e, o->target_ep(), o); } @@ -82,15 +84,19 @@ bootstrap::bootstrap( , done_callback const& callback) : refresh(node, target, callback) { - // make it more resilient to nodes not responding. - // we don't want to terminate early when we're bootstrapping - m_num_target_nodes *= 2; } char const* bootstrap::name() const { return "bootstrap"; } void bootstrap::done() { + // TODO: 4 when bootstrapping against our own IP completes, + // continue to issue another bootstrap against the deepest, + // non-full bucket. when it completes, issue a bootstrap against + // one bucket above it, and so on until the bootstrap lookup + // against the top level bucket (bucket 0) completes. That's + // when the bootstrap is done + #ifdef TORRENT_DHT_VERBOSE_LOGGING TORRENT_LOG(traversal) << "[" << this << "]" << " bootstrap done, pinging remaining nodes"; diff --git a/src/kademlia/routing_table.cpp b/src/kademlia/routing_table.cpp index 94d60b4f5..68cb7b5f2 100644 --- a/src/kademlia/routing_table.cpp +++ b/src/kademlia/routing_table.cpp @@ -48,6 +48,15 @@ POSSIBILITY OF SUCH DAMAGE. using boost::uint8_t; +#if BOOST_VERSION <= 104700 +namespace boost { +size_t hash_value(libtorrent::address_v4::bytes_type ip) +{ + return boost::hash_value(*reinterpret_cast(&ip[0])); +} +} +#endif + namespace libtorrent { namespace dht { @@ -58,12 +67,12 @@ TORRENT_DEFINE_LOG(table) routing_table::routing_table(node_id const& id, int bucket_size , dht_settings const& settings) : m_settings(settings) - , m_bucket_size(bucket_size) , m_id(id) , m_depth(0) , m_last_bootstrap(min_time()) , m_last_refresh(min_time()) , m_last_self_refresh(min_time()) + , m_bucket_size(bucket_size) { m_buckets.reserve(30); } @@ -297,7 +306,7 @@ bool routing_table::need_refresh(node_id& target) const ptime now = time_now(); // refresh our own bucket once every 15 minutes - if (now - m_last_self_refresh > minutes(15)) + if (now - minutes(15) > m_last_self_refresh) { m_last_self_refresh = now; target = m_id; @@ -312,8 +321,8 @@ bool routing_table::need_refresh(node_id& target) const table_t::const_iterator i = std::min_element(m_buckets.begin(), m_buckets.end() , &compare_bucket_refresh); - if (now - i->last_active < minutes(15)) return false; - if (now - m_last_refresh < seconds(45)) return false; + if (now - minutes(15) < i->last_active) return false; + if (now - seconds(45) < m_last_refresh) return false; // generate a random node_id within the given bucket target = generate_random_id(); @@ -973,7 +982,7 @@ bool routing_table::node_seen(node_id const& id, udp::endpoint ep, int rtt) bool routing_table::need_bootstrap() const { ptime now = time_now(); - if (now - m_last_bootstrap < seconds(30)) return false; + if (now - seconds(30) < m_last_bootstrap) return false; for (table_t::const_iterator i = m_buckets.begin() , end(m_buckets.end()); i != end; ++i) @@ -1081,7 +1090,7 @@ void routing_table::find_node(node_id const& target #if TORRENT_USE_INVARIANT_CHECKS void routing_table::check_invariant() const { - std::multiset all_ips; + boost::unordered_multiset all_ips; for (table_t::const_iterator i = m_buckets.begin() , end(m_buckets.end()); i != end; ++i) diff --git a/src/kademlia/rpc_manager.cpp b/src/kademlia/rpc_manager.cpp index 8dce64cf0..6d3a37e72 100644 --- a/src/kademlia/rpc_manager.cpp +++ b/src/kademlia/rpc_manager.cpp @@ -66,7 +66,7 @@ TORRENT_DEFINE_LOG(rpc) void intrusive_ptr_add_ref(observer const* o) { TORRENT_ASSERT(o != 0); - TORRENT_ASSERT(o->m_refs >= 0); + TORRENT_ASSERT(o->m_refs < 0xffff); ++o->m_refs; } @@ -211,7 +211,7 @@ rpc_manager::~rpc_manager() for (transactions_t::iterator i = m_transactions.begin() , end(m_transactions.end()); i != end; ++i) { - (*i)->abort(); + i->second->abort(); } } @@ -243,7 +243,7 @@ void rpc_manager::check_invariant() const for (transactions_t::const_iterator i = m_transactions.begin() , end(m_transactions.end()); i != end; ++i) { - TORRENT_ASSERT(*i); + TORRENT_ASSERT(i->second); } } #endif @@ -257,10 +257,10 @@ void rpc_manager::unreachable(udp::endpoint const& ep) for (transactions_t::iterator i = m_transactions.begin(); i != m_transactions.end();) { - TORRENT_ASSERT(*i); - observer_ptr const& o = *i; + TORRENT_ASSERT(i->second); + observer_ptr const& o = i->second; if (o->target_ep() != ep) { ++i; continue; } - observer_ptr ptr = *i; + observer_ptr ptr = i->second; i = m_transactions.erase(i); #ifdef TORRENT_DHT_VERBOSE_LOGGING TORRENT_LOG(rpc) << " found transaction [ tid: " << ptr->transaction_id() << " ]"; @@ -293,18 +293,11 @@ bool rpc_manager::incoming(msg const& m, node_id* id, libtorrent::dht_settings c int tid = transaction_id.size() != 2 ? -1 : io::read_uint16(i); observer_ptr o; - - for (transactions_t::iterator i = m_transactions.begin() - , end(m_transactions.end()); i != end;) + std::pair range = m_transactions.equal_range(tid); + for (transactions_t::iterator i = range.first; i != range.second; ++i) { - TORRENT_ASSERT(*i); - if ((*i)->transaction_id() != tid - || m.addr.address() != (*i)->target_addr()) - { - ++i; - continue; - } - o = *i; + if (m.addr.address() != i->second->target_addr()) continue; + o = i->second; i = m_transactions.erase(i); break; } @@ -394,70 +387,49 @@ time_duration rpc_manager::tick() if (m_transactions.empty()) return seconds(short_timeout); - std::list timeouts; + std::vector timeouts; + std::vector short_timeouts; time_duration ret = seconds(short_timeout); ptime now = time_now(); -#if TORRENT_USE_ASSERTS - ptime last = min_time(); - for (transactions_t::iterator i = m_transactions.begin(); - i != m_transactions.end(); ++i) - { - TORRENT_ASSERT((*i)->sent() >= last); - last = (*i)->sent(); - } -#endif - for (transactions_t::iterator i = m_transactions.begin(); i != m_transactions.end();) { - observer_ptr o = *i; + observer_ptr o = i->second; - // if we reach an observer that hasn't timed out - // break, because every observer after this one will - // also not have timed out yet time_duration diff = now - o->sent(); - if (diff < seconds(timeout)) + if (diff >= seconds(timeout)) { - ret = seconds(timeout) - diff; - break; - } - #ifdef TORRENT_DHT_VERBOSE_LOGGING - TORRENT_LOG(rpc) << "[" << o->m_algorithm.get() << "] Timing out transaction id: " - << (*i)->transaction_id() << " from " << o->target_ep(); + TORRENT_LOG(rpc) << "[" << o->m_algorithm.get() << "] Timing out transaction id: " + << o->transaction_id() << " from " << o->target_ep(); #endif - i = m_transactions.erase(i); - timeouts.push_back(o); + m_transactions.erase(i++); + timeouts.push_back(o); + continue; + } + + // don't call short_timeout() again if we've + // already called it once + if (diff >= seconds(short_timeout) && !o->has_short_timeout()) + { +#ifdef TORRENT_DHT_VERBOSE_LOGGING + TORRENT_LOG(rpc) << "[" << o->m_algorithm.get() << "] Short-Timing out transaction id: " + << o->transaction_id() << " from " << o->target_ep(); +#endif + ++i; + + short_timeouts.push_back(o); + continue; + } + + ret = std::min(seconds(timeout) - diff, ret); + ++i; } std::for_each(timeouts.begin(), timeouts.end(), boost::bind(&observer::timeout, _1)); - timeouts.clear(); - - for (transactions_t::iterator i = m_transactions.begin(); - i != m_transactions.end(); ++i) - { - observer_ptr o = *i; - - // if we reach an observer that hasn't timed out - // break, because every observer after this one will - // also not have timed out yet - time_duration diff = now - o->sent(); - if (diff < seconds(short_timeout)) - { - ret = seconds(short_timeout) - diff; - break; - } - - // don't call short_timeout() again if we've - // already called it once - if (o->has_short_timeout()) continue; - - timeouts.push_back(o); - } - - std::for_each(timeouts.begin(), timeouts.end(), boost::bind(&observer::short_timeout, _1)); + std::for_each(short_timeouts.begin(), short_timeouts.end(), boost::bind(&observer::short_timeout, _1)); return ret; } @@ -495,7 +467,7 @@ bool rpc_manager::invoke(entry& e, udp::endpoint target_addr if (m_sock->send_packet(e, target_addr, 1)) { - m_transactions.push_back(o); + m_transactions.insert(std::make_pair(tid,o)); #if TORRENT_USE_ASSERTS o->m_was_sent = true; #endif diff --git a/src/kademlia/traversal_algorithm.cpp b/src/kademlia/traversal_algorithm.cpp index cc284dc9c..964bc9b06 100644 --- a/src/kademlia/traversal_algorithm.cpp +++ b/src/kademlia/traversal_algorithm.cpp @@ -37,7 +37,6 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include "libtorrent/broadcast_socket.hpp" // for cidr_distance #include // for read_*_endpoint #include @@ -84,14 +83,13 @@ observer_ptr traversal_algorithm::new_observer(void* ptr traversal_algorithm::traversal_algorithm( node_impl& node , node_id target) - : m_ref_count(0) - , m_node(node) + : m_node(node) , m_target(target) + , m_ref_count(0) , m_invoke_count(0) , m_branch_factor(3) , m_responses(0) , m_timeouts(0) - , m_num_target_nodes(m_node.m_table.bucket_size()) { #ifdef TORRENT_DHT_VERBOSE_LOGGING TORRENT_LOG(traversal) << "[" << this << "] NEW" @@ -101,21 +99,6 @@ traversal_algorithm::traversal_algorithm( #endif } -// returns true of lhs and rhs are too close to each other to appear -// in the same DHT search under different node IDs -bool compare_ip_cidr(observer_ptr const& lhs, observer_ptr const& rhs) -{ - if (lhs->target_addr().is_v4() != rhs->target_addr().is_v4()) - return false; - // the number of bits in the IPs that may match. If - // more bits that this matches, something suspicious is - // going on and we shouldn't add the second one to our - // routing table - int cutoff = rhs->target_addr().is_v4() ? 4 : 64; - int dist = cidr_distance(lhs->target_addr(), rhs->target_addr()); - return dist <= cutoff; -} - void traversal_algorithm::resort_results() { std::sort( @@ -176,11 +159,11 @@ void traversal_algorithm::add_entry(node_id const& id, udp::endpoint addr, unsig if (m_node.settings().restrict_search_ips && !(flags & observer::flag_initial)) { - // don't allow multiple entries from IPs very close to each other - std::vector::iterator j = std::find_if( - m_results.begin(), m_results.end(), boost::bind(&compare_ip_cidr, _1, o)); + // mask the lower octet + boost::uint32_t prefix4 = o->target_addr().to_v4().to_ulong(); + prefix4 &= 0xffffff00; - if (j != m_results.end()) + if (m_peer4_prefixes.count(prefix4) > 0) { // we already have a node in this search with an IP very // close to this one. We know that it's not the same, because @@ -189,14 +172,13 @@ void traversal_algorithm::add_entry(node_id const& id, udp::endpoint addr, unsig TORRENT_LOG(traversal) << "[" << this << "] IGNORING result " << "id: " << o->id() << " address: " << o->target_addr() - << " existing node: " - << (*j)->id() << " " << (*j)->target_addr() - << " distance: " << distance_exp(m_target, o->id()) << " type: " << name() ; #endif return; } + + m_peer4_prefixes.insert(prefix4); } TORRENT_ASSERT((o->flags & observer::flag_no_id) || std::find_if(m_results.begin(), m_results.end() @@ -364,7 +346,7 @@ void traversal_algorithm::failed(observer_ptr o, int flags) void traversal_algorithm::done() { #ifdef TORRENT_DHT_VERBOSE_LOGGING - int results_target = m_num_target_nodes; + int results_target = m_node.m_table.bucket_size(); int closest_target = 160; for (std::vector::iterator i = m_results.begin() @@ -399,7 +381,7 @@ void traversal_algorithm::done() bool traversal_algorithm::add_requests() { - int results_target = m_num_target_nodes; + int results_target = m_node.m_table.bucket_size(); // this only counts outstanding requests at the top of the // target list. This is <= m_invoke count. m_invoke_count @@ -469,7 +451,7 @@ bool traversal_algorithm::add_requests() } } - // this is the completion condition. If we found m_num_target_nodes + // this is the completion condition. If we found m_node.m_table.bucket_size() // (i.e. k=8) completed results, without finding any still // outstanding requests, we're done. // also, if invoke count is 0, it means we didn't even find 'k' @@ -574,7 +556,6 @@ void traversal_observer::reply(msg const& m) void traversal_algorithm::abort() { - m_num_target_nodes = 0; for (std::vector::iterator i = m_results.begin() , end(m_results.end()); i != end; ++i) { diff --git a/src/lazy_bdecode.cpp b/src/lazy_bdecode.cpp index 30a047f50..4034a59d4 100644 --- a/src/lazy_bdecode.cpp +++ b/src/lazy_bdecode.cpp @@ -242,6 +242,16 @@ namespace libtorrent return 0; } + int lazy_entry::capacity() const + { + TORRENT_ASSERT(m_type == dict_t || m_type == list_t); + if (m_data.list == NULL) return 0; + if (m_type == dict_t) + return m_data.dict[0].val.m_len; + else + return m_data.list[0].m_len; + } + boost::int64_t lazy_entry::int_value() const { TORRENT_ASSERT(m_type == int_t); @@ -259,28 +269,29 @@ namespace libtorrent lazy_entry* lazy_entry::dict_append(char const* name) { TORRENT_ASSERT(m_type == dict_t); - TORRENT_ASSERT(m_size <= m_capacity); - if (m_capacity == 0) + TORRENT_ASSERT(m_size <= this->capacity()); + if (m_data.dict == NULL) { int capacity = lazy_entry_dict_init; - m_data.dict = new (std::nothrow) lazy_dict_entry[capacity]; - if (m_data.dict == 0) return 0; - m_capacity = capacity; + m_data.dict = new (std::nothrow) lazy_dict_entry[capacity+1]; + if (m_data.dict == NULL) return NULL; + m_data.dict[0].val.m_len = capacity; } - else if (m_size == m_capacity) + else if (m_size == this->capacity()) { - int capacity = m_capacity * lazy_entry_grow_factor / 100; - lazy_dict_entry* tmp = new (std::nothrow) lazy_dict_entry[capacity]; - if (tmp == 0) return 0; - std::memcpy(tmp, m_data.dict, sizeof(lazy_dict_entry) * m_size); - for (int i = 0; i < int(m_size); ++i) m_data.dict[i].val.release(); + int capacity = this->capacity() * lazy_entry_grow_factor / 100; + lazy_dict_entry* tmp = new (std::nothrow) lazy_dict_entry[capacity+1]; + if (tmp == NULL) return NULL; + std::memcpy(tmp, m_data.dict, sizeof(lazy_dict_entry) * (m_size + 1)); + for (int i = 0; i < int(m_size); ++i) m_data.dict[i+1].val.release(); + delete[] m_data.dict; m_data.dict = tmp; - m_capacity = capacity; + m_data.dict[0].val.m_len = capacity; } - TORRENT_ASSERT(m_size < m_capacity); - lazy_dict_entry& ret = m_data.dict[m_size++]; + TORRENT_ASSERT(m_size < this->capacity()); + lazy_dict_entry& ret = m_data.dict[1+m_size++]; ret.name = name; return &ret.val; } @@ -338,7 +349,7 @@ namespace libtorrent { TORRENT_ASSERT(m_type == dict_t); TORRENT_ASSERT(i < int(m_size)); - lazy_dict_entry const& e = m_data.dict[i]; + lazy_dict_entry const& e = m_data.dict[i+1]; return std::make_pair(std::string(e.name, e.val.m_begin - e.name), &e.val); } @@ -396,7 +407,7 @@ namespace libtorrent TORRENT_ASSERT(m_type == dict_t); for (int i = 0; i < int(m_size); ++i) { - lazy_dict_entry& e = m_data.dict[i]; + lazy_dict_entry& e = m_data.dict[i+1]; if (string_equal(name, e.name, e.val.m_begin - e.name)) return &e.val; } @@ -406,28 +417,29 @@ namespace libtorrent lazy_entry* lazy_entry::list_append() { TORRENT_ASSERT(m_type == list_t); - TORRENT_ASSERT(m_size <= m_capacity); - if (m_capacity == 0) + TORRENT_ASSERT(m_size <= this->capacity()); + if (m_data.start == NULL) { int capacity = lazy_entry_list_init; - m_data.list = new (std::nothrow) lazy_entry[capacity]; + m_data.list = new (std::nothrow) lazy_entry[capacity+1]; if (m_data.list == 0) return 0; - m_capacity = capacity; + m_data.list[0].m_len = capacity; } - else if (m_size == m_capacity) + else if (m_size == this->capacity()) { - int capacity = m_capacity * lazy_entry_grow_factor / 100; - lazy_entry* tmp = new (std::nothrow) lazy_entry[capacity]; - if (tmp == 0) return 0; - std::memcpy(tmp, m_data.list, sizeof(lazy_entry) * m_size); - for (int i = 0; i < int(m_size); ++i) m_data.list[i].release(); + int capacity = this->capacity() * lazy_entry_grow_factor / 100; + lazy_entry* tmp = new (std::nothrow) lazy_entry[capacity+1]; + if (tmp == NULL) return NULL; + std::memcpy(tmp, m_data.list, sizeof(lazy_entry) * (m_size+1)); + for (int i = 0; i < int(m_size); ++i) m_data.list[i+1].release(); + delete[] m_data.list; m_data.list = tmp; - m_capacity = capacity; + m_data.list[0].m_len = capacity; } - TORRENT_ASSERT(m_size < m_capacity); - return m_data.list + (m_size++); + TORRENT_ASSERT(m_size < this->capacity()); + return &m_data.list[1 + (m_size++)]; } std::string lazy_entry::list_string_value_at(int i) const @@ -455,13 +467,16 @@ namespace libtorrent { switch (m_type) { - case list_t: delete[] m_data.list; break; - case dict_t: delete[] m_data.dict; break; + case list_t: + delete[] m_data.list; + break; + case dict_t: + delete[] m_data.dict; + break; default: break; } - m_data.start = 0; + m_data.start = NULL; m_size = 0; - m_capacity = 0; m_type = none_t; } diff --git a/src/lt_trackers.cpp b/src/lt_trackers.cpp index 1f3d727fb..67b2edb60 100644 --- a/src/lt_trackers.cpp +++ b/src/lt_trackers.cpp @@ -37,6 +37,7 @@ POSSIBILITY OF SUCH DAMAGE. #endif #include +#include #ifdef _MSC_VER #pragma warning(pop) @@ -55,6 +56,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/extensions.hpp" #include "libtorrent/extensions/lt_trackers.hpp" #include "libtorrent/alert_types.hpp" +#include "libtorrent/io.hpp" #include "libtorrent/escape_string.hpp" #include "libtorrent/parse_url.hpp" #ifdef TORRENT_STATS @@ -202,7 +204,7 @@ namespace libtorrent { namespace int ret = lazy_bdecode(body.begin, body.end, msg, ec); if (ret != 0 || msg.type() != lazy_entry::dict_t) { - m_pc.disconnect(errors::invalid_lt_tracker_message, 2); + m_pc.disconnect(errors::invalid_lt_tracker_message, peer_connection_interface::op_bittorrent, 2); return true; } diff --git a/src/metadata_transfer.cpp b/src/metadata_transfer.cpp index aa19e3f45..9caa183bb 100644 --- a/src/metadata_transfer.cpp +++ b/src/metadata_transfer.cpp @@ -30,6 +30,7 @@ POSSIBILITY OF SUCH DAMAGE. */ +#ifndef TORRENT_NO_DEPRECATE #ifndef TORRENT_DISABLE_EXTENSIONS #ifdef _MSC_VER @@ -37,6 +38,7 @@ POSSIBILITY OF SUCH DAMAGE. #endif #include +#include #ifdef _MSC_VER #pragma warning(pop) @@ -56,6 +58,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/extensions/metadata_transfer.hpp" #include "libtorrent/alert_types.hpp" #include "libtorrent/buffer.hpp" +#include "libtorrent/io.hpp" namespace libtorrent { namespace { @@ -106,6 +109,21 @@ namespace libtorrent { namespace m_requested_metadata.resize(256, 0); } + bool need_loaded() + { return m_torrent.need_loaded(); } + + virtual void on_unload() + { + m_metadata.reset(); + } + + virtual void on_load() + { + // initialize m_metadata_size + TORRENT_ASSERT(m_torrent.is_loaded()); + metadata(); + } + virtual void on_files_checked() { // if the torrent is a seed, make a reference to @@ -337,6 +355,9 @@ namespace libtorrent { namespace detail::write_uint32((int)m_tp.metadata().left(), ptr); detail::write_uint32(offset.first, ptr); m_pc.send_buffer(msg, sizeof(msg)); + + // TODO: this is not safe. The torrent could be unloaded while + // we're still sending the metadata char const* metadata = m_tp.metadata().begin; m_pc.append_const_send_buffer(metadata + offset.first, offset.second); } @@ -368,7 +389,7 @@ namespace libtorrent { namespace if (length > 500 * 1024) { - m_pc.disconnect(errors::metadata_too_large, 2); + m_pc.disconnect(errors::metadata_too_large, peer_connection_interface::op_bittorrent, 2); return true; } @@ -391,7 +412,7 @@ namespace libtorrent { namespace if (length != 3) { // invalid metadata request - m_pc.disconnect(errors::invalid_metadata_request, 2); + m_pc.disconnect(errors::invalid_metadata_request, peer_connection_interface::op_bittorrent, 2); return true; } @@ -411,24 +432,24 @@ namespace libtorrent { namespace ,total_size, offset, data_size); #endif - if (total_size > m_torrent.session().settings().max_metadata_size) + if (total_size > m_torrent.session().settings().get_int(settings_pack::max_metadata_size)) { - m_pc.disconnect(errors::metadata_too_large, 2); + m_pc.disconnect(errors::metadata_too_large, peer_connection_interface::op_bittorrent, 2); return true; } if (total_size <= 0) { - m_pc.disconnect(errors::invalid_metadata_size, 2); + m_pc.disconnect(errors::invalid_metadata_size, peer_connection_interface::op_bittorrent, 2); return true; } if (offset > total_size || offset < 0) { - m_pc.disconnect(errors::invalid_metadata_offset, 2); + m_pc.disconnect(errors::invalid_metadata_offset, peer_connection_interface::op_bittorrent, 2); return true; } if (offset + data_size > total_size) { - m_pc.disconnect(errors::invalid_metadata_message, 2); + m_pc.disconnect(errors::invalid_metadata_message, peer_connection_interface::op_bittorrent, 2); return true; } @@ -455,7 +476,7 @@ namespace libtorrent { namespace break; default: { - m_pc.disconnect(errors::invalid_metadata_message, 2); + m_pc.disconnect(errors::invalid_metadata_message, peer_connection_interface::op_bittorrent, 2); } } return true; @@ -483,7 +504,7 @@ namespace libtorrent { namespace bool has_metadata() const { - return time_now() - m_no_metadata > minutes(5); + return time_now() - minutes(5) > m_no_metadata; } private: @@ -579,4 +600,5 @@ namespace libtorrent } #endif +#endif diff --git a/src/parse_url.cpp b/src/parse_url.cpp index 06f2fb42b..03dcc424a 100644 --- a/src/parse_url.cpp +++ b/src/parse_url.cpp @@ -52,7 +52,7 @@ namespace libtorrent // PARSE URL std::string::iterator start = url.begin(); // remove white spaces in front of the url - while (start != url.end() && (*start == ' ' || *start == '\t')) + while (start != url.end() && is_space(*start)) ++start; std::string::iterator end = std::find(url.begin(), url.end(), ':'); diff --git a/src/part_file.cpp b/src/part_file.cpp new file mode 100644 index 000000000..062bd5771 --- /dev/null +++ b/src/part_file.cpp @@ -0,0 +1,393 @@ +/* + +Copyright (c) 2012-2013, 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. + +*/ + + +/* + + The part_file file format is an array of piece sized blocks with + a simple header. For a given number of pieces, the header has a + fixed size. The header size is rounded up to an even multiple of + 1024, in an attempt at improving disk I/O performance by aligning + reads and writes to clusters on the drive. This is the file header + format. All values are stored big endian on disk. + + + // the size of the torrent (and can be used to calculate the size + // of the file header) + uint32_t num_pieces; + + // the number of bytes in each piece. This determines the size of + // each slot in the part file. This is typically an even power of 2, + // but it is not guaranteed to be. + uint32_t piece_size; + + // this is an array specifying which slots a particular piece resides in, + // A value of 0xffffffff (-1 if you will) means the piece is not in the part_file + // Any other value means the piece resides in the slot with that index + uint32_t piece[num_pieces]; + + // unused, n is defined as the number to align the size of this + // header to an even multiple of 1024 bytes. + uint8_t padding[n]; + +*/ + +#include "libtorrent/part_file.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/assert.hpp" +#include + +#ifdef TORRENT_USE_VALGRIND +#include +#endif + +namespace +{ + // round up to even kilobyte + int round_up(int n) + { return (n + 1023) & ~0x3ff; } +} + +namespace libtorrent +{ + part_file::part_file(std::string const& path, std::string const& name + , int num_pieces, int piece_size) + : m_path(path) + , m_name(name) + , m_num_allocated(0) + , m_max_pieces(num_pieces) + , m_piece_size(piece_size) + , m_header_size(round_up((2 + num_pieces) * 4)) + , m_dirty_metadata(false) + { + TORRENT_ASSERT(num_pieces > 0); + TORRENT_ASSERT(m_piece_size > 0); + + error_code ec; + std::string fn = combine_path(m_path, m_name); + m_file.open(fn, file::read_only, ec); + if (!ec) + { + // parse header + boost::scoped_array header(new boost::uint32_t[m_header_size]); + file::iovec_t b = {header.get(), size_t(m_header_size) }; + int n = m_file.readv(0, &b, 1, ec); + if (ec) return; + + // we don't have a full header. consider the file empty + if (n < m_header_size) return; + using namespace libtorrent::detail; + + char* ptr = (char*)header.get(); + // we have a header. Parse it + int num_pieces_ = read_uint32(ptr); + int piece_size_ = read_uint32(ptr); + + // if there is a mismatch in number of pieces or piece size + // consider the file empty and overwrite anything in there + if (num_pieces != num_pieces_ || m_piece_size != piece_size_) return; + + // this is used to determine which slots are free, and how many + // slots are allocated + std::vector free_slots; + free_slots.resize(num_pieces, true); + + for (int i = 0; i < num_pieces; ++i) + { + int slot = read_uint32(ptr); + if (slot == 0xffffffff) continue; + + // invalid part-file + TORRENT_ASSERT(slot < num_pieces); + if (slot >= num_pieces) continue; + + if (slot >= m_num_allocated) + m_num_allocated = slot + 1; + + free_slots[slot] = false; + m_piece_map[i] = slot; + } + + // now, populate the free_list with the "holes" + for (int i = 0; i < m_num_allocated; ++i) + { + if (free_slots[i]) m_free_slots.push_back(i); + } + + m_file.close(); + } + } + + part_file::~part_file() + { + error_code ec; + flush_metadata_impl(ec); + } + + int part_file::allocate_slot(int piece) + { + // the mutex is assumed to be held here, since this is a private function + + TORRENT_ASSERT(m_piece_map.find(piece) == m_piece_map.end()); + int slot = -1; + if (!m_free_slots.empty()) + { + slot = m_free_slots.front(); + m_free_slots.erase(m_free_slots.begin()); + } + else + { + slot = m_num_allocated; + ++m_num_allocated; + } + + m_piece_map[piece] = slot; + m_dirty_metadata = true; + return slot; + } + + int part_file::writev(file::iovec_t const* bufs, int num_bufs, int piece, int offset, error_code& ec) + { + TORRENT_ASSERT(offset >= 0); + mutex::scoped_lock l(m_mutex); + + open_file(file::read_write, ec); + if (ec) return -1; + + int slot = -1; + boost::unordered_map::iterator i = m_piece_map.find(piece); + if (i == m_piece_map.end()) + slot = allocate_slot(piece); + else + slot = i->second; + + l.unlock(); + + size_type slot_offset = size_type(m_header_size) + size_type(slot) * m_piece_size; + return m_file.writev(slot_offset + offset, bufs, num_bufs, ec); + } + + int part_file::readv(file::iovec_t const* bufs, int num_bufs + , int piece, int offset, error_code& ec) + { + TORRENT_ASSERT(offset >= 0); + mutex::scoped_lock l(m_mutex); + + boost::unordered_map::iterator i = m_piece_map.find(piece); + if (i == m_piece_map.end()) + { + ec = error_code(boost::system::errc::no_such_file_or_directory + , boost::system::generic_category()); + return -1; + } + + int slot = i->second; + + open_file(file::read_write, ec); + if (ec) return -1; + + l.unlock(); + + size_type slot_offset = size_type(m_header_size) + size_type(slot) * m_piece_size; + return m_file.readv(slot_offset + offset, bufs, num_bufs, ec); + } + + void part_file::open_file(int mode, error_code& ec) + { + if (m_file.is_open() + && ((m_file.open_mode() & file::rw_mask) == mode + || mode == file::read_only)) return; + + std::string fn = combine_path(m_path, m_name); + m_file.open(fn, mode, ec); + if (((mode & file::rw_mask) != file::read_only) + && ec == boost::system::errc::no_such_file_or_directory) + { + // this means the directory the file is in doesn't exist. + // so create it + ec.clear(); + create_directories(m_path, ec); + + if (ec) return; + m_file.open(fn, mode, ec); + } + } + + void part_file::free_piece(int piece, error_code& ec) + { + mutex::scoped_lock l(m_mutex); + + boost::unordered_map::iterator i = m_piece_map.find(piece); + if (i == m_piece_map.end()) return; + + // TODO: what do we do if someone is currently reading from the disk + // from this piece? does it matter? Since we won't actively erase the + // data from disk, but it may be overwritten soon, it's probably not that + // big of a deal + + m_free_slots.push_back(i->second); + m_piece_map.erase(i); + m_dirty_metadata = true; + } + + void part_file::move_partfile(std::string const& path, error_code& ec) + { + mutex::scoped_lock l(m_mutex); + + flush_metadata_impl(ec); + if (ec) return; + + m_file.close(); + + if (!m_piece_map.empty()) + { + std::string old_path = combine_path(m_path, m_name); + std::string new_path = combine_path(path, m_name); + + rename(old_path, new_path, ec); + if (ec == boost::system::errc::no_such_file_or_directory) + ec.clear(); + + if (ec) + { + copy_file(old_path, new_path, ec); + if (ec) return; + remove(old_path, ec); + } + } + m_path = path; + } + + void part_file::import_file(file& f, size_type offset, size_type size, error_code& ec) + { + // not implemented + assert(false); + } + + void part_file::export_file(file& f, size_type offset, size_type size, error_code& ec) + { + int piece = offset / m_piece_size; + int end = ((offset + size) + m_piece_size - 1) / m_piece_size; + + boost::scoped_array buf; + + size_type piece_offset = offset - size_type(piece) * m_piece_size; + size_type file_offset = 0; + for (; piece < end; ++piece) + { + boost::unordered_map::iterator i = m_piece_map.find(piece); + int block_to_copy = (std::min)(m_piece_size - piece_offset, size); + if (i != m_piece_map.end()) + { + open_file(file::read_only, ec); + if (ec) return; + + if (!buf) buf.reset(new char[m_piece_size]); + + size_type slot_offset = size_type(m_header_size) + size_type(i->second) * m_piece_size; + file::iovec_t v = { buf.get(), size_t(block_to_copy) }; + int ret = m_file.readv(slot_offset + piece_offset, &v, 1, ec); + if (ec) return; + + ret = f.writev(file_offset, &v, 1, ec); + if (ec) return; + + if (block_to_copy == m_piece_size) + { + m_free_slots.push_back(i->second); + m_piece_map.erase(i); + m_dirty_metadata = true; + } + } + file_offset += block_to_copy; + piece_offset = 0; + size -= block_to_copy; + } + } + + void part_file::flush_metadata(error_code& ec) + { + mutex::scoped_lock l(m_mutex); + + flush_metadata_impl(ec); + } + + // TODO: instead of rebuilding the whole file header + // and flushing it, update the slot entries as we go + void part_file::flush_metadata_impl(error_code& ec) + { + // do we need to flush the metadata? + if (m_dirty_metadata == false) return; + + if (m_piece_map.empty()) + { + // if we don't have any pieces left in the + // part file, remove it + std::string p = combine_path(m_path, m_name); + remove(p, ec); + + if (ec == boost::system::errc::no_such_file_or_directory) + ec.clear(); + return; + } + + open_file(file::read_write, ec); + if (ec) return; + + boost::scoped_array header(new boost::uint32_t[m_header_size]); + + using namespace libtorrent::detail; + + char* ptr = (char*)header.get(); + + write_uint32(m_max_pieces, ptr); + write_uint32(m_piece_size, ptr); + + for (int piece = 0; piece < m_max_pieces; ++piece) + { + boost::unordered_map::iterator i = m_piece_map.find(piece); + int slot = 0xffffffff; + if (i != m_piece_map.end()) + slot = i->second; + write_uint32(slot, ptr); + } + memset(ptr, 0, m_header_size - (ptr - (char*)header.get())); + +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_MEM_IS_DEFINED(header.get(), m_header_size); +#endif + file::iovec_t b = {header.get(), size_t(m_header_size) }; + m_file.writev(0, &b, 1, ec); + if (ec) return; + } +} + diff --git a/src/peer_class.cpp b/src/peer_class.cpp new file mode 100644 index 000000000..105ad53ba --- /dev/null +++ b/src/peer_class.cpp @@ -0,0 +1,140 @@ +/* + +Copyright (c) 2011-2013, 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. + +*/ + +#include "libtorrent/peer_class.hpp" +#include "libtorrent/peer_connection.hpp" + +#ifdef TORRENT_USE_VALGRIND +#include +#endif + +namespace libtorrent +{ + void peer_class::set_upload_limit(int limit) + { + TORRENT_ASSERT(limit >= -1); + if (limit < 0) limit = 0; + if (limit < 10 && limit > 0) limit = 10; + channel[peer_connection::upload_channel].throttle(limit); + } + + void peer_class::set_download_limit(int limit) + { + TORRENT_ASSERT(limit >= -1); + if (limit < 0) limit = 0; + if (limit < 10 && limit > 0) limit = 10; + channel[peer_connection::download_channel].throttle(limit); + } + + void peer_class::get_info(peer_class_info* pci) const + { + pci->ignore_unchoke_slots = ignore_unchoke_slots; + pci->connection_limit_factor = connection_limit_factor; + pci->label = label; + pci->upload_limit = channel[peer_connection::upload_channel].throttle(); + pci->download_limit = channel[peer_connection::download_channel].throttle(); + pci->upload_priority = priority[peer_connection::upload_channel]; + pci->download_priority = priority[peer_connection::download_channel]; + } + + void peer_class::set_info(peer_class_info const* pci) + { + ignore_unchoke_slots = pci->ignore_unchoke_slots; + connection_limit_factor = pci->connection_limit_factor; + label = pci->label; + set_upload_limit(pci->upload_limit); + set_download_limit(pci->download_limit); + priority[peer_connection::upload_channel] = (std::max)(1, (std::min)(255, pci->upload_priority)); + priority[peer_connection::download_channel] = (std::max)(1, (std::min)(255, pci->download_priority)); + } + + peer_class_t peer_class_pool::new_peer_class(std::string const& label) + { + peer_class_t ret = 0; + if (!m_free_list.empty()) + { + ret = m_free_list.back(); + m_free_list.pop_back(); + } + else + { + ret = m_peer_classes.size(); + m_peer_classes.push_back(boost::intrusive_ptr()); + } + + TORRENT_ASSERT(m_peer_classes[ret].get() == 0); + m_peer_classes[ret] = new peer_class(label); + return ret; + } + + void peer_class_pool::decref(peer_class_t c) + { +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_VALUE_IS_DEFINED(c); +#endif + TORRENT_ASSERT(c < m_peer_classes.size()); + TORRENT_ASSERT(m_peer_classes[c].get()); + + --m_peer_classes[c]->references; + if (m_peer_classes[c]->references) return; + m_peer_classes[c].reset(); + m_free_list.push_back(c); + } + + void peer_class_pool::incref(peer_class_t c) + { +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_VALUE_IS_DEFINED(c); +#endif + TORRENT_ASSERT(c < m_peer_classes.size()); + TORRENT_ASSERT(m_peer_classes[c].get()); + + ++m_peer_classes[c]->references; + } + + peer_class* peer_class_pool::at(peer_class_t c) + { +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_VALUE_IS_DEFINED(c); +#endif + if (c >= m_peer_classes.size()) return 0; + return m_peer_classes[c].get(); + } + + peer_class const* peer_class_pool::at(peer_class_t c) const + { + if (c >= m_peer_classes.size()) return 0; + return m_peer_classes[c].get(); + } + +} + diff --git a/src/peer_class_set.cpp b/src/peer_class_set.cpp new file mode 100644 index 000000000..03fa4ce6d --- /dev/null +++ b/src/peer_class_set.cpp @@ -0,0 +1,60 @@ +/* + +Copyright (c) 2003-2013, 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. + +*/ + +#include "libtorrent/peer_class_set.hpp" +#include "libtorrent/peer_class.hpp" +#include +#include // for_find + +namespace libtorrent +{ + void peer_class_set::add_class(peer_class_pool& pool, peer_class_t c) + { + if (std::find(m_class.begin(), m_class.end(), c) != m_class.end()) return; + m_class.push_back(c); + pool.incref(c); + } + + bool peer_class_set::has_class(peer_class_t c) const + { + return std::find(m_class.begin(), m_class.end(), c) != m_class.end(); + } + + void peer_class_set::remove_class(peer_class_pool& pool, peer_class_t c) + { + std::vector::iterator i = std::find(m_class.begin(), m_class.end(), c); + if (i == m_class.end()) return; + m_class.erase(i); + pool.decref(c); + } +} + diff --git a/src/peer_connection.cpp b/src/peer_connection.cpp index 98ea89c2e..3d4a14bdd 100644 --- a/src/peer_connection.cpp +++ b/src/peer_connection.cpp @@ -50,7 +50,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/file.hpp" #include "libtorrent/version.hpp" #include "libtorrent/extensions.hpp" -#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/aux_/session_interface.hpp" #include "libtorrent/policy.hpp" #include "libtorrent/socket_type.hpp" #include "libtorrent/assert.hpp" @@ -58,19 +58,37 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/torrent.hpp" #include "libtorrent/peer_info.hpp" #include "libtorrent/bt_peer_connection.hpp" +#include "libtorrent/network_thread_pool.hpp" #include "libtorrent/error.hpp" +#include "libtorrent/alloca.hpp" +#include "libtorrent/disk_interface.hpp" +#include "libtorrent/bandwidth_manager.hpp" +#include "libtorrent/request_blocks.hpp" // for request_a_block +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/alert_manager.hpp" // for alert_manageralert_manager #ifdef TORRENT_DEBUG #include #endif +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING +#include "libtorrent/escape_string.hpp" +#include "libtorrent/socket_io.hpp" +#endif + //#define TORRENT_CORRUPT_DATA using boost::shared_ptr; -using libtorrent::aux::session_impl; namespace libtorrent { + + enum + { + // the limits of the download queue size + min_request_queue = 2, + }; + int round_up8(int v) { return ((v & 7) == 0) ? v : v + (8 - (v & 7)); @@ -108,30 +126,29 @@ namespace libtorrent // outbound connection peer_connection::peer_connection( - session_impl& ses + aux::session_interface& ses + , aux::session_settings const& sett + , buffer_allocator_interface& allocator + , disk_interface& disk_thread + , io_service& ios , boost::weak_ptr tor , shared_ptr s , tcp::endpoint const& endp - , policy::peer* peerinfo + , torrent_peer* peerinfo , bool outgoing) - : m_received_listen_port(false) - , m_have_all(false) - , m_peer_interested(false) - , m_peer_choked(true) - , m_interesting(false) - , m_choked(true) - , m_failed(false) - , m_disconnecting(false) - , m_bitfield_received(false) - , m_endgame_mode(false) - , m_sent_suggests(false) - , m_holepunch_mode(false) - , m_ignore_stats(false) - , m_corked(false) - , m_has_metadata(true) - , m_exceeded_limit(false) - , m_ses(ses) - , m_work(ses.m_io_service) + : peer_connection_hot_members(tor, ses, sett, s, outgoing) + , m_socket(s) + , m_peer_info(peerinfo) + , m_num_pieces(0) + , m_rtt(0) + , m_recv_start(0) + , m_desired_queue_size(2) + , m_max_out_request_queue(m_settings.get_int(settings_pack::max_out_request_queue)) + , m_remote(endp) + , m_disk_thread(disk_thread) + , m_allocator(allocator) + , m_ios(ios) + , m_work(m_ios) , m_last_piece(time_now()) , m_last_request(time_now()) , m_last_incoming_request(min_time()) @@ -148,58 +165,66 @@ namespace libtorrent , m_downloaded_at_last_round(0) , m_uploaded_at_last_round(0) , m_uploaded_at_last_unchoke(0) - , m_disk_recv_buffer(ses, 0) - , m_socket(s) - , m_torrent(tor) - , m_peer_info(peerinfo) + , m_soft_packet_size(0) + , m_outstanding_bytes(0) + , m_disk_recv_buffer(allocator, 0) , m_last_seen_complete(0) , m_receiving_block(piece_block::invalid) - , m_remote(endp) , m_timeout_extend(0) - , m_outstanding_bytes(0) , m_extension_outstanding_bytes(0) , m_queued_time_critical(0) - , m_num_pieces(0) - , m_timeout(m_ses.settings().peer_timeout) - , m_packet_size(0) - , m_soft_packet_size(0) - , m_recv_pos(0) + , m_recv_end(0) , m_disk_recv_buffer_size(0) , m_reading_bytes(0) + , m_picker_options(0) , m_num_invalid_requests(0) - , m_priority(1) - , m_upload_limit(0) - , m_download_limit(0) - , m_speed(slow) , m_connection_ticket(-1) , m_remote_pieces_dled(0) , m_remote_dl_rate(0) , m_outstanding_writing_bytes(0) , m_download_rate_peak(0) , m_upload_rate_peak(0) - , m_max_out_request_queue(m_ses.settings().max_out_request_queue) - , m_rtt(0) + , m_speed(slow) , m_prefer_whole_pieces(0) - , m_desired_queue_size(2) - , m_fast_reconnect(false) + , m_disk_read_failures(0) + , m_outstanding_piece_verification(0) , m_outgoing(outgoing) - , m_ignore_bandwidth_limits(false) - , m_ignore_unchoke_slots(false) - , m_connecting(outgoing) + , m_received_listen_port(false) + , m_fast_reconnect(false) + , m_failed(false) + , m_connected(!outgoing) , m_queued(outgoing) , m_request_large_blocks(false) , m_share_mode(false) , m_upload_only(false) - , m_snubbed(false) + , m_bitfield_received(false) , m_no_download(false) + , m_sent_suggests(false) + , m_holepunch_mode(false) + , m_peer_choked(true) + , m_have_all(false) + , m_peer_interested(false) + , m_need_interest_update(false) + , m_has_metadata(true) + , m_queued_for_connection(false) + , m_exceeded_limit(false) #if TORRENT_USE_ASSERTS , m_in_constructor(true) , m_disconnect_started(false) , m_initialized(false) , m_in_use(1337) , m_received_in_piece(0) + , m_destructed(false) + , m_socket_is_writing(false) #endif { + m_ses.inc_stats_counter(counters::num_tcp_peers + m_socket->type() - 1); + + if (m_connected) + m_ses.inc_stats_counter(counters::num_peers_connected); + else if (m_connecting) + m_ses.inc_stats_counter(counters::num_peers_half_open); + m_superseed_piece[0] = -1; m_superseed_piece[1] = -1; boost::shared_ptr t = m_torrent.lock(); @@ -207,15 +232,7 @@ namespace libtorrent // we can't decrement the connecting counter TORRENT_ASSERT(t || !m_connecting); if (m_connecting && t) t->inc_num_connecting(); - m_est_reciprocation_rate = m_ses.m_settings.default_est_reciprocation_rate; - -#if TORRENT_USE_I2P - if (peerinfo && peerinfo->is_i2p_addr) - { - // quadruple the timeout for i2p peers - m_timeout *= 4; - } -#endif + m_est_reciprocation_rate = m_settings.get_int(settings_pack::default_est_reciprocation_rate); m_channel_state[upload_channel] = peer_info::bw_idle; m_channel_state[download_channel] = peer_info::bw_idle; @@ -267,13 +284,26 @@ namespace libtorrent std::fill(m_peer_id.begin(), m_peer_id.end(), 0); } -#ifdef TORRENT_DISK_STATS + int peer_connection::timeout() const + { + int ret = m_settings.get_int(settings_pack::peer_timeout); +#if TORRENT_USE_I2P + if (m_peer_info && m_peer_info->is_i2p_addr) + { + // quadruple the timeout for i2p peers + ret *= 4; + } +#endif + return ret; + } + +#ifdef TORRENT_BUFFER_STATS void peer_connection::log_buffer_usage(char* buffer, int size, char const* label) { - if (m_ses.m_disk_thread.is_disk_buffer(buffer)) - m_ses.m_disk_thread.rename_buffer(buffer, label); + if (m_disk_thread.is_disk_buffer(buffer)) + m_disk_thread.rename_buffer(buffer, label); - m_ses.m_buffer_usage_logger << log_time() << " append_send_buffer: " << size << std::endl; + m_ses.buffer_usage_logger() << log_time() << " append_send_buffer: " << size << std::endl; m_ses.log_buffer_usage(); } #endif @@ -281,17 +311,17 @@ namespace libtorrent void peer_connection::increase_est_reciprocation_rate() { m_est_reciprocation_rate += m_est_reciprocation_rate - * m_ses.m_settings.increase_est_reciprocation_rate / 100; + * m_settings.get_int(settings_pack::increase_est_reciprocation_rate) / 100; } void peer_connection::decrease_est_reciprocation_rate() { m_est_reciprocation_rate -= m_est_reciprocation_rate - * m_ses.m_settings.decrease_est_reciprocation_rate / 100; + * m_settings.get_int(settings_pack::decrease_est_reciprocation_rate) / 100; } bool peer_connection::bittyrant_unchoke_compare( - boost::intrusive_ptr const& p) const + peer_connection const* p) const { TORRENT_ASSERT(p); peer_connection const& rhs = *p; @@ -305,14 +335,9 @@ namespace libtorrent u1 = uploaded_in_last_round(); u2 = rhs.uploaded_in_last_round(); - boost::shared_ptr t1 = m_torrent.lock(); - TORRENT_ASSERT(t1); - boost::shared_ptr t2 = rhs.associated_torrent().lock(); - TORRENT_ASSERT(t2); - // take torrent priority into account - d1 *= 1 + t1->priority(); - d2 *= 1 + t2->priority(); + d1 *= get_priority(upload_channel); + d2 *= rhs.get_priority(upload_channel); d1 = d1 * 1000 / (std::max)(size_type(1), u1); d2 = d2 * 1000 / (std::max)(size_type(1), u2); @@ -325,7 +350,7 @@ namespace libtorrent } // return true if 'this' peer should be preferred to be unchoke over p - bool peer_connection::unchoke_compare(boost::intrusive_ptr const& p) const + bool peer_connection::unchoke_compare(peer_connection const* p) const { TORRENT_ASSERT(p); peer_connection const& rhs = *p; @@ -337,8 +362,11 @@ namespace libtorrent boost::shared_ptr t2 = rhs.associated_torrent().lock(); TORRENT_ASSERT(t2); - if (t1->priority() != t2->priority()) - return t1->priority() > t2->priority(); + int prio1 = get_priority(upload_channel); + int prio2 = rhs.get_priority(upload_channel); + + if (prio1 != prio2) + return prio1 > prio2; // compare how many bytes they've sent us size_type c1; @@ -348,7 +376,8 @@ namespace libtorrent if (c1 != c2) return c1 > c2; - if (m_ses.settings().seed_choking_algorithm == session_settings::round_robin) + if (m_settings.get_int(settings_pack::seed_choking_algorithm) + == settings_pack::round_robin) { // the amount uploaded since unchoked (not just in the last round) c1 = uploaded_since_unchoked(); @@ -360,7 +389,7 @@ namespace libtorrent // peers that are unchoked, but have sent more than one quota // since they were unchoked, they get de-prioritized. - int pieces = m_ses.settings().seeding_piece_quota; + int pieces = m_settings.get_int(settings_pack::seeding_piece_quota); // if a peer is already unchoked, and the number of bytes sent since it was unchoked // is greater than the send quanta, then it's done with it' upload slot, and we // can de-prioritize it @@ -382,19 +411,21 @@ namespace libtorrent // fall through and rely on the logic to prioritize peers who have waited // the longest to be unchoked } - else if (m_ses.settings().seed_choking_algorithm == session_settings::fastest_upload) + else if (m_settings.get_int(settings_pack::seed_choking_algorithm) + == settings_pack::fastest_upload) { c1 = uploaded_in_last_round(); c2 = rhs.uploaded_in_last_round(); // take torrent priority into account - c1 *= 1 + t1->priority(); - c2 *= 1 + t2->priority(); + c1 *= prio1; + c2 *= prio2; if (c1 > c2) return true; if (c2 > c1) return false; } - else if (m_ses.settings().seed_choking_algorithm == session_settings::anti_leech) + else if (m_settings.get_int(settings_pack::seed_choking_algorithm) + == settings_pack::anti_leech) { // the anti-leech seeding algorithm is based on the paper "Improving // BitTorrent: A Simple Approach" from Chow et. al. and ranks peers based @@ -432,22 +463,40 @@ namespace libtorrent return m_last_unchoke < rhs.m_last_unchoke; } + int peer_connection::get_priority(int channel) const + { + TORRENT_ASSERT(channel >= 0 && channel < 2); + int prio = 1; + for (int i = 0; i < num_classes(); ++i) + { + int class_prio = m_ses.peer_classes().at(class_at(i))->priority[channel]; + if (prio < class_prio) prio = class_prio; + } + + boost::shared_ptr t = associated_torrent().lock(); + + if (t) + { + for (int i = 0; i < t->num_classes(); ++i) + { + int class_prio = m_ses.peer_classes().at(t->class_at(i))->priority[channel]; + if (prio < class_prio) prio = class_prio; + } + } + return prio; + } + bool peer_connection::upload_rate_compare(peer_connection const* p) const { size_type c1; size_type c2; - boost::shared_ptr t1 = m_torrent.lock(); - TORRENT_ASSERT(t1); - boost::shared_ptr t2 = p->associated_torrent().lock(); - TORRENT_ASSERT(t2); - c1 = uploaded_in_last_round(); c2 = p->uploaded_in_last_round(); // take torrent priority into account - c1 *= 1 + t1->priority(); - c2 *= 1 + t2->priority(); + c1 *= get_priority(upload_channel); + c2 *= p->get_priority(upload_channel); return c1 > c2; } @@ -470,24 +519,42 @@ namespace libtorrent m_socket->io_control(ioc, ec); if (ec) { - disconnect(ec); + disconnect(ec, op_iocontrol); return; } m_remote = m_socket->remote_endpoint(ec); if (ec) { - disconnect(ec); + disconnect(ec, op_getpeername); return; } - if (m_remote.address().is_v4() && m_ses.settings().peer_tos != 0) + m_local = m_socket->local_endpoint(ec); + if (ec) { - m_socket->set_option(type_of_service(m_ses.settings().peer_tos), ec); + disconnect(ec, op_getname); + return; + } + if (m_remote.address().is_v4() && m_settings.get_int(settings_pack::peer_tos) != 0) + { + m_socket->set_option(type_of_service(m_settings.get_int(settings_pack::peer_tos)), ec); #if defined TORRENT_VERBOSE_LOGGING - peer_log(">>> SET_TOS[ tos: %d e: %s ]", m_ses.settings().peer_tos, ec.message().c_str()); + peer_log(">>> SET_TOS[ tos: %d e: %s ]", m_settings.get_int(settings_pack::peer_tos), ec.message().c_str()); #endif } } +#if defined TORRENT_VERBOSE_LOGGING + peer_log("*** SET_PEER_CLASS [ a: %s ]", print_address(m_remote.address()).c_str()); +#endif + + m_ses.set_peer_classes(this, m_remote.address(), m_socket->type()); + +#if defined TORRENT_VERBOSE_LOGGING + for (int i = 0; i < num_classes(); ++i) + { + peer_log("*** CLASS [ %s ]", m_ses.peer_classes().at(class_at(i))->label.c_str()); + } +#endif if (t && t->ready_for_connections()) { init(); @@ -496,6 +563,24 @@ namespace libtorrent void peer_connection::update_interest() { + if (!m_need_interest_update) + { + // we're the first to request an interest update + // post a message in order to delay it enough for + // any potential other messages already in the queue + // to not trigger another one. This effectively defer + // the update until the current message queue is + // flushed + m_ios.post(boost::bind(&peer_connection::do_update_interest, self())); + } + m_need_interest_update = true; + } + + void peer_connection::do_update_interest() + { + TORRENT_ASSERT(m_need_interest_update); + m_need_interest_update = false; + boost::shared_ptr t = m_torrent.lock(); if (!t) return; @@ -520,13 +605,14 @@ namespace libtorrent bool interested = false; if (!t->is_upload_only()) { + t->need_picker(); piece_picker const& p = t->picker(); int num_pieces = p.num_pieces(); for (int j = 0; j != num_pieces; ++j) { - if (!p.have_piece(j) + if (m_have_piece[j] && t->piece_priority(j) > 0 - && m_have_piece[j]) + && !p.has_piece_passed(j)) { interested = true; #if defined TORRENT_VERBOSE_LOGGING @@ -545,7 +631,7 @@ namespace libtorrent #endif if (!interested) send_not_interested(); - else t->get_policy().peer_is_interesting(*this); + else t->peer_is_interesting(*this); TORRENT_ASSERT(in_handshake() || is_interesting() == interested); } @@ -607,7 +693,7 @@ namespace libtorrent return; } - int num_allowed_pieces = m_ses.settings().allowed_fast_set_size; + int num_allowed_pieces = m_settings.get_int(settings_pack::allowed_fast_set_size); if (num_allowed_pieces == 0) return; int num_pieces = t->torrent_file().num_pieces(); @@ -746,18 +832,28 @@ namespace libtorrent #ifdef TORRENT_VERBOSE_LOGGING peer_log("*** THIS IS A SEED [ p: %p ]", m_peer_info); #endif + + TORRENT_ASSERT(m_have_piece.all_set()); + TORRENT_ASSERT(m_have_piece.count() == m_have_piece.size()); + TORRENT_ASSERT(m_have_piece.size() == t->torrent_file().num_pieces()); + // if this is a web seed. we don't have a peer_info struct - t->get_policy().set_seed(m_peer_info, true); + t->set_seed(m_peer_info, true); m_upload_only = true; t->peer_has_all(this); + +#if TORRENT_USE_INVARIANT_CHECKS + if (t && t->has_picker()) + t->picker().check_peer_invariant(m_have_piece, this); +#endif if (t->is_upload_only()) send_not_interested(); - else t->get_policy().peer_is_interesting(*this); + else t->peer_is_interesting(*this); return; } // if we're a seed, we don't keep track of piece availability - if (!t->is_seed()) + if (t->has_picker()) { t->peer_has(m_have_piece, this); bool interesting = false; @@ -771,7 +867,7 @@ namespace libtorrent interesting = true; } } - if (interesting) t->get_policy().peer_is_interesting(*this); + if (interesting) t->peer_is_interesting(*this); else send_not_interested(); } else @@ -782,18 +878,41 @@ namespace libtorrent peer_connection::~peer_connection() { + m_ses.inc_stats_counter(counters::num_tcp_peers + m_socket->type() - 1, -1); + + TORRENT_ASSERT(!m_queued_for_connection); // INVARIANT_CHECK; TORRENT_ASSERT(!m_in_constructor); TORRENT_ASSERT(m_disconnecting); TORRENT_ASSERT(m_disconnect_started); - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(!m_destructed); +#if TORRENT_USE_ASSERTS + m_destructed = true; +#endif + TORRENT_ASSERT(m_ses.is_single_thread()); #if TORRENT_USE_ASSERTS m_in_use = 0; #endif - // defensive + // decrement the stats counter + set_endgame(false); + if (m_interesting) + m_ses.inc_stats_counter(counters::num_peers_down_interested, -1); + if (m_peer_interested) + m_ses.inc_stats_counter(counters::num_peers_up_interested, -1); + if (!m_choked) + m_ses.inc_stats_counter(counters::num_peers_up_unchoked, -1); + if (!m_peer_choked) + m_ses.inc_stats_counter(counters::num_peers_down_unchoked, -1); + if (m_connected) + m_ses.inc_stats_counter(counters::num_peers_connected, -1); + m_connected = false; + if (!m_download_queue.empty()) + m_ses.inc_stats_counter(counters::num_peers_down_requests, -1); + + // defensive boost::shared_ptr t = m_torrent.lock(); // if t is NULL, we better not be connecting, since // we can't decrement the connecting counter @@ -801,9 +920,10 @@ namespace libtorrent // we should really have dealt with this already TORRENT_ASSERT(!m_connecting); - if (m_connecting && t) + if (m_connecting) { - t->dec_num_connecting(); + m_ses.inc_stats_counter(counters::num_peers_half_open, -1); + if (t) t->dec_num_connecting(); m_connecting = false; } @@ -816,21 +936,22 @@ namespace libtorrent #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING peer_log("*** CONNECTION CLOSED"); #endif -// TORRENT_ASSERT(!m_ses.has_peer(this)); TORRENT_ASSERT(m_request_queue.empty()); TORRENT_ASSERT(m_download_queue.empty()); + #if TORRENT_USE_ASSERTS - for (aux::session_impl::torrent_map::const_iterator i = m_ses.m_torrents.begin() - , end(m_ses.m_torrents.end()); i != end; ++i) - TORRENT_ASSERT(!i->second->has_peer(this)); if (m_peer_info) TORRENT_ASSERT(m_peer_info->connection == 0); #endif } + bool peer_connection::on_parole() const + { return peer_info_struct() && peer_info_struct()->on_parole; } + int peer_connection::picker_options() const { - int ret = 0; + int ret = m_picker_options; + boost::shared_ptr t = m_torrent.lock(); TORRENT_ASSERT(t); if (!t) return 0; @@ -844,7 +965,7 @@ namespace libtorrent { ret |= piece_picker::sequential; } - else if (t->num_have() < t->settings().initial_picker_threshold) + else if (t->num_have() < m_settings.get_int(settings_pack::initial_picker_threshold)) { // if we have fewer pieces than a certain threshols // don't pick rare pieces, just pick random ones, @@ -865,7 +986,7 @@ namespace libtorrent ret |= piece_picker::reverse; } - if (t->settings().prioritize_partial_pieces) + if (m_settings.get_bool(settings_pack::prioritize_partial_pieces)) ret |= piece_picker::prioritize_partials; if (on_parole()) ret |= piece_picker::on_parole @@ -883,7 +1004,8 @@ namespace libtorrent return; m_fast_reconnect = r; peer_info_struct()->last_connected = (boost::uint16_t)m_ses.session_time(); - int rewind = m_ses.settings().min_reconnect_time * m_ses.settings().max_failcount; + int rewind = m_settings.get_int(settings_pack::min_reconnect_time) + * m_settings.get_int(settings_pack::max_failcount); if (peer_info_struct()->last_connected < rewind) peer_info_struct()->last_connected = 0; else peer_info_struct()->last_connected -= rewind; @@ -891,11 +1013,15 @@ namespace libtorrent ++peer_info_struct()->fast_reconnects; } - void peer_connection::announce_piece(int index) + void peer_connection::received_piece(int index) { // dont announce during handshake if (in_handshake()) return; +#if defined TORRENT_VERBOSE_LOGGING + peer_log("<<< RECEIVED [ piece: %d ]", index); +#endif + // remove suggested pieces once we have them std::vector::iterator i = std::find( m_suggested_pieces.begin(), m_suggested_pieces.end(), index); @@ -913,10 +1039,26 @@ namespace libtorrent // interested anymore update_interest(); if (is_disconnecting()) return; + } + if (disconnect_if_redundant()) return; + +#if TORRENT_USE_ASSERTS + boost::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); +#endif + } + + void peer_connection::announce_piece(int index) + { + // dont announce during handshake + if (in_handshake()) return; + + if (has_piece(index)) + { // optimization, don't send have messages // to peers that already have the piece - if (!m_ses.settings().send_redundant_have) + if (!m_settings.get_bool(settings_pack::send_redundant_have)) { #ifdef TORRENT_VERBOSE_LOGGING peer_log("==> HAVE [ piece: %d ] SUPRESSED", index); @@ -925,8 +1067,7 @@ namespace libtorrent } } - disconnect_if_redundant(); - if (is_disconnecting()) return; + if (disconnect_if_redundant()) return; #ifdef TORRENT_VERBOSE_LOGGING peer_log("==> HAVE [ piece: %d ]", index); @@ -983,12 +1124,12 @@ namespace libtorrent // we don't know what rate we can get from this peer. Instead of assuming // the lowest possible rate, assume the average. - // TODO: this should only be peers we're trying to download from - int peers_with_requests = m_ses.num_connections(); + int peers_with_requests = m_ses.stats_counters()[counters::num_peers_down_requests]; // avoid division by 0 if (peers_with_requests == 0) peers_with_requests = 1; - rate = m_ses.m_stat.transfer_rate(stat::download_payload) / peers_with_requests; + // TODO: this should be the global download rate + rate = t->statistics().transfer_rate(stat::download_payload) / peers_with_requests; } else { @@ -1010,6 +1151,51 @@ namespace libtorrent m_statistics.add_stat(downloaded, uploaded); } + void peer_connection::received_bytes(int bytes_payload, int bytes_protocol) + { + m_statistics.received_bytes(bytes_payload, bytes_protocol); + if (m_ignore_stats) return; + boost::shared_ptr t = m_torrent.lock(); + if (!t) return; + t->received_bytes(bytes_payload, bytes_protocol); + } + + void peer_connection::sent_bytes(int bytes_payload, int bytes_protocol) + { + m_statistics.sent_bytes(bytes_payload, bytes_protocol); + if (m_ignore_stats) return; + boost::shared_ptr t = m_torrent.lock(); + if (!t) return; + t->sent_bytes(bytes_payload, bytes_protocol); + } + + void peer_connection::trancieve_ip_packet(int bytes, bool ipv6) + { + m_statistics.trancieve_ip_packet(bytes, ipv6); + if (m_ignore_stats) return; + boost::shared_ptr t = m_torrent.lock(); + if (!t) return; + t->trancieve_ip_packet(bytes, ipv6); + } + + void peer_connection::sent_syn(bool ipv6) + { + m_statistics.sent_syn(ipv6); + if (m_ignore_stats) return; + boost::shared_ptr t = m_torrent.lock(); + if (!t) return; + t->sent_syn(ipv6); + } + + void peer_connection::received_synack(bool ipv6) + { + m_statistics.received_synack(ipv6); + if (m_ignore_stats) return; + boost::shared_ptr t = m_torrent.lock(); + if (!t) return; + t->received_synack(ipv6); + } + bitfield const& peer_connection::get_bitfield() const { return m_have_piece; @@ -1069,6 +1255,10 @@ namespace libtorrent { INVARIANT_CHECK; +#if defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + m_connect_time = time_now_hires(); +#endif + TORRENT_ASSERT(!m_disconnecting); TORRENT_ASSERT(m_torrent.expired()); boost::weak_ptr wpt = m_ses.find_torrent(ih); @@ -1093,24 +1283,28 @@ namespace libtorrent t.reset(); } + if (!t) + { + t = m_ses.delay_load_torrent(ih, this); +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING + if (t) + peer_log("*** Delay loaded torrent: %s:", to_hex(ih.to_string()).c_str()); +#endif + } + if (!t) { // we couldn't find the torrent! #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING peer_log("*** couldn't find a torrent with the given info_hash: %s torrents:", to_hex(ih.to_string()).c_str()); - session_impl::torrent_map const& torrents = m_ses.m_torrents; - for (session_impl::torrent_map::const_iterator i = torrents.begin() - , end(torrents.end()); i != end; ++i) - { - peer_log(" %s", to_hex(i->second->torrent_file().info_hash().to_string()).c_str()); - } + m_ses.log_all_torrents(this); #endif - disconnect(errors::invalid_info_hash, 1); + disconnect(errors::invalid_info_hash, op_bittorrent, 1); return; } if (t->is_paused() && (!t->is_auto_managed() - || !m_ses.m_settings.incoming_starts_queued_torrents)) + || !m_settings.get_bool(settings_pack::incoming_starts_queued_torrents))) { // paused torrents will not accept // incoming connections unless they are auto managed @@ -1120,20 +1314,21 @@ namespace libtorrent #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING peer_log("rejected connection to paused torrent"); #endif - disconnect(errors::torrent_paused, 2); + disconnect(errors::torrent_paused, op_bittorrent, 2); return; } #if TORRENT_USE_I2P i2p_stream* i2ps = m_socket->get(); - if (!i2ps && t->torrent_file().is_i2p() && !m_ses.m_settings.allow_i2p_mixed) + if (!i2ps && t->torrent_file().is_i2p() + && !m_settings.get_bool(settings_pack::allow_i2p_mixed)) { // the torrent is an i2p torrent, the peer is a regular peer // and we don't allow mixed mode. Disconnect the peer. #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING peer_log("rejected regular connection to i2p torrent"); #endif - disconnect(errors::peer_banned, 2); + disconnect(errors::peer_banned, op_bittorrent, 2); return; } #endif // TORRENT_USE_I2P @@ -1141,7 +1336,7 @@ namespace libtorrent TORRENT_ASSERT(m_torrent.expired()); if (t->is_paused() - && m_ses.m_settings.incoming_starts_queued_torrents + && m_settings.get_bool(settings_pack::incoming_starts_queued_torrents) && !m_ses.is_paused() && !t->is_aborted() && !m_ses.is_aborted()) @@ -1166,17 +1361,17 @@ namespace libtorrent { if (other_t->num_peers() <= t->num_peers()) { - disconnect(errors::too_many_connections); + disconnect(errors::too_many_connections, op_bittorrent); return; } // find the lowest ranking peer and disconnect that peer_connection* p = other_t->find_lowest_ranking_peer(); - p->disconnect(errors::too_many_connections); + p->disconnect(errors::too_many_connections, op_bittorrent); peer_disconnected_other(); } else { - disconnect(errors::too_many_connections); + disconnect(errors::too_many_connections, op_bittorrent); return; } } @@ -1223,6 +1418,16 @@ namespace libtorrent // ----------- CHOKE ----------- // ----------------------------- + void peer_connection::set_endgame(bool b) + { + if (m_endgame_mode == b) return; + m_endgame_mode = b; + if (m_endgame_mode) + m_ses.inc_stats_counter(counters::num_peers_end_game); + else + m_ses.inc_stats_counter(counters::num_peers_end_game, -1); + } + void peer_connection::incoming_choke() { INVARIANT_CHECK; @@ -1239,6 +1444,9 @@ namespace libtorrent #ifdef TORRENT_VERBOSE_LOGGING peer_log("<== CHOKE"); #endif + if (m_peer_choked == false) + m_ses.inc_stats_counter(counters::num_peers_down_unchoked, -1); + m_peer_choked = true; set_endgame(false); @@ -1249,20 +1457,22 @@ namespace libtorrent { boost::shared_ptr t = m_torrent.lock(); TORRENT_ASSERT(t); + if (!t->has_picker()) + { + m_request_queue.clear(); + return; + } // clear the requests that haven't been sent yet if (peer_info_struct() == 0 || !peer_info_struct()->on_parole) { // if the peer is not in parole mode, clear the queued // up block requests - if (!t->is_seed()) + piece_picker& p = t->picker(); + for (std::vector::const_iterator i = m_request_queue.begin() + , end(m_request_queue.end()); i != end; ++i) { - piece_picker& p = t->picker(); - for (std::vector::const_iterator i = m_request_queue.begin() - , end(m_request_queue.end()); i != end; ++i) - { - p.abort_download(i->block, peer_info_struct()); - } + p.abort_download(i->block, peer_info_struct()); } m_request_queue.clear(); m_queued_time_critical = 0; @@ -1289,7 +1499,7 @@ namespace libtorrent TORRENT_ASSERT(t); #ifdef TORRENT_VERBOSE_LOGGING - peer_log("<== REJECT_PIECE [ piece: %d | s: %d | l: %d ]" + peer_log("<== REJECT_PIECE [ piece: %d | s: %x | l: %x ]" , r.piece, r.start, r.length); #endif @@ -1316,6 +1526,9 @@ namespace libtorrent TORRENT_ASSERT(m_outstanding_bytes >= r.length); m_outstanding_bytes -= r.length; if (m_outstanding_bytes < 0) m_outstanding_bytes = 0; + + if (m_download_queue.empty()) + m_ses.inc_stats_counter(counters::num_peers_down_requests, -1); // if the peer is in parole mode, keep the request if (peer_info_struct() && peer_info_struct()->on_parole) @@ -1359,10 +1572,8 @@ namespace libtorrent if (m_request_queue.empty() && m_download_queue.size() < 2) { -#ifdef TORRENT_STATS - ++m_ses.m_reject_piece_picks; -#endif - request_a_block(*t, *this); + if (request_a_block(*t, *this)) + m_ses.inc_stats_counter(counters::reject_piece_picks); send_block_requests(); } } @@ -1415,7 +1626,7 @@ namespace libtorrent return; } - if (int(m_suggested_pieces.size()) > m_ses.m_settings.max_suggest_pieces) + if (int(m_suggested_pieces.size()) > m_settings.get_int(settings_pack::max_suggest_pieces)) m_suggested_pieces.erase(m_suggested_pieces.begin()); m_suggested_pieces.push_back(index); @@ -1436,6 +1647,11 @@ namespace libtorrent boost::shared_ptr t = m_torrent.lock(); TORRENT_ASSERT(t); +#if defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + m_unchoke_time = time_now_hires(); + t->debug_log("UNCHOKE [%p] (%d ms)", this, int(total_milliseconds(m_unchoke_time - m_bitfield_time))); +#endif + #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) @@ -1447,16 +1663,17 @@ namespace libtorrent #ifdef TORRENT_VERBOSE_LOGGING peer_log("<== UNCHOKE"); #endif + if (m_peer_choked) + m_ses.inc_stats_counter(counters::num_peers_down_unchoked); + m_peer_choked = false; m_last_unchoked = time_now(); if (is_disconnecting()) return; if (is_interesting()) { -#ifdef TORRENT_STATS - ++m_ses.m_unchoke_piece_picks; -#endif - request_a_block(*t, *this); + if (request_a_block(*t, *this)) + m_ses.inc_stats_counter(counters::unchoke_piece_picks); send_block_requests(); } } @@ -1483,6 +1700,9 @@ namespace libtorrent #ifdef TORRENT_VERBOSE_LOGGING peer_log("<== INTERESTED"); #endif + if (m_peer_interested == false) + m_ses.inc_stats_counter(counters::num_peers_up_interested); + m_peer_interested = true; if (is_disconnecting()) return; @@ -1492,16 +1712,29 @@ namespace libtorrent disconnect_if_redundant(); if (is_disconnecting()) return; - if (is_choked() && !t->graceful_pause()) + if (t->graceful_pause()) + { +#if defined TORRENT_VERBOSE_LOGGING + peer_log("DID NOT UNCHOKE [ graceful pause mode ]"); +#endif + return; + } + + if (is_choked()) { if (ignore_unchoke_slots()) { +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("ABOUT TO UNCHOKE [ peer ignores unchoke slots ]"); +#endif // if this peer is expempted from the choker // just unchoke it immediately send_unchoke(); } - else if ((m_ses.num_uploads() < m_ses.settings().unchoke_slots_limit - || m_ses.settings().unchoke_slots_limit < 0)) + // TODO: 3 we should probably use ses.m_allowed_upload_slots here instead + // to work with auto-unchoke logic + else if (m_ses.num_uploads() < m_settings.get_int(settings_pack::unchoke_slots_limit) + || m_settings.get_int(settings_pack::unchoke_slots_limit) < 0) { // if the peer is choked and we have upload slots left, // then unchoke it. Another condition that has to be met @@ -1518,18 +1751,27 @@ namespace libtorrent #if defined TORRENT_VERBOSE_LOGGING else { - peer_log("DID NOT UNCHOKE [ the number of uploads (%d)" + peer_log("DID NOT UNCHOKE [ the number of uploads (%d) " "is more than or equal to the limit (%d) ]" - , m_ses.num_uploads(), m_ses.settings().unchoke_slots_limit); + , m_ses.num_uploads(), m_settings.get_int(settings_pack::unchoke_slots_limit)); } #endif } -#if defined TORRENT_VERBOSE_LOGGING - else if (t->graceful_pause()) + else { - peer_log("DID NOT UNCHOKE [ graceful pause mode ]"); - } + // the reason to send an extra unchoke message here is that + // because of the handshake-round-trip optimization, we may + // end up sending an unchoke before the other end sends us + // an interested message. This may confuse clients, not reacting + // to the first unchoke, and then not check whether it's unchoked + // when sending the interested message. If the other end's client + // has this problem, sending another unchoke here will kick it + // to react to the fact that it's unchoked. +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("SENDING REDUNDANT UNCHOKE"); #endif + write_unchoke(); + } } // ----------------------------- @@ -1553,6 +1795,9 @@ namespace libtorrent #ifdef TORRENT_VERBOSE_LOGGING peer_log("<== NOT_INTERESTED"); #endif + if (m_peer_interested) + m_ses.inc_stats_counter(counters::num_peers_up_interested, -1); + m_peer_interested = false; if (is_disconnecting()) return; @@ -1570,10 +1815,10 @@ namespace libtorrent if (m_peer_info && m_peer_info->optimistically_unchoked) { m_peer_info->optimistically_unchoked = false; - m_ses.m_optimistic_unchoke_time_scaler = 0; + m_ses.trigger_optimistic_unchoke(); } m_ses.choke_peer(*this); - m_ses.m_unchoke_time_scaler = 0; + m_ses.trigger_unchoke(); } } @@ -1637,11 +1882,14 @@ namespace libtorrent // if we got an invalid message, abort if (index >= int(m_have_piece.size()) || index < 0) { - disconnect(errors::invalid_have, 2); +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("*** ERROR: [ have-metadata have_piece.size: %d ]", index, int(m_have_piece.size())); +#endif + disconnect(errors::invalid_have, op_bittorrent, 2); return; } - if (t->super_seeding() && !m_ses.settings().strict_super_seeding) + if (t->super_seeding() && !m_settings.get_bool(settings_pack::strict_super_seeding)) { // if we're superseeding and the peer just told // us that it completed the piece we're superseeding @@ -1696,24 +1944,34 @@ namespace libtorrent #ifdef TORRENT_VERBOSE_LOGGING peer_log("*** THIS IS A SEED [ p: %p ]", m_peer_info); #endif + + TORRENT_ASSERT(m_have_piece.all_set()); + TORRENT_ASSERT(m_have_piece.count() == m_have_piece.size()); + TORRENT_ASSERT(m_have_piece.size() == t->torrent_file().num_pieces()); + t->seen_complete(); - t->get_policy().set_seed(m_peer_info, true); + t->set_seed(m_peer_info, true); m_upload_only = true; + +#if TORRENT_USE_INVARIANT_CHECKS + if (t && t->has_picker()) + t->picker().check_peer_invariant(m_have_piece, this); +#endif disconnect_if_redundant(); if (is_disconnecting()) return; } - if (!t->have_piece(index) + if (!t->has_piece_passed(index) && !t->is_seed() && !is_interesting() - && t->picker().piece_priority(index) != 0) - t->get_policy().peer_is_interesting(*this); + && (!t->has_picker() || t->picker().piece_priority(index) != 0)) + t->peer_is_interesting(*this); // if we're super seeding, this might mean that somebody // forwarded this piece. In which case we need to give // a new piece to that peer if (t->super_seeding() - && m_ses.settings().strict_super_seeding + && m_settings.get_bool(settings_pack::strict_super_seeding) && (!super_seeded_piece(index) || t->num_peers() == 1)) { for (torrent::peer_iterator i = t->begin() @@ -1755,7 +2013,7 @@ namespace libtorrent // if we got an invalid message, abort if (index >= int(m_have_piece.size()) || index < 0) { - disconnect(errors::invalid_dont_have, 2); + disconnect(errors::invalid_dont_have, op_bittorrent, 2); return; } @@ -1781,7 +2039,7 @@ namespace libtorrent t->peer_lost(index, this); if (was_seed) - t->get_policy().set_seed(m_peer_info, false); + t->set_seed(m_peer_info, false); } // ----------------------------- @@ -1818,7 +2076,7 @@ namespace libtorrent if (t->valid_metadata() && (bits.size() + 7) / 8 != (m_have_piece.size() + 7) / 8) { - disconnect(errors::invalid_bitfield_size, 2); + disconnect(errors::invalid_bitfield_size, op_bittorrent, 2); return; } @@ -1832,6 +2090,10 @@ namespace libtorrent m_bitfield_received = true; +#if defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + m_bitfield_time = time_now_hires(); + t->debug_log("HANDSHAKE [%p] (%d ms)", this, int(total_milliseconds(m_bitfield_time - m_connect_time))); +#endif // if we don't have metadata yet // just remember the bitmask // don't update the piecepicker @@ -1844,7 +2106,12 @@ namespace libtorrent #endif m_have_piece = bits; m_num_pieces = bits.count(); - t->get_policy().set_seed(m_peer_info, m_num_pieces == int(bits.size())); + t->set_seed(m_peer_info, m_num_pieces == int(bits.size())); + +#if TORRENT_USE_INVARIANT_CHECKS + if (t && t->has_picker()) + t->picker().check_peer_invariant(m_have_piece, this); +#endif return; } @@ -1856,15 +2123,25 @@ namespace libtorrent #ifdef TORRENT_VERBOSE_LOGGING peer_log("*** THIS IS A SEED [ p: %p ]", m_peer_info); #endif + // if this is a web seed. we don't have a peer_info struct - t->get_policy().set_seed(m_peer_info, true); + t->set_seed(m_peer_info, true); m_upload_only = true; m_have_piece.set_all(); m_num_pieces = num_pieces; t->peer_has_all(this); + + TORRENT_ASSERT(m_have_piece.all_set()); + TORRENT_ASSERT(m_have_piece.count() == m_have_piece.size()); + TORRENT_ASSERT(m_have_piece.size() == t->torrent_file().num_pieces()); + +#if TORRENT_USE_INVARIANT_CHECKS + if (t && t->has_picker()) + t->picker().check_peer_invariant(m_have_piece, this); +#endif if (!t->is_upload_only()) - t->get_policy().peer_is_interesting(*this); + t->peer_is_interesting(*this); disconnect_if_redundant(); @@ -1877,49 +2154,67 @@ namespace libtorrent bool interesting = false; t->peer_has(bits, this); + if (!t->is_upload_only()) + { + for (int i = 0; i < (int)m_have_piece.size(); ++i) + { + bool have = bits[i]; + if (!have || m_have_piece[i]) continue; + // if we don't have a picker, the assumption is that the piece + // priority is 1, or that we're a seed, but in that case have_piece + // would have returned true. + if (!t->have_piece(i) && (!t->has_picker() || t->picker().piece_priority(i) != 0)) + interesting = true; + } + } + m_have_piece = bits; m_num_pieces = num_pieces; - if (interesting) t->get_policy().peer_is_interesting(*this); + if (interesting) t->peer_is_interesting(*this); else if (upload_only() && can_disconnect(error_code(errors::upload_upload_connection, get_libtorrent_category()))) - disconnect(errors::upload_upload_connection); + disconnect(errors::upload_upload_connection, op_bittorrent); } - void peer_connection::disconnect_if_redundant() + bool peer_connection::disconnect_if_redundant() { + if (m_disconnecting) return false; + // we cannot disconnect in a constructor TORRENT_ASSERT(m_in_constructor == false); - if (!m_ses.settings().close_redundant_connections) return; + if (!m_settings.get_bool(settings_pack::close_redundant_connections)) return false; boost::shared_ptr t = m_torrent.lock(); - if (!t) return; + if (!t) return false; // if we don't have the metadata yet, don't disconnect // also, if the peer doesn't have metadata we shouldn't // disconnect it, since it may want to request the // metadata from us - if (!t->valid_metadata() || !has_metadata()) return; + if (!t->valid_metadata() || !has_metadata()) return false; // don't close connections in share mode, we don't know if we need them - if (t->share_mode()) return; + if (t->share_mode()) return false; - if (m_upload_only && t->is_upload_only()) + if (m_upload_only && t->is_upload_only() + && can_disconnect(error_code(errors::upload_upload_connection, get_libtorrent_category()))) { - if (!can_disconnect(error_code(errors::upload_upload_connection, get_libtorrent_category()))) return; - disconnect(errors::upload_upload_connection); - return; + disconnect(errors::upload_upload_connection, op_bittorrent); + return true; } if (m_upload_only && !m_interesting && m_bitfield_received - && t->are_files_checked()) + && t->are_files_checked() + && can_disconnect(error_code(errors::uninteresting_upload_peer, get_libtorrent_category()))) { - if (!can_disconnect(error_code(errors::uninteresting_upload_peer, get_libtorrent_category()))) return; - disconnect(errors::uninteresting_upload_peer); - return; + disconnect(errors::uninteresting_upload_peer, op_bittorrent); + return true; } + + return false; } bool peer_connection::can_disconnect(error_code const& ec) const @@ -1945,21 +2240,17 @@ namespace libtorrent boost::shared_ptr t = m_torrent.lock(); TORRENT_ASSERT(t); -#ifdef TORRENT_STATS - ++m_ses.m_piece_requests; -#endif + m_ses.inc_stats_counter(counters::piece_requests); #if defined TORRENT_VERBOSE_LOGGING - peer_log("<== REQUEST [ piece: %d s: %d l: %d ]" + peer_log("<== REQUEST [ piece: %d s: %x l: %x ]" , r.piece, r.start, r.length); #endif if (t->super_seeding() && !super_seeded_piece(r.piece)) { -#ifdef TORRENT_STATS - ++m_ses.m_invalid_piece_requests; -#endif + m_ses.inc_stats_counter(counters::invalid_piece_requests); ++m_num_invalid_requests; #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING peer_log("*** INVALID_REQUEST [ piece not superseeded " @@ -1967,7 +2258,7 @@ namespace libtorrent , m_peer_interested , int(t->torrent_file().piece_size(r.piece)) , t->torrent_file().num_pieces() - , t->have_piece(r.piece) + , t->has_piece_passed(r.piece) , m_superseed_piece[0] , m_superseed_piece[1]); #endif @@ -1998,25 +2289,21 @@ namespace libtorrent if (!t->valid_metadata()) { -#ifdef TORRENT_STATS - ++m_ses.m_invalid_piece_requests; -#endif + m_ses.inc_stats_counter(counters::invalid_piece_requests); // if we don't have valid metadata yet, // we shouldn't get a request #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING peer_log("*** INVALID_REQUEST [ we don't have metadata yet ]"); - peer_log("==> REJECT_PIECE [ piece: %d s: %d l: %d ]" + peer_log("==> REJECT_PIECE [ piece: %d | s: %x | l: %x ] no metadata" , r.piece , r.start , r.length); #endif write_reject_request(r); return; } - if (int(m_requests.size()) > m_ses.settings().max_allowed_in_request_queue) + if (int(m_requests.size()) > m_settings.get_int(settings_pack::max_allowed_in_request_queue)) { -#ifdef TORRENT_STATS - ++m_ses.m_max_piece_requests; -#endif + m_ses.inc_stats_counter(counters::max_piece_requests); // don't allow clients to abuse our // memory consumption. // ignore requests if the client @@ -2024,7 +2311,7 @@ namespace libtorrent #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING peer_log("*** INVALID_REQUEST [ incoming request queue full %d ]" , int(m_requests.size())); - peer_log("==> REJECT_PIECE [ piece: %d s: %d l: %d ]" + peer_log("==> REJECT_PIECE [ piece: %d | s: %x | l: %x ] too many requests" , r.piece , r.start , r.length); #endif write_reject_request(r); @@ -2039,81 +2326,27 @@ namespace libtorrent // make sure this request // is legal and that the peer // is not choked - if (r.piece >= 0 - && r.piece < t->torrent_file().num_pieces() - && t->have_piece(r.piece) - && r.start >= 0 - && r.start < t->torrent_file().piece_size(r.piece) - && r.length > 0 - && r.length + r.start <= t->torrent_file().piece_size(r.piece) - && m_peer_interested - && r.length <= t->block_size()) + if (r.piece < 0 + || r.piece >= t->torrent_file().num_pieces() + || (!t->has_piece_passed(r.piece) + && !t->is_predictive_piece(r.piece) + && !t->seed_mode()) + || r.start < 0 + || r.start >= t->torrent_file().piece_size(r.piece) + || r.length <= 0 + || r.length + r.start > t->torrent_file().piece_size(r.piece) + || !m_peer_interested + || r.length > t->block_size()) { - // if we have choked the client - // ignore the request - const int blocks_per_piece = static_cast( - (t->torrent_file().piece_length() + t->block_size() - 1) / t->block_size()); + m_ses.inc_stats_counter(counters::invalid_piece_requests); - // disconnect peers that downloads more than foo times an allowed - // fast piece - if (m_choked && fast_idx != -1 - && m_accept_fast_piece_cnt[fast_idx] >= 3 * blocks_per_piece - && can_disconnect(error_code(errors::too_many_requests_when_choked, get_libtorrent_category()))) - { - disconnect(errors::too_many_requests_when_choked); - return; - } - - if (m_choked && fast_idx == -1) - { -#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING - peer_log("*** REJECTING REQUEST [ peer choked and piece not in allowed fast set ]"); - peer_log(" ==> REJECT_PIECE [ piece: %d | s: %d | l: %d ]" - , r.piece, r.start, r.length); -#endif -#ifdef TORRENT_STATS - ++m_ses.m_choked_piece_requests; -#endif - write_reject_request(r); - - time_duration since_choked = time_now() - m_last_choke; - - // allow peers to send request up to 2 seconds after getting choked, - // the disconnect them - if (total_milliseconds(since_choked) > 2000 - && can_disconnect(error_code(errors::too_many_requests_when_choked, get_libtorrent_category()))) - { - disconnect(errors::too_many_requests_when_choked, 2); - return; - } - } - else - { - // increase the allowed fast set counter - if (fast_idx != -1) - ++m_accept_fast_piece_cnt[fast_idx]; - - m_requests.push_back(r); -#ifdef TORRENT_REQUEST_LOGGING - if (m_ses.m_request_log) - write_request_log(m_ses.m_request_log, t->info_hash(), this, r); -#endif - m_last_incoming_request = time_now(); - fill_send_buffer(); - } - } - else - { -#ifdef TORRENT_STATS - ++m_ses.m_invalid_piece_requests; -#endif #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING peer_log("*** INVALID_REQUEST [ " "i: %d t: %d n: %d h: %d block_limit: %d ]" , m_peer_interested , int(t->torrent_file().piece_size(r.piece)) , t->torrent_file().num_pieces() - , t->have_piece(r.piece) + , t->has_piece_passed(r.piece) , t->block_size()); peer_log("==> REJECT_PIECE [ piece: %d | s: %d | l: %d ] invalid request" @@ -2128,6 +2361,92 @@ namespace libtorrent t->alerts().post_alert(invalid_request_alert( t->get_handle(), m_remote, m_peer_id, r)); } + + // every ten invalid request, remind the peer that it's choked + if (!m_peer_interested && m_num_invalid_requests % 10 == 0 && m_choked) + { + if (m_num_invalid_requests > 300 && !m_peer_choked + && can_disconnect(error_code(errors::too_many_requests_when_choked, get_libtorrent_category()))) + { + disconnect(errors::too_many_requests_when_choked, op_bittorrent, 2); + return; + } +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("==> CHOKE"); +#endif + write_choke(); + } + + return; + } + + // if we have choked the client + // ignore the request + const int blocks_per_piece = static_cast( + (t->torrent_file().piece_length() + t->block_size() - 1) / t->block_size()); + + // disconnect peers that downloads more than foo times an allowed + // fast piece + if (m_choked && fast_idx != -1 && m_accept_fast_piece_cnt[fast_idx] >= 3 * blocks_per_piece + && can_disconnect(error_code(errors::too_many_requests_when_choked, get_libtorrent_category()))) + { + disconnect(errors::too_many_requests_when_choked, op_bittorrent, 2); + return; + } + + if (m_choked && fast_idx == -1) + { +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING + peer_log("*** REJECTING REQUEST [ peer choked and piece not in allowed fast set ]"); + peer_log(" ==> REJECT_PIECE [ piece: %d | s: %d | l: %d ] peer choked" + , r.piece, r.start, r.length); +#endif + m_ses.inc_stats_counter(counters::choked_piece_requests); + write_reject_request(r); + time_duration since_choked = time_now() - m_last_choke; + + // allow peers to send request up to 2 seconds after getting choked, + // then disconnect them + if (total_milliseconds(since_choked) > 2000 + && can_disconnect(error_code(errors::too_many_requests_when_choked, get_libtorrent_category()))) + { + disconnect(errors::too_many_requests_when_choked, op_bittorrent, 2); + return; + } + } + else + { + // increase the allowed fast set counter + if (fast_idx != -1) + ++m_accept_fast_piece_cnt[fast_idx]; + + if (m_requests.empty()) + m_ses.inc_stats_counter(counters::num_peers_up_requests); + + m_requests.push_back(r); +#ifdef TORRENT_REQUEST_LOGGING + FILE* log = m_ses.get_request_log(); + if (log) + write_request_log(log, t->info_hash(), this, r); +#endif + m_last_incoming_request = time_now(); + fill_send_buffer(); + } + } + + // reject all requests to this piece + void peer_connection::reject_piece(int index) + { + for (std::vector::iterator i = m_requests.begin() + , end(m_requests.end()); i != end; ++i) + { + peer_request const& r = *i; + if (r.piece != index) continue; + write_reject_request(r); + i = m_requests.erase(i); + + if (m_requests.empty()) + m_ses.inc_stats_counter(counters::num_peers_up_requests, -1); } } @@ -2171,7 +2490,7 @@ namespace libtorrent peer_log("*** INVALID_PIECE [ piece: %d s: %d l: %d ]" , r.piece, r.start, r.length); #endif - disconnect(errors::invalid_piece, 2); + disconnect(errors::invalid_piece, op_bittorrent, 2); return; } @@ -2203,6 +2522,9 @@ namespace libtorrent break; } + if (m_download_queue.empty()) + m_ses.inc_stats_counter(counters::num_peers_down_requests); + m_download_queue.insert(m_download_queue.begin(), b); if (!in_req_queue) { @@ -2221,7 +2543,7 @@ namespace libtorrent } } -#ifdef TORRENT_DEBUG +#if TORRENT_USE_INVARIANT_CHECKS struct check_postcondition { check_postcondition(boost::shared_ptr const& t_ @@ -2258,13 +2580,26 @@ namespace libtorrent void peer_connection::incoming_piece(peer_request const& p, char const* data) { - char* buffer = m_ses.allocate_disk_buffer("receive buffer"); + bool exceeded = false; + char* buffer = m_allocator.allocate_disk_buffer(exceeded, self(), "receive buffer"); + if (buffer == 0) { - disconnect(errors::no_memory); + disconnect(errors::no_memory, op_alloc_recvbuf); return; } - disk_buffer_holder holder(m_ses, buffer); + + if (exceeded) + { + if ((m_channel_state[download_channel] & peer_info::bw_disk) == 0) + m_ses.inc_stats_counter(counters::num_peers_down_disk); + m_channel_state[download_channel] |= peer_info::bw_disk; +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("*** exceeded disk buffer watermark"); +#endif + } + + disk_buffer_holder holder(m_allocator, buffer); std::memcpy(buffer, data, p.length); incoming_piece(p, holder); } @@ -2314,9 +2649,9 @@ namespace libtorrent #endif if (is_disconnecting()) return; -#ifdef TORRENT_DEBUG - check_postcondition post_checker_(t); #if TORRENT_USE_INVARIANT_CHECKS + check_postcondition post_checker_(t); +#if defined TORRENT_EXPENSIVE_INVARIANT_CHECKS t->check_invariant(); #endif #endif @@ -2324,7 +2659,7 @@ namespace libtorrent #ifdef TORRENT_VERBOSE_LOGGING hasher h; h.update(data.get(), p.length); - peer_log("<== PIECE [ piece: %d | s: %d | l: %d | ds: %d | qs: %d | q: %d | hash: %s ]" + peer_log("<== PIECE [ piece: %d | s: %x | l: %x | ds: %d | qs: %d | q: %d | hash: %s ]" , p.piece, p.start, p.length, statistics().download_rate() , int(m_desired_queue_size), int(m_download_queue.size()) , to_hex(h.final().to_string()).c_str()); @@ -2335,7 +2670,7 @@ namespace libtorrent if (t->alerts().should_post()) { t->alerts().post_alert(peer_error_alert(t->get_handle(), m_remote - , m_peer_id, errors::peer_sent_empty_piece)); + , m_peer_id, op_bittorrent, errors::peer_sent_empty_piece)); } // This is used as a reject-request by bitcomet incoming_reject_request(p); @@ -2350,15 +2685,21 @@ namespace libtorrent TORRENT_ASSERT(m_received_in_piece == p.length); m_received_in_piece = 0; #endif - if (!m_download_queue.empty()) m_download_queue.erase(m_download_queue.begin()); + if (!m_download_queue.empty()) + { + m_download_queue.erase(m_download_queue.begin()); + if (m_download_queue.empty()) + m_ses.inc_stats_counter(counters::num_peers_down_requests, -1); + } t->add_redundant_bytes(p.length, torrent::piece_seed); return; } ptime now = time_now(); + t->need_picker(); + piece_picker& picker = t->picker(); - piece_manager& fs = t->filesystem(); piece_block block_finished(p.piece, p.start / t->block_size()); TORRENT_ASSERT(verify_piece(p)); @@ -2397,59 +2738,6 @@ namespace libtorrent return; } -#if TORRENT_USE_ASSERTS - pending_block pending_b = *b; -#endif - - int block_index = b - m_download_queue.begin(); - TORRENT_ASSERT(m_download_queue[block_index] == pending_b); - for (int i = 0; i < block_index; ++i) - { - pending_block& qe = m_download_queue[i]; - TORRENT_ASSERT(m_download_queue[block_index] == pending_b); - TORRENT_ASSERT(i < block_index); - -#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING - peer_log("*** SKIPPED_PIECE [ piece: %d b: %d dqs: %d ]" - , qe.block.piece_index, qe.block.block_index, int(m_desired_queue_size)); -#endif - - ++qe.skipped; - // if the number of times a block is skipped by out of order - // blocks exceeds the size of the outstanding queue, assume that - // the other end dropped the request. - if (m_ses.m_settings.drop_skipped_requests - && qe.skipped > m_desired_queue_size * 2) - { - if (m_ses.m_alerts.should_post()) - m_ses.m_alerts.post_alert(request_dropped_alert(t->get_handle() - , remote(), pid(), qe.block.block_index, qe.block.piece_index)); - -#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING - peer_log("*** DROPPED_PIECE [ piece: %d b: %d dqs: %d skip: %d ]" - , qe.block.piece_index, qe.block.block_index - , int(m_desired_queue_size), qe.skipped); -#endif - if (!qe.timed_out && !qe.not_wanted) - picker.abort_download(qe.block, peer_info_struct()); - - TORRENT_ASSERT(m_outstanding_bytes >= t->to_req(qe.block).length); - m_outstanding_bytes -= t->to_req(qe.block).length; - if (m_outstanding_bytes < 0) m_outstanding_bytes = 0; - TORRENT_ASSERT(m_download_queue[block_index] == pending_b); - m_download_queue.erase(m_download_queue.begin() + i); - --i; - --block_index; - TORRENT_ASSERT(m_download_queue[block_index] == pending_b); -#if TORRENT_USE_INVARIANT_CHECKS - check_invariant(); -#endif - } - } - TORRENT_ASSERT(int(m_download_queue.size()) > block_index); - b = m_download_queue.begin() + block_index; - TORRENT_ASSERT(*b == pending_b); - #if TORRENT_USE_ASSERTS TORRENT_ASSERT_VAL(m_received_in_piece == p.length, m_received_in_piece); m_received_in_piece = 0; @@ -2466,63 +2754,79 @@ namespace libtorrent t->add_redundant_bytes(p.length, reason); m_download_queue.erase(b); + if (m_download_queue.empty()) + m_ses.inc_stats_counter(counters::num_peers_down_requests, -1); + m_timeout_extend = 0; + if (m_disconnecting) return; + if (!m_download_queue.empty()) m_requested = now; -#ifdef TORRENT_STATS - ++m_ses.m_incoming_redundant_piece_picks; -#endif - request_a_block(*t, *this); + if (request_a_block(*t, *this)) + m_ses.inc_stats_counter(counters::incoming_redundant_piece_picks); send_block_requests(); return; } if (total_seconds(now - m_requested) - < m_ses.settings().request_timeout + < m_settings.get_int(settings_pack::request_timeout) && m_snubbed) { m_snubbed = false; - if (m_ses.m_alerts.should_post()) + if (t->alerts().should_post()) { - m_ses.m_alerts.post_alert(peer_unsnubbed_alert(t->get_handle() + t->alerts().post_alert(peer_unsnubbed_alert(t->get_handle() , m_remote, m_peer_id)); } } +#if defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + t->debug_log("PIECE [%p] (%d ms) (%d)", this + , int(total_milliseconds(time_now_hires() - m_unchoke_time)), t->num_have()); +#endif + +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("*** FILE ASYNC WRITE [ piece: %d | s: %x | l: %x ]" + , p.piece, p.start, p.length); +#endif + m_download_queue.erase(b); + if (m_download_queue.empty()) + m_ses.inc_stats_counter(counters::num_peers_down_requests, -1); + if (t->is_deleted()) return; - int write_queue_size = fs.async_write(p, data, boost::bind(&peer_connection::on_disk_write_complete - , self(), _1, _2, p, t)); - m_outstanding_writing_bytes += p.length; - m_download_queue.erase(b); - - if (write_queue_size / 16 / 1024 > m_ses.m_settings.cache_size / 2 - && m_ses.m_settings.cache_size > 5 - && (now - m_ses.m_last_disk_queue_performance_warning) > seconds(10) - && m_ses.m_alerts.should_post()) + if (!t->need_loaded()) + { + t->add_redundant_bytes(p.length, torrent::piece_unknown); + return; + } + t->inc_refcount("async_write"); + m_disk_thread.async_write(&t->storage(), p, data + , boost::bind(&peer_connection::on_disk_write_complete + , self(), _1, p, t)); + + boost::uint64_t write_queue_size = m_ses.inc_stats_counter( + counters::queued_write_bytes, p.length); + m_outstanding_writing_bytes += p.length; + + boost::uint64_t max_queue_size = m_settings.get_int( + settings_pack::max_queued_disk_bytes); + if (write_queue_size > max_queue_size + && write_queue_size - p.length < max_queue_size + && m_settings.get_int(settings_pack::cache_size) > 5 + && t->alerts().should_post()) { - m_ses.m_last_disk_queue_performance_warning = now; t->alerts().post_alert(performance_alert(t->get_handle() , performance_alert::too_high_disk_queue_limit)); } - if (!m_ses.can_write_to_disk() - && m_ses.settings().max_queued_disk_bytes - && t->alerts().should_post() - && (now - m_ses.m_last_disk_performance_warning) > seconds(10)) - { - m_ses.m_last_disk_performance_warning = now; - t->alerts().post_alert(performance_alert(t->get_handle() - , performance_alert::outstanding_disk_buffer_limit_reached)); - } - if (!m_download_queue.empty()) { m_timeout_extend = (std::max)(m_timeout_extend - - m_ses.settings().request_timeout, 0); - m_requested += seconds(m_ses.settings().request_timeout); + - m_settings.get_int(settings_pack::request_timeout), 0); + m_requested += seconds(m_settings.get_int(settings_pack::request_timeout)); if (m_requested > now) m_requested = now; } else @@ -2533,12 +2837,50 @@ namespace libtorrent bool was_finished = picker.is_piece_finished(p.piece); // did we request this block from any other peers? bool multi = picker.num_peers(block_finished) > 1; +// fprintf(stderr, "peer_connection mark_as_writing peer: %p piece: %d block: %d\n" +// , peer_info_struct(), block_finished.piece_index, block_finished.block_index); picker.mark_as_writing(block_finished, peer_info_struct()); TORRENT_ASSERT(picker.num_peers(block_finished) == 0); // if we requested this block from other peers, cancel it now if (multi) t->cancel_block(block_finished); + if (m_settings.get_int(settings_pack::predictive_piece_announce)) + { + int piece = block_finished.piece_index; + piece_picker::downloading_piece st; + t->picker().piece_info(piece, st); + + int num_blocks = t->picker().blocks_in_piece(piece); + if (st.requested > 0 && st.writing + st.finished + st.requested == num_blocks) + { + std::vector d; + t->picker().get_requestors(d, piece); + if (d.size() == 1) + { + // only make predictions if all remaining + // blocks are requested from the same peer + torrent_peer* p = (torrent_peer*)d[0]; + if (p->connection) + { + // we have a connection. now, what is the current + // download rate from this peer, and how many blocks + // do we have left to download? + boost::int64_t rate = p->connection->statistics().download_payload_rate(); + boost::int64_t bytes_left = boost::int64_t(st.requested) * t->block_size(); + // the settings unit is milliseconds, so calculate the + // number of milliseconds worth of bytes left in the piece + if (rate > 1000 + && (bytes_left * 1000) / rate < m_settings.get_int(settings_pack::predictive_piece_announce)) + { + // we predict we will complete this piece very soon. + t->predicted_have_piece(piece, (bytes_left * 1000) / rate); + } + } + } + } + } + TORRENT_ASSERT(picker.num_peers(block_finished) == 0); #if TORRENT_USE_INVARIANT_CHECKS \ @@ -2559,78 +2901,110 @@ namespace libtorrent // to disk or are in the disk write cache if (picker.is_piece_finished(p.piece) && !was_finished) { -#ifdef TORRENT_DEBUG +#if TORRENT_USE_INVARIANT_CHECKS check_postcondition post_checker2_(t, false); #endif - t->async_verify_piece(p.piece, boost::bind(&torrent::piece_finished, t - , p.piece, _1)); + t->verify_piece(p.piece); } if (is_disconnecting()) return; -#ifdef TORRENT_STATS - ++m_ses.m_incoming_piece_picks; -#endif - request_a_block(*t, *this); + if (request_a_block(*t, *this)) + m_ses.inc_stats_counter(counters::incoming_piece_picks); send_block_requests(); } - void peer_connection::on_disk_write_complete(int ret, disk_io_job const& j + void peer_connection::on_disk_write_complete(disk_io_job const* j , peer_request p, boost::shared_ptr t) { -#ifdef TORRENT_STATS - ++m_ses.m_num_messages[aux::session_impl::on_disk_write_counter]; -#endif - TORRENT_ASSERT(m_ses.is_network_thread()); + torrent_ref_holder h(t.get(), "async_write"); + if (t) t->dec_refcount("async_write"); + TORRENT_ASSERT(m_ses.is_single_thread()); - // flush send buffer at the end of this scope - // TODO: 1 peers should really be corked/uncorked outside of - // all completed disk operations - cork _c(*this); +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("*** FILE ASYNC WRITE COMPLETE [ ret: %d | piece: %d | s: %x | l: %x | e: %s ]" + , j->ret, p.piece, p.start, p.length, j->error.ec.message().c_str()); +#endif + + m_ses.inc_stats_counter(counters::queued_write_bytes, -p.length); + m_outstanding_writing_bytes -= p.length; + + TORRENT_ASSERT(m_outstanding_writing_bytes >= 0); + + // flush send buffer at the end of + // this burst of disk events +// m_ses.cork_burst(this); INVARIANT_CHECK; - m_outstanding_writing_bytes -= p.length; - TORRENT_ASSERT(m_outstanding_writing_bytes >= 0); - -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) -// (*m_ses.m_logger) << time_now_string() << " *** DISK_WRITE_COMPLETE [ p: " -// << p.piece << " o: " << p.start << " ]\n"; -#endif - if (!t) { - disconnect(j.error); + disconnect(j->error.ec, op_file_write); return; } + t->schedule_storage_tick(); + // in case the outstanding bytes just dropped down // to allow to receive more data setup_receive(read_async); piece_block block_finished(p.piece, p.start / t->block_size()); - if (ret == -1) + if (j->ret < 0) { // handle_disk_error may disconnect us t->handle_disk_error(j, this); return; } + TORRENT_ASSERT(j->ret == p.length); + if (!t->has_picker()) return; piece_picker& picker = t->picker(); - TORRENT_ASSERT(p.piece == j.piece); - TORRENT_ASSERT(p.start == j.offset); + TORRENT_ASSERT(p.piece == j->piece); + TORRENT_ASSERT(p.start == j->d.io.offset); TORRENT_ASSERT(picker.num_peers(block_finished) == 0); + + if (j->ret == -1 && j->error.ec == boost::system::errc::operation_canceled) + { + TORRENT_ASSERT(false); // how do we get here? + picker.mark_as_canceled(block_finished, peer_info_struct()); + return; + } +// fprintf(stderr, "peer_connection mark_as_finished peer: %p piece: %d block: %d\n" +// , peer_info_struct(), block_finished.piece_index, block_finished.block_index); picker.mark_as_finished(block_finished, peer_info_struct()); + + t->maybe_done_flushing(); + if (t->alerts().should_post()) { t->alerts().post_alert(block_finished_alert(t->get_handle(), remote(), pid(), block_finished.block_index, block_finished.piece_index)); } + disconnect_if_redundant(); + + if (m_disconnecting) return; + +#if TORRENT_USE_ASSERTS + if (t->has_picker()) + { + const std::vector& q + = picker.get_download_queue(); + + for (std::vector::const_iterator + i = q.begin(), end(q.end()); i != end; ++i) + { + if (i->index != block_finished.piece_index) continue; + piece_picker::downloading_piece const& p = *i; + TORRENT_ASSERT(p.info[block_finished.block_index].state == piece_picker::block_info::state_finished); + } + } +#endif if (t->is_aborted()) return; } @@ -2652,7 +3026,7 @@ namespace libtorrent if (is_disconnecting()) return; #ifdef TORRENT_VERBOSE_LOGGING - peer_log("<== CANCEL [ piece: %d | s: %d | l: %d ]", r.piece, r.start, r.length); + peer_log("<== CANCEL [ piece: %d | s: %x | l: %x ]", r.piece, r.start, r.length); #endif std::vector::iterator i @@ -2660,18 +3034,25 @@ namespace libtorrent if (i != m_requests.end()) { -#ifdef TORRENT_STATS - ++m_ses.m_cancelled_piece_requests; -#endif + m_ses.inc_stats_counter(counters::cancelled_piece_requests); m_requests.erase(i); + + if (m_requests.empty()) + m_ses.inc_stats_counter(counters::num_peers_up_requests, -1); + #ifdef TORRENT_VERBOSE_LOGGING - peer_log("==> REJECT_PIECE [ piece: %d s: %d l: %d ]" + peer_log("==> REJECT_PIECE [ piece: %d s: %x l: %x ] cancelled" , r.piece , r.start , r.length); #endif write_reject_request(r); } else { + // TODO: 3 since we throw away the queue entry once we issue + // the disk job, this may happen. Instead, we should keep the + // queue entry around, mark it as having been requested from + // disk and once the disk job comes back, discard it if it has + // been cancelled. Maybe even be able to cancel disk jobs? #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING peer_log("*** GOT CANCEL NOT IN THE QUEUE"); #endif @@ -2732,10 +3113,15 @@ namespace libtorrent peer_log("*** THIS IS A SEED [ p: %p ]", m_peer_info); #endif - t->get_policy().set_seed(m_peer_info, true); + t->set_seed(m_peer_info, true); m_upload_only = true; m_bitfield_received = true; +#if defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + m_bitfield_time = time_now_hires(); + t->debug_log("HANDSHAKE [%p] (%d ms)", this, int(total_milliseconds(m_bitfield_time - m_connect_time))); +#endif + // if we don't have metadata yet // just remember the bitmask // don't update the piecepicker @@ -2744,12 +3130,9 @@ namespace libtorrent { // assume seeds are interesting when we // don't even have the metadata - t->get_policy().peer_is_interesting(*this); + t->peer_is_interesting(*this); disconnect_if_redundant(); - // TODO: this might need something more - // so that once we have the metadata - // we can construct a full bitfield return; } @@ -2759,9 +3142,18 @@ namespace libtorrent t->peer_has_all(this); +#if TORRENT_USE_INVARIANT_CHECKS + if (t && t->has_picker()) + t->picker().check_peer_invariant(m_have_piece, this); +#endif + + TORRENT_ASSERT(m_have_piece.all_set()); + TORRENT_ASSERT(m_have_piece.count() == m_have_piece.size()); + TORRENT_ASSERT(m_have_piece.size() == t->torrent_file().num_pieces()); + // if we're finished, we're not interested if (t->is_upload_only()) send_not_interested(); - else t->get_policy().peer_is_interesting(*this); + else t->peer_is_interesting(*this); disconnect_if_redundant(); } @@ -2793,9 +3185,13 @@ namespace libtorrent if (m_bitfield_received) t->peer_lost(m_have_piece, this); - t->get_policy().set_seed(m_peer_info, false); + t->set_seed(m_peer_info, false); m_bitfield_received = true; +#if defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + m_bitfield_time = time_now_hires(); + t->debug_log("HANDSHAKE [%p] (%d ms)", this, int(total_milliseconds(m_bitfield_time - m_connect_time))); +#endif m_have_piece.clear_all(); m_num_pieces = 0; @@ -2820,6 +3216,14 @@ namespace libtorrent boost::shared_ptr t = m_torrent.lock(); TORRENT_ASSERT(t); +#ifdef TORRENT_LOGGING + { + ptime now = time_now_hires(); + t->debug_log("ALLOW FAST [%p] (%d ms)", this, int(total_milliseconds(now - m_connect_time))); + if (m_peer_choked) m_unchoke_time = now; + } +#endif + #ifdef TORRENT_VERBOSE_LOGGING peer_log("<== ALLOWED_FAST [ %d ]", index); #endif @@ -2865,11 +3269,12 @@ namespace libtorrent // to download it, request it if (int(m_have_piece.size()) > index && m_have_piece[index] + && !t->has_piece_passed(index) && t->valid_metadata() && t->has_picker() && t->picker().piece_priority(index) > 0) { - t->get_policy().peer_is_interesting(*this); + t->peer_is_interesting(*this); } } @@ -2940,8 +3345,22 @@ namespace libtorrent TORRENT_ASSERT(std::find(m_request_queue.begin(), m_request_queue.end() , block) == m_request_queue.end()); - if (t->upload_mode()) return false; - if (m_disconnecting) return false; + if (t->upload_mode()) + { +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("*** PIECE_PICKER [ not_picking: %d,%d upload_mode ]" + , block.piece_index, block.block_index); +#endif + return false; + } + if (m_disconnecting) + { +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("*** PIECE_PICKER [ not_picking: %d,%d disconnecting ]" + , block.piece_index, block.block_index); +#endif + return false; + } piece_picker::piece_state_t state; peer_speed_t speed = peer_speed(); @@ -2973,18 +3392,38 @@ namespace libtorrent for (std::vector::const_iterator i = m_download_queue.begin() , end(m_download_queue.end()); i != end; ++i) { - if (i->busy) return false; + if (i->busy) + { +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("*** PIECE_PICKER [ not_picking: %d,%d already in download queue & busy ]" + , block.piece_index, block.block_index); +#endif + return false; + } } for (std::vector::const_iterator i = m_request_queue.begin() , end(m_request_queue.end()); i != end; ++i) { - if (i->busy) return false; + if (i->busy) + { +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("*** PIECE_PICKER [ not_picking: %d,%d already in request queue & busy ]" + , block.piece_index, block.block_index); +#endif + return false; + } } } if (!t->picker().mark_as_downloading(block, peer_info_struct(), state)) + { +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("*** PIECE_PICKER [ not_picking: %d,%d failed to mark_as_downloading ]" + , block.piece_index, block.block_index); +#endif return false; + } if (t->alerts().should_post()) { @@ -3139,6 +3578,7 @@ namespace libtorrent peer_log("==> CHOKE"); #endif write_choke(); + m_ses.inc_stats_counter(counters::num_peers_up_unchoked, -1); m_choked = true; m_last_choke = time_now(); @@ -3156,15 +3596,16 @@ namespace libtorrent continue; } peer_request const& r = *i; -#ifdef TORRENT_STATS - ++m_ses.m_choked_piece_requests; -#endif + m_ses.inc_stats_counter(counters::choked_piece_requests); #ifdef TORRENT_VERBOSE_LOGGING peer_log("==> REJECT_PIECE [ piece: %d s: %d l: %d ] choking" , r.piece , r.start , r.length); #endif write_reject_request(r); i = m_requests.erase(i); + + if (m_requests.empty()) + m_ses.inc_stats_counter(counters::num_peers_up_requests, -1); } return true; } @@ -3179,13 +3620,17 @@ namespace libtorrent if (!m_sent_suggests) { - std::vector ret; - t->get_suggested_pieces(ret); - for (std::vector::iterator i = ret.begin() + std::vector const& ret + = t->get_suggested_pieces(); + + for (std::vector::const_iterator i = ret.begin() , end(ret.end()); i != end; ++i) { - TORRENT_ASSERT(*i >= 0); - send_suggest(*i); + TORRENT_ASSERT(i->piece_index >= 0); + // this can happen if a piece fail to be + // flushed to disk for whatever reason + if (!t->has_piece_passed(i->piece_index)) continue; + send_suggest(i->piece_index); } m_sent_suggests = true; @@ -3193,6 +3638,7 @@ namespace libtorrent m_last_unchoke = time_now(); write_unchoke(); + m_ses.inc_stats_counter(counters::num_peers_up_unchoked); m_choked = false; m_uploaded_at_last_unchoke = m_statistics.total_payload_upload(); @@ -3209,6 +3655,7 @@ namespace libtorrent boost::shared_ptr t = m_torrent.lock(); if (!t->ready_for_connections()) return; m_interesting = true; + m_ses.inc_stats_counter(counters::num_peers_down_interested); write_interested(); #ifdef TORRENT_VERBOSE_LOGGING @@ -3231,6 +3678,11 @@ namespace libtorrent boost::shared_ptr t = m_torrent.lock(); if (!t->ready_for_connections()) return; m_interesting = false; + m_ses.inc_stats_counter(counters::num_peers_down_interested, -1); + + disconnect_if_redundant(); + if (m_disconnecting) return; + write_not_interested(); m_became_uninteresting = time_now(); @@ -3238,7 +3690,6 @@ namespace libtorrent #ifdef TORRENT_VERBOSE_LOGGING peer_log("==> NOT_INTERESTED"); #endif - disconnect_if_redundant(); } void peer_connection::send_suggest(int piece) @@ -3248,13 +3699,29 @@ namespace libtorrent // don't suggest a piece that the peer already has // don't suggest anything to a peer that isn't interested - if (has_piece(piece) - || !m_peer_interested) + if (has_piece(piece) || !m_peer_interested) return; -#ifdef TORRENT_VERBOSE_LOGGING - peer_log("==> SUGGEST [ %d ]", piece); + // we cannot suggest a piece we don't have! +#if TORRENT_USE_ASSERTS + boost::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + TORRENT_ASSERT(t->has_piece_passed(piece)); #endif + + TORRENT_ASSERT(piece >= 0 && piece < t->torrent_file().num_pieces()); + + if (m_sent_suggested_pieces.empty()) + { + boost::shared_ptr t = m_torrent.lock(); + m_sent_suggested_pieces.resize(t->torrent_file().num_pieces(), false); + } + + TORRENT_ASSERT(piece >= 0 && piece < m_sent_suggested_pieces.size()); + + if (m_sent_suggested_pieces[piece]) return; + m_sent_suggested_pieces.set_bit(piece); + write_suggest(piece); } @@ -3272,7 +3739,7 @@ namespace libtorrent #ifdef TORRENT_VERBOSE_LOGGING peer_log("*** GRACEFUL PAUSE [ NO MORE DOWNLOAD ]"); #endif - disconnect(errors::torrent_paused); + disconnect(errors::torrent_paused, op_bittorrent); return; } @@ -3322,6 +3789,9 @@ namespace libtorrent r.start = block_offset; r.length = block_size; + if (m_download_queue.empty()) + m_ses.inc_stats_counter(counters::num_peers_down_requests); + TORRENT_ASSERT(verify_piece(t->to_req(block.block))); m_download_queue.push_back(block); m_outstanding_bytes += block_size; @@ -3354,6 +3824,10 @@ namespace libtorrent block = m_request_queue.front(); m_request_queue.erase(m_request_queue.begin()); TORRENT_ASSERT(verify_piece(t->to_req(block.block))); + + if (m_download_queue.empty()) + m_ses.inc_stats_counter(counters::num_peers_down_requests); + m_download_queue.push_back(block); if (m_queued_time_critical) --m_queued_time_critical; @@ -3395,7 +3869,7 @@ namespace libtorrent } #ifdef TORRENT_VERBOSE_LOGGING - peer_log("==> REQUEST [ piece: %d | s: %d | l: %d | ds: %d B/s | " + peer_log("==> REQUEST [ piece: %d | s: %x | l: %x | ds: %d B/s | " "dqs: %d rqs: %d blk: %s ]" , r.piece, r.start, r.length, statistics().download_rate() , int(m_desired_queue_size), int(m_download_queue.size()) @@ -3409,13 +3883,24 @@ namespace libtorrent { // This means we just added a request to this connection m_requested = time_now(); +#if defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + t->debug_log("REQUEST [%p] (%d ms)", this, int(total_milliseconds(time_now_hires() - m_unchoke_time))); +#endif } } - void peer_connection::on_timeout() + void peer_connection::on_connect_timeout() { - TORRENT_ASSERT(m_ses.is_network_thread()); + m_queued_for_connection = false; + TORRENT_ASSERT(m_ses.is_single_thread()); +#if defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + boost::shared_ptr t = m_torrent.lock(); + if (t) + { + t->debug_log("END queue peer (timed out) [%p]", this); + } +#endif connect_failed(errors::timed_out); } @@ -3427,24 +3912,23 @@ namespace libtorrent peer_log("CONNECTION FAILED: %s", print_endpoint(m_remote).c_str()); #endif #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING - (*m_ses.m_logger) << time_now_string() << " CONNECTION FAILED: " << print_endpoint(m_remote) << "\n"; + m_ses.session_log(" CONNECTION FAILED: %s", print_endpoint(m_remote).c_str()); #endif -#ifdef TORRENT_STATS - ++m_ses.m_connect_timeouts; -#endif + m_ses.inc_stats_counter(counters::connect_timeouts); boost::shared_ptr t = m_torrent.lock(); TORRENT_ASSERT(!m_connecting || t); - if (m_connecting && t) + if (m_connecting) { - t->dec_num_connecting(); + m_ses.inc_stats_counter(counters::num_peers_half_open, -1); + if (t) t->dec_num_connecting(); m_connecting = false; } if (m_connection_ticket != -1) { - if (m_ses.m_half_open.done(m_connection_ticket)) + if (m_ses.half_open_done(m_connection_ticket)) m_connection_ticket = -1; } @@ -3458,10 +3942,10 @@ namespace libtorrent { m_peer_info->supports_utp = false; // reconnect immediately using TCP - policy::peer* pi = peer_info_struct(); + torrent_peer* pi = peer_info_struct(); boost::shared_ptr t = m_torrent.lock(); fast_reconnect(true); - disconnect(e, 0); + disconnect(e, op_connect, 0); if (t && pi) t->connect_to_peer(pi, true); return; } @@ -3471,7 +3955,7 @@ namespace libtorrent #ifndef TORRENT_DISABLE_EXTENSIONS if ((!is_utp(*m_socket) - || !m_ses.m_settings.enable_outgoing_tcp) + || !m_settings.get_bool(settings_pack::enable_outgoing_tcp)) && m_peer_info && m_peer_info->supports_holepunch && !m_holepunch_mode) @@ -3484,36 +3968,157 @@ namespace libtorrent } #endif - disconnect(e, 1); + disconnect(e, op_connect, 1); return; } // the error argument defaults to 0, which means deliberate disconnect // 1 means unexpected disconnect/error // 2 protocol error (client sent something invalid) - void peer_connection::disconnect(error_code const& ec, int error) + void peer_connection::disconnect(error_code const& ec, operation_t op, int error) { + TORRENT_ASSERT(m_ses.is_single_thread()); + #if TORRENT_USE_ASSERTS m_disconnect_started = true; #endif if (m_disconnecting) return; + // while being disconnected, it's possible that our torrent_peer + // pointer gets cleared. Make sure we save it to be able to keep + // proper books in the piece_picker (when debugging is enabled) + torrent_peer* self_peer = peer_info_struct(); + #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING switch (error) { case 0: - peer_log("*** CONNECTION CLOSED %s", ec.message().c_str()); + peer_log("*** CONNECTION CLOSED [op: %d] %s", op, ec.message().c_str()); break; case 1: - peer_log("*** CONNECTION FAILED %s", ec.message().c_str()); + peer_log("*** CONNECTION FAILED [op: %d] %s", op, ec.message().c_str()); break; case 2: - peer_log("*** PEER ERROR %s", ec.message().c_str()); + peer_log("*** PEER ERROR [op: %d] %s", op, ec.message().c_str()); break; } #endif + if ((m_channel_state[upload_channel] & peer_info::bw_network) == 0) + { + // make sure we free up all send buffers that are owned + // by the disk thread + m_send_buffer.clear(); + m_disk_recv_buffer.reset(); + m_disk_recv_buffer_size = 0; + } + + // we cannot do this in a constructor + TORRENT_ASSERT(m_in_constructor == false); + if (error > 0) m_failed = true; + + if (m_connected) + m_ses.inc_stats_counter(counters::num_peers_connected, -1); + m_connected = false; + + // for incoming connections, we get invalid argument errors + // when asking for the remote endpoint and the socket already + // closed, which is an edge case, but possible to happen when + // a peer makes a TCP and uTP connection in parallel. + // for outgoing connections however, why would we get this? +// TORRENT_ASSERT(ec != error::invalid_argument || !m_outgoing); + + m_ses.inc_stats_counter(counters::disconnected_peers); + if (error == 2) m_ses.inc_stats_counter(counters::error_peers); + if (ec == error::connection_reset) m_ses.inc_stats_counter(counters::connreset_peers); + else if (ec == error::eof) m_ses.inc_stats_counter(counters::eof_peers); + else if (ec == error::connection_refused) m_ses.inc_stats_counter(counters::connrefused_peers); + else if (ec == error::connection_aborted) m_ses.inc_stats_counter(counters::connaborted_peers); + else if (ec == error::no_permission) m_ses.inc_stats_counter(counters::perm_peers); + else if (ec == error::no_buffer_space) m_ses.inc_stats_counter(counters::buffer_peers); + else if (ec == error::host_unreachable) m_ses.inc_stats_counter(counters::unreachable_peers); + else if (ec == error::broken_pipe) m_ses.inc_stats_counter(counters::broken_pipe_peers); + else if (ec == error::address_in_use) m_ses.inc_stats_counter(counters::addrinuse_peers); + else if (ec == error::access_denied) m_ses.inc_stats_counter(counters::no_access_peers); + else if (ec == error::invalid_argument) m_ses.inc_stats_counter(counters::invalid_arg_peers); + else if (ec == error::operation_aborted) m_ses.inc_stats_counter(counters::aborted_peers); + else if (ec == error_code(errors::upload_upload_connection) + || ec == error_code(errors::uninteresting_upload_peer) + || ec == error_code(errors::torrent_aborted) + || ec == error_code(errors::self_connection) + || ec == error_code(errors::torrent_paused)) + m_ses.inc_stats_counter(counters::uninteresting_peers); + + if (ec == error_code(errors::timed_out) + || ec == error::timed_out) + m_ses.inc_stats_counter(counters::transport_timeout_peers); + + if (ec == error_code(errors::timed_out_inactivity) + || ec == error_code(errors::timed_out_no_request) + || ec == error_code(errors::timed_out_no_interest)) + m_ses.inc_stats_counter(counters::timeout_peers); + + if (ec == error_code(errors::no_memory)) + m_ses.inc_stats_counter(counters::no_memory_peers); + + if (ec == error_code(errors::too_many_connections)) + m_ses.inc_stats_counter(counters::too_many_peers); + + if (ec == error_code(errors::timed_out_no_handshake)) + m_ses.inc_stats_counter(counters::connect_timeouts); + + if (error > 0) + { + if (is_utp(*m_socket)) m_ses.inc_stats_counter(counters::error_utp_peers); + else m_ses.inc_stats_counter(counters::error_tcp_peers); + + if (m_outgoing) m_ses.inc_stats_counter(counters::error_outgoing_peers); + else m_ses.inc_stats_counter(counters::error_incoming_peers); + +#ifndef TORRENT_DISABLE_ENCRYPTION + if (type() == bittorrent_connection) + { + bt_peer_connection* bt = static_cast(this); + if (bt->supports_encryption()) m_ses.inc_stats_counter( + counters::error_encrypted_peers); + if (bt->rc4_encrypted() && bt->supports_encryption()) + m_ses.inc_stats_counter(counters::error_rc4_peers); + } +#endif // TORRENT_DISABLE_ENCRYPTION + } + + boost::shared_ptr me(self()); + + INVARIANT_CHECK; + + if (m_channel_state[upload_channel] & peer_info::bw_disk) + { + m_ses.inc_stats_counter(counters::num_peers_up_disk, -1); + m_channel_state[upload_channel] &= ~peer_info::bw_disk; + } + if (m_channel_state[download_channel] & peer_info::bw_disk) + { + m_ses.inc_stats_counter(counters::num_peers_down_disk, -1); + m_channel_state[download_channel] &= ~peer_info::bw_disk; + } + + boost::shared_ptr t = m_torrent.lock(); + if (m_connecting) + { + m_ses.inc_stats_counter(counters::num_peers_half_open, -1); + if (t) t->dec_num_connecting(); + m_connecting = false; + } + if (m_connection_ticket >= 0) + { + if (m_ses.half_open_done(m_connection_ticket)) + m_connection_ticket = -1; + } + + torrent_handle handle; + if (t) handle = t->get_handle(); + #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) @@ -3521,133 +4126,36 @@ namespace libtorrent (*i)->on_disconnect(ec); } #endif - // for incoming connections, we get invalid argument errors - // when asking for the remote endpoint and the socket already - // closed, which is an edge case, but possible to happen when - // a peer makes a TCP and uTP connection in parallel. - // for outgoing connections however, why would we get this? - TORRENT_ASSERT(ec != error::invalid_argument || !m_outgoing); - -#ifdef TORRENT_STATS - ++m_ses.m_disconnected_peers; - if (error == 2) ++m_ses.m_error_peers; - if (ec == error::connection_reset) ++m_ses.m_connreset_peers; - else if (ec == error::eof) ++m_ses.m_eof_peers; - else if (ec == error::connection_refused) ++m_ses.m_connrefused_peers; - else if (ec == error::connection_aborted) ++m_ses.m_connaborted_peers; - else if (ec == error::no_permission) ++m_ses.m_perm_peers; - else if (ec == error::no_buffer_space) ++m_ses.m_buffer_peers; - else if (ec == error::host_unreachable) ++m_ses.m_unreachable_peers; - else if (ec == error::broken_pipe) ++m_ses.m_broken_pipe_peers; - else if (ec == error::address_in_use) ++m_ses.m_addrinuse_peers; - else if (ec == error::access_denied) ++m_ses.m_no_access_peers; - else if (ec == error::invalid_argument) ++m_ses.m_invalid_arg_peers; - else if (ec == error::operation_aborted) ++m_ses.m_aborted_peers; - else if (ec == error_code(errors::upload_upload_connection) - || ec == error_code(errors::uninteresting_upload_peer) - || ec == error_code(errors::torrent_aborted) - || ec == error_code(errors::self_connection) - || ec == error_code(errors::torrent_paused)) - ++m_ses.m_uninteresting_peers; - - if (ec == error_code(errors::timed_out) - || ec == error::timed_out) - ++m_ses.m_transport_timeout_peers; - - if (ec == error_code(errors::timed_out_inactivity) - || ec == error_code(errors::timed_out_no_request) - || ec == error_code(errors::timed_out_no_interest)) - ++m_ses.m_timeout_peers; - - if (ec == error_code(errors::no_memory)) - ++m_ses.m_no_memory_peers; - - if (ec == error_code(errors::too_many_connections)) - ++m_ses.m_too_many_peers; - - if (ec == error_code(errors::timed_out_no_handshake)) - ++m_ses.m_connect_timeouts; - - if (is_utp(*m_socket)) ++m_ses.m_error_utp_peers; - else ++m_ses.m_error_tcp_peers; - - if (m_outgoing) ++m_ses.m_error_outgoing_peers; - else ++m_ses.m_error_incoming_peers; - - -#ifndef TORRENT_DISABLE_ENCRYPTION - if (type() == bittorrent_connection) - { - bt_peer_connection* bt = static_cast(this); - if (bt->supports_encryption()) ++m_ses.m_error_encrypted_peers; - if (bt->rc4_encrypted() && bt->supports_encryption()) ++m_ses.m_error_rc4_peers; - } -#endif // TORRENT_DISABLE_ENCRYPTION -#endif - - // we cannot do this in a constructor - TORRENT_ASSERT(m_in_constructor == false); - if (error > 0) m_failed = true; - boost::intrusive_ptr me(this); - - INVARIANT_CHECK; - - if (m_channel_state[upload_channel] & peer_info::bw_disk) - { - m_ses.dec_disk_queue(upload_channel); - m_channel_state[upload_channel] &= ~peer_info::bw_disk; - } - if (m_channel_state[download_channel] & peer_info::bw_disk) - { - m_ses.dec_disk_queue(download_channel); - m_channel_state[download_channel] &= ~peer_info::bw_disk; - } - - boost::shared_ptr t = m_torrent.lock(); - if (m_connecting) - { - t->dec_num_connecting(); - m_connecting = false; - } - if (m_connection_ticket >= 0) - { - if (m_ses.m_half_open.done(m_connection_ticket)) - m_connection_ticket = -1; - } - - torrent_handle handle; - if (t) handle = t->get_handle(); if (ec == error::address_in_use - && m_ses.m_settings.outgoing_ports.first != 0) + && m_settings.get_int(settings_pack::outgoing_port) != 0 + && t) { - if (m_ses.m_alerts.should_post()) - m_ses.m_alerts.post_alert(performance_alert( + if (t->alerts().should_post()) + t->alerts().post_alert(performance_alert( handle, performance_alert::too_few_outgoing_ports)); } - if (ec) - { - if ((error > 1 || ec.category() == get_socks_category()) - && m_ses.m_alerts.should_post()) - { - m_ses.m_alerts.post_alert( - peer_error_alert(handle, remote(), pid(), ec)); - } - else if (error <= 1 && m_ses.m_alerts.should_post()) - { - m_ses.m_alerts.post_alert( - peer_disconnected_alert(handle, remote(), pid(), ec)); - } - } - if (t) { + if (ec) + { + if ((error > 1 || ec.category() == get_socks_category()) + && t->alerts().should_post()) + { + t->alerts().post_alert( + peer_error_alert(handle, remote(), pid(), op, ec)); + } + else if (error <= 1 && t->alerts().should_post()) + { + t->alerts().post_alert( + peer_disconnected_alert(handle, remote(), pid(), op, ec)); + } + } + // make sure we keep all the stats! if (!m_ignore_stats) { - t->add_stats(statistics()); - // report any partially received payload as redundant boost::optional pbp = downloading_piece_progress(); if (pbp @@ -3666,14 +4174,16 @@ namespace libtorrent { pending_block& qe = m_download_queue.back(); if (!qe.timed_out && !qe.not_wanted) - picker.abort_download(qe.block, peer_info_struct()); + picker.abort_download(qe.block, self_peer); m_outstanding_bytes -= t->to_req(qe.block).length; if (m_outstanding_bytes < 0) m_outstanding_bytes = 0; m_download_queue.pop_back(); } while (!m_request_queue.empty()) { - picker.abort_download(m_request_queue.back().block, peer_info_struct()); + pending_block& qe = m_request_queue.back(); + if (!qe.timed_out && !qe.not_wanted) + picker.abort_download(qe.block, self_peer); m_request_queue.pop_back(); } } @@ -3689,7 +4199,6 @@ namespace libtorrent check_invariant(); #endif t->remove_peer(this); - m_torrent.reset(); } else { @@ -3700,9 +4209,7 @@ namespace libtorrent #if defined TORRENT_DEBUG && defined TORRENT_EXPENSIVE_INVARIANT_CHECKS // since this connection doesn't have a torrent reference // no torrent should have a reference to this connection either - for (aux::session_impl::torrent_map::const_iterator i = m_ses.m_torrents.begin() - , end(m_ses.m_torrents.end()); i != end; ++i) - TORRENT_ASSERT(!i->second->has_peer(this)); + TORRENT_ASSERT(!m_ses.any_torrent_has_peer(this)); #endif m_disconnecting = true; @@ -3710,47 +4217,18 @@ namespace libtorrent async_shutdown(*m_socket, m_socket); - m_ses.close_connection(this, ec); - - // we should only disconnect while we still have - // at least one reference left to the connection - TORRENT_ASSERT(refcount() > 0); - } - - int peer_connection::get_upload_limit() const - { - return m_upload_limit; - } - - int peer_connection::get_download_limit() const - { - return m_download_limit; - } - - void peer_connection::set_upload_limit(int limit) - { - TORRENT_ASSERT(limit >= -1); - if (limit < 0) limit = 0; - if (limit < 10 && limit > 0) limit = 10; - m_upload_limit = limit; - m_bandwidth_channel[upload_channel].throttle(m_upload_limit); - } - - void peer_connection::set_download_limit(int limit) - { - TORRENT_ASSERT(limit >= -1); - if (limit < 0) limit = 0; - if (limit < 10 && limit > 0) limit = 10; - m_download_limit = limit; - m_bandwidth_channel[download_channel].throttle(m_download_limit); + m_ses.close_connection(this, ec, m_queued_for_connection); + m_queued_for_connection = false; } bool peer_connection::ignore_unchoke_slots() const { - return m_ignore_unchoke_slots - || (m_ses.settings().ignore_limits_on_local_network - && on_local_network() - && m_ses.m_local_upload_channel.throttle() == 0); + if (num_classes() == 0) return true; + + if (m_ses.ignore_unchoke_slots_set(*this)) return true; + boost::shared_ptr t = m_torrent.lock(); + if (t && m_ses.ignore_unchoke_slots_set(*t)) return true; + return false; } // defined in upnp.cpp @@ -3779,11 +4257,13 @@ namespace libtorrent p.pid = pid(); p.ip = remote(); p.pending_disk_bytes = m_outstanding_writing_bytes; + p.pending_disk_read_bytes = m_reading_bytes; p.send_quota = m_quota[upload_channel]; p.receive_quota = m_quota[download_channel]; p.num_pieces = m_num_pieces; if (m_download_queue.empty()) p.request_timeout = -1; - else p.request_timeout = int(total_seconds(m_requested - now) + m_ses.settings().request_timeout + else p.request_timeout = int(total_seconds(m_requested - now) + + m_settings.get_int(settings_pack::request_timeout) + m_timeout_extend); #ifndef TORRENT_DISABLE_GEO_IP p.inet_as_name = m_inet_as_name; @@ -3801,16 +4281,11 @@ namespace libtorrent p.total_download = statistics().total_payload_download(); p.total_upload = statistics().total_payload_upload(); - - if (m_bandwidth_channel[upload_channel].throttle() == 0) - p.upload_limit = -1; - else - p.upload_limit = m_bandwidth_channel[upload_channel].throttle(); - - if (m_bandwidth_channel[download_channel].throttle() == 0) - p.download_limit = -1; - else - p.download_limit = m_bandwidth_channel[download_channel].throttle(); +#ifndef TORRENT_NO_DEPRECATE + p.upload_limit = -1; + p.download_limit = -1; + p.load_balancing = 0; +#endif p.download_queue_length = int(download_queue().size() + m_request_queue.size()); p.requests_in_buffer = int(m_requests_in_buffer.size() + m_request_queue.size()); @@ -3855,7 +4330,7 @@ namespace libtorrent p.flags |= m_holepunch_mode ? peer_info::holepunched : 0; if (peer_info_struct()) { - policy::peer* pi = peer_info_struct(); + torrent_peer* pi = peer_info_struct(); TORRENT_ASSERT(pi->in_use); p.source = pi->source; p.failcount = pi->failcount; @@ -3915,17 +4390,18 @@ namespace libtorrent bool peer_connection::allocate_disk_receive_buffer(int disk_buffer_size) { INVARIANT_CHECK; - + TORRENT_ASSERT(m_packet_size > 0); TORRENT_ASSERT(m_recv_pos <= m_packet_size - disk_buffer_size); TORRENT_ASSERT(!m_disk_recv_buffer); + TORRENT_ASSERT(m_disk_recv_buffer_size == 0); TORRENT_ASSERT(disk_buffer_size <= 16 * 1024); if (disk_buffer_size == 0) return true; if (disk_buffer_size > 16 * 1024) { - disconnect(errors::invalid_piece_size, 2); + disconnect(errors::invalid_piece_size, op_bittorrent, 2); return false; } @@ -3933,24 +4409,43 @@ namespace libtorrent m_disk_recv_buffer.reset(); // then allocate a new one - m_disk_recv_buffer.reset(m_ses.allocate_disk_buffer("receive buffer")); + bool exceeded = false; + m_disk_recv_buffer.reset(m_allocator.allocate_disk_buffer(exceeded, self(), "receive buffer")); + if (!m_disk_recv_buffer) { - disconnect(errors::no_memory); + disconnect(errors::no_memory, op_alloc_recvbuf); return false; } + + if (exceeded) + { +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("*** exceeded disk buffer watermark"); +#endif + if ((m_channel_state[download_channel] & peer_info::bw_disk) == 0) + m_ses.inc_stats_counter(counters::num_peers_down_disk); + m_channel_state[download_channel] |= peer_info::bw_disk; + } + m_disk_recv_buffer_size = disk_buffer_size; return true; } char* peer_connection::release_disk_receive_buffer() { + if (!m_disk_recv_buffer) return 0; + + TORRENT_ASSERT(m_disk_recv_buffer_size <= m_recv_end); + TORRENT_ASSERT(m_recv_start <= m_recv_end - m_disk_recv_buffer_size); + m_recv_end -= m_disk_recv_buffer_size; m_disk_recv_buffer_size = 0; return m_disk_recv_buffer.release(); } // size = the packet size to remove from the receive buffer // packet_size = the next packet size to receive in the buffer + // offset = the offset into the receive buffer where to remove `size` bytes void peer_connection::cut_receive_buffer(int size, int packet_size, int offset) { INVARIANT_CHECK; @@ -3960,26 +4455,54 @@ namespace libtorrent TORRENT_ASSERT(int(m_recv_buffer.size()) >= m_recv_pos); TORRENT_ASSERT(m_recv_pos >= size + offset); TORRENT_ASSERT(offset >= 0); + TORRENT_ASSERT(m_recv_buffer.size() >= m_recv_end); + TORRENT_ASSERT(m_recv_start <= m_recv_end); TORRENT_ASSERT(size >= 0); - if (size > 0) + if (offset > 0) { - if (m_recv_pos - size - offset > 0) - std::memmove(&m_recv_buffer[0] + offset, &m_recv_buffer[0] + offset + size, m_recv_pos - size - offset); - m_recv_pos -= size; + TORRENT_ASSERT(m_recv_start - size <= m_recv_end); - // defensive. If this actually happens, we would have triggered - // an assert already (if asserts are enabled). - if (m_recv_pos < 0) m_recv_pos = 0; - } + if (size > 0) + std::memmove(&m_recv_buffer[0] + m_recv_start + offset + , &m_recv_buffer[0] + m_recv_start + offset + size + , m_recv_end - m_recv_start - size - offset); + + m_recv_pos -= size; + m_recv_end -= size; #ifdef TORRENT_DEBUG - std::fill(m_recv_buffer.begin() + m_recv_pos, m_recv_buffer.end(), 0); + std::fill(m_recv_buffer.begin() + m_recv_end, m_recv_buffer.end(), 0xcc); #endif + } + else + { + TORRENT_ASSERT(m_recv_start + size <= m_recv_end); + m_recv_start += size; + m_recv_pos -= size; + } m_packet_size = packet_size; } + // the purpose of this function is to free up and cut off all messages + // in the receive buffer that have been parsed and processed. + void peer_connection::normalize_receive_buffer() + { + TORRENT_ASSERT(m_recv_end >= m_recv_start); + if (m_recv_start == 0) return; + + if (m_recv_end > m_recv_start) + std::memmove(&m_recv_buffer[0], &m_recv_buffer[0] + m_recv_start, m_recv_end - m_recv_start); + + m_recv_end -= m_recv_start; + m_recv_start = 0; + +#ifdef TORRENT_DEBUG + std::fill(m_recv_buffer.begin() + m_recv_end, m_recv_buffer.end(), 0xcc); +#endif + } + void peer_connection::superseed_piece(int replace_piece, int new_piece) { if (new_piece == -1) @@ -3994,14 +4517,10 @@ namespace libtorrent boost::shared_ptr t = m_torrent.lock(); assert(t); - for (int i = 0; i < int(m_have_piece.size()); ++i) - { - if (m_have_piece[i] || !t->have_piece(i)) continue; -#ifdef TORRENT_VERBOSE_LOGGING - peer_log("==> HAVE [ piece: %d] (ending super seed)", i); -#endif - write_have(i); - } + // this will either send a full bitfield or + // a have-all message, effectively terminating + // super-seeding, since the peer may pick any piece + write_bitfield(); return; } @@ -4035,7 +4554,7 @@ namespace libtorrent int download_rate = statistics().download_payload_rate(); // calculate the desired download queue size - const int queue_time = m_ses.settings().request_queue_time; + const int queue_time = m_settings.get_int(settings_pack::request_queue_time); // (if the latency is more than this, the download will stall) // so, the queue size is queue_time * down_rate / 16 kiB // (16 kB is the size of each request) @@ -4058,7 +4577,7 @@ namespace libtorrent void peer_connection::second_tick(int tick_interval_ms) { ptime now = time_now(); - boost::intrusive_ptr me(self()); + boost::shared_ptr me(self()); // the invariant check must be run before me is destructed // in case the peer got disconnected @@ -4066,72 +4585,43 @@ namespace libtorrent boost::shared_ptr t = m_torrent.lock(); + int warning = 0; // drain the IP overhead from the bandwidth limiters - if (m_ses.m_settings.rate_limit_ip_overhead) + if (m_settings.get_bool(settings_pack::rate_limit_ip_overhead) && t) { - int download_overhead = m_statistics.download_ip_overhead(); - int upload_overhead = m_statistics.upload_ip_overhead(); - m_bandwidth_channel[download_channel].use_quota(download_overhead); - m_bandwidth_channel[upload_channel].use_quota(upload_overhead); + warning |= m_ses.use_quota_overhead(*this, m_statistics.download_ip_overhead() + , m_statistics.upload_ip_overhead()); + warning |= m_ses.use_quota_overhead(*t, m_statistics.download_ip_overhead() + , m_statistics.upload_ip_overhead()); + } - bandwidth_channel* upc = 0; - bandwidth_channel* downc = 0; - if (m_ignore_bandwidth_limits) + if (warning && t->alerts().should_post()) + { + for (int channel = 0; channel < 2; ++channel) { - upc = &m_ses.m_local_upload_channel; - downc = &m_ses.m_local_download_channel; + if ((warning & (1 << channel)) == 0) return; + t->alerts().post_alert(performance_alert(t->get_handle() + , channel == peer_connection::download_channel + ? performance_alert::download_limit_too_low + : performance_alert::upload_limit_too_low)); } - else - { - upc = &m_ses.m_upload_channel; - downc = &m_ses.m_download_channel; - } - - int up_limit = m_bandwidth_channel[upload_channel].throttle(); - int down_limit = m_bandwidth_channel[download_channel].throttle(); - - if (t) - { - if (!m_ignore_bandwidth_limits) - { - t->m_bandwidth_channel[download_channel].use_quota(download_overhead); - t->m_bandwidth_channel[upload_channel].use_quota(upload_overhead); - } - - if (down_limit > 0 - && download_overhead >= down_limit - && t->alerts().should_post()) - { - t->alerts().post_alert(performance_alert(t->get_handle() - , performance_alert::download_limit_too_low)); - } - - if (up_limit > 0 - && upload_overhead >= up_limit - && t->alerts().should_post()) - { - t->alerts().post_alert(performance_alert(t->get_handle() - , performance_alert::upload_limit_too_low)); - } - } - downc->use_quota(download_overhead); - upc->use_quota(upload_overhead); } if (!t || m_disconnecting) { if (m_connection_ticket != -1) { - if (m_ses.m_half_open.done(m_connection_ticket)) + if (m_ses.half_open_done(m_connection_ticket)) m_connection_ticket = -1; } TORRENT_ASSERT(t || !m_connecting); - if (m_connecting && t) + if (m_connecting) { - t->dec_num_connecting(); + m_ses.inc_stats_counter(counters::num_peers_half_open, -1); + if (t) t->dec_num_connecting(); m_connecting = false; } - disconnect(errors::torrent_aborted); + disconnect(errors::torrent_aborted, op_bittorrent); return; } @@ -4139,7 +4629,7 @@ namespace libtorrent && m_interesting && m_download_queue.empty() && m_request_queue.empty() - && total_seconds(now - m_last_request) >= 5) + && now - seconds(5) >= m_last_request) { // this happens when we're in strict end-game // mode and the peer could not request any blocks @@ -4148,11 +4638,9 @@ namespace libtorrent // might not be any unrequested blocks anymore, so // we should try to pick another block to see // if we can pick a busy one -#ifdef TORRENT_STATS - ++m_ses.m_end_game_piece_picks; -#endif m_last_request = now; - request_a_block(*t, *this); + if (request_a_block(*t, *this)) + m_ses.inc_stats_counter(counters::end_game_piece_picks); if (m_disconnecting) return; send_block_requests(); } @@ -4178,13 +4666,13 @@ namespace libtorrent // the peer and disconnect it bool may_timeout = (m_channel_state[download_channel] & peer_info::bw_network) != 0; - if (may_timeout && d > seconds(m_timeout) && !m_connecting + if (may_timeout && d > seconds(timeout()) && !m_connecting && m_reading_bytes == 0 && can_disconnect(error_code(errors::timed_out_inactivity, get_libtorrent_category()))) { #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING peer_log("*** LAST ACTIVITY [ %d seconds ago ] ***", int(total_seconds(d))); #endif - disconnect(errors::timed_out_inactivity); + disconnect(errors::timed_out_inactivity, op_bittorrent); return; } @@ -4192,17 +4680,17 @@ namespace libtorrent if (may_timeout && !m_connecting && in_handshake() - && d > seconds(m_ses.settings().handshake_timeout)) + && d > seconds(m_settings.get_int(settings_pack::handshake_timeout))) { #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING peer_log("*** NO HANDSHAKE [ waited %d seconds ] ***", int(total_seconds(d))); #endif - disconnect(errors::timed_out_no_handshake); + disconnect(errors::timed_out_no_handshake, op_bittorrent); return; } // disconnect peers that we unchoked, but - // they didn't send a request within 20 seconds. + // they didn't send a request within 60 seconds. // but only if we're a seed d = now - (std::max)(m_last_unchoke, m_last_incoming_request); if (may_timeout @@ -4212,13 +4700,13 @@ namespace libtorrent && !m_choked && m_peer_interested && t && t->is_upload_only() - && d > seconds(20) + && d > seconds(60) && can_disconnect(error_code(errors::timed_out_no_request, get_libtorrent_category()))) { #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING peer_log("*** NO REQUEST [ waited %d seconds ] ***", int(total_seconds(d))); #endif - disconnect(errors::timed_out_no_request); + disconnect(errors::timed_out_no_request, op_bittorrent); return; } @@ -4230,7 +4718,7 @@ namespace libtorrent d1 = now - m_became_uninterested; d2 = now - m_became_uninteresting; time_duration time_limit = seconds( - m_ses.settings().inactivity_timeout); + m_settings.get_int(settings_pack::inactivity_timeout)); // don't bother disconnect peers we haven't been interested // in (and that hasn't been interested in us) for a while @@ -4240,7 +4728,7 @@ namespace libtorrent && !m_peer_interested && d1 > time_limit && d2 > time_limit - && (m_ses.num_connections() >= m_ses.settings().connections_limit + && (m_ses.num_connections() >= m_settings.get_int(settings_pack::connections_limit) || (t && t->num_peers() >= t->max_connections())) && can_disconnect(error_code(errors::timed_out_no_interest, get_libtorrent_category()))) { @@ -4248,14 +4736,14 @@ namespace libtorrent peer_log("*** MUTUAL NO INTEREST [ t1: %d t2: %d ]" , total_seconds(d1), total_seconds(d2)); #endif - disconnect(errors::timed_out_no_interest); + disconnect(errors::timed_out_no_interest, op_bittorrent); return; } if (may_timeout && !m_download_queue.empty() && m_quota[download_channel] > 0 - && now > m_requested + seconds(m_ses.settings().request_timeout + && now > m_requested + seconds(m_settings.get_int(settings_pack::request_timeout) + m_timeout_extend)) { snub_peer(); @@ -4264,9 +4752,6 @@ namespace libtorrent // if we haven't sent something in too long, send a keep-alive keep_alive(); - m_ignore_bandwidth_limits = m_ses.settings().ignore_limits_on_local_network - && on_local_network(); - m_statistics.second_tick(tick_interval_ms); if (m_statistics.upload_payload_rate() > m_upload_rate_peak) @@ -4298,23 +4783,7 @@ namespace libtorrent , performance_alert::outstanding_request_limit_reached)); } - int piece_timeout = m_ses.settings().piece_timeout; - int rate_limit = INT_MAX; - if (m_bandwidth_channel[download_channel].throttle() > 0) - rate_limit = (std::min)(m_bandwidth_channel[download_channel].throttle(), rate_limit); - if (t->bandwidth_throttle(download_channel) > 0) - rate_limit = (std::min)(t->bandwidth_throttle(download_channel) / t->num_peers(), rate_limit); - if (m_ses.m_download_channel.throttle() > 0) - rate_limit = (std::min)(m_ses.m_download_channel.throttle() - / m_ses.num_connections(), rate_limit); - - // rate_limit is an approximation of what this connection is - // allowed to download. If it is impossible to beat the piece - // timeout at this rate, adjust it to be realistic - - const int block_size = t->block_size(); - int rate_limit_timeout = rate_limit / block_size; - if (piece_timeout < rate_limit_timeout) piece_timeout = rate_limit_timeout; + int piece_timeout = m_settings.get_int(settings_pack::piece_timeout); if (!m_download_queue.empty() && m_quota[download_channel] > 0 @@ -4362,9 +4831,9 @@ namespace libtorrent if (!m_snubbed) { m_snubbed = true; - if (m_ses.m_alerts.should_post()) + if (t->alerts().should_post()) { - m_ses.m_alerts.post_alert(peer_snubbed_alert(t->get_handle() + t->alerts().post_alert(peer_snubbed_alert(t->get_handle() , m_remote, m_peer_id)); } } @@ -4372,7 +4841,7 @@ namespace libtorrent if (on_parole()) { - m_timeout_extend += m_ses.settings().request_timeout; + m_timeout_extend += m_settings.get_int(settings_pack::request_timeout); return; } if (!t->has_picker()) return; @@ -4394,10 +4863,8 @@ namespace libtorrent // picking the same block again, stalling the // same piece indefinitely. m_desired_queue_size = 2; -#ifdef TORRENT_STATS - ++m_ses.m_snubbed_piece_picks; -#endif - request_a_block(*t, *this); + if (request_a_block(*t, *this)) + m_ses.inc_stats_counter(counters::snubbed_piece_picks); // the block we just picked (potentially) // hasn't been put in m_download_queue yet. @@ -4430,13 +4897,14 @@ namespace libtorrent - p.finished - p.writing - p.requested; if (free_blocks > 0) { - m_timeout_extend += m_ses.settings().request_timeout; + m_timeout_extend += m_settings.get_int(settings_pack::request_timeout); + send_block_requests(); return; } - if (m_ses.m_alerts.should_post()) + if (t->alerts().should_post()) { - m_ses.m_alerts.post_alert(block_timeout_alert(t->get_handle() + t->alerts().post_alert(block_timeout_alert(t->get_handle() , remote(), pid(), qe.block.block_index, qe.block.piece_index)); } qe.timed_out = true; @@ -4446,11 +4914,10 @@ namespace libtorrent send_block_requests(); } - std::pair peer_connection::preferred_caching() const + int peer_connection::preferred_caching() const { int line_size = 0; - int expiry = 0; - if (m_ses.m_settings.guided_read_cache) + if (m_settings.get_bool(settings_pack::guided_read_cache)) { boost::shared_ptr t = m_torrent.lock(); int upload_rate = m_statistics.upload_payload_rate(); @@ -4461,19 +4928,14 @@ namespace libtorrent // assume half of the cache is write cache if we're downloading // this torrent as well - int cache_size = m_ses.m_settings.cache_size / num_uploads; + int cache_size = m_settings.get_int(settings_pack::cache_size) / num_uploads; if (!t->is_upload_only()) cache_size /= 2; // cache_size is the amount of cache we have per peer. The // cache line should not be greater than this - // try to avoid locking caches for more than a couple of seconds - expiry = cache_size * 16 * 1024 / upload_rate; - if (expiry < 1) expiry = 1; - else if (expiry > 10) expiry = 10; - line_size = cache_size; } - return std::make_pair(line_size, expiry); + return line_size; } void peer_connection::fill_send_buffer() @@ -4492,114 +4954,263 @@ namespace libtorrent boost::uint64_t upload_rate = int(m_statistics.upload_rate()); int buffer_size_watermark = int(upload_rate - * m_ses.settings().send_buffer_watermark_factor / 100); + * m_settings.get_int(settings_pack::send_buffer_watermark_factor) / 100); - if (buffer_size_watermark < m_ses.settings().send_buffer_low_watermark) + if (buffer_size_watermark < m_settings.get_int(settings_pack::send_buffer_low_watermark)) { - buffer_size_watermark = m_ses.settings().send_buffer_low_watermark; + buffer_size_watermark = m_settings.get_int(settings_pack::send_buffer_low_watermark); } - else if (buffer_size_watermark > m_ses.settings().send_buffer_watermark) + else if (buffer_size_watermark > m_settings.get_int(settings_pack::send_buffer_watermark)) { - buffer_size_watermark = m_ses.settings().send_buffer_watermark; + buffer_size_watermark = m_settings.get_int(settings_pack::send_buffer_watermark); } - while (!m_requests.empty() - && (send_buffer_size() + m_reading_bytes < buffer_size_watermark)) + // don't just pop the front element here, since in seed mode one request may + // be blocked because we have to verify the hash first, so keep going with the + // next request. However, only let each peer have one hash verification outstanding + // at any given time + for (int i = 0; i < m_requests.size() + && (send_buffer_size() + m_reading_bytes < buffer_size_watermark); ++i) { TORRENT_ASSERT(t->ready_for_connections()); - peer_request& r = m_requests.front(); + peer_request& r = m_requests[i]; TORRENT_ASSERT(r.piece >= 0); TORRENT_ASSERT(r.piece < (int)m_have_piece.size()); - TORRENT_ASSERT(t->have_piece(r.piece)); +// TORRENT_ASSERT(t->have_piece(r.piece)); TORRENT_ASSERT(r.start + r.length <= t->torrent_file().piece_size(r.piece)); TORRENT_ASSERT(r.length > 0 && r.start >= 0); - std::pair cache = preferred_caching(); - - if (!t->seed_mode() || t->verified_piece(r.piece)) + if (t->is_deleted()) { - t->filesystem().async_read(r, boost::bind(&peer_connection::on_disk_read_complete - , self(), _1, _2, r), cache.first, cache.second); +#if defined TORRENT_VERBOSE_LOGGING + peer_log("==> REJECT_PIECE [ piece: %d s: %x l: %x ] torrent deleted" + , r.piece , r.start , r.length); +#endif + write_reject_request(r); + continue; + } + + if (t->seed_mode() && !t->verified_piece(r.piece)) + { + // we're still verifying the hash of this piece + // so we can't return it yet. + if (t->verifying_piece(r.piece)) continue; + + // only have three outstanding hash check per peer + if (m_outstanding_piece_verification >= 3) continue; + + ++m_outstanding_piece_verification; + +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("*** SEED-MODE FILE ASYNC HASH [ piece: %d ]", r.piece); +#endif + // this means we're in seed mode and we haven't yet + // verified this piece (r.piece) + if (!t->need_loaded()) return; + t->inc_refcount("async_seed_hash"); + m_disk_thread.async_hash(&t->storage(), r.piece, 0 + , boost::bind(&peer_connection::on_seed_mode_hashed, self(), _1) + , this); + t->verifying(r.piece); + continue; + } + + // in seed mode, we might end up accepting a request + // which it later turns out we cannot serve, if we ended + // up not having that piece + if (!t->has_piece_passed(r.piece)) + { + // we don't have this piece yet, but we anticipate to have + // it very soon, so we have told our peers we have it. + // hold off on sending it. If the piece fails later + // we will reject this request + if (t->is_predictive_piece(r.piece)) continue; +#if defined TORRENT_VERBOSE_LOGGING + peer_log("==> REJECT_PIECE [ piece: %d s: %x l: %x ] piece not passed hash check" + , r.piece , r.start , r.length); +#endif + write_reject_request(r); } else { - // this means we're in seed mode and we haven't yet - // verified this piece (r.piece) - t->filesystem().async_read_and_hash(r, boost::bind(&peer_connection::on_disk_read_complete - , self(), _1, _2, r), cache.second); - t->verified(r.piece); +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("*** FILE ASYNC READ [ piece: %d | s: %x | l: %x ]" + , r.piece, r.start, r.length); +#endif + m_reading_bytes += r.length; + sent_a_piece = true; + + // the callback function may be called immediately, instead of being posted + if (!t->need_loaded()) return; + t->inc_refcount("async_read"); + m_disk_thread.async_read(&t->storage(), r + , boost::bind(&peer_connection::on_disk_read_complete + , self(), _1, r), this); } + m_requests.erase(m_requests.begin() + i); - m_reading_bytes += r.length; + if (m_requests.empty()) + m_ses.inc_stats_counter(counters::num_peers_up_requests, -1); - m_requests.erase(m_requests.begin()); - sent_a_piece = true; + --i; } if (t->share_mode() && sent_a_piece) t->recalc_share_mode(); } - void peer_connection::on_disk_read_complete(int ret, disk_io_job const& j, peer_request r) + // this is called when a previously unchecked piece has been + // checked, while in seed-mode + void peer_connection::on_seed_mode_hashed(disk_io_job const* j) { - // flush send buffer at the end of this scope - // TODO: peers should really be corked/uncorked outside of - // all completed disk operations - cork _c(*this); - -#ifdef TORRENT_STATS - ++m_ses.m_num_messages[aux::session_impl::on_disk_read_counter]; -#endif - TORRENT_ASSERT(m_ses.is_network_thread()); - - m_reading_bytes -= r.length; - - disk_buffer_holder buffer(m_ses, j.buffer); -#if TORRENT_DISK_STATS - if (j.buffer) m_ses.m_disk_thread.rename_buffer(j.buffer, "received send buffer"); -#endif + TORRENT_ASSERT(m_ses.is_single_thread()); + INVARIANT_CHECK; boost::shared_ptr t = m_torrent.lock(); + torrent_ref_holder h(t.get(), "async_seed_hash"); + if (t) t->dec_refcount("async_seed_hash"); + + TORRENT_ASSERT(m_outstanding_piece_verification > 0); + --m_outstanding_piece_verification; + + if (!t || t->is_aborted()) return; + + if (j->error) + { + t->handle_disk_error(j, this); + t->leave_seed_mode(false); + return; + } + + // we're using the piece hashes here, we need the torrent to be loaded + if (!t->need_loaded()) return; + + if (!m_settings.get_bool(settings_pack::disable_hash_checks) + && sha1_hash(j->d.piece_hash) != t->torrent_file().hash_for_piece(j->piece)) + { +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("*** SEED-MODE FILE HASH [ piece: %d failed ]", j->piece); +#endif + + t->leave_seed_mode(false); + } + else + { + TORRENT_ASSERT(t->verifying_piece(j->piece)); + if (t->seed_mode()) t->verified(j->piece); + +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("*** SEED-MODE FILE HASH [ piece: %d passed ]", j->piece); +#endif + if (t) + { + if (t->seed_mode() && t->all_verified()) + t->leave_seed_mode(true); + } + } + + // try to service the requests again, now that the piece + // has been verified + fill_send_buffer(); + } + + void peer_connection::on_disk_read_complete(disk_io_job const* j, peer_request r) + { + // return value: + // 0: success, piece passed hash check + // -1: disk failure + + TORRENT_ASSERT(m_ses.is_single_thread()); + +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("*** FILE ASYNC READ COMPLETE [ ret: %d | piece: %d | s: %x | l: %x" + " | b: %p | c: %s | e: %s ]" + , j->ret, r.piece, r.start, r.length, j->buffer + , (j->flags & disk_io_job::cache_hit ? "cache hit" : "cache miss") + , j->error.ec.message().c_str()); +#endif + m_reading_bytes -= r.length; + + boost::shared_ptr t = m_torrent.lock(); + torrent_ref_holder h(t.get(), "async_read"); + if (t) t->dec_refcount("async_read"); + + if (j->ret < 0) + { + if (!t) + { + disconnect(j->error.ec, op_file_read); + return; + } + + TORRENT_ASSERT(j->buffer == 0); + write_dont_have(r.piece); + write_reject_request(r); + if (t->alerts().should_post()) + t->alerts().post_alert(file_error_alert(j->error.ec + , t->resolve_filename(j->error.file) + , j->error.operation_str(), t->get_handle())); + + ++m_disk_read_failures; + if (m_disk_read_failures > 100) disconnect(j->error.ec, op_file_read); + return; + } + + // we're only interested in failures in a row. + // if we every now and then successfully send a + // block, the peer is still useful + m_disk_read_failures = 0; + + TORRENT_ASSERT(j->ret == r.length); + + // even if we're disconnecting, we need to free this block + // otherwise the disk thread will hang, waiting for the network + // thread to be done with it + disk_buffer_holder buffer(m_allocator, *j); + + if (m_disconnecting) return; + + // flush send buffer at the end of + // this burst of disk events +// m_ses.cork_burst(this); + +#if TORRENT_BUFFER_STATS + if (j->buffer && j->d.io.ref.storage == 0) + m_disk_thread.rename_buffer(j->buffer, "received send buffer"); +#endif + if (!t) { - disconnect(j.error); + disconnect(j->error.ec, op_file_read); return; } - if (ret != r.length) + if (j->ret != r.length) { - if (ret == -3) - { -#if defined TORRENT_VERBOSE_LOGGING - peer_log("==> REJECT_PIECE [ piece: %d s: %d l: %d ]" - , r.piece , r.start , r.length); -#endif - write_reject_request(r); - if (t->seed_mode()) t->leave_seed_mode(false); - } - else - { - // handle_disk_error may disconnect us - t->handle_disk_error(j, this); - } + // handle_disk_error may disconnect us + t->handle_disk_error(j, this); return; } - if (t) - { - if (t->seed_mode() && t->all_verified()) - t->leave_seed_mode(true); - } - #if defined TORRENT_VERBOSE_LOGGING - peer_log("==> PIECE [ piece: %d s: %d l: %d ]" + peer_log("==> PIECE [ piece: %d s: %x l: %x ]" , r.piece, r.start, r.length); #endif -#if TORRENT_DISK_STATS - if (j.buffer) m_ses.m_disk_thread.rename_buffer(j.buffer, "dispatched send buffer"); +#if TORRENT_BUFFER_STATS + if (j->buffer && j->d.io.ref.storage == 0) + m_disk_thread.rename_buffer(j->buffer, "dispatched send buffer"); #endif + + // we probably just pulled this piece into the cache. + // if it's rare enough to make it into the suggested piece + // push another piece out + if (m_settings.get_int(settings_pack::suggest_mode) == settings_pack::suggest_read_cache + && (j->flags & disk_io_job::cache_hit) == 0) + { + t->add_suggest_piece(r.piece); + } write_piece(r, buffer); } @@ -4630,143 +5241,96 @@ namespace libtorrent } } - int peer_connection::request_upload_bandwidth( - bandwidth_channel* bwc1 - , bandwidth_channel* bwc2 - , bandwidth_channel* bwc3 - , bandwidth_channel* bwc4) + // the number of bytes we expect to receive, or want to send + // channel either refer to upload or download. This is used + // by the rate limiter to allocate quota for this peer + int peer_connection::wanted_transfer(int channel) { - // we can only have one outstanding bandwidth request at a time - if (m_channel_state[upload_channel] & peer_info::bw_limit) return 0; - - int bytes = (std::max)(m_send_buffer.size() - , int(boost::int64_t(m_statistics.upload_rate()) * 2 - * m_ses.m_settings.tick_interval / 1000)); - - // we already have quota for the bytes we want to send - if (m_quota[upload_channel] >= bytes) return 0; - - // deduct the bytes we already have quota for - bytes -= m_quota[upload_channel]; - shared_ptr t = m_torrent.lock(); - int priority; - if (t && m_ses.m_settings.choking_algorithm == session_settings::bittyrant_choker - && !t->upload_mode() && !t->is_upload_only()) - { - // when we use the bittyrant choker, the priority of a peer - // is decided based on the estimated reciprocation rate and - // the share it represents of the total upload rate capacity - // the torrent priority is taken into account when unchoking peers - int upload_capacity = m_ses.settings().upload_rate_limit; - if (upload_capacity == 0) - { - // we don't know at what rate we can upload. If we have a - // measurement of the peak, use that + 10kB/s, otherwise - // assume 20 kB/s - upload_capacity = (std::max)(20000, m_ses.m_peak_up_rate + 10000); - } - int estimated_reciprocation_rate = m_est_reciprocation_rate; - // we cannot send faster than our upload rate anyway - if (estimated_reciprocation_rate < upload_capacity) - estimated_reciprocation_rate = upload_capacity; - priority = (boost::uint64_t(estimated_reciprocation_rate) << 14) / upload_capacity; - if (priority > 0xffff) priority = 0xffff; + if (channel == download_channel) + { + return (std::max)((std::max)(m_outstanding_bytes + , m_packet_size - m_recv_pos) + 30 + , int(boost::int64_t(m_statistics.download_rate()) * 2 + / (1000 / m_settings.get_int(settings_pack::tick_interval)))); } else { - priority = 1 + is_interesting() * 2 + m_requests_in_buffer.size(); - if (priority > 255) priority = 255; - priority += t ? t->priority() << 8 : 0; + return (std::max)((std::max)(m_reading_bytes + , m_send_buffer.size()) + , int((boost::int64_t(m_statistics.upload_rate()) * 2 + * m_settings.get_int(settings_pack::tick_interval)) / 1000)); } - TORRENT_ASSERT(priority <= 0xffff); - - // peers that we are not interested in are non-prioritized -#ifdef TORRENT_VERBOSE_LOGGING - peer_log(">>> REQUEST_BANDWIDTH [ upload: %d prio: %d " - "channels: %p %p %p %p limits: %d %d %d %d ignore: %d ]" - , int(m_send_buffer.size()), priority - , bwc1, bwc2, bwc3, bwc4 - , (bwc1?bwc1->throttle():0) - , (bwc2?bwc2->throttle():0) - , (bwc3?bwc3->throttle():0) - , (bwc4?bwc4->throttle():0) - , m_ignore_bandwidth_limits); -#endif - - int ret = m_ses.m_upload_rate.request_bandwidth(self() - , bytes - , priority - , bwc1, bwc2, bwc3, bwc4); - - if (ret == 0) - { - m_channel_state[upload_channel] |= peer_info::bw_limit; - } - else - { - m_quota[upload_channel] += ret; -#ifdef TORRENT_VERBOSE_LOGGING - peer_log(">>> ASSIGN BANDWIDTH [ bytes: %d ]", ret); -#endif - } - return ret; } - int peer_connection::request_download_bandwidth( - bandwidth_channel* bwc1 - , bandwidth_channel* bwc2 - , bandwidth_channel* bwc3 - , bandwidth_channel* bwc4) + int peer_connection::request_bandwidth(int channel, int bytes) { INVARIANT_CHECK; // we can only have one outstanding bandwidth request at a time - if (m_channel_state[download_channel] & peer_info::bw_limit) return 0; + if (m_channel_state[channel] & peer_info::bw_limit) return 0; - int bytes = (std::max)((std::max)(m_outstanding_bytes, m_packet_size - m_recv_pos) + 30 - , int(boost::int64_t(m_statistics.download_rate()) * 2 - * m_ses.m_settings.tick_interval / 1000)); - - // we already have enough quota - if (m_quota[download_channel] >= bytes) return 0; - - // deduct the bytes we already have quota for - bytes -= m_quota[download_channel]; - shared_ptr t = m_torrent.lock(); -#ifdef TORRENT_VERBOSE_LOGGING - peer_log("<<< REQUEST_BANDWIDTH [ download: %d prio: %d " - "channels: %p %p %p %p limits: %d %d %d %d ignore: %d ]" - , int(m_download_queue.size() * 16 * 1024 + 30), m_priority - , bwc1, bwc2, bwc3, bwc4 - , (bwc1?bwc1->throttle():0) - , (bwc2?bwc2->throttle():0) - , (bwc3?bwc3->throttle():0) - , (bwc4?bwc4->throttle():0) - , m_ignore_bandwidth_limits); + bytes = (std::max)(wanted_transfer(channel), bytes); + + // we already have enough quota + if (m_quota[channel] >= bytes) return 0; + + // deduct the bytes we already have quota for + bytes -= m_quota[channel]; + + int priority = get_priority(channel); + + int max_channels = num_classes() + (t ? t->num_classes() : 0) + 2; + bandwidth_channel** channels = TORRENT_ALLOCA(bandwidth_channel*, max_channels); + + // collect the pointers to all bandwidth channels + // that apply to this torrent + int c = 0; + + c += m_ses.copy_pertinent_channels(*this, channel + , channels + c, max_channels - c); + if (t) + { + c += m_ses.copy_pertinent_channels(*t, channel + , channels + c, max_channels - c); + } + +#ifdef TORRENT_DEBUG + // make sure we don't have duplicates + std::set unique_classes; + for (int i = 0; i < c; ++i) + { + TORRENT_ASSERT(unique_classes.count(channels[i]) == 0); + unique_classes.insert(channels[i]); + } #endif - TORRENT_ASSERT(m_priority <= 255); - int priority = m_priority + (t ? (t->priority() << 8) : 0); +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("%s REQUEST_BANDWIDTH [ bytes: %d quota: %d wanted_transfer: %d " + "prio: %d num_channels: %d ]" + , channel == download_channel ? "<<<" : ">>>", bytes + , m_quota[channel], wanted_transfer(channel), priority, c); +#endif - TORRENT_ASSERT(m_outstanding_bytes >= 0); - TORRENT_ASSERT((m_channel_state[download_channel] & peer_info::bw_limit) == 0); + TORRENT_ASSERT((m_channel_state[channel] & peer_info::bw_limit) == 0); - int ret = m_ses.m_download_rate.request_bandwidth(self() - , bytes, priority, bwc1, bwc2, bwc3, bwc4); - if (ret == 0) - { - m_channel_state[download_channel] |= peer_info::bw_limit; - } + bandwidth_manager* manager = m_ses.get_bandwidth_manager(channel); + + int ret = manager->request_bandwidth(self() + , bytes, priority, channels, c); + + if (ret == 0) m_channel_state[channel] |= peer_info::bw_limit; else { #ifdef TORRENT_VERBOSE_LOGGING - peer_log("<<< ASSIGN BANDWIDTH [ bytes: %d ]", ret); + peer_log("%s ASSIGN BANDWIDTH [ bytes: %d ]" + , channel == download_channel ? "<<<" : ">>>", ret); #endif - m_quota[download_channel] += ret; + m_quota[channel] += ret; } + return ret; } @@ -4781,33 +5345,8 @@ namespace libtorrent { if (m_disconnecting) return; - shared_ptr t = m_torrent.lock(); - // we may want to request more quota at this point - bool utp = m_socket->get() != 0; - bool ignore_limits = m_ignore_bandwidth_limits - || (!m_ses.m_settings.rate_limit_utp && utp); - if (!ignore_limits) - { - // in this case, we have data to send, but no - // bandwidth. So, we simply request bandwidth - // from the bandwidth manager - request_upload_bandwidth( - &m_ses.m_upload_channel - , t ? &t->m_bandwidth_channel[upload_channel] : 0 - , &m_bandwidth_channel[upload_channel] - , !utp ? &m_ses.m_tcp_upload_channel : 0); - } - else - { - // in this case, we're a local peer, and the settings - // are set to ignore rate limits for local peers. So, - // instead we rate limit ourself against the special - // global bandwidth channel for local peers, which defaults - // to unthrottled - request_upload_bandwidth(&m_ses.m_local_upload_channel - , &m_bandwidth_channel[upload_channel]); - } + request_bandwidth(upload_channel); if (m_channel_state[upload_channel] & peer_info::bw_network) return; @@ -4825,13 +5364,18 @@ namespace libtorrent && quota_left > 0) { if ((m_channel_state[upload_channel] & peer_info::bw_disk) == 0) - m_ses.inc_disk_queue(upload_channel); + m_ses.inc_stats_counter(counters::num_peers_up_disk); m_channel_state[upload_channel] |= peer_info::bw_disk; +#ifdef TORRENT_VERBOSE_LOGGING + peer_log(">>> waiting for disk [outstanding: %d]", m_reading_bytes); +#endif if (!m_connecting && !m_requests.empty() - && m_reading_bytes > m_ses.settings().send_buffer_watermark - 0x4000) + && m_reading_bytes > m_settings.get_int(settings_pack::send_buffer_watermark) - 0x4000) { + boost::shared_ptr t = m_torrent.lock(); + // we're stalled on the disk. We want to write and we can write // but our send buffer is empty, waiting to be refilled from the disk // this either means the disk is slower than the network connection @@ -4842,9 +5386,9 @@ namespace libtorrent // upload rate being virtually 0. If m_requests is empty, it doesn't // matter anyway, because we don't have any more requests from the // peer to hang on to the disk - if (m_ses.m_alerts.should_post()) + if (t && t->alerts().should_post()) { - m_ses.m_alerts.post_alert(performance_alert(t->get_handle() + t->alerts().post_alert(performance_alert(t->get_handle() , performance_alert::send_buffer_watermark_too_low)); } } @@ -4852,7 +5396,7 @@ namespace libtorrent else { if (m_channel_state[upload_channel] & peer_info::bw_disk) - m_ses.dec_disk_queue(upload_channel); + m_ses.inc_stats_counter(counters::num_peers_up_disk, -1); m_channel_state[upload_channel] &= ~peer_info::bw_disk; } @@ -4862,16 +5406,16 @@ namespace libtorrent if (m_send_buffer.empty()) { peer_log(">>> SEND BUFFER DEPLETED [" - " quota: %d ignore: %s buf: %d connecting: %s disconnecting: %s pending_disk: %d ]" - , m_quota[upload_channel], m_ignore_bandwidth_limits?"yes":"no" + " quota: %d buf: %d connecting: %s disconnecting: %s pending_disk: %d ]" + , m_quota[upload_channel] , int(m_send_buffer.size()), m_connecting?"yes":"no" , m_disconnecting?"yes":"no", m_reading_bytes); } else { peer_log(">>> CANNOT WRITE [" - " quota: %d ignore: %s buf: %d connecting: %s disconnecting: %s pending_disk: %d ]" - , m_quota[upload_channel], m_ignore_bandwidth_limits?"yes":"no" + " quota: %d buf: %d connecting: %s disconnecting: %s pending_disk: %d ]" + , m_quota[upload_channel] , int(m_send_buffer.size()), m_connecting?"yes":"no" , m_disconnecting?"yes":"no", m_reading_bytes); } @@ -4891,20 +5435,37 @@ namespace libtorrent #ifdef TORRENT_VERBOSE_LOGGING peer_log(">>> CORKED WRITE [ bytes: %d ]", amount_to_send); #endif - return; + return; } TORRENT_ASSERT((m_channel_state[upload_channel] & peer_info::bw_network) == 0); #ifdef TORRENT_VERBOSE_LOGGING peer_log(">>> ASYNC_WRITE [ bytes: %d ]", amount_to_send); #endif - std::list const& vec = m_send_buffer.build_iovec(amount_to_send); + std::vector const& vec = m_send_buffer.build_iovec(amount_to_send); #if defined TORRENT_ASIO_DEBUGGING add_outstanding_async("peer_connection::on_send_data"); #endif - m_socket->async_write_some( - vec, make_write_handler(boost::bind( + +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(!m_socket_is_writing); + m_socket_is_writing = true; +#endif + + // uTP sockets aren't thread safe... + if (is_utp(*m_socket)) + { + m_socket->async_write_some(vec, make_write_handler(boost::bind( &peer_connection::on_send_data, self(), _1, _2))); + } + else + { + socket_job j; + j.type = socket_job::write_job; + j.vec = &vec; + j.peer = self(); + m_ses.post_socket_job(j); + } m_channel_state[upload_channel] |= peer_info::bw_network; } @@ -4912,46 +5473,40 @@ namespace libtorrent void peer_connection::on_disk() { if ((m_channel_state[download_channel] & peer_info::bw_disk) == 0) return; - boost::intrusive_ptr me(this); + boost::shared_ptr me(self()); - m_ses.dec_disk_queue(download_channel); +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("*** dropped below disk buffer watermark"); +#endif + m_ses.inc_stats_counter(counters::num_peers_down_disk, -1); m_channel_state[download_channel] &= ~peer_info::bw_disk; setup_receive(read_async); } + void peer_connection::on_allocate_disk_buffer(char* buffer, int buffer_size) + { + INVARIANT_CHECK; + + TORRENT_ASSERT(!m_disk_recv_buffer); + TORRENT_ASSERT(m_channel_state[download_channel] & peer_info::bw_disk); + + m_disk_recv_buffer.reset(buffer); + m_disk_recv_buffer_size = buffer_size; + + m_ses.inc_stats_counter(counters::num_peers_down_disk, -1); + m_channel_state[download_channel] &= ~peer_info::bw_disk; + + setup_receive(read_async); + } + void peer_connection::setup_receive(sync_t sync) { INVARIANT_CHECK; if (m_disconnecting) return; - shared_ptr t = m_torrent.lock(); - // we may want to request more quota at this point - bool utp = m_socket->get() != 0; - bool ignore_limits = m_ignore_bandwidth_limits - || (!m_ses.m_settings.rate_limit_utp && utp); - if (!ignore_limits) - { - // in this case, we have outstanding data to - // receive, but no bandwidth quota. So, we simply - // request bandwidth from the bandwidth manager - request_download_bandwidth( - &m_ses.m_download_channel - , t ? &t->m_bandwidth_channel[download_channel] : 0 - , &m_bandwidth_channel[download_channel] - , !utp ? &m_ses.m_tcp_download_channel : 0); - } - else - { - // in this case, we're a local peer, and the settings - // are set to ignore rate limits for local peers. So, - // instead we rate limit ourself against the special - // global bandwidth channel for local peers, which defaults - // to unthrottled - request_download_bandwidth(&m_ses.m_local_download_channel - , &m_bandwidth_channel[download_channel]); - } + request_bandwidth(download_channel); if (m_channel_state[download_channel] & peer_info::bw_network) return; @@ -4961,15 +5516,14 @@ namespace libtorrent return; } - if (!can_read(&m_channel_state[download_channel])) + if (!can_read()) { #ifdef TORRENT_VERBOSE_LOGGING - peer_log("<<< CANNOT READ [ quota: %d ignore: %s " + peer_log("<<< CANNOT READ [ quota: %d " "can-write-to-disk: %s queue-limit: %d disconnecting: %s ]" , m_quota[download_channel] - , (m_ignore_bandwidth_limits?"yes":"no") - , (m_ses.can_write_to_disk()?"yes":"no") - , m_ses.settings().max_queued_disk_bytes + , ((m_channel_state[download_channel] & peer_info::bw_disk)?"no":"yes") + , m_settings.get_int(settings_pack::max_queued_disk_bytes) , (m_disconnecting?"yes":"no")); #endif // if we block reading, waiting for the disk, we will wake up @@ -4978,13 +5532,55 @@ namespace libtorrent return; } error_code ec; + try_read(read_async, ec); } size_t peer_connection::try_read(sync_t s, error_code& ec) { - TORRENT_ASSERT(m_packet_size > 0); + if (m_quota[download_channel] == 0) + { + ec = asio::error::would_block; + return 0; + } + + if (!can_read()) + { + ec = asio::error::would_block; + return 0; + } + int max_receive = m_packet_size - m_recv_pos; + + boost::array vec; + int num_bufs = 0; + // only apply the contiguous receive buffer when we don't have any + // outstanding requests. When we're likely to receive pieces, we'll + // save more time from avoiding copying data from the socket + if ((m_settings.get_bool(settings_pack::contiguous_recv_buffer) + || m_download_queue.empty()) && !m_disk_recv_buffer) + { + if (s == read_sync) + { + ec = asio::error::would_block; + return 0; + } + + TORRENT_ASSERT((m_channel_state[download_channel] & peer_info::bw_network) == 0); + m_channel_state[download_channel] |= peer_info::bw_network; +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("<<< ASYNC_READ [ ]"); +#endif + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("peer_connection::on_receive_data_nb"); +#endif + m_socket->async_read_some(asio::null_buffers(), make_read_handler( + boost::bind(&peer_connection::on_receive_data_nb, self(), _1, _2))); + return 0; + } + + TORRENT_ASSERT(m_packet_size > 0); TORRENT_ASSERT(max_receive >= 0); if (m_recv_pos >= m_soft_packet_size) m_soft_packet_size = 0; @@ -5003,19 +5599,11 @@ namespace libtorrent TORRENT_ASSERT(m_recv_pos >= 0); TORRENT_ASSERT(m_packet_size > 0); - if (!can_read()) - { - ec = asio::error::would_block; - return 0; - } - int regular_buffer_size = m_packet_size - m_disk_recv_buffer_size; if (int(m_recv_buffer.size()) < regular_buffer_size) m_recv_buffer.resize(round_up8(regular_buffer_size)); - boost::array vec; - int num_bufs = 0; if (!m_disk_recv_buffer || regular_buffer_size >= m_recv_pos + max_receive) { // only receive into regular buffer @@ -5054,20 +5642,40 @@ namespace libtorrent peer_log("<<< ASYNC_READ [ max: %d bytes ]", max_receive); #endif + // utp sockets aren't thread safe... #if defined TORRENT_ASIO_DEBUGGING add_outstanding_async("peer_connection::on_receive_data"); #endif - if (num_bufs == 1) + if (is_utp(*m_socket)) { - m_socket->async_read_some( - asio::mutable_buffers_1(vec[0]), make_read_handler( - boost::bind(&peer_connection::on_receive_data, self(), _1, _2))); + if (num_bufs == 1) + { + m_socket->async_read_some( + asio::mutable_buffers_1(vec[0]), make_read_handler( + boost::bind(&peer_connection::on_receive_data, self(), _1, _2))); + } + else + { + m_socket->async_read_some( + vec, make_read_handler( + boost::bind(&peer_connection::on_receive_data, self(), _1, _2))); + } } else { - m_socket->async_read_some( - vec, make_read_handler( - boost::bind(&peer_connection::on_receive_data, self(), _1, _2))); + socket_job j; + j.type = socket_job::read_job; + j.peer = self(); + if (num_bufs == 1) + { + j.recv_buf = asio::buffer_cast(vec[0]); + j.buf_size = asio::buffer_size(vec[0]); + } + else + { + j.read_vec = vec; + } + m_ses.post_socket_job(j); } return 0; } @@ -5081,6 +5689,8 @@ namespace libtorrent { ret = m_socket->read_some(vec, ec); } + // this is weird. You would imagine read_some() would do this + if (ret == 0 && !ec) ec = asio::error::eof; #ifdef TORRENT_VERBOSE_LOGGING peer_log("<<< SYNC_READ [ max: %d ret: %d e: %s ]", max_receive, ret, ec ? ec.message().c_str() : ""); @@ -5100,8 +5710,8 @@ namespace libtorrent TORRENT_ASSERT(regular_buffer_size >= 0); if (!m_disk_recv_buffer || regular_buffer_size >= m_recv_pos) { - vec.first = buffer::interval(&m_recv_buffer[0] - + m_recv_pos - bytes, &m_recv_buffer[0] + m_recv_pos); + vec.first = buffer::interval(&m_recv_buffer[0] + m_recv_start + + m_recv_pos - bytes, &m_recv_buffer[0] + m_recv_start + m_recv_pos); vec.second = buffer::interval(0,0); } else if (m_recv_pos - bytes >= regular_buffer_size) @@ -5115,8 +5725,8 @@ namespace libtorrent { TORRENT_ASSERT(m_recv_pos - bytes < regular_buffer_size); TORRENT_ASSERT(m_recv_pos > regular_buffer_size); - vec.first = buffer::interval(&m_recv_buffer[0] + m_recv_pos - bytes - , &m_recv_buffer[0] + regular_buffer_size); + vec.first = buffer::interval(&m_recv_buffer[0] + m_recv_start + m_recv_pos - bytes + , &m_recv_buffer[0] + m_recv_start + regular_buffer_size); vec.second = buffer::interval(m_disk_recv_buffer.get() , m_disk_recv_buffer.get() + m_recv_pos - regular_buffer_size); } @@ -5127,27 +5737,55 @@ namespace libtorrent void peer_connection::reset_recv_buffer(int packet_size) { + TORRENT_ASSERT(m_recv_buffer.size() >= m_recv_end); TORRENT_ASSERT(packet_size > 0); - if (m_recv_pos > m_packet_size) + if (m_recv_end > m_packet_size) { cut_receive_buffer(m_packet_size, packet_size); return; } + m_recv_pos = 0; + m_recv_start = 0; + m_recv_end = 0; m_packet_size = packet_size; } - void nop(char*) {} - - void peer_connection::append_const_send_buffer(char const* buffer, int size) + void peer_connection::append_send_buffer(char* buffer, int size + , chained_buffer::free_buffer_fun destructor, void* userdata + , block_cache_reference ref, bool encrypted) { - m_send_buffer.append_buffer((char*)buffer, size, size, &nop); -#if defined TORRENT_STATS && defined TORRENT_DISK_STATS - m_ses.m_buffer_usage_logger << log_time() << " append_const_send_buffer: " << size << std::endl; +#if defined TORRENT_BUFFER_STATS + log_buffer_usage(buffer, size, "queued send buffer"); +#endif + // bittorrent connections should never use this function, since + // they might be encrypted and this would circumvent the actual + // encryption. bt_peer_connection overrides this function with + // its own version. + TORRENT_ASSERT(encrypted || type() != bittorrent_connection); + m_send_buffer.append_buffer(buffer, size, size, destructor + , userdata, ref); + } + + void peer_connection::append_const_send_buffer(char const* buffer, int size + , chained_buffer::free_buffer_fun destructor, void* userdata + , block_cache_reference ref) + { + m_send_buffer.append_buffer((char*)buffer, size, size, destructor + , userdata, ref); + +#if defined TORRENT_STATS && defined TORRENT_BUFFER_STATS + m_ses.buffer_usage_logger() << log_time() << " append_const_send_buffer: " << size << std::endl; m_ses.log_buffer_usage(); #endif } + void session_free_buffer(char* buffer, void* userdata, block_cache_reference) + { + aux::session_interface* ses = (aux::session_interface*)userdata; + ses->free_buffer(buffer); + } + void peer_connection::send_buffer(char const* buf, int size, int flags , void (*fun)(char*, int, void*), void* userdata) { @@ -5163,16 +5801,16 @@ namespace libtorrent if (fun) fun(dst, free_space, userdata); size -= free_space; buf += free_space; -#if defined TORRENT_STATS && defined TORRENT_DISK_STATS - m_ses.m_buffer_usage_logger << log_time() << " send_buffer: " +#if defined TORRENT_STATS && defined TORRENT_BUFFER_STATS + m_ses.buffer_usage_logger() << log_time() << " send_buffer: " << free_space << std::endl; m_ses.log_buffer_usage(); #endif } if (size <= 0) return; -#if defined TORRENT_STATS && defined TORRENT_DISK_STATS - m_ses.m_buffer_usage_logger << log_time() << " send_buffer_alloc: " << size << std::endl; +#if defined TORRENT_STATS && defined TORRENT_BUFFER_STATS + m_ses.buffer_usage_logger() << log_time() << " send_buffer_alloc: " << size << std::endl; m_ses.log_buffer_usage(); #endif int i = 0; @@ -5181,17 +5819,18 @@ namespace libtorrent char* chain_buf = m_ses.allocate_buffer(); if (chain_buf == 0) { - disconnect(errors::no_memory); + disconnect(errors::no_memory, op_alloc_sndbuf); return; } - int buf_size = (std::min)(int(aux::session_impl::send_buffer_size), size); + const int alloc_buf_size = m_ses.send_buffer_size(); + int buf_size = (std::min)(alloc_buf_size, size); memcpy(chain_buf, buf, buf_size); if (fun) fun(chain_buf, buf_size, userdata); buf += buf_size; size -= buf_size; - m_send_buffer.append_buffer(chain_buf, aux::session_impl::send_buffer_size, buf_size - , boost::bind(&session_impl::free_buffer, boost::ref(m_ses), _1)); + m_send_buffer.append_buffer(chain_buf, alloc_buf_size, buf_size + , &session_free_buffer, &m_ses); ++i; } setup_send(); @@ -5208,39 +5847,121 @@ namespace libtorrent bool m_cond; }; + void peer_connection::on_receive_data_nb(const error_code& error + , std::size_t bytes_transferred) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("peer_connection::on_receive_data_nb"); +#endif + + // leave this bit set until we're done looping, reading from the socket. + // that way we don't trigger any async read calls until the end of this + // function. + TORRENT_ASSERT(m_channel_state[download_channel] & peer_info::bw_network); + + TORRENT_ASSERT(m_ses.is_single_thread()); + + // nb is short for null_buffers. In this mode we don't actually + // allocate a receive buffer up-front, but get notified when + // we can read from the socket, and then determine how much there + // is to read. + + error_code ec; + std::size_t buffer_size = m_socket->available(ec); + if (ec) + { + disconnect(ec, op_available); + return; + } + +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("<<< READ_AVAILABLE [ bytes: %d ]", buffer_size); +#endif + + // at this point the ioctl told us the socket doesn't have any + // pending bytes. This probably means some error happened. + // in order to find out though, we need to initiate a read + // operation + if (buffer_size == 0) + { + // try to read one byte. The socket is non-blocking anyway + // so worst case, we'll fail with EWOULDBLOCK + buffer_size = 1; + } + else + { + if (buffer_size > m_quota[download_channel]) + { + request_bandwidth(download_channel, buffer_size); + buffer_size = m_quota[download_channel]; + } + // we're already waiting to get some more + // quota from the bandwidth manager + if (buffer_size == 0) + { + // allow reading from the socket again + TORRENT_ASSERT(m_channel_state[download_channel] & peer_info::bw_network); + m_channel_state[download_channel] &= ~peer_info::bw_network; + return; + } + } + + if (buffer_size > 2097152) buffer_size = 2097152; + + m_recv_buffer.resize(m_recv_pos + buffer_size); + TORRENT_ASSERT(m_recv_start == 0); + + // utp sockets aren't thread safe... + if (is_utp(*m_socket)) + { + bytes_transferred = m_socket->read_some(asio::buffer(&m_recv_buffer[m_recv_pos] + , buffer_size), ec); + + if (ec) + { + if (ec == boost::asio::error::try_again || ec == boost::asio::error::would_block) + { + // allow reading from the socket again + TORRENT_ASSERT(m_channel_state[download_channel] & peer_info::bw_network); + m_channel_state[download_channel] &= ~peer_info::bw_network; + setup_receive(read_async); + return; + } + disconnect(ec, op_sock_read); + return; + } + } + else + { +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("peer_connection::on_receive_data"); +#endif + socket_job j; + j.type = socket_job::read_job; + j.recv_buf = &m_recv_buffer[m_recv_pos]; + j.buf_size = buffer_size; + j.peer = self(); + m_ses.post_socket_job(j); + return; + } + + receive_data_impl(error, bytes_transferred, 0); + } + // -------------------------- // RECEIVE DATA // -------------------------- + // nb is true if this callback is due to a null_buffers() + // invocation of async_read_some(). In that case, we need + // to disregard bytes_transferred. + // at all exit points of this function, one of the following MUST hold: + // 1. the socket is disconnecting + // 2. m_channel_state[download_channel] & peer_info::bw_network == 0 + void peer_connection::on_receive_data(const error_code& error , std::size_t bytes_transferred) { -#ifdef TORRENT_STATS - ++m_ses.m_num_messages[aux::session_impl::on_read_counter]; - int size = 8; - int index = 0; - while (bytes_transferred > size + 13) { size <<= 1; ++index; } - int num_max = sizeof(m_ses.m_recv_buffer_sizes)/sizeof(m_ses.m_recv_buffer_sizes[0]); - if (index >= num_max) index = num_max - 1; - ++m_ses.m_recv_buffer_sizes[index]; -#endif - TORRENT_ASSERT(m_ses.is_network_thread()); - - // keep ourselves alive in until this function exits in - // case we disconnect - // this needs to be created before the invariant check, - // to keep the object alive through the exit check - boost::intrusive_ptr me(self()); - - // flush the send buffer at the end of this function - cork _c(*this); - - INVARIANT_CHECK; - -#ifdef TORRENT_VERBOSE_LOGGING - peer_log("<<< ON_RECEIVE_DATA [ bytes: %d error: %s ]" - , bytes_transferred, error.message().c_str()); -#endif #if defined TORRENT_ASIO_DEBUGGING complete_async("peer_connection::on_receive_data"); #endif @@ -5250,10 +5971,34 @@ namespace libtorrent // function. TORRENT_ASSERT(m_channel_state[download_channel] & peer_info::bw_network); - int bytes_in_loop = bytes_transferred; + TORRENT_ASSERT(m_ses.is_single_thread()); - if (m_extension_outstanding_bytes > 0) - m_extension_outstanding_bytes -= (std::min)(m_extension_outstanding_bytes, int(bytes_transferred)); + receive_data_impl(error, bytes_transferred, 10); + } + + void peer_connection::receive_data_impl(const error_code& error + , std::size_t bytes_transferred, int read_loops) + { +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("<<< ON_RECEIVE_DATA [ bytes: %d error: %s ]" + , bytes_transferred, error.message().c_str()); +#endif + + // submit all disk jobs later + m_ses.deferred_submit_jobs(); + + // keep ourselves alive in until this function exits in + // case we disconnect + // this needs to be created before the invariant check, + // to keep the object alive through the exit check + boost::shared_ptr me(self()); + + // flush the send buffer at the end of this function + cork _c(*this); + + INVARIANT_CHECK; + + int bytes_in_loop = bytes_transferred; if (error) { @@ -5261,16 +6006,26 @@ namespace libtorrent peer_log("*** ERROR [ in peer_connection::on_receive_data error: %s ]" , error.message().c_str()); #endif - m_statistics.trancieve_ip_packet(bytes_in_loop, m_remote.address().is_v6()); + trancieve_ip_packet(bytes_in_loop, m_remote.address().is_v6()); on_receive(error, bytes_transferred); - disconnect(error); + disconnect(error, op_sock_read); return; } + m_ses.inc_stats_counter(counters::on_read_counter); + m_ses.received_buffer(bytes_transferred); + +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("<<< ON_RECEIVE_DATA [ bytes: %d error: %s ]" + , bytes_transferred, error.message().c_str()); +#endif + + if (m_extension_outstanding_bytes > 0) + m_extension_outstanding_bytes -= (std::min)(m_extension_outstanding_bytes, int(bytes_transferred)); + int num_loops = 0; do { - TORRENT_ASSERT(int(m_recv_pos + bytes_transferred) <= m_packet_size); #ifdef TORRENT_VERBOSE_LOGGING peer_log("<<< read %d bytes", int(bytes_transferred)); #endif @@ -5280,35 +6035,48 @@ namespace libtorrent if (m_disconnecting) { - m_statistics.trancieve_ip_packet(bytes_in_loop, m_remote.address().is_v6()); + trancieve_ip_packet(bytes_in_loop, m_remote.address().is_v6()); return; } TORRENT_ASSERT(m_packet_size > 0); TORRENT_ASSERT(bytes_transferred > 0); - m_last_receive = time_now(); - m_recv_pos += bytes_transferred; + m_recv_end += bytes_transferred; TORRENT_ASSERT(m_recv_pos <= int(m_recv_buffer.size() + m_disk_recv_buffer_size)); -#if TORRENT_USE_ASSERTS - size_type cur_payload_dl = m_statistics.last_payload_downloaded(); - size_type cur_protocol_dl = m_statistics.last_protocol_downloaded(); -#endif - { + int bytes = bytes_transferred; + int sub_transferred = 0; + do { INVARIANT_CHECK; - on_receive(error, bytes_transferred); - } #if TORRENT_USE_ASSERTS - TORRENT_ASSERT(m_statistics.last_payload_downloaded() - cur_payload_dl >= 0); - TORRENT_ASSERT(m_statistics.last_protocol_downloaded() - cur_protocol_dl >= 0); - size_type stats_diff = m_statistics.last_payload_downloaded() - cur_payload_dl + - m_statistics.last_protocol_downloaded() - cur_protocol_dl; - TORRENT_ASSERT(stats_diff == int(bytes_transferred)); + size_type cur_payload_dl = m_statistics.last_payload_downloaded(); + size_type cur_protocol_dl = m_statistics.last_protocol_downloaded(); +#endif + int packet_size = m_soft_packet_size ? m_soft_packet_size : m_packet_size; + int limit = packet_size > m_recv_pos ? packet_size - m_recv_pos : packet_size; + sub_transferred = (std::min)(bytes, limit); + m_recv_pos += sub_transferred; + on_receive(error, sub_transferred); + bytes -= sub_transferred; + TORRENT_ASSERT(sub_transferred > 0); + +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_statistics.last_payload_downloaded() - cur_payload_dl >= 0); + TORRENT_ASSERT(m_statistics.last_protocol_downloaded() - cur_protocol_dl >= 0); + size_type stats_diff = m_statistics.last_payload_downloaded() - cur_payload_dl + + m_statistics.last_protocol_downloaded() - cur_protocol_dl; + TORRENT_ASSERT(stats_diff == int(sub_transferred)); #endif if (m_disconnecting) return; + } while (bytes > 0 && sub_transferred > 0); + + normalize_receive_buffer(); + + TORRENT_ASSERT(m_recv_pos == m_recv_end); + TORRENT_ASSERT(m_packet_size > 0); if (m_peer_choked @@ -5321,30 +6089,32 @@ namespace libtorrent if (m_recv_pos >= m_soft_packet_size) m_soft_packet_size = 0; - if (num_loops > 20) break; + if (num_loops > read_loops) break; error_code ec; bytes_transferred = try_read(read_sync, ec); TORRENT_ASSERT(bytes_transferred > 0 || ec); - if (ec && ec != asio::error::would_block) + if (ec == asio::error::would_block || ec == asio::error::try_again) break; + if (ec) { - m_statistics.trancieve_ip_packet(bytes_in_loop, m_remote.address().is_v6()); - disconnect(ec); + trancieve_ip_packet(bytes_in_loop, m_remote.address().is_v6()); + disconnect(ec, op_sock_read); return; } - if (ec == asio::error::would_block) break; bytes_in_loop += bytes_transferred; ++num_loops; } while (bytes_transferred > 0); + m_last_receive = time_now(); + if (is_seed()) { boost::shared_ptr t = m_torrent.lock(); if (t) t->seen_complete(); } - m_statistics.trancieve_ip_packet(bytes_in_loop, m_remote.address().is_v6()); + trancieve_ip_packet(bytes_in_loop, m_remote.address().is_v6()); // allow reading from the socket again TORRENT_ASSERT(m_channel_state[download_channel] & peer_info::bw_network); @@ -5362,53 +6132,79 @@ namespace libtorrent && !m_connecting; } - bool peer_connection::can_read(boost::uint8_t* state) const + bool peer_connection::can_read() { + INVARIANT_CHECK; + boost::shared_ptr t = m_torrent.lock(); bool bw_limit = m_quota[download_channel] > 0; if (!bw_limit) return false; - bool disk = m_ses.settings().max_queued_disk_bytes == 0 - || m_ses.can_write_to_disk() - // don't block this peer because of disk saturation - // if we're not downloading any pieces from it - || m_outstanding_bytes == 0; - - if (!disk) + if (m_outstanding_bytes > 0) { - if (state) + // if we're expecting to download piece data, we might not + // want to read from the socket in case we're out of disk + // cache space right now + + if (m_channel_state[download_channel] & peer_info::bw_disk) return false; +/* + // if we already have a disk buffer, we might as well use it + // if contiguous recv buffer is true, don't apply this logic, but + // actually wait until we try to allocate a buffer and exceed the limit + if (m_disk_recv_buffer == NULL + && !m_settings.get_bool(settings_pack::contiguous_recv_buffer)) { - if ((*state & peer_info::bw_disk) == 0) - m_ses.inc_disk_queue(download_channel); - *state |= peer_info::bw_disk; + m_disk_recv_buffer.reset(m_ses.async_allocate_disk_buffer("receive buffer", + boost::bind(&peer_connection::on_allocate_disk_buffer, self(), _1, #error buffer_size))); + + if (m_disk_recv_buffer == NULL) + { + m_ses.inc_stats_counter(counters::num_peers_down_disk); + const_cast(this)->m_channel_state[download_channel] |= peer_info::bw_disk; + +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("*** exceeded disk buffer watermark"); +#endif + return false; + } } - return false; +*/ } return !m_connecting && !m_disconnecting; } - void peer_connection::on_connect(int ticket) + void peer_connection::on_allow_connect(int ticket) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_queued_for_connection); + m_queued_for_connection = false; + +#if defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + { + boost::shared_ptr t = m_torrent.lock(); + t->debug_log("END queue peer [%p]", this); + } +#endif + + TORRENT_ASSERT(m_ses.is_single_thread()); #if TORRENT_USE_ASSERTS // in case we disconnect here, we need to // keep the connection alive until the // exit invariant check is run - boost::intrusive_ptr me(self()); + boost::shared_ptr me(self()); #endif INVARIANT_CHECK; error_code ec; #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING - (*m_ses.m_logger) << time_now_string() << " ON_CONNECT: " << print_endpoint(m_remote) << "\n"; + m_ses.session_log("ON_CONNECT: %s", print_endpoint(m_remote).c_str()); #endif if (ticket == -1) { - disconnect(asio::error::operation_aborted); + disconnect(asio::error::operation_aborted, op_bittorrent); return; } @@ -5420,7 +6216,7 @@ namespace libtorrent if (!t) { TORRENT_ASSERT(!m_connecting); - disconnect(errors::torrent_aborted); + disconnect(errors::torrent_aborted, op_bittorrent); return; } @@ -5432,61 +6228,38 @@ namespace libtorrent m_socket->open(m_remote.protocol(), ec); if (ec) { - disconnect(ec); + disconnect(ec, op_sock_open); return; } - tcp::endpoint bind_interface = t->get_interface(); - - std::pair const& out_ports = m_ses.settings().outgoing_ports; - if (out_ports.first > 0 && out_ports.second >= out_ports.first) - { -#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING - peer_log(">>> SET_REUSE_ADDRESS"); + tcp::endpoint bound_ip = m_ses.bind_outgoing_socket(*m_socket + , m_remote.address(), ec); +#if defined TORRENT_VERBOSE_LOGGING + peer_log(">>> BIND [ dst: %s ec: %s ]", print_endpoint(bound_ip).c_str() + , ec.message().c_str()); #endif - m_socket->set_option(socket_acceptor::reuse_address(true), ec); - // ignore errors because the underlying socket may not - // be opened yet. This happens when we're routing through - // a proxy. In that case, we don't yet know the address of - // the proxy server, and more importantly, we don't know - // the address family of its address. This means we can't - // open the socket yet. The socks abstraction layer defers - // opening it. - ec.clear(); - bind_interface.port(m_ses.next_port()); - } - - // if we're not binding to a specific interface, bind - // to the same protocol family as the target endpoint - if (is_any(bind_interface.address())) - { -#if TORRENT_USE_IPV6 - if (m_remote.address().is_v6()) - bind_interface.address(address_v6::any()); - else -#endif - bind_interface.address(address_v4::any()); - } - -#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING - peer_log(">>> BIND [ ep: %s ]", print_endpoint(bind_interface).c_str()); -#endif - m_socket->bind(bind_interface, ec); if (ec) { - disconnect(ec); + disconnect(ec, op_sock_bind); return; } + #if defined TORRENT_VERBOSE_LOGGING peer_log(">>> ASYNC_CONNECT [ dst: %s ]", print_endpoint(m_remote).c_str()); #endif #if defined TORRENT_ASIO_DEBUGGING add_outstanding_async("peer_connection::on_connection_complete"); #endif + +#if defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + t->debug_log("START connect [%p] (%d)", this, int(t->num_peers())); +#endif + m_socket->async_connect(m_remote , boost::bind(&peer_connection::on_connection_complete, self(), _1)); m_connect = time_now_hires(); - m_statistics.sent_syn(m_remote.address().is_v6()); + + sent_syn(m_remote.address().is_v6()); if (t->alerts().should_post()) { @@ -5505,22 +6278,29 @@ namespace libtorrent #endif ptime completed = time_now_hires(); - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); INVARIANT_CHECK; m_rtt = boost::uint16_t(total_milliseconds(completed - m_connect)); +#if defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + { + boost::shared_ptr t = m_torrent.lock(); + t->debug_log("END connect [%p] (%d ms)", this, m_rtt); + m_connect_time = completed; + } +#endif + #ifdef TORRENT_USE_OPENSSL // add this RTT to the PRNG seed, to add more unpredictability boost::uint64_t now = total_microseconds(completed - m_connect); // assume 12 bits of entropy (i.e. about 8 milliseconds) RAND_add(&now, 8, 1.5); #endif - - if (m_disconnecting) return; - error_code ec; + if (m_disconnecting) return; + if (e) { connect_failed(e); @@ -5531,20 +6311,52 @@ namespace libtorrent // we can't decrement the connecting counter boost::shared_ptr t = m_torrent.lock(); TORRENT_ASSERT(t || !m_connecting); - if (m_connecting && t) + if (m_connecting) { - t->dec_num_connecting(); + m_ses.inc_stats_counter(counters::num_peers_half_open, -1); + if (t) t->dec_num_connecting(); m_connecting = false; } if (m_connection_ticket != -1) { - if (m_ses.m_half_open.done(m_connection_ticket)) + if (m_ses.half_open_done(m_connection_ticket)) m_connection_ticket = -1; } + TORRENT_ASSERT(!m_connected); + m_connected = true; + m_ses.inc_stats_counter(counters::num_peers_connected); + if (m_disconnecting) return; m_last_receive = time_now(); + error_code ec; + m_local = m_socket->local_endpoint(ec); + if (ec) + { + disconnect(ec, op_getname); + return; + } + + // if there are outgoing interfaces specified, verify this + // peer is correctly bound to on of them + if (!m_settings.get_str(settings_pack::outgoing_interfaces).empty()) + { + if (!m_ses.verify_bound_address(m_local.address() + , is_utp(*m_socket), ec)) + { + if (ec) + { + disconnect(ec, op_get_interface); + return; + } + disconnect(error_code( + boost::system::errc::no_such_device, generic_category()) + , op_connect); + return; + } + } + if (is_utp(*m_socket) && m_peer_info) { m_peer_info->confirmed_supports_utp = true; @@ -5553,7 +6365,7 @@ namespace libtorrent // this means the connection just succeeded - m_statistics.received_synack(m_remote.address().is_v6()); + received_synack(m_remote.address().is_v6()); TORRENT_ASSERT(m_socket); #if defined TORRENT_VERBOSE_LOGGING @@ -5569,7 +6381,7 @@ namespace libtorrent m_socket->io_control(ioc, ec); if (ec) { - disconnect(ec); + disconnect(ec, op_iocontrol); return; } @@ -5577,17 +6389,17 @@ namespace libtorrent { // if the remote endpoint is the same as the local endpoint, we're connected // to ourselves - if (m_peer_info && t) t->get_policy().ban_peer(m_peer_info); - disconnect(errors::self_connection, 1); + if (m_peer_info && t) t->ban_peer(m_peer_info); + disconnect(errors::self_connection, op_bittorrent, 1); return; } - if (m_remote.address().is_v4() && m_ses.settings().peer_tos != 0) + if (m_remote.address().is_v4() && m_settings.get_int(settings_pack::peer_tos) != 0) { error_code ec; - m_socket->set_option(type_of_service(m_ses.settings().peer_tos), ec); + m_socket->set_option(type_of_service(m_settings.get_int(settings_pack::peer_tos)), ec); #if defined TORRENT_VERBOSE_LOGGING - peer_log(">>> SET_TOS[ tos: %d e: %s ]", m_ses.settings().peer_tos, ec.message().c_str()); + peer_log(">>> SET_TOS[ tos: %d e: %s ]", m_settings.get_int(settings_pack::peer_tos), ec.message().c_str()); #endif } @@ -5611,16 +6423,18 @@ namespace libtorrent void peer_connection::on_send_data(error_code const& error , std::size_t bytes_transferred) { -#ifdef TORRENT_STATS - ++m_ses.m_num_messages[aux::session_impl::on_write_counter]; - int size = 8; - int index = 0; - while (bytes_transferred > size + 13) { size <<= 1; ++index; } - int num_max = sizeof(m_ses.m_send_buffer_sizes)/sizeof(m_ses.m_send_buffer_sizes[0]); - if (index >= num_max) index = num_max - 1; - ++m_ses.m_send_buffer_sizes[index]; + m_ses.inc_stats_counter(counters::on_write_counter); + m_ses.sent_buffer(bytes_transferred); + TORRENT_ASSERT(m_ses.is_single_thread()); + +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(m_socket_is_writing); + m_socket_is_writing = false; #endif - TORRENT_ASSERT(m_ses.is_network_thread()); + + // submit all disk jobs when we've processed all messages + // in the current message queue + m_ses.deferred_submit_jobs(); #if defined TORRENT_VERBOSE_LOGGING peer_log("*** ON_SEND_DATA [ bytes: %d error: %s ]" @@ -5634,7 +6448,7 @@ namespace libtorrent #endif // keep ourselves alive in until this function exits in // case we disconnect - boost::intrusive_ptr me(self()); + boost::shared_ptr me(self()); TORRENT_ASSERT(m_channel_state[upload_channel] & peer_info::bw_network); @@ -5653,7 +6467,7 @@ namespace libtorrent TORRENT_ASSERT(int(bytes_transferred) <= m_quota[upload_channel]); m_quota[upload_channel] -= bytes_transferred; - m_statistics.trancieve_ip_packet(bytes_transferred, m_remote.address().is_v6()); + trancieve_ip_packet(bytes_transferred, m_remote.address().is_v6()); #ifdef TORRENT_VERBOSE_LOGGING peer_log(">>> wrote %d bytes", int(bytes_transferred)); @@ -5664,10 +6478,17 @@ namespace libtorrent #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING peer_log("**ERROR**: %s [in peer_connection::on_send_data]", error.message().c_str()); #endif - disconnect(error); + disconnect(error, op_sock_write); + return; + } + if (m_disconnecting) + { + // make sure we free up all send buffers that are owned + // by the disk thread + m_send_buffer.clear(); + m_disk_recv_buffer.reset(); return; } - if (m_disconnecting) return; TORRENT_ASSERT(!m_connecting); TORRENT_ASSERT(bytes_transferred > 0); @@ -5707,29 +6528,34 @@ namespace libtorrent { TORRENT_ASSERT(m_in_use == 1337); TORRENT_ASSERT(m_queued_time_critical <= int(m_request_queue.size())); + TORRENT_ASSERT(m_recv_end >= m_recv_start); TORRENT_ASSERT(m_accept_fast.size() == m_accept_fast_piece_cnt.size()); TORRENT_ASSERT(bool(m_disk_recv_buffer) == (m_disk_recv_buffer_size > 0)); - TORRENT_ASSERT(m_upload_limit >= 0); - TORRENT_ASSERT(m_download_limit >= 0); - - // It's not obvious why this invariant breaks when the peer disconnects - if (!m_disconnecting) + for (int i = 0; i < 2; ++i) { - if (m_channel_state[upload_channel] & peer_info::bw_limit) - TORRENT_ASSERT(m_ses.m_upload_rate.is_queued(this)); - if (m_channel_state[download_channel] & peer_info::bw_limit) - TORRENT_ASSERT(m_ses.m_download_rate.is_queued(this)); + if (m_channel_state[i] & peer_info::bw_limit) + { + // if we're waiting for bandwidth, we should be in the + // bandwidth manager's queue + TORRENT_ASSERT(m_ses.get_bandwidth_manager(i)->is_queued(this)); + } } boost::shared_ptr t = m_torrent.lock(); +#if TORRENT_USE_INVARIANT_CHECKS \ + && !defined TORRENT_NO_EXPENSIVE_INVARIANT_CHECK + if (t && t->has_picker() && !m_disconnecting) + t->picker().check_peer_invariant(m_have_piece, this); +#endif + if (!m_disconnect_started && m_initialized) { // none of this matters if we're disconnecting anyway if (t->is_finished()) - TORRENT_ASSERT(!is_interesting()); + TORRENT_ASSERT(!is_interesting() || m_need_interest_update); if (is_seed()) TORRENT_ASSERT(upload_only()); } @@ -5738,7 +6564,6 @@ namespace libtorrent { TORRENT_ASSERT(m_download_queue.empty()); TORRENT_ASSERT(m_request_queue.empty()); - TORRENT_ASSERT(!t); TORRENT_ASSERT(m_disconnect_started); } else if (!m_in_constructor) @@ -5805,9 +6630,7 @@ namespace libtorrent #ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS // since this connection doesn't have a torrent reference // no torrent should have a reference to this connection either - for (aux::session_impl::torrent_map::const_iterator i = m_ses.m_torrents.begin() - , end(m_ses.m_torrents.end()); i != end; ++i) - TORRENT_ASSERT(!i->second->has_peer((peer_connection*)this)); + TORRENT_ASSERT(!m_ses.any_torrent_has_peer(this)); #endif return; } @@ -5816,7 +6639,7 @@ namespace libtorrent TORRENT_ASSERT(t->torrent_file().num_pieces() == int(m_have_piece.size())); // in share mode we don't close redundant connections - if (m_ses.settings().close_redundant_connections && !t->share_mode()) + if (m_settings.get_bool(settings_pack::close_redundant_connections) && !t->share_mode()) { bool ok_to_disconnect = can_disconnect(error_code(errors::upload_upload_connection, get_libtorrent_category())) @@ -5844,10 +6667,11 @@ namespace libtorrent TORRENT_ASSERT(m_disconnect_started); } - if (!m_disconnect_started && m_initialized && m_ses.settings().close_redundant_connections) + if (!m_disconnect_started && m_initialized + && m_ses.settings().get_bool(settings_pack::close_redundant_connections)) { // none of this matters if we're disconnecting anyway - if (t->is_upload_only()) + if (t->is_upload_only() && !m_need_interest_update) TORRENT_ASSERT(!m_interesting || t->graceful_pause() || t->has_error()); if (is_seed()) TORRENT_ASSERT(m_upload_only); @@ -5860,9 +6684,7 @@ namespace libtorrent for (torrent::const_peer_iterator i = t->begin(); i != t->end(); ++i) { // make sure this peer is not a dangling pointer -#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS TORRENT_ASSERT(m_ses.has_peer(*i)); -#endif peer_connection const& p = *(*i); for (std::vector::const_iterator i = p.request_queue().begin() , end(p.request_queue().end()); i != end; ++i) @@ -5897,39 +6719,16 @@ namespace libtorrent TORRENT_ASSERT(picker_count == count); } } - - if (m_peer_info && type() == bittorrent_connection) - { - policy::const_iterator i = t->get_policy().begin_peer(); - policy::const_iterator end = t->get_policy().end_peer(); - for (; i != end; ++i) - { - if (*i == m_peer_info) break; - } - TORRENT_ASSERT(i != end); - } #endif /* if (t->has_picker() && !t->is_aborted()) { - // make sure that pieces that have completed the download - // of all their blocks are in the disk io thread's queue - // to be checked. - const std::vector& dl_queue - = t->picker().get_download_queue(); - for (std::vector::const_iterator i = - dl_queue.begin(); i != dl_queue.end(); ++i) + for (std::vector::const_iterator i = m_download_queue.begin() + , end(m_download_queue.end()); i != end; ++i) { - const int blocks_per_piece = t->picker().blocks_in_piece(i->index); - - bool complete = true; - for (int j = 0; j < blocks_per_piece; ++j) - { - if (i->info[j].state == piece_picker::block_info::state_finished) - continue; - complete = false; - break; - } + pending_block const& pb = *i; + if (pb.timed_out || pb.not_wanted) continue; + TORRENT_ASSERT(t->picker().get_block_state(pb.block) != piece_picker::block_info::state_none); TORRENT_ASSERT(complete); } } @@ -5984,7 +6783,7 @@ namespace libtorrent else m_speed = slow; - return m_speed; + return peer_connection::peer_speed_t(m_speed); } void peer_connection::keep_alive() @@ -5995,7 +6794,7 @@ namespace libtorrent time_duration d; d = time_now() - m_last_sent; - if (total_seconds(d) < m_timeout / 2) return; + if (total_seconds(d) < timeout() / 2) return; if (m_connecting) return; if (in_handshake()) return; @@ -6036,7 +6835,7 @@ namespace libtorrent m_upload_only = u; boost::shared_ptr t = associated_torrent().lock(); - t->get_policy().set_seed(m_peer_info, u); + t->set_seed(m_peer_info, u); disconnect_if_redundant(); } diff --git a/src/performance_counters.cpp b/src/performance_counters.cpp new file mode 100644 index 000000000..7687008a5 --- /dev/null +++ b/src/performance_counters.cpp @@ -0,0 +1,87 @@ +/* + +Copyright (c) 2013, 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. + +*/ + +#include "libtorrent/performance_counters.hpp" +#include "libtorrent/assert.hpp" +#include // for memset + +#ifdef TORRENT_USE_VALGRIND +#include +#endif + +namespace libtorrent { + + + counters::counters() + { + memset(m_stats_counter, 0, sizeof(m_stats_counter)); + } + + boost::int64_t counters::operator[](int i) const + { + TORRENT_ASSERT(i >= 0); + TORRENT_ASSERT(i < num_counters); +#ifdef TORRENT_USE_VALGRIND + VALGRIND_CHECK_VALUE_IS_DEFINED(m_stats_counter[i]); +#endif + return m_stats_counter[i]; + } + + // the argument specifies which counter to + // increment or decrement + boost::uint64_t counters::inc_stats_counter(int c, boost::int64_t value) + { + // if c >= num_stats_counters, it means it's not + // a monotonically increasing counter, but a gauge + // and it's allowed to be decremented + TORRENT_ASSERT(value >= 0 || c >= num_stats_counters); + TORRENT_ASSERT(c >= 0); + TORRENT_ASSERT(c < num_counters); + + TORRENT_ASSERT(m_stats_counter[c] + value >= 0); + return m_stats_counter[c] += value; + } + + void counters::set_value(int c, boost::int64_t value) + { + TORRENT_ASSERT(c >= 0); + TORRENT_ASSERT(c < num_counters); + + // if this assert fires, someone is trying to decrement a counter + // which is not allowed. Counters are monotonically increasing + TORRENT_ASSERT(value >= m_stats_counter[c] || c >= num_stats_counters); + + m_stats_counter[c] = value; + } + +} + diff --git a/src/piece_picker.cpp b/src/piece_picker.cpp index b1aa8d4e3..84eb54d9e 100644 --- a/src/piece_picker.cpp +++ b/src/piece_picker.cpp @@ -41,11 +41,13 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/piece_picker.hpp" #include "libtorrent/bitfield.hpp" #include "libtorrent/random.hpp" +#include "libtorrent/alloca.hpp" +#include "libtorrent/performance_counters.hpp" // for counters #if TORRENT_USE_ASSERTS #include "libtorrent/peer_connection.hpp" #include "libtorrent/torrent.hpp" -#include "libtorrent/policy.hpp" // for policy::peer +#include "libtorrent/torrent_peer.hpp" #endif #ifdef TORRENT_USE_VALGRIND @@ -58,8 +60,6 @@ POSSIBILITY OF SUCH DAMAGE. //#define TORRENT_NO_EXPENSIVE_INVARIANT_CHECK //#define TORRENT_PIECE_PICKER_INVARIANT_CHECK -//#define TORRENT_PICKER_LOG - namespace libtorrent { @@ -67,20 +67,21 @@ namespace libtorrent piece_picker::piece_picker() : m_seeds(0) + , m_num_passed(0) , m_priority_boundries(1, int(m_pieces.size())) , m_blocks_per_piece(0) , m_blocks_in_last_piece(0) , m_num_filtered(0) , m_num_have_filtered(0) - , m_num_have(0) , m_cursor(0) , m_reverse_cursor(0) , m_sparse_regions(1) + , m_num_have(0) , m_num_pad_files(0) , m_dirty(false) { #ifdef TORRENT_PICKER_LOG - std::cerr << "new piece_picker" << std::endl; + std::cerr << "[" << this << "] " << "new piece_picker" << std::endl; #endif #if TORRENT_USE_INVARIANT_CHECKS check_invariant(); @@ -93,7 +94,7 @@ namespace libtorrent TORRENT_ASSERT(total_num_pieces > 0); #ifdef TORRENT_PICKER_LOG - std::cerr << "piece_picker::init()" << std::endl; + std::cerr << "[" << this << "] " << "piece_picker::init()" << std::endl; #endif // allocate the piece_map to cover all pieces // and make them invalid (as if we don't have a single piece) @@ -101,18 +102,20 @@ namespace libtorrent m_reverse_cursor = int(m_piece_map.size()); m_cursor = 0; - m_downloads.clear(); + for (int i = 0; i < num_download_categories; ++i) + m_downloads[i].clear(); m_block_info.clear(); m_num_filtered += m_num_have_filtered; m_num_have_filtered = 0; m_num_have = 0; + m_num_passed = 0; m_dirty = true; for (std::vector::iterator i = m_piece_map.begin() , end(m_piece_map.end()); i != end; ++i) { i->peer_count = 0; - i->downloading = 0; + i->state = piece_pos::piece_open; i->index = 0; #ifdef TORRENT_DEBUG_REFCOUNTS i->have_peers.clear(); @@ -146,10 +149,11 @@ namespace libtorrent TORRENT_ASSERT(index >= 0); TORRENT_ASSERT(index < int(m_piece_map.size())); - if (m_piece_map[index].downloading) + int state = m_piece_map[index].state; + if (state > piece_pos::piece_open) { - std::vector::const_iterator piece = find_dl_piece(index); - TORRENT_ASSERT(piece != m_downloads.end()); + std::vector::const_iterator piece = find_dl_piece(state - 1, index); + TORRENT_ASSERT(piece != m_downloads[state - 1].end()); st = *piece; return; } @@ -165,29 +169,43 @@ namespace libtorrent st.finished = 0; } - piece_picker::downloading_piece& piece_picker::add_download_piece(int piece) + piece_picker::dlpiece_iter piece_picker::add_download_piece(int piece) { TORRENT_ASSERT(piece >= 0); TORRENT_ASSERT(piece < int(m_piece_map.size())); - int num_downloads = m_downloads.size(); +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + + int num_downloads = 0; + for (int k = 0; k < num_download_categories; ++k) num_downloads += m_downloads[k].size(); + int block_index = num_downloads * m_blocks_per_piece; if (int(m_block_info.size()) < block_index + m_blocks_per_piece) { - block_info* base = 0; + block_info* base = NULL; if (!m_block_info.empty()) base = &m_block_info[0]; m_block_info.resize(block_index + m_blocks_per_piece); - if (!m_downloads.empty() && &m_block_info[0] != base) + if (base != NULL && &m_block_info[0] != base) { - // this means the memory was reallocated, update the pointers - for (int i = 0; i < int(m_downloads.size()); ++i) - m_downloads[i].info = &m_block_info[m_downloads[i].info - base]; + for (int k = 0; k < num_download_categories; ++k) + { + // this means the memory was reallocated, update the pointers + for (int i = 0; i < int(m_downloads[k].size()); ++i) + { + TORRENT_ASSERT(m_downloads[k][i].info >= base); + TORRENT_ASSERT(m_downloads[k][i].info < base + m_block_info.size()); + m_downloads[k][i].info = &m_block_info[m_downloads[k][i].info - base]; + } + } } } + // always insert into bucket 0 (piece_downloading) downloading_piece ret; ret.index = piece; - std::vector::iterator i = std::lower_bound(m_downloads.begin() - , m_downloads.end(), ret); - TORRENT_ASSERT(i == m_downloads.end() || i->index != piece); + std::vector::iterator i = std::lower_bound(m_downloads[0].begin() + , m_downloads[0].end(), ret); + TORRENT_ASSERT(i == m_downloads[0].end() || i->index != piece); ret.info = &m_block_info[block_index]; TORRENT_ASSERT(ret.info >= &m_block_info[0]); TORRENT_ASSERT(ret.info < &m_block_info[0] + m_block_info.size()); @@ -205,40 +223,131 @@ namespace libtorrent #endif #if TORRENT_USE_ASSERTS ret.info[i].piece_index = piece; + ret.info[i].peers.clear(); #endif } - #ifdef TORRENT_USE_VALGRIND VALGRIND_CHECK_VALUE_IS_DEFINED(ret.info); VALGRIND_CHECK_VALUE_IS_DEFINED(ret.index); - VALGRIND_CHECK_VALUE_IS_DEFINED(ret.finished); - VALGRIND_CHECK_VALUE_IS_DEFINED(ret.writing); - VALGRIND_CHECK_VALUE_IS_DEFINED(ret.requested); - VALGRIND_CHECK_VALUE_IS_DEFINED(ret.state); #endif - i = m_downloads.insert(i, ret); - return *i; + i = m_downloads[0].insert(i, ret); + +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + return i; } void piece_picker::erase_download_piece(std::vector::iterator i) { - std::vector::iterator other = std::find_if( - m_downloads.begin(), m_downloads.end() - , boost::bind(&downloading_piece::info, _1) - == &m_block_info[(m_downloads.size() - 1) * m_blocks_per_piece]); - TORRENT_ASSERT(other != m_downloads.end()); +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif - if (i != other) + int state = m_piece_map[i->index].state; + TORRENT_ASSERT(state > piece_pos::piece_open); + int queue = state - 1; + TORRENT_ASSERT(find_dl_piece(queue, i->index) == i); +#if TORRENT_USE_ASSERTS + int prev_size = m_downloads[queue].size(); +#endif + + int total_downloading_pieces = 0; + for (int k = 0; k < num_download_categories; ++k) total_downloading_pieces += m_downloads[k].size(); + + std::vector::iterator other; + bool found = false; + for (int k = 0; k < num_download_categories; ++k) + { + other = std::find_if( + m_downloads[k].begin(), m_downloads[k].end() + , boost::bind(&downloading_piece::info, _1) + == &m_block_info[(total_downloading_pieces - 1) * m_blocks_per_piece]); + if (other != m_downloads[k].end()) + { + found = true; + break; + } + } + TORRENT_ASSERT(found); + + if (found && i->index != other->index) { std::copy(other->info, other->info + m_blocks_per_piece, i->info); other->info = i->info; } - m_piece_map[i->index].downloading = false; - m_downloads.erase(i); + m_piece_map[i->index].state = piece_pos::piece_open; + m_downloads[queue].erase(i); + + TORRENT_ASSERT(prev_size == m_downloads[queue].size() + 1); + +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + } + + std::vector piece_picker::get_download_queue() const + { +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + + std::vector ret; + for (int k = 0; k < num_download_categories; ++k) + ret.insert(ret.end(), m_downloads[k].begin(), m_downloads[k].end()); + return ret; + } + + int piece_picker::get_download_queue_size() const + { + int ret = 0; + for (int k = 0; k < num_download_categories; ++k) + ret += m_downloads[k].size(); + return ret; + } + + void piece_picker::get_download_queue_sizes(int* partial, int* full, int* finished, int* zero_prio) const + { + *partial = m_downloads[0].size(); + *full = m_downloads[1].size(); + *finished = m_downloads[2].size(); + *zero_prio = m_downloads[3].size(); } #if TORRENT_USE_INVARIANT_CHECKS + void piece_picker::check_piece_state() const + { +#ifndef TORRENT_DISABLE_INVARIANT_CHECKS + for (int k = 0; k < num_download_categories; ++k) + { + if (!m_downloads[k].empty()) + { + for (std::vector::const_iterator i = m_downloads[k].begin(); + i != m_downloads[k].end() - 1; ++i) + { + downloading_piece const& dp = *i; + downloading_piece const& next = *(i + 1); +// TORRENT_ASSERT(dp.finished + dp.writing >= next.finished + next.writing); + TORRENT_ASSERT(dp.index < next.index); + TORRENT_ASSERT(dp.info >= &m_block_info[0]); + TORRENT_ASSERT(dp.info < &m_block_info[0] + m_block_info.size()); + TORRENT_ASSERT((dp.info - &m_block_info[0]) % m_blocks_per_piece == 0); + for (int k = 0; k < m_blocks_per_piece; ++k) + { + if (dp.info[k].peer) + { + torrent_peer* p = (torrent_peer*)dp.info[k].peer; + TORRENT_ASSERT(p->in_use); + TORRENT_ASSERT(p->connection == NULL || static_cast(p->connection)->m_in_use); + } + } + } + } + } +#endif + } + void piece_picker::verify_pick(std::vector const& picked , bitfield const& bits) const { @@ -272,17 +381,21 @@ namespace libtorrent #if defined TORRENT_PICKER_LOG void piece_picker::print_pieces() const { + int limit = 10; + std::cerr << "[" << this << "] "; for (std::vector::const_iterator i = m_priority_boundries.begin() , end(m_priority_boundries.end()); i != end; ++i) { std::cerr << *i << " "; } - std::cout << std::endl; + std::cerr << std::endl; int index = 0; + std::cerr << "[" << this << "] "; std::vector::const_iterator j = m_priority_boundries.begin(); for (std::vector::const_iterator i = m_pieces.begin() , end(m_pieces.end()); i != end; ++i, ++index) { + if (limit == 0) break; if (*i == -1) break; while (j != m_priority_boundries.end() && *j <= index) { @@ -290,6 +403,7 @@ namespace libtorrent ++j; } std::cerr << *i << "(" << m_piece_map[*i].index << ") "; + --limit; } std::cerr << std::endl; } @@ -297,10 +411,23 @@ namespace libtorrent #endif // TORRENT_USE_INVARIANT_CHECKS #if TORRENT_USE_INVARIANT_CHECKS - void piece_picker::check_invariant(const torrent* t) const + void piece_picker::check_peer_invariant(bitfield const& have + , void const* p) const + { +#ifdef TORRENT_DEBUG_REFCOUNTS + int num_pieces = have.size(); + for (int i = 0; i < num_pieces; ++i) + { + int h = have[i]; + TORRENT_ASSERT(m_piece_map[i].have_peers.count(p) == h); + } +#endif + } + + void piece_picker::check_invariant(torrent const* t) const { #ifndef TORRENT_DEBUG_REFCOUNTS -#if TORRENT_COMPACT_PICKER +#if TORRENT_OPTIMIZE_MEMORY_USAGE TORRENT_ASSERT(sizeof(piece_pos) == 4); #else TORRENT_ASSERT(sizeof(piece_pos) == 8); @@ -311,56 +438,109 @@ namespace libtorrent TORRENT_ASSERT(m_num_filtered >= 0); TORRENT_ASSERT(m_seeds >= 0); - if (!m_downloads.empty()) + for (int k = 0; k < num_download_categories; ++k) { - for (std::vector::const_iterator i = m_downloads.begin(); - i != m_downloads.end() - 1; ++i) + if (!m_downloads[k].empty()) { - downloading_piece const& dp = *i; - downloading_piece const& next = *(i + 1); -// TORRENT_ASSERT(dp.finished + dp.writing >= next.finished + next.writing); - TORRENT_ASSERT(dp.index < next.index); + for (std::vector::const_iterator i = m_downloads[k].begin(); + i != m_downloads[k].end() - 1; ++i) + { + downloading_piece const& dp = *i; + downloading_piece const& next = *(i + 1); +// TORRENT_ASSERT(dp.finished + dp.writing >= next.finished + next.writing); + TORRENT_ASSERT(dp.index < next.index); + TORRENT_ASSERT(dp.info >= &m_block_info[0]); + TORRENT_ASSERT(dp.info < &m_block_info[0] + m_block_info.size()); + TORRENT_ASSERT((dp.info - &m_block_info[0]) % m_blocks_per_piece == 0); +#if TORRENT_USE_ASSERTS + for (int k = 0; k < m_blocks_per_piece; ++k) + { + if (dp.info[k].peer) + { + torrent_peer* p = (torrent_peer*)dp.info[k].peer; + TORRENT_ASSERT(p->in_use); + TORRENT_ASSERT(p->connection == NULL + || static_cast(p->connection)->m_in_use); + } + } +#endif + } } } if (t != 0) TORRENT_ASSERT((int)m_piece_map.size() == t->torrent_file().num_pieces()); - for (std::vector::const_iterator i = m_downloads.begin() - , end(m_downloads.end()); i != end; ++i) + for (int j = 0; j < num_download_categories; ++j) { - bool blocks_requested = false; - int num_blocks = blocks_in_piece(i->index); - int num_requested = 0; - int num_finished = 0; - int num_writing = 0; - for (int k = 0; k < num_blocks; ++k) + for (std::vector::const_iterator i = m_downloads[j].begin() + , end(m_downloads[j].end()); i != end; ++i) { - TORRENT_ASSERT(i->info[k].piece_index == i->index); - TORRENT_ASSERT(i->info[k].peer == 0 || static_cast(i->info[k].peer)->in_use); - if (i->info[k].state == block_info::state_finished) + TORRENT_ASSERT(m_piece_map[i->index].state == j + 1); + bool blocks_requested = false; + int num_blocks = blocks_in_piece(i->index); + int num_requested = 0; + int num_finished = 0; + int num_writing = 0; + int num_open = 0; + for (int k = 0; k < num_blocks; ++k) { - ++num_finished; - TORRENT_ASSERT(i->info[k].num_peers == 0); + TORRENT_ASSERT(i->info[k].piece_index == i->index); + TORRENT_ASSERT(i->info[k].peer == 0 || static_cast(i->info[k].peer)->in_use); + if (i->info[k].state == block_info::state_finished) + { + ++num_finished; + TORRENT_ASSERT(i->info[k].num_peers == 0); + } + else if (i->info[k].state == block_info::state_requested) + { + ++num_requested; + blocks_requested = true; + TORRENT_ASSERT(i->info[k].num_peers > 0); + } + else if (i->info[k].state == block_info::state_writing) + { + ++num_writing; + TORRENT_ASSERT(i->info[k].num_peers == 0); + } + else if (i->info[k].state == block_info::state_none) + { + ++num_open; + TORRENT_ASSERT(i->info[k].num_peers == 0); + } } - else if (i->info[k].state == block_info::state_requested) + + switch(j + 1) { - ++num_requested; - blocks_requested = true; - TORRENT_ASSERT(i->info[k].num_peers > 0); - } - else if (i->info[k].state == block_info::state_writing) - { - ++num_writing; - TORRENT_ASSERT(i->info[k].num_peers == 0); + case piece_pos::piece_downloading: + TORRENT_ASSERT(!m_piece_map[i->index].filtered()); + TORRENT_ASSERT(num_open > 0); + break; + case piece_pos::piece_full: + TORRENT_ASSERT(!m_piece_map[i->index].filtered()); + TORRENT_ASSERT(num_open == 0); + // if requested == 0, the piece should be in the finished state + TORRENT_ASSERT(num_requested > 0); + break; + case piece_pos::piece_finished: + TORRENT_ASSERT(!m_piece_map[i->index].filtered()); + TORRENT_ASSERT(num_open == 0); + TORRENT_ASSERT(num_requested == 0); + TORRENT_ASSERT(num_finished + num_writing == num_blocks); + break; + case piece_pos::piece_zero_prio: + TORRENT_ASSERT(m_piece_map[i->index].filtered()); + break; } + + TORRENT_ASSERT(blocks_requested == (i->state != none)); + TORRENT_ASSERT(num_requested == i->requested); + TORRENT_ASSERT(num_writing == i->writing); + TORRENT_ASSERT(num_finished == i->finished); + if (m_piece_map[i->index].state > piece_pos::piece_downloading + && m_piece_map[i->index].state < piece_pos::piece_zero_prio) + TORRENT_ASSERT(num_finished + num_writing + num_requested == num_blocks); } - TORRENT_ASSERT(blocks_requested == (i->state != none)); - TORRENT_ASSERT(num_requested == i->requested); - TORRENT_ASSERT(num_writing == i->writing); - TORRENT_ASSERT(num_finished == i->finished); - if (m_piece_map[i->index].full) - TORRENT_ASSERT(num_finished + num_writing + num_requested == num_blocks); } int num_pieces = int(m_piece_map.size()); TORRENT_ASSERT(m_cursor >= 0); @@ -428,7 +608,6 @@ namespace libtorrent #ifdef TORRENT_DEBUG_REFCOUNTS TORRENT_ASSERT(p.have_peers.size() == p.peer_count + m_seeds); #endif - if (p.index == piece_pos::we_have_index) ++num_have; @@ -455,7 +634,7 @@ namespace libtorrent ++num_downloaders; } - if (i->downloading) + if (i->downloading()) { TORRENT_ASSERT(num_downloaders == 1); } @@ -470,14 +649,14 @@ namespace libtorrent if (p.index == piece_pos::we_have_index) { TORRENT_ASSERT(t == 0 || t->have_piece(index)); - TORRENT_ASSERT(p.downloading == 0); + TORRENT_ASSERT(p.downloading() == false); } if (t != 0) TORRENT_ASSERT(!t->have_piece(index)); int prio = p.priority(this); - TORRENT_ASSERT(prio == -1 || p.downloading == (prio % piece_picker::prio_factor == 0)); + TORRENT_ASSERT(prio == -1 || p.downloading() == (prio % piece_picker::prio_factor == 0)); if (!m_dirty) { @@ -499,16 +678,31 @@ namespace libtorrent } } - int count = std::count_if(m_downloads.begin(), m_downloads.end() + int count_downloading = std::count_if(m_downloads[0].begin(), m_downloads[0].end() , has_index(index)); - if (i->downloading == 1) + int count_full = std::count_if(m_downloads[1].begin(), m_downloads[1].end() + , has_index(index)); + int count_finished = std::count_if(m_downloads[2].begin(), m_downloads[2].end() + , has_index(index)); + TORRENT_ASSERT(i->state == piece_pos::piece_open + || i->state == piece_pos::piece_zero_prio + || count_downloading + count_full + count_finished == 1); + + switch(i->state) { - TORRENT_ASSERT(count == 1); - } - else - { - TORRENT_ASSERT(count == 0); - } + case piece_pos::piece_open: + TORRENT_ASSERT(count_downloading + count_full + count_finished == 0); + break; + case piece_pos::piece_downloading: + TORRENT_ASSERT(count_downloading == 1); + break; + case piece_pos::piece_full: + TORRENT_ASSERT(count_full == 1); + break; + case piece_pos::piece_finished: + TORRENT_ASSERT(count_finished == 1); + break; + }; } TORRENT_ASSERT(num_have == m_num_have); TORRENT_ASSERT(num_filtered == m_num_filtered); @@ -597,7 +791,11 @@ namespace libtorrent else new_index = random() % (range_end - range_start + 1) + range_start; #ifdef TORRENT_PICKER_LOG - std::cerr << "add " << index << " (" << priority << ")" << std::endl; + std::cerr << "[" << this << "] " << "add " << index << " (" << priority << ")" << std::endl; + std::cerr << "[" << this << "] " << " p: state: " << p.state + << " peer_count: " << p.peer_count + << " prio: " << p.piece_priority + << " index: " << p.index << std::endl; print_pieces(); #endif m_pieces.push_back(-1); @@ -617,7 +815,7 @@ namespace libtorrent new_index = temp; #ifdef TORRENT_PICKER_LOG print_pieces(); - std::cerr << " index: " << index + std::cerr << "[" << this << "] " << " index: " << index << " prio: " << priority << " new_index: " << new_index << std::endl; @@ -649,7 +847,7 @@ namespace libtorrent TORRENT_ASSERT(elem_index >= 0); #ifdef TORRENT_PICKER_LOG - std::cerr << "remove " << m_pieces[elem_index] << " (" << priority << ")" << std::endl; + std::cerr << "[" << this << "] " << "remove " << m_pieces[elem_index] << " (" << priority << ")" << std::endl; #endif int next_index = elem_index; TORRENT_ASSERT(m_piece_map[m_pieces[elem_index]].priority(this) == -1); @@ -690,11 +888,18 @@ namespace libtorrent void piece_picker::update(int priority, int elem_index) { TORRENT_ASSERT(!m_dirty); - TORRENT_ASSERT(priority >= 0); TORRENT_ASSERT(elem_index >= 0); - + TORRENT_ASSERT(elem_index < int(m_piece_map.size())); + TORRENT_ASSERT(priority >= 0); TORRENT_ASSERT(int(m_priority_boundries.size()) > priority); + // make sure the passed in elem_index actually lives in the specified + // priority bucket. If it doesn't, it means this piece changed + // state without updating the corresponding entry in the pieces list + TORRENT_ASSERT(m_priority_boundries[priority] >= elem_index); + TORRENT_ASSERT(priority == 0 || m_priority_boundries[priority-1] <= elem_index); + TORRENT_ASSERT(priority + 1 == m_priority_boundries.size() || m_priority_boundries[priority+1] > elem_index); + int index = m_pieces[elem_index]; // update the piece_map piece_pos& p = m_piece_map[index]; @@ -714,7 +919,7 @@ namespace libtorrent m_priority_boundries.resize(new_priority + 1, m_pieces.size()); #ifdef TORRENT_PICKER_LOG - std::cerr << "update " << index << " (" << priority << "->" << new_priority << ")" << std::endl; + std::cerr << "[" << this << "] " << "update " << index << " (" << priority << "->" << new_priority << ")" << std::endl; #endif if (priority > new_priority) { @@ -725,8 +930,10 @@ namespace libtorrent #ifdef TORRENT_PICKER_LOG print_pieces(); #endif + TORRENT_ASSERT(priority > 0); --priority; new_index = m_priority_boundries[priority]++; + TORRENT_ASSERT(new_index >= 0); TORRENT_ASSERT(new_index < int(m_pieces.size())); if (temp != m_pieces[new_index]) { @@ -762,7 +969,10 @@ namespace libtorrent #ifdef TORRENT_PICKER_LOG print_pieces(); #endif + TORRENT_ASSERT(priority >= 0); + TORRENT_ASSERT(priority < int(m_priority_boundries.size())); new_index = --m_priority_boundries[priority]; + TORRENT_ASSERT(new_index >= 0); TORRENT_ASSERT(new_index < int(m_pieces.size())); if (temp != m_pieces[new_index]) { @@ -795,7 +1005,7 @@ namespace libtorrent void piece_picker::shuffle(int priority, int elem_index) { #ifdef TORRENT_PICKER_LOG - std::cerr << "shuffle()" << std::endl; + std::cerr << "[" << this << "] " << "shuffle()" << std::endl; #endif TORRENT_ASSERT(!m_dirty); @@ -820,69 +1030,50 @@ namespace libtorrent p2.index = temp; std::swap(m_pieces[other_index], m_pieces[elem_index]); } -/* - void piece_picker::sort_piece(std::vector::iterator dp) - { - TORRENT_ASSERT(m_piece_map[dp->index].downloading); - int complete = dp->writing + dp->finished; - if (dp != m_downloads.begin()) - { - for (std::vector::iterator j(dp-1); - dp != m_downloads.begin(); --dp, --j) - { - TORRENT_ASSERT(j >= m_downloads.begin()); - if (j->finished + j->writing >= complete) break; - using std::swap; - swap(*j, *dp); - if (j == m_downloads.begin()) return; - } - } - TORRENT_ASSERT(dp != m_downloads.end()); - for (std::vector::iterator j(dp+1); - dp != m_downloads.end() - 1; ++dp, ++j) - { - TORRENT_ASSERT(j < m_downloads.end()); - if (j->finished + j->writing <= complete) break; - using std::swap; - swap(*j, *dp); - if (j == m_downloads.end() - 1) return; - } - } -*/ void piece_picker::restore_piece(int index) { TORRENT_PIECE_PICKER_INVARIANT_CHECK; +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "restore_piece(" << index << ")" << std::endl; +#endif TORRENT_ASSERT(index >= 0); TORRENT_ASSERT(index < (int)m_piece_map.size()); - TORRENT_ASSERT(m_piece_map[index].downloading == 1); + int state = m_piece_map[index].state; + TORRENT_ASSERT(state != piece_pos::piece_open); + if (state == piece_pos::piece_open) return; - std::vector::iterator i = find_dl_piece(index); + std::vector::iterator i = find_dl_piece(state - 1, index); - TORRENT_ASSERT(i != m_downloads.end()); + TORRENT_ASSERT(i != m_downloads[state - 1].end()); TORRENT_ASSERT(i->info >= &m_block_info[0] && i->info < &m_block_info[0] + m_block_info.size()); -#ifdef TORRENT_DEBUG - int num_blocks = blocks_in_piece(i->index); - for (int k = 0; k < num_blocks; ++k) - { - TORRENT_ASSERT(i->info[k].piece_index == index); - TORRENT_ASSERT(i->info[k].state == block_info::state_finished); - TORRENT_ASSERT(i->info[k].num_peers == 0); - } -#endif + + i->locked = false; piece_pos& p = m_piece_map[index]; int prev_priority = p.priority(this); erase_download_piece(i); int new_priority = p.priority(this); +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + if (new_priority == prev_priority) return; if (m_dirty) return; if (prev_priority == -1) add(index); else update(prev_priority, p.index); + +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif } void piece_picker::inc_refcount_all(const void* peer) @@ -899,7 +1090,6 @@ namespace libtorrent // didn't have any peers m_dirty = true; } - #ifdef TORRENT_DEBUG_REFCOUNTS for (std::vector::iterator i = m_piece_map.begin() , end(m_piece_map.end()); i != end; ++i) @@ -959,6 +1149,9 @@ namespace libtorrent TORRENT_PIECE_PICKER_INVARIANT_CHECK; #endif +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "inc_refcount(" << index << ")" << std::endl; +#endif piece_pos& p = m_piece_map[index]; #ifdef TORRENT_DEBUG_REFCOUNTS @@ -1005,13 +1198,12 @@ namespace libtorrent TORRENT_PIECE_PICKER_INVARIANT_CHECK; #endif - piece_pos& p = m_piece_map[index]; - -#ifdef TORRENT_DEBUG_REFCOUNTS - TORRENT_ASSERT(p.have_peers.count(peer) == 1); - p.have_peers.erase(peer); +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "dec_refcount(" << index << ")" << std::endl; #endif + piece_pos& p = m_piece_map[index]; + if (p.peer_count == 0) { TORRENT_ASSERT(m_seeds > 0); @@ -1023,6 +1215,12 @@ namespace libtorrent } int prev_priority = p.priority(this); + +#ifdef TORRENT_DEBUG_REFCOUNTS + TORRENT_ASSERT(p.have_peers.count(peer) == 1); + p.have_peers.erase(peer); +#endif + TORRENT_ASSERT(p.peer_count > 0); --p.peer_count; if (m_dirty) return; @@ -1034,7 +1232,69 @@ namespace libtorrent #ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS TORRENT_PIECE_PICKER_INVARIANT_CHECK; #endif - TORRENT_ASSERT(bitmask.size() == m_piece_map.size()); + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "inc_refcount(bitfield)" << std::endl; +#endif + + // nothing set, nothing to do here + if (bitmask.none_set()) return; + + if (bitmask.all_set() && bitmask.size() == m_piece_map.size()) + { + inc_refcount_all(peer); + return; + } + + const int size = (std::min)(50, int(bitmask.size()/2)); + + // this is an optimization where if just a few + // pieces end up changing, instead of making + // the piece list dirty, just update those pieces + // instead + int* incremented = TORRENT_ALLOCA(int, size); + int num_inc = 0; + + if (!m_dirty) + { + // first count how many pieces we're updating. If it's few (less than half) + // we'll just update them one at a time. Othewise we'll just update the counters + // and mark the picker as dirty, so we'll rebuild it next time we need it. + // this only matters if we're not already dirty, in which case the fasted + // thing to do is to just update the counters and be done + int index = 0; + for (bitfield::const_iterator i = bitmask.begin() + , end(bitmask.end()); i != end; ++i, ++index) + { + if (!*i) continue; + if (num_inc < size) incremented[num_inc] = index; + ++num_inc; + if (num_inc >= size) break; + } + + if (num_inc < size) + { + // not that many pieces were updated + // just update those individually instead of + // rebuilding the whole piece list + for (int i = 0; i < num_inc; ++i) + { + int piece = incremented[i]; + piece_pos& p = m_piece_map[piece]; + int prev_priority = p.priority(this); + ++p.peer_count; +#ifdef TORRENT_DEBUG_REFCOUNTS + TORRENT_ASSERT(p.have_peers.count(peer) == 0); + p.have_peers.insert(peer); +#endif + int new_priority = p.priority(this); + if (prev_priority == new_priority) continue; + else if (prev_priority >= 0) update(prev_priority, p.index); + else add(piece); + } + return; + } + } int index = 0; bool updated = false; @@ -1053,6 +1313,9 @@ namespace libtorrent } } + // if we're already dirty, no point in doing anything more + if (m_dirty) return; + if (updated) m_dirty = true; } @@ -1063,41 +1326,110 @@ namespace libtorrent #endif TORRENT_ASSERT(bitmask.size() <= m_piece_map.size()); +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "dec_refcount(bitfield)" << std::endl; +#endif + + // nothing set, nothing to do here + if (bitmask.none_set()) return; + + if (bitmask.all_set() && bitmask.size() == m_piece_map.size()) + { + dec_refcount_all(peer); + return; + } + + const int size = (std::min)(50, int(bitmask.size()/2)); + + // this is an optimization where if just a few + // pieces end up changing, instead of making + // the piece list dirty, just update those pieces + // instead + int* decremented = TORRENT_ALLOCA(int, size); + int num_dec = 0; + + if (!m_dirty) + { + // first count how many pieces we're updating. If it's few (less than half) + // we'll just update them one at a time. Othewise we'll just update the counters + // and mark the picker as dirty, so we'll rebuild it next time we need it. + // this only matters if we're not already dirty, in which case the fasted + // thing to do is to just update the counters and be done + int index = 0; + for (bitfield::const_iterator i = bitmask.begin() + , end(bitmask.end()); i != end; ++i, ++index) + { + if (!*i) continue; + if (num_dec < size) decremented[num_dec] = index; + ++num_dec; + if (num_dec >= size) break; + } + + if (num_dec < size) + { + // not that many pieces were updated + // just update those individually instead of + // rebuilding the whole piece list + for (int i = 0; i < num_dec; ++i) + { + int piece = decremented[i]; + piece_pos& p = m_piece_map[piece]; + int prev_priority = p.priority(this); + + if (p.peer_count == 0) + { + TORRENT_ASSERT(m_seeds > 0); + // this is the case where we have one or more + // seeds, and one of them saying: I don't have this + // piece anymore. we need to break up one of the seed + // counters into actual peer counters on the pieces + break_one_seed(); + } + +#ifdef TORRENT_DEBUG_REFCOUNTS + TORRENT_ASSERT(p.have_peers.count(peer) == 1); + p.have_peers.erase(peer); +#endif + TORRENT_ASSERT(p.peer_count > 0); + --p.peer_count; + if (!m_dirty && prev_priority >= 0) update(prev_priority, p.index); + } + return; + } + } + int index = 0; bool updated = false; -#if TORRENT_USE_ASSERTS - bool seed_broken = false; -#endif for (bitfield::const_iterator i = bitmask.begin() , end(bitmask.end()); i != end; ++i, ++index) { if (*i) { -#ifdef TORRENT_DEBUG_REFCOUNTS - TORRENT_ASSERT(m_piece_map[index].have_peers.count(peer) == 1); - m_piece_map[index].have_peers.erase(peer); -#endif piece_pos& p = m_piece_map[index]; - if (p.peer_count == 0) { - TORRENT_ASSERT(!seed_broken); TORRENT_ASSERT(m_seeds > 0); // this is the case where we have one or more // seeds, and one of them saying: I don't have this // piece anymore. we need to break up one of the seed // counters into actual peer counters on the pieces break_one_seed(); -#if TORRENT_USE_ASSERTS - seed_broken = true; -#endif } +#ifdef TORRENT_DEBUG_REFCOUNTS + TORRENT_ASSERT(p.have_peers.count(peer) == 1); + p.have_peers.erase(peer); +#endif + + TORRENT_ASSERT(p.peer_count > 0); --p.peer_count; updated = true; } } + // if we're already dirty, no point in doing anything more + if (m_dirty) return; + if (updated) m_dirty = true; } @@ -1106,7 +1438,7 @@ namespace libtorrent TORRENT_ASSERT(m_dirty); if (m_priority_boundries.empty()) m_priority_boundries.resize(1, 0); #ifdef TORRENT_PICKER_LOG - std::cerr << "update_pieces" << std::endl; + std::cerr << "[" << this << "] " << "update_pieces" << std::endl; #endif std::fill(m_priority_boundries.begin(), m_priority_boundries.end(), 0); for (std::vector::iterator i = m_piece_map.begin() @@ -1171,6 +1503,27 @@ namespace libtorrent #endif } + void piece_picker::piece_passed(int index) + { + piece_pos& p = m_piece_map[index]; + int state = p.state; + if (state == piece_pos::piece_open) return; + + std::vector::iterator i = find_dl_piece(state - 1, index); + TORRENT_ASSERT(i != m_downloads[state - 1].end()); + + TORRENT_ASSERT(i->locked == false); + if (i->locked) return; + + TORRENT_ASSERT(!i->passed_hash_check); + i->passed_hash_check = true; + ++m_num_passed; + + if (i->finished < blocks_in_piece(index)) return; + + we_have(index); + } + void piece_picker::we_dont_have(int index) { TORRENT_PIECE_PICKER_INVARIANT_CHECK; @@ -1178,13 +1531,31 @@ namespace libtorrent TORRENT_ASSERT(index < (int)m_piece_map.size()); piece_pos& p = m_piece_map[index]; - TORRENT_ASSERT(p.downloading == 0); + TORRENT_ASSERT(p.downloading() == false); #ifdef TORRENT_PICKER_LOG - std::cerr << "piece_picker::we_dont_have(" << index << ")" << std::endl; + std::cerr << "[" << this << "] " << "piece_picker::we_dont_have(" << index << ")" << std::endl; #endif - if (!p.have()) return; + if (!p.have()) + { + // even though we don't have the piece, it + // might still have passed hash check + int state = p.state; + if (state == piece_pos::piece_open) return; + std::vector::iterator i + = find_dl_piece(state - 1, index); + if (i->passed_hash_check) + { + i->passed_hash_check = false; + TORRENT_ASSERT(m_num_passed > 0); + --m_num_passed; + } + return; + } + + TORRENT_ASSERT(m_num_passed > 0); + --m_num_passed; if (p.filtered()) { ++m_num_filtered; @@ -1224,23 +1595,24 @@ namespace libtorrent TORRENT_ASSERT(index < (int)m_piece_map.size()); #ifdef TORRENT_PICKER_LOG - std::cerr << "piece_picker::we_have(" << index << ")" << std::endl; + std::cerr << "[" << this << "] " << "piece_picker::we_have(" << index << ")" << std::endl; #endif piece_pos& p = m_piece_map[index]; int info_index = p.index; int priority = p.priority(this); TORRENT_ASSERT(priority < int(m_priority_boundries.size()) || m_dirty); - if (p.downloading) + if (p.state != piece_pos::piece_open) { std::vector::iterator i - = find_dl_piece(index); - TORRENT_ASSERT(i != m_downloads.end()); + = find_dl_piece(p.state - 1, index); + TORRENT_ASSERT(i != m_downloads[p.state - 1].end()); + // decrement num_passed here to compensate + // for the unconditional increment further down + if (i->passed_hash_check) --m_num_passed; erase_download_piece(i); } - TORRENT_ASSERT(find_dl_piece(index) == m_downloads.end()); - if (p.have()) return; // maintain sparse_regions @@ -1270,6 +1642,7 @@ namespace libtorrent ++m_num_have_filtered; } ++m_num_have; + ++m_num_passed; p.set_have(); if (m_cursor == m_reverse_cursor - 1 && m_cursor == index) @@ -1309,6 +1682,10 @@ namespace libtorrent #ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS TORRENT_PIECE_PICKER_INVARIANT_CHECK; #endif + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "set_piece_priority(" << index << ", " << new_piece_priority << ")" << std::endl; +#endif TORRENT_ASSERT(new_piece_priority >= 0); TORRENT_ASSERT(new_piece_priority <= 7); TORRENT_ASSERT(index >= 0); @@ -1390,6 +1767,13 @@ namespace libtorrent p.piece_priority = new_piece_priority; int new_priority = p.priority(this); + if (p.state > 0) + { + std::vector::iterator i = find_dl_piece(p.state - 1, index); + if (i != m_downloads[p.state - 1].end()) + update_piece_state(i); + } + if (prev_priority == new_priority) return ret; if (m_dirty) return ret; @@ -1464,7 +1848,7 @@ namespace libtorrent // prefer_whole_pieces can be set if this peer should download // whole pieces rather than trying to download blocks from the // same piece as other peers. - // the void* is the pointer to the policy::peer of the peer we're + // the void* is the pointer to the torrent_peer of the peer we're // picking pieces from. This is used when downloading whole pieces, // to only pick from the same piece the same peer is downloading // from. state is supposed to be set to fast if the peer is downloading @@ -1500,15 +1884,24 @@ namespace libtorrent , std::vector& interesting_blocks, int num_blocks , int prefer_whole_pieces, void* peer, piece_state_t speed , int options, std::vector const& suggested_pieces - , int num_peers) const + , int num_peers + , counters& pc + ) const { - TORRENT_ASSERT(peer == 0 || static_cast(peer)->in_use); + TORRENT_ASSERT(peer == 0 || static_cast(peer)->in_use); // prevent the number of partial pieces to grow indefinitely // make this scale by the number of peers we have. For large // scale clients, we would have more peers, and allow a higher // threshold for the number of partials - if (int(m_downloads.size()) > m_num_pad_files + num_peers * 3 / 2) options |= prioritize_partials; + if (int(m_downloads[0].size()) > m_num_pad_files + num_peers * 3 / 2) + { + // if we have too many partial pieces, prioritize completing + // them. In order for this to have an affect, also disable + // prefer whole pieces (otherwise partial pieces would be de-prioritized) + options |= prioritize_partials; + prefer_whole_pieces = 0; + } if (options & ignore_whole_pieces) prefer_whole_pieces = 0; @@ -1541,16 +1934,17 @@ namespace libtorrent if (options & prioritize_partials) { - for (std::vector::const_iterator i = m_downloads.begin() - , end(m_downloads.end()); i != end; ++i) + for (std::vector::const_iterator i = m_downloads[0].begin() + , end(m_downloads[0].end()); i != end; ++i) { // in time critical mode, only pick prio 7 pieces if ((options & time_critical_mode) && piece_priority(i->index) != 7) continue; + pc.inc_stats_counter(counters::piece_picker_partial_loops); if (!is_piece_free(i->index, pieces)) continue; - if (m_piece_map[i->index].full - && int(backup_blocks.size()) >= num_blocks + TORRENT_ASSERT(m_piece_map[i->index].state == piece_pos::piece_downloading); + if (int(backup_blocks.size()) >= num_blocks && int(backup_blocks2.size()) >= num_blocks) continue; @@ -1578,6 +1972,7 @@ namespace libtorrent if ((options & time_critical_mode) && piece_priority(*i) != 7) continue; + pc.inc_stats_counter(counters::piece_picker_suggest_loops); if (!is_piece_free(*i, pieces)) continue; num_blocks = add_blocks(*i, pieces , interesting_blocks, backup_blocks @@ -1612,6 +2007,7 @@ namespace libtorrent { for (int i = m_reverse_cursor - 1; i >= m_cursor; --i) { + pc.inc_stats_counter(counters::piece_picker_sequential_loops); if (!is_piece_free(i, pieces)) continue; // we've already added prio 7 pieces if (piece_priority(i) == 7) continue; @@ -1626,7 +2022,8 @@ namespace libtorrent else { for (int i = m_cursor; i < m_reverse_cursor; ++i) - { + { + pc.inc_stats_counter(counters::piece_picker_sequential_loops); if (!is_piece_free(i, pieces)) continue; // we've already added prio 7 pieces if (piece_priority(i) == 7) continue; @@ -1670,6 +2067,8 @@ namespace libtorrent int start = prio == 0 ? 0 : m_priority_boundries[prio - 1]; for (int p = start; p < m_priority_boundries[prio]; ++p) { + pc.inc_stats_counter(counters::piece_picker_reverse_rare_loops); + if (!is_piece_free(m_pieces[p], pieces)) continue; num_blocks = add_blocks(m_pieces[p], pieces , interesting_blocks, backup_blocks @@ -1686,6 +2085,8 @@ namespace libtorrent for (std::vector::const_iterator i = m_pieces.begin(); i != m_pieces.end(); ++i) { + pc.inc_stats_counter(counters::piece_picker_rare_loops); + // in time critical mode, only pick prio 7 pieces // it's safe to break here because in this mode we // pick pieces in priority order. Once we hit a lower priority @@ -1694,6 +2095,7 @@ namespace libtorrent break; if (!is_piece_free(*i, pieces)) continue; + num_blocks = add_blocks(*i, pieces , interesting_blocks, backup_blocks , backup_blocks2, num_blocks @@ -1735,9 +2137,10 @@ namespace libtorrent // since we've already picked those while (!can_pick(piece, pieces) || std::find(suggested_pieces.begin() - , suggested_pieces.end(), piece) - != suggested_pieces.end()) + , suggested_pieces.end(), piece) + != suggested_pieces.end()) { + pc.inc_stats_counter(counters::piece_picker_rand_start_loops); ++piece; if (piece == int(m_piece_map.size())) piece = 0; // could not find any more pieces @@ -1746,19 +2149,20 @@ namespace libtorrent if (done) break; TORRENT_ASSERT(can_pick(piece, pieces)); - TORRENT_ASSERT(m_piece_map[piece].downloading == false); + TORRENT_ASSERT(m_piece_map[piece].downloading() == false); int start, end; - boost::tie(start, end) = expand_piece(piece, prefer_whole_pieces, pieces); + boost::tie(start, end) = expand_piece(piece, prefer_whole_pieces, pieces, options); for (int k = start; k < end; ++k) { - TORRENT_ASSERT(m_piece_map[k].downloading == false); + TORRENT_ASSERT(m_piece_map[k].downloading() == false); TORRENT_ASSERT(m_piece_map[k].priority(this) >= 0); int num_blocks_in_piece = blocks_in_piece(k); if (prefer_whole_pieces == 0 && num_blocks_in_piece > num_blocks) num_blocks_in_piece = num_blocks; for (int j = 0; j < num_blocks_in_piece; ++j) { + pc.inc_stats_counter(counters::piece_picker_rand_loops); TORRENT_ASSERT(is_piece_free(k, pieces)); interesting_blocks.push_back(piece_block(k, j)); --num_blocks; @@ -1769,58 +2173,16 @@ namespace libtorrent // could not find any more pieces if (piece == start_piece) break; } - } if (num_blocks <= 0) return; - // we might have to re-pick some backup blocks - // from full pieces, since we skipped those the - // first pass over - for (std::vector::const_iterator i = m_downloads.begin() - , end(m_downloads.end()); i != end; ++i) - { - if (!pieces[i->index]) continue; - // we've already considered the non-full pieces - if (!m_piece_map[i->index].full) continue; - std::vector temp; - add_blocks_downloading(*i, pieces - , temp, backup_blocks, backup_blocks2 - , num_blocks, prefer_whole_pieces, peer, speed, options); - } - #if TORRENT_USE_INVARIANT_CHECKS verify_pick(interesting_blocks, pieces); verify_pick(backup_blocks, pieces); verify_pick(backup_blocks2, pieces); #endif - std::vector temp; - for (std::vector::const_iterator i = m_downloads.begin() - , end(m_downloads.end()); i != end; ++i) - { - if (!pieces[i->index]) continue; - if (piece_priority(i->index) == 0) continue; - - int num_blocks_in_piece = blocks_in_piece(i->index); - - // fill in with blocks requested from other peers - // as backups - bool done = false; - for (int j = 0; j < num_blocks_in_piece; ++j) - { - block_info const& info = i->info[j]; - TORRENT_ASSERT(info.peer == 0 || static_cast(info.peer)->in_use); - TORRENT_ASSERT(info.piece_index == i->index); - if (info.state != block_info::state_requested - || info.peer == peer) - continue; - temp.push_back(piece_block(i->index, j)); - done = true; - } - if (done) break; - } - num_blocks = append_blocks(interesting_blocks, backup_blocks , num_blocks); if (num_blocks <= 0) return; @@ -1828,22 +2190,120 @@ namespace libtorrent num_blocks = append_blocks(interesting_blocks, backup_blocks2, num_blocks); if (num_blocks <= 0) return; + // ===== THIS IS FOR END-GAME MODE ===== + // don't double-pick anything if the peer is on parole if (options & on_parole) return; - // pick one random block from the first busy piece we encountered - // none of these blocks have more than one request to them - if (!temp.empty()) interesting_blocks.push_back(temp[random() % temp.size()]); + // in end game mode we pick a single block + // that has already been requested from someone + // all pieces that are interesting are in + // m_downloads[0] and m_download[1] (i.e. partial and full pieces) + + std::vector temp; + + // pick one random block from one random partial piece. + // only pick from non-downloaded blocks. + // first, create a temporary array of the partial pieces + // this peer has, and can pick from. Cap the stack allocation + // at 200 pieces. + + int partials_size = (std::min)(200, int(m_downloads[0].size() + + m_downloads[1].size())); + if (partials_size == 0) return; + + downloading_piece const** partials + = TORRENT_ALLOCA(downloading_piece const*, partials_size); + int c = 0; + +#ifdef TORRENT_DEBUG + for (std::vector::const_iterator i + = m_downloads[0].begin(), end(m_downloads[0].end()); i != end; ++i) + { + downloading_piece const& dp = *i; + + if ((options & time_critical_mode) && piece_priority(dp.index) != 7) + continue; + + // we either don't have this piece, or we've already requested from it + bool found = false; + for (std::vector::const_iterator i + = interesting_blocks.begin(), end(interesting_blocks.end()); + i != end; ++i) + { + if (i->piece_index != dp.index) continue; + found = true; + break; + } + TORRENT_ASSERT(!pieces[dp.index] || found || dp.locked); + } +#endif + + for (std::vector::const_iterator i + = m_downloads[1].begin(), end(m_downloads[1].end()); + i != end; ++i) + { + if (c == partials_size) break; + + downloading_piece const& dp = *i; + TORRENT_ASSERT(dp.requested > 0); + // this peer doesn't have this piece, try again + if (!pieces[dp.index]) continue; + // don't pick pieces with priority 0 + TORRENT_ASSERT(piece_priority(dp.index) > 0); + + if ((options & time_critical_mode) && piece_priority(dp.index) != 7) + continue; + + partials[c++] = &dp; + } + + partials_size = c; + while (partials_size > 0) + { + pc.inc_stats_counter(counters::piece_picker_busy_loops); + int piece = random() % partials_size; + downloading_piece const* dp = partials[piece]; + TORRENT_ASSERT(pieces[dp->index]); + TORRENT_ASSERT(piece_priority(dp->index) > 0); + // fill in with blocks requested from other peers + // as backups + int num_blocks_in_piece = blocks_in_piece(dp->index); + TORRENT_ASSERT(dp->requested > 0); + for (int j = 0; j < num_blocks_in_piece; ++j) + { + block_info const& info = dp->info[j]; + TORRENT_ASSERT(info.peer == 0 || static_cast(info.peer)->in_use); + TORRENT_ASSERT(info.piece_index == dp->index); + if (info.state != block_info::state_requested + || info.peer == peer) + continue; + temp.push_back(piece_block(dp->index, j)); + } + // are we done? + if (!temp.empty()) + { + interesting_blocks.push_back(temp[random() % temp.size()]); + --num_blocks; + break; + } + + // the piece we picked only had blocks outstanding requested + // by ourself. Remove it and pick another one. + partials[piece] = partials[partials_size-1]; + --partials_size; + } #ifdef TORRENT_DEBUG // make sure that we at this point have added requests to all unrequested blocks // in all downloading pieces - for (std::vector::const_iterator i = m_downloads.begin() - , end(m_downloads.end()); i != end; ++i) + for (std::vector::const_iterator i = m_downloads[0].begin() + , end(m_downloads[0].end()); i != end; ++i) { if (!pieces[i->index]) continue; if (piece_priority(i->index) == 0) continue; + if (i->locked) continue; if ((options & time_critical_mode) && piece_priority(i->index) != 7) continue; @@ -1864,8 +2324,8 @@ namespace libtorrent fprintf(stderr, "(%d, %d)", k->piece_index, k->block_index); fprintf(stderr, "\nnum_blocks: %d\n", num_blocks); - for (std::vector::const_iterator l = m_downloads.begin() - , end(m_downloads.end()); l != end; ++l) + for (std::vector::const_iterator l = m_downloads[0].begin() + , end(m_downloads[0].end()); l != end; ++l) { fprintf(stderr, "%d : ", l->index); int num_blocks_in_piece = blocks_in_piece(l->index); @@ -1878,7 +2338,7 @@ namespace libtorrent } } - if (interesting_blocks.empty() && !(options & time_critical_mode)) + if (interesting_blocks.empty()) { // print_pieces(); for (int i = 0; i < num_pieces(); ++i) @@ -1887,10 +2347,12 @@ namespace libtorrent if (m_piece_map[i].priority(this) <= 0) continue; if (have_piece(i)) continue; - std::vector::const_iterator k = find_dl_piece(i); + int state = m_piece_map[i].state; + if (state == 0) continue; + std::vector::const_iterator k = find_dl_piece(state - 1, i); - TORRENT_ASSERT(k != m_downloads.end()); - if (k == m_downloads.end()) continue; + TORRENT_ASSERT(k != m_downloads[state-1].end()); + if (k == m_downloads[state-1].end()) continue; // this assert is not valid for web_seeds /* @@ -1909,6 +2371,16 @@ namespace libtorrent } + // have piece means that the piece passed hash check + // AND has been successfully written to disk + bool piece_picker::have_piece(int index) const + { + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_piece_map.size())); + piece_pos const& p = m_piece_map[index]; + return p.index == piece_pos::we_have_index; + } + int piece_picker::blocks_in_piece(int index) const { TORRENT_ASSERT(index >= 0); @@ -1932,16 +2404,28 @@ namespace libtorrent TORRENT_ASSERT(piece >= 0 && piece < int(m_piece_map.size())); return bitmask[piece] && !m_piece_map[piece].have() - && !m_piece_map[piece].downloading + // TODO: when expanding pieces for cache stripe reasons, + // the !downloading condition doesn't make much sense + && !m_piece_map[piece].downloading() && !m_piece_map[piece].filtered(); } +#if TORRENT_USE_INVARIANT_CHECKS + void piece_picker::check_peers() + { + for (std::vector::iterator i = m_block_info.begin() + , end(m_block_info.end()); i != end; ++i) + { + TORRENT_ASSERT(i->peer == 0 || static_cast(i->peer)->in_use); + } + } +#endif + void piece_picker::clear_peer(void* peer) { for (std::vector::iterator i = m_block_info.begin() , end(m_block_info.end()); i != end; ++i) { - TORRENT_ASSERT(i->peer == 0 || static_cast(i->peer)->in_use); if (i->peer == peer) i->peer = 0; } } @@ -1960,7 +2444,7 @@ namespace libtorrent for (int j = 0; j < num_blocks_in_piece; ++j) { piece_picker::block_info const& info = p.info[j]; - TORRENT_ASSERT(info.peer == 0 || static_cast(info.peer)->in_use); + TORRENT_ASSERT(info.peer == 0 || static_cast(info.peer)->in_use); TORRENT_ASSERT(info.piece_index == p.index); if (info.state != piece_picker::block_info::state_none && info.peer != peer) @@ -1997,17 +2481,16 @@ namespace libtorrent // ignore pieces found in the ignore list if (std::find(ignore.begin(), ignore.end(), piece) != ignore.end()) return num_blocks; + if (m_piece_map[piece].state > piece_pos::piece_downloading) return num_blocks; TORRENT_ASSERT(m_piece_map[piece].priority(this) >= 0); - if (m_piece_map[piece].downloading) + if (m_piece_map[piece].state == piece_pos::piece_downloading) { - if (m_piece_map[piece].full) return num_blocks; - // if we're prioritizing partials, we've already // looked through the downloading pieces if (options & prioritize_partials) return num_blocks; - std::vector::const_iterator i = find_dl_piece(piece); - TORRENT_ASSERT(i != m_downloads.end()); + std::vector::const_iterator i = find_dl_piece(0, piece); + TORRENT_ASSERT(i != m_downloads[0].end()); // std::cout << "add_blocks_downloading(" << piece << ")" << std::endl; @@ -2031,7 +2514,7 @@ namespace libtorrent else { int start, end; - boost::tie(start, end) = expand_piece(piece, prefer_whole_pieces, pieces); + boost::tie(start, end) = expand_piece(piece, prefer_whole_pieces, pieces, options); for (int k = start; k < end; ++k) { TORRENT_ASSERT(m_piece_map[k].priority(this) > 0); @@ -2060,17 +2543,14 @@ namespace libtorrent , void* peer, piece_state_t speed, int options) const { if (!pieces[dp.index]) return num_blocks; - if (m_piece_map[dp.index].filtered()) return num_blocks; + TORRENT_ASSERT(!m_piece_map[dp.index].filtered()); + + // this piece failed to write. We're currently restoring + // it. It's not OK to send more requests to it right now. + if (dp.locked) return num_blocks; int num_blocks_in_piece = blocks_in_piece(dp.index); - // if all blocks have been requested (and we don't need any backup - // blocks), we might as well return immediately -/* if (int(backup_blocks2.size()) >= num_blocks - && int(backup_blocks.size()) >= num_blocks - && dp.requested + dp.writing + dp.finished == num_blocks_in_piece) - return num_blocks; -*/ // is true if all the other pieces that are currently // requested from this piece are from the same // peer as 'peer'. @@ -2158,20 +2638,38 @@ namespace libtorrent } std::pair piece_picker::expand_piece(int piece, int whole_pieces - , bitfield const& have) const + , bitfield const& have, int options) const { if (whole_pieces == 0) return std::make_pair(piece, piece + 1); - int start = piece - 1; - int lower_limit = piece - whole_pieces; - if (lower_limit < -1) lower_limit = -1; - while (start > lower_limit - && can_pick(start, have)) + int start = piece; + int lower_limit; + + if (options & align_expanded_pieces) + { + lower_limit = piece - (piece % whole_pieces); + } + else + { + lower_limit = piece - whole_pieces + 1; + if (lower_limit < 0) lower_limit = 0; + } + + while (start - 1 >= lower_limit + && can_pick(start - 1, have)) --start; - ++start; + TORRENT_ASSERT(start >= 0); int end = piece + 1; - int upper_limit = start + whole_pieces; + int upper_limit ; + if (options & align_expanded_pieces) + { + upper_limit = lower_limit + whole_pieces; + } + else + { + upper_limit = start + whole_pieces; + } if (upper_limit > int(m_piece_map.size())) upper_limit = int(m_piece_map.size()); while (end < upper_limit && can_pick(end, have)) @@ -2184,13 +2682,19 @@ namespace libtorrent TORRENT_ASSERT(index < (int)m_piece_map.size()); TORRENT_ASSERT(index >= 0); - if (m_piece_map[index].downloading == 0) + piece_pos const& p = m_piece_map[index]; + if (p.index == piece_pos::we_have_index) return true; + + int state = p.state; + if (state == piece_pos::piece_open) { - TORRENT_ASSERT(find_dl_piece(index) == m_downloads.end()); + TORRENT_ASSERT(find_dl_piece(0, index) == m_downloads[0].end()); + TORRENT_ASSERT(find_dl_piece(1, index) == m_downloads[1].end()); + TORRENT_ASSERT(find_dl_piece(2, index) == m_downloads[2].end()); return false; } - std::vector::const_iterator i = find_dl_piece(index); - TORRENT_ASSERT(i != m_downloads.end()); + std::vector::const_iterator i = find_dl_piece(state - 1,index); + TORRENT_ASSERT(i != m_downloads[state - 1].end()); TORRENT_ASSERT((int)i->finished <= m_blocks_per_piece); int max_blocks = blocks_in_piece(index); if (int(i->finished) + int(i->writing) < max_blocks) return false; @@ -2208,34 +2712,149 @@ namespace libtorrent return true; } - std::vector::iterator piece_picker::find_dl_piece(int index) + bool piece_picker::has_piece_passed(int index) const { -// return std::find_if(m_downloads.begin(), m_downloads.end(), has_index(index)); + TORRENT_ASSERT(index < (int)m_piece_map.size()); + TORRENT_ASSERT(index >= 0); + + piece_pos const& p = m_piece_map[index]; + if (p.index == piece_pos::we_have_index) return true; + + int state = p.state; + if (state == piece_pos::piece_open) + { + TORRENT_ASSERT(find_dl_piece(0, index) == m_downloads[0].end()); + TORRENT_ASSERT(find_dl_piece(1, index) == m_downloads[1].end()); + TORRENT_ASSERT(find_dl_piece(2, index) == m_downloads[2].end()); + return false; + } + std::vector::const_iterator i = find_dl_piece(state - 1,index); + TORRENT_ASSERT(i != m_downloads[state - 1].end()); + return i->passed_hash_check; + } + + std::vector::iterator piece_picker::find_dl_piece( + int queue, int index) + { + TORRENT_ASSERT(queue >= 0 && queue < num_download_categories); +// return std::find_if(m_downloads[queue].begin(), m_downloads[queue].end(), has_index(index)); downloading_piece cmp; cmp.index = index; std::vector::iterator i = std::lower_bound( - m_downloads.begin(), m_downloads.end(), cmp); - if (i == m_downloads.end()) return i; + m_downloads[queue].begin(), m_downloads[queue].end(), cmp); + if (i == m_downloads[queue].end()) return i; if (i->index == index) return i; - return m_downloads.end(); + return m_downloads[queue].end(); } - std::vector::const_iterator piece_picker::find_dl_piece(int index) const + std::vector::const_iterator piece_picker::find_dl_piece( + int queue, int index) const { -// return std::find_if(m_downloads.begin(), m_downloads.end(), has_index(index)); + TORRENT_ASSERT(queue >= 0 && queue < num_download_categories); +// return std::find_if(m_downloads[queue].begin(), m_downloads[queue].end(), has_index(index)); downloading_piece cmp; cmp.index = index; std::vector::const_iterator i = std::lower_bound( - m_downloads.begin(), m_downloads.end(), cmp); - if (i == m_downloads.end()) return i; + m_downloads[queue].begin(), m_downloads[queue].end(), cmp); + if (i == m_downloads[queue].end()) return i; if (i->index == index) return i; - return m_downloads.end(); + return m_downloads[queue].end(); } - void piece_picker::update_full(downloading_piece& dp) + std::vector::iterator piece_picker::update_piece_state( + std::vector::iterator dp) { - int num_blocks = blocks_in_piece(dp.index); - m_piece_map[dp.index].full = dp.requested + dp.finished + dp.writing == num_blocks; +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "update_piece_state(" << dp->index << ")" << std::endl; +#endif + + int num_blocks = blocks_in_piece(dp->index); + piece_pos& p = m_piece_map[dp->index]; + int current_state = p.state; + TORRENT_ASSERT(current_state != piece_pos::piece_open); + if (current_state == piece_pos::piece_open) + return dp; + + // this function is not allowed to create new downloading pieces + int new_state = 0; + if (p.filtered()) + { + new_state = piece_pos::piece_zero_prio; + } + else if (dp->requested + dp->finished + dp->writing == 0) + { + new_state = piece_pos::piece_open; + } + else if (dp->requested + dp->finished + dp->writing < num_blocks) + { + new_state = piece_pos::piece_downloading; + } + else if (dp->requested > 0) + { + TORRENT_ASSERT(dp->requested + dp->finished + dp->writing == num_blocks); + new_state = piece_pos::piece_full; + } + else + { + TORRENT_ASSERT(dp->finished + dp->writing == num_blocks); + new_state = piece_pos::piece_finished; + } + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << " new_state: " << new_state << " current_state: " << current_state << std::endl; +#endif + if (new_state == current_state) return dp; + if (new_state == piece_pos::piece_open) return dp; + + // assert that the iterator that was passed-in in fact lives in + // the correct list + TORRENT_ASSERT(find_dl_piece(current_state - 1, dp->index) == dp); + + // remove the download_piece from the list corresponding + // to the old state + downloading_piece dp_info = *dp; + m_downloads[current_state - 1].erase(dp); + + // insert the download_piece in the list corresponding to + // the new state + downloading_piece cmp; + cmp.index = dp_info.index; + std::vector::iterator i = std::lower_bound( + m_downloads[new_state - 1].begin(), m_downloads[new_state - 1].end(), cmp); + TORRENT_ASSERT(i == m_downloads[new_state - 1].end() || i->index != dp_info.index); + i = m_downloads[new_state - 1].insert(i, dp_info); + + int prio = p.priority(this); + p.state = new_state; +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << " " << dp_info.index << " state (" << current_state << " -> " << new_state << ")" << std::endl; +#endif + if (!m_dirty) + { + if (prio == -1 && p.priority(this) != -1) add(dp_info.index); + else if (prio != -1) update(prio, p.index); + } + + return i; + } + + int piece_picker::get_block_state(piece_block block) const + { + TORRENT_ASSERT(block.block_index != piece_block::invalid.block_index); + TORRENT_ASSERT(block.piece_index != piece_block::invalid.piece_index); + TORRENT_ASSERT(block.piece_index < m_piece_map.size()); + + // if we have the piece, the block state is considered finished + if (m_piece_map[block.piece_index].index == piece_pos::we_have_index) + return block_info::state_finished; + + int state = m_piece_map[block.piece_index].state; + if (state == piece_pos::piece_open) return block_info::state_none; + std::vector::const_iterator i = find_dl_piece(state - 1, block.piece_index); + + TORRENT_ASSERT(i != m_downloads[state - 1].end()); + TORRENT_ASSERT(i->info[block.block_index].piece_index == block.piece_index); + return i->info[block.block_index].state; } bool piece_picker::is_requested(piece_block block) const @@ -2247,10 +2866,11 @@ namespace libtorrent TORRENT_ASSERT(block.piece_index != piece_block::invalid.piece_index); TORRENT_ASSERT(block.piece_index < m_piece_map.size()); - if (m_piece_map[block.piece_index].downloading == 0) return false; - std::vector::const_iterator i = find_dl_piece(block.piece_index); + int state = m_piece_map[block.piece_index].state; + if (state == piece_pos::piece_open) return false; + std::vector::const_iterator i = find_dl_piece(state - 1, block.piece_index); - TORRENT_ASSERT(i != m_downloads.end()); + TORRENT_ASSERT(i != m_downloads[state - 1].end()); TORRENT_ASSERT(i->info >= &m_block_info[0] && i->info < &m_block_info[0] + m_block_info.size()); TORRENT_ASSERT(i->info[block.block_index].piece_index == block.piece_index); @@ -2267,9 +2887,10 @@ namespace libtorrent TORRENT_ASSERT(block.piece_index < m_piece_map.size()); if (m_piece_map[block.piece_index].index == piece_pos::we_have_index) return true; - if (m_piece_map[block.piece_index].downloading == 0) return false; - std::vector::const_iterator i = find_dl_piece(block.piece_index); - TORRENT_ASSERT(i != m_downloads.end()); + int state = m_piece_map[block.piece_index].state; + if (state == piece_pos::piece_open) return false; + std::vector::const_iterator i = find_dl_piece(state - 1, block.piece_index); + TORRENT_ASSERT(i != m_downloads[state-1].end()); TORRENT_ASSERT(i->info >= &m_block_info[0] && i->info < &m_block_info[0] + m_block_info.size()); TORRENT_ASSERT(i->info[block.block_index].piece_index == block.piece_index); @@ -2288,9 +2909,9 @@ namespace libtorrent piece_pos const& p = m_piece_map[block.piece_index]; if (p.index == piece_pos::we_have_index) return true; - if (p.downloading == 0) return false; - std::vector::const_iterator i = find_dl_piece(block.piece_index); - TORRENT_ASSERT(i != m_downloads.end()); + if (p.state == piece_pos::piece_open) return false; + std::vector::const_iterator i = find_dl_piece(p.state - 1, block.piece_index); + TORRENT_ASSERT(i != m_downloads[p.state - 1].end()); TORRENT_ASSERT(i->info >= &m_block_info[0] && i->info < &m_block_info[0] + m_block_info.size()); TORRENT_ASSERT(i->info[block.block_index].piece_index == block.piece_index); @@ -2300,7 +2921,11 @@ namespace libtorrent bool piece_picker::mark_as_downloading(piece_block block , void* peer, piece_state_t state) { - TORRENT_ASSERT(peer == 0 || static_cast(peer)->in_use); +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "mark_as_downloading( {" << block.piece_index << ", " << block.block_index << "} )" << std::endl; +#endif + + TORRENT_ASSERT(peer == 0 || static_cast(peer)->in_use); TORRENT_ASSERT(state != piece_picker::none); TORRENT_ASSERT(block.block_index != piece_block::invalid.block_index); TORRENT_ASSERT(block.piece_index != piece_block::invalid.piece_index); @@ -2309,7 +2934,7 @@ namespace libtorrent TORRENT_ASSERT(!m_piece_map[block.piece_index].have()); piece_pos& p = m_piece_map[block.piece_index]; - if (p.downloading == 0) + if (p.state == piece_pos::piece_open) { #ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS TORRENT_PIECE_PICKER_INVARIANT_CHECK; @@ -2317,33 +2942,42 @@ namespace libtorrent int prio = p.priority(this); TORRENT_ASSERT(prio < int(m_priority_boundries.size()) || m_dirty); - p.downloading = 1; + p.state = piece_pos::piece_downloading; if (prio >= 0 && !m_dirty) update(prio, p.index); - downloading_piece& dp = add_download_piece(block.piece_index); - dp.state = state; - block_info& info = dp.info[block.block_index]; + dlpiece_iter dp = add_download_piece(block.piece_index); + dp->state = state; + block_info& info = dp->info[block.block_index]; TORRENT_ASSERT(info.piece_index == block.piece_index); info.state = block_info::state_requested; info.peer = peer; info.num_peers = 1; - ++dp.requested; - update_full(dp); +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(info.peers.count(peer) == 0); + info.peers.insert(peer); +#endif + ++dp->requested; + // update_full may move the downloading piece to + // a different vector, so 'dp' may be invalid after + // this call + update_piece_state(dp); } else { #ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS TORRENT_PIECE_PICKER_INVARIANT_CHECK; #endif - std::vector::iterator i = find_dl_piece(block.piece_index); - TORRENT_ASSERT(i != m_downloads.end()); + std::vector::iterator i = find_dl_piece(p.state - 1, block.piece_index); + TORRENT_ASSERT(i != m_downloads[p.state - 1].end()); block_info& info = i->info[block.block_index]; TORRENT_ASSERT(&info >= &m_block_info[0]); TORRENT_ASSERT(&info < &m_block_info[0] + m_block_info.size()); TORRENT_ASSERT(info.piece_index == block.piece_index); if (info.state == block_info::state_writing || info.state == block_info::state_finished) + { return false; + } TORRENT_ASSERT(info.state == block_info::state_none || (info.state == block_info::state_requested && (info.num_peers > 0))); @@ -2352,9 +2986,13 @@ namespace libtorrent { info.state = block_info::state_requested; ++i->requested; - update_full(*i); + i = update_piece_state(i); } ++info.num_peers; +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(info.peers.count(peer) == 0); + info.peers.insert(peer); +#endif if (i->state == none) i->state = state; } return true; @@ -2368,10 +3006,10 @@ namespace libtorrent TORRENT_ASSERT(int(block.block_index) < blocks_in_piece(block.piece_index)); piece_pos const& p = m_piece_map[block.piece_index]; - if (!p.downloading) return 0; + if (!p.downloading()) return 0; - std::vector::const_iterator i = find_dl_piece(block.piece_index); - TORRENT_ASSERT(i != m_downloads.end()); + std::vector::const_iterator i = find_dl_piece(p.state - 1, block.piece_index); + TORRENT_ASSERT(i != m_downloads[p.state - 1].end()); block_info const& info = i->info[block.block_index]; TORRENT_ASSERT(&info >= &m_block_info[0]); @@ -2392,21 +3030,33 @@ namespace libtorrent *j = i->peer_count + m_seeds; } + int piece_picker::get_availability(int piece) const + { + TORRENT_ASSERT(piece >= 0 && piece < int(m_piece_map.size())); + return m_piece_map[piece].peer_count + m_seeds; + } + bool piece_picker::mark_as_writing(piece_block block, void* peer) { #ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS TORRENT_PIECE_PICKER_INVARIANT_CHECK; #endif - TORRENT_ASSERT(peer == 0 || static_cast(peer)->in_use); +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "mark_as_writing( {" << block.piece_index << ", " << block.block_index << "} )" << std::endl; +#endif + + TORRENT_ASSERT(peer == 0 || static_cast(peer)->in_use); TORRENT_ASSERT(block.block_index != piece_block::invalid.block_index); TORRENT_ASSERT(block.piece_index != piece_block::invalid.piece_index); TORRENT_ASSERT(block.piece_index < m_piece_map.size()); TORRENT_ASSERT(int(block.block_index) < blocks_in_piece(block.piece_index)); + // this is not valid for web peers + // TORRENT_ASSERT(peer != 0); piece_pos& p = m_piece_map[block.piece_index]; - if (p.downloading == 0) + if (p.downloading() == 0) { // if we already have this piece, just ignore this if (have_piece(block.piece_index)) return false; @@ -2417,28 +3067,31 @@ namespace libtorrent int prio = p.priority(this); TORRENT_ASSERT(prio < int(m_priority_boundries.size()) || m_dirty); - p.downloading = 1; + p.state = piece_pos::piece_downloading; // prio being -1 can happen if a block is requested before // the piece priority was set to 0 if (prio >= 0 && !m_dirty) update(prio, p.index); - downloading_piece& dp = add_download_piece(block.piece_index); - dp.state = none; - block_info& info = dp.info[block.block_index]; + dlpiece_iter dp = add_download_piece(block.piece_index); + dp->state = none; + block_info& info = dp->info[block.block_index]; TORRENT_ASSERT(&info >= &m_block_info[0]); TORRENT_ASSERT(&info < &m_block_info[0] + m_block_info.size()); TORRENT_ASSERT(info.piece_index == block.piece_index); info.state = block_info::state_writing; info.peer = peer; info.num_peers = 0; - dp.writing = 1; - update_full(dp); -// sort_piece(m_downloads.end()-1); +#if TORRENT_USE_ASSERTS + info.peers.clear(); +#endif + dp->writing = 1; + + update_piece_state(dp); } else { - std::vector::iterator i = find_dl_piece(block.piece_index); - TORRENT_ASSERT(i != m_downloads.end()); + std::vector::iterator i = find_dl_piece(p.state - 1, block.piece_index); + TORRENT_ASSERT(i != m_downloads[p.state - 1].end()); block_info& info = i->info[block.block_index]; TORRENT_ASSERT(&info >= &m_block_info[0]); @@ -2459,6 +3112,9 @@ namespace libtorrent // all other requests for this block should have been // cancelled now info.num_peers = 0; +#if TORRENT_USE_ASSERTS + info.peers.clear(); +#endif if (i->requested == 0) { @@ -2466,18 +3122,66 @@ namespace libtorrent // remove the fast/slow state from it i->state = none; } -// sort_piece(i); + update_piece_state(i); } return true; } + // calling this function prevents this piece from being picked + // by the piece picker until the pieces is restored. This allow + // the disk thread to synchronize and flush any failed state + // (used for disk write failures and piece hash failures). + void piece_picker::lock_piece(int piece) + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "lock_piece(" << piece << ")" << std::endl; +#endif + + int state = m_piece_map[piece].state; + if (state == piece_pos::piece_open) return; + std::vector::iterator i = find_dl_piece(state - 1, piece); + if (i == m_downloads[state - 1].end()) return; + + TORRENT_ASSERT(i->passed_hash_check == false); + if (i->passed_hash_check) + { + // it's not clear why this would happen, + // but it seems reasonable to not break the + // accounting over it. + i->passed_hash_check = false; + TORRENT_ASSERT(m_num_passed > 0); + --m_num_passed; + } + + // prevent this piece from being picked until it's restored + i->locked = true; + } + + // TODO: 3 it would be nice if this could be folded into lock_piece() + // the main distinction is that this also maintains the m_num_passed + // counter and the passed_hash_check member void piece_picker::write_failed(piece_block block) { TORRENT_PIECE_PICKER_INVARIANT_CHECK; - std::vector::iterator i = find_dl_piece(block.piece_index); - TORRENT_ASSERT(i != m_downloads.end()); - if (i == m_downloads.end()) return; +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "write_failed( {" << block.piece_index << ", " << block.block_index << "} )" << std::endl; +#endif + + int state = m_piece_map[block.piece_index].state; + if (state == piece_pos::piece_open) return; + std::vector::iterator i = find_dl_piece(state - 1, block.piece_index); + if (i == m_downloads[state - 1].end()) return; block_info& info = i->info[block.block_index]; TORRENT_ASSERT(&info >= &m_block_info[0]); @@ -2493,10 +3197,23 @@ namespace libtorrent if (info.state == block_info::state_writing) --i->writing; info.peer = 0; - info.state = block_info::state_none; + if (i->passed_hash_check) + { + // the hash was good, but we failed to write + // some of the blocks to disk, which means we + // can't consider the piece complete + i->passed_hash_check = false; + TORRENT_ASSERT(m_num_passed > 0); + --m_num_passed; + } - update_full(*i); + // prevent this hash job from actually completing + // this piece, by setting the failure state. + // the piece is unlocked in the call to restore_piece() + i->locked = true; + + i = update_piece_state(i); if (i->finished + i->writing + i->requested == 0) { @@ -2510,15 +3227,18 @@ namespace libtorrent if (prev_priority == -1) add(block.piece_index); else update(prev_priority, p.index); } - else - { -// sort_piece(i); - } } - void piece_picker::mark_as_finished(piece_block block, void* peer) + void piece_picker::mark_as_canceled(piece_block block, void* peer) { - TORRENT_ASSERT(peer == 0 || static_cast(peer)->in_use); +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "mark_as_cancelled( {" << block.piece_index << ", " << block.block_index << "} )" << std::endl; +#endif + +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + TORRENT_ASSERT(block.piece_index >= 0); TORRENT_ASSERT(block.block_index >= 0); TORRENT_ASSERT(block.piece_index < m_piece_map.size()); @@ -2526,7 +3246,54 @@ namespace libtorrent piece_pos& p = m_piece_map[block.piece_index]; - if (p.downloading == 0) + if (p.state == piece_pos::piece_open) return; + + std::vector::iterator i = find_dl_piece(p.state - 1, block.piece_index); + + TORRENT_ASSERT(i != m_downloads[p.state - 1].end()); + block_info& info = i->info[block.block_index]; + + if (info.state == block_info::state_finished) return; + + TORRENT_ASSERT(info.num_peers == 0); + info.peer = peer; + TORRENT_ASSERT(info.state == block_info::state_writing + || peer == 0); + TORRENT_ASSERT(i->writing >= 0); + if (info.state == block_info::state_writing) + { + --i->writing; + info.state = block_info::state_none; + } + else + { + TORRENT_ASSERT(info.state == block_info::state_none); + } + +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + } + + void piece_picker::mark_as_finished(piece_block block, void* peer) + { +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "mark_as_finished( {" << block.piece_index << ", " << block.block_index << "} )" << std::endl; +#endif + + TORRENT_ASSERT(peer == 0 || static_cast(peer)->in_use); + TORRENT_ASSERT(block.piece_index >= 0); + TORRENT_ASSERT(block.block_index >= 0); + TORRENT_ASSERT(block.piece_index < m_piece_map.size()); + TORRENT_ASSERT(int(block.block_index) < blocks_in_piece(block.piece_index)); + + piece_pos& p = m_piece_map[block.piece_index]; + + if (p.state == piece_pos::piece_open) { // if we already have this piece, just ignore this if (have_piece(block.piece_index)) return; @@ -2535,28 +3302,25 @@ namespace libtorrent TORRENT_PIECE_PICKER_INVARIANT_CHECK; #endif - TORRENT_ASSERT(peer == 0); int prio = p.priority(this); TORRENT_ASSERT(prio < int(m_priority_boundries.size()) || m_dirty); - p.downloading = 1; + p.state = piece_pos::piece_downloading; if (prio >= 0 && !m_dirty) update(prio, p.index); - downloading_piece& dp = add_download_piece(block.piece_index); - dp.state = none; - block_info& info = dp.info[block.block_index]; + dlpiece_iter dp = add_download_piece(block.piece_index); + dp->state = none; + block_info& info = dp->info[block.block_index]; TORRENT_ASSERT(&info >= &m_block_info[0]); TORRENT_ASSERT(&info < &m_block_info[0] + m_block_info.size()); TORRENT_ASSERT(info.piece_index == block.piece_index); info.peer = peer; TORRENT_ASSERT(info.state == block_info::state_none); TORRENT_ASSERT(info.num_peers == 0); - if (info.state != block_info::state_finished) - { - ++dp.finished; -// sort_piece(m_downloads.end() - 1); - } + ++dp->finished; info.state = block_info::state_finished; + // dp may be invalid after this call + update_piece_state(dp); } else { @@ -2564,8 +3328,8 @@ namespace libtorrent TORRENT_PIECE_PICKER_INVARIANT_CHECK; #endif - std::vector::iterator i = find_dl_piece(block.piece_index); - TORRENT_ASSERT(i != m_downloads.end()); + std::vector::iterator i = find_dl_piece(p.state - 1, block.piece_index); + TORRENT_ASSERT(i != m_downloads[p.state - 1].end()); block_info& info = i->info[block.block_index]; TORRENT_ASSERT(&info >= &m_block_info[0]); TORRENT_ASSERT(&info < &m_block_info[0] + m_block_info.size()); @@ -2582,12 +3346,10 @@ namespace libtorrent if (info.state != block_info::state_writing || peer != 0) info.peer = peer; - TORRENT_ASSERT(info.state == block_info::state_writing - || peer == 0); - TORRENT_ASSERT(i->writing >= 0); ++i->finished; if (info.state == block_info::state_writing) { + TORRENT_ASSERT(i->writing > 0); --i->writing; info.state = block_info::state_finished; } @@ -2595,41 +3357,98 @@ namespace libtorrent { TORRENT_ASSERT(info.state == block_info::state_none); info.state = block_info::state_finished; -// sort_piece(i); } + + i = update_piece_state(i); + + if (i->finished < blocks_in_piece(i->index)) + return; + + if (i->passed_hash_check) + we_have(i->index); + } + +#if TORRENT_USE_INVARIANT_CHECKS + check_piece_state(); +#endif + + } + + void piece_picker::mark_as_checking(int index) + { + int state = m_piece_map[index].state; + if (state == piece_pos::piece_open) return; + std::vector::iterator i = find_dl_piece(state - 1, index); + if (i == m_downloads[state - 1].end()) return; + TORRENT_ASSERT(i->outstanding_hash_check == false); + i->outstanding_hash_check = true; + } + + void piece_picker::mark_as_done_checking(int index) + { + int state = m_piece_map[index].state; + if (state == piece_pos::piece_open) return; + std::vector::iterator i = find_dl_piece(state - 1, index); + if (i == m_downloads[state - 1].end()) return; + i->outstanding_hash_check = false; + } + + void piece_picker::get_requestors(std::vector& d, int index) const + { + TORRENT_ASSERT(index >= 0 && index <= (int)m_piece_map.size()); + + int state = m_piece_map[index].state; + TORRENT_ASSERT(state != piece_pos::piece_open); + std::vector::const_iterator i = find_dl_piece(state - 1, index); + TORRENT_ASSERT(i != m_downloads[state - 1].end()); + + d.clear(); + for (int j = 0, end(blocks_in_piece(index)); j != end; ++j) + { + if (i->info[j].state != block_info::state_requested) continue; + if (i->info[j].peer == 0) continue; + d.push_back(i->info[j].peer); } } void piece_picker::get_downloaders(std::vector& d, int index) const { TORRENT_ASSERT(index >= 0 && index <= (int)m_piece_map.size()); - std::vector::const_iterator i = find_dl_piece(index); - TORRENT_ASSERT(i != m_downloads.end()); - TORRENT_ASSERT(i->info >= &m_block_info[0] - && i->info < &m_block_info[0] + m_block_info.size()); d.clear(); + int state = m_piece_map[index].state; + if (state == piece_pos::piece_open) + { + int num_blocks = blocks_in_piece(index); + for (int i = 0; i < num_blocks; ++i) d.push_back(0); + return; + } + + std::vector::const_iterator i = find_dl_piece(state - 1, index); + TORRENT_ASSERT(i != m_downloads[state - 1].end()); + TORRENT_ASSERT(i->info >= &m_block_info[0] + && i->info < &m_block_info[0] + m_block_info.size()); for (int j = 0, end(blocks_in_piece(index)); j != end; ++j) { - TORRENT_ASSERT(i->info[j].peer == 0 || static_cast(i->info[j].peer)->in_use); + TORRENT_ASSERT(i->info[j].peer == 0 || static_cast(i->info[j].peer)->in_use); d.push_back(i->info[j].peer); } } void* piece_picker::get_downloader(piece_block block) const { - std::vector::const_iterator i = find_dl_piece(block.piece_index); + int state = m_piece_map[block.piece_index].state; + if (state == piece_pos::piece_open) return 0; - if (i == m_downloads.end()) return 0; + std::vector::const_iterator i = find_dl_piece(state - 1, block.piece_index); TORRENT_ASSERT(block.block_index != piece_block::invalid.block_index); - TORRENT_ASSERT(i->info[block.block_index].piece_index == block.piece_index); if (i->info[block.block_index].state == block_info::state_none) return 0; void* peer = i->info[block.block_index].peer; - TORRENT_ASSERT(peer == 0 || static_cast(peer)->in_use); + TORRENT_ASSERT(peer == 0 || static_cast(peer)->in_use); return peer; } @@ -2641,72 +3460,71 @@ namespace libtorrent TORRENT_PIECE_PICKER_INVARIANT_CHECK; #endif - TORRENT_ASSERT(peer == 0 || static_cast(peer)->in_use); +#ifdef TORRENT_PICKER_LOG + std::cerr << "[" << this << "] " << "abort_download( {" << block.piece_index << ", " << block.block_index << "} )" << std::endl; +#endif + TORRENT_ASSERT(peer == 0 || static_cast(peer)->in_use); TORRENT_ASSERT(block.block_index != piece_block::invalid.block_index); TORRENT_ASSERT(block.piece_index != piece_block::invalid.piece_index); TORRENT_ASSERT(block.piece_index < m_piece_map.size()); TORRENT_ASSERT(int(block.block_index) < blocks_in_piece(block.piece_index)); - if (m_piece_map[block.piece_index].downloading == 0) - { - TORRENT_ASSERT(find_dl_piece(block.piece_index) == m_downloads.end()); - return; - } + int state = m_piece_map[block.piece_index].state; + if (state == piece_pos::piece_open) return; - std::vector::iterator i = find_dl_piece(block.piece_index); - TORRENT_ASSERT(i != m_downloads.end()); + std::vector::iterator i = find_dl_piece(state - 1, block.piece_index); + TORRENT_ASSERT(i != m_downloads[state - 1].end()); block_info& info = i->info[block.block_index]; TORRENT_ASSERT(&info >= &m_block_info[0]); TORRENT_ASSERT(&info < &m_block_info[0] + m_block_info.size()); - TORRENT_ASSERT(info.peer == 0 || static_cast(info.peer)->in_use); + TORRENT_ASSERT(info.peer == 0 || static_cast(info.peer)->in_use); TORRENT_ASSERT(info.piece_index == block.piece_index); TORRENT_ASSERT(info.state != block_info::state_none); - if (info.state == block_info::state_finished - || info.state == block_info::state_none - || info.state == block_info::state_writing) - return; + if (info.state != block_info::state_requested) return; - if (info.state == block_info::state_requested) - { - TORRENT_ASSERT(info.num_peers > 0); - if (info.num_peers > 0) --info.num_peers; - if (info.peer == peer) info.peer = 0; + piece_pos& p = m_piece_map[block.piece_index]; + int prev_prio = p.priority(this); - TORRENT_ASSERT(int(block.block_index) < blocks_in_piece(block.piece_index)); +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(info.peers.count(peer)); + info.peers.erase(peer); +#endif + TORRENT_ASSERT(info.num_peers > 0); + if (info.num_peers > 0) --info.num_peers; + if (info.peer == peer) info.peer = 0; + TORRENT_ASSERT(info.peers.size() == info.num_peers); - // if there are other peers, leave the block requested - if (info.num_peers > 0) return; + TORRENT_ASSERT(int(block.block_index) < blocks_in_piece(block.piece_index)); - // clear the downloader of this block - info.peer = 0; + // if there are other peers, leave the block requested + if (info.num_peers > 0) return; - // clear this block as being downloaded - info.state = block_info::state_none; - --i->requested; - update_full(*i); - } + // clear the downloader of this block + info.peer = 0; + + // clear this block as being downloaded + info.state = block_info::state_none; + TORRENT_ASSERT(i->requested > 0); + --i->requested; // if there are no other blocks in this piece // that's being downloaded, remove it from the list if (i->requested + i->finished + i->writing == 0) { - piece_pos& p = m_piece_map[block.piece_index]; - int prev_prio = p.priority(this); TORRENT_ASSERT(prev_prio < int(m_priority_boundries.size()) || m_dirty); erase_download_piece(i); + int prio = p.priority(this); if (!m_dirty) { - int prio = p.priority(this); if (prev_prio == -1 && prio >= 0) add(block.piece_index); else if (prev_prio >= 0) update(prev_prio, p.index); } - - TORRENT_ASSERT(find_dl_piece(block.piece_index) == m_downloads.end()); + return; } else if (i->requested == 0) { @@ -2714,15 +3532,20 @@ namespace libtorrent // remove the fast/slow state from it i->state = none; } + + i = update_piece_state(i); } int piece_picker::unverified_blocks() const { int counter = 0; - for (std::vector::const_iterator i = m_downloads.begin(); - i != m_downloads.end(); ++i) + for (int k = 0; k < num_download_categories; ++k) { - counter += (int)i->finished; + for (std::vector::const_iterator i = m_downloads[k].begin(); + i != m_downloads[k].end(); ++i) + { + counter += (int)i->finished; + } } return counter; } diff --git a/src/platform_util.cpp b/src/platform_util.cpp new file mode 100644 index 000000000..aa3628da3 --- /dev/null +++ b/src/platform_util.cpp @@ -0,0 +1,100 @@ +/* + +Copyright (c) 2012-2013, 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. + +*/ + +#include "libtorrent/config.hpp" +#include + +#if TORRENT_USE_RLIMIT +#include +#endif + +#ifdef TORRENT_BSD +#include +#include +#endif + +#if defined TORRENT_WINDOWS +#include +#endif + +namespace libtorrent +{ + + boost::uint64_t total_physical_ram() + { + // figure out how much physical RAM there is in + // this machine. This is used for automatically + // sizing the disk cache size when it's set to + // automatic. + boost::uint64_t ret = 0; + +#ifdef TORRENT_BSD +#ifdef HW_MEMSIZE + int mib[2] = { CTL_HW, HW_MEMSIZE }; +#else + // not entirely sure this sysctl supports 64 + // bit return values, but it's probably better + // than not building + int mib[2] = { CTL_HW, HW_PHYSMEM }; +#endif + size_t len = sizeof(ret); + if (sysctl(mib, 2, &ret, &len, NULL, 0) != 0) + ret = 0; +#elif defined TORRENT_WINDOWS + MEMORYSTATUSEX ms; + ms.dwLength = sizeof(MEMORYSTATUSEX); + if (GlobalMemoryStatusEx(&ms)) + ret = ms.ullTotalPhys; + else + ret = 0; +#elif defined TORRENT_LINUX + ret = sysconf(_SC_PHYS_PAGES); + ret *= sysconf(_SC_PAGESIZE); +#elif defined TORRENT_AMIGA + ret = AvailMem(MEMF_PUBLIC); +#endif + +#if TORRENT_USE_RLIMIT + if (ret > 0) + { + struct rlimit r; + if (getrlimit(RLIMIT_AS, &r) == 0 && r.rlim_cur != RLIM_INFINITY) + { + if (ret > r.rlim_cur) + ret = r.rlim_cur; + } + } +#endif + return ret; + } +} + diff --git a/src/policy.cpp b/src/policy.cpp index a4af151b5..3693cb9a6 100644 --- a/src/policy.cpp +++ b/src/policy.cpp @@ -36,7 +36,6 @@ POSSIBILITY OF SUCH DAMAGE. #include #include -#include #ifdef _MSC_VER #pragma warning(pop) @@ -45,22 +44,32 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/peer_connection.hpp" #include "libtorrent/web_peer_connection.hpp" #include "libtorrent/policy.hpp" -#include "libtorrent/torrent.hpp" #include "libtorrent/socket.hpp" -#include "libtorrent/alert_types.hpp" +#include "libtorrent/socket_type.hpp" #include "libtorrent/invariant_check.hpp" #include "libtorrent/time.hpp" -#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/aux_/session_interface.hpp" #include "libtorrent/piece_picker.hpp" #include "libtorrent/broadcast_socket.hpp" #include "libtorrent/peer_info.hpp" #include "libtorrent/random.hpp" #include "libtorrent/extensions.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/torrent_peer_allocator.hpp" #ifdef TORRENT_DEBUG #include "libtorrent/bt_peer_connection.hpp" #endif +#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS +#include "libtorrent/socket_io.hpp" // for print_endpoint +#endif + +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING +#include "libtorrent/socket_io.hpp" // for print_endpoint +#include "libtorrent/ip_voter.hpp" // for external_ip +#endif + namespace { using namespace libtorrent; @@ -71,7 +80,7 @@ namespace : m_ep(ep) {} - bool operator()(policy::peer const* p) const + bool operator()(torrent_peer const* p) const { TORRENT_ASSERT(p->in_use); return p->address() == m_ep.address() && p->port == m_ep.port(); @@ -83,22 +92,22 @@ namespace #if TORRENT_USE_ASSERTS struct match_peer_connection { - match_peer_connection(peer_connection const& c) : m_conn(c) {} + match_peer_connection(peer_connection_interface const& c) : m_conn(c) {} - bool operator()(policy::peer const* p) const + bool operator()(torrent_peer const* p) const { TORRENT_ASSERT(p->in_use); return p->connection == &m_conn; } - peer_connection const& m_conn; + peer_connection_interface const& m_conn; }; struct match_peer_connection_or_endpoint { - match_peer_connection_or_endpoint(peer_connection const& c) : m_conn(c) {} + match_peer_connection_or_endpoint(peer_connection_interface const& c) : m_conn(c) {} - bool operator()(policy::peer const* p) const + bool operator()(torrent_peer const* p) const { TORRENT_ASSERT(p->in_use); return p->connection == &m_conn @@ -106,7 +115,7 @@ namespace && p->connectable); } - peer_connection const& m_conn; + peer_connection_interface const& m_conn; }; #endif @@ -114,389 +123,38 @@ namespace namespace libtorrent { - - void apply_mask(boost::uint8_t* b, boost::uint8_t const* mask, int size) - { - for (int i = 0; i < size; ++i) - { - *b &= *mask; - ++b; - ++mask; - } - } - - // 1. if the IP addresses are identical, hash the ports in 16 bit network-order - // binary representation, ordered lowest first. - // 2. if the IPs are in the same /24, hash the IPs ordered, lowest first. - // 3. if the IPs are in the ame /16, mask the IPs by 0xffffff55, hash them - // ordered, lowest first. - // 4. if IPs are not in the same /16, mask the IPs by 0xffff5555, hash them - // ordered, lowest first. - // - // * for IPv6 peers, just use the first 64 bits and widen the masks. - // like this: 0xffff5555 -> 0xffffffff55555555 - // the lower 64 bits are always unmasked - // - // * for IPv6 addresses, compare /32 and /48 instead of /16 and /24 - // - // * the two IP addresses that are used to calculate the rank must - // always be of the same address family - // - // * all IP addresses are in network byte order when hashed - boost::uint32_t peer_priority(tcp::endpoint e1, tcp::endpoint e2) - { - TORRENT_ASSERT(e1.address().is_v4() == e2.address().is_v4()); - - using std::swap; - - // this is the crc32c (Castagnoli) polynomial - // TODO: 2 this could be optimized if SSE 4.2 is - // available. It could also be optimized given - // that we have a fixed length - boost::crc_optimal<32, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, true, true> crc; - - if (e1.address() == e2.address()) - { - if (e1.port() > e2.port()) - swap(e1, e2); - boost::uint16_t p[2]; - p[0] = htons(e1.port()); - p[1] = htons(e2.port()); - crc.process_bytes((char const*)&p[0], 4); - } -#if TORRENT_USE_IPV6 - else if (e1.address().is_v6()) - { - const static boost::uint8_t v6mask[][8] = { - { 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55 }, - { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55 }, - { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } - }; - - if (e2 < e1) swap(e1, e2); - address_v6::bytes_type b1 = e1.address().to_v6().to_bytes(); - address_v6::bytes_type b2 = e2.address().to_v6().to_bytes(); - int mask = memcmp(&b1[0], &b2[0], 4) ? 0 - : memcmp(&b1[0], &b2[0], 6) ? 1 : 2; - apply_mask(&b1[0], v6mask[mask], 8); - apply_mask(&b2[0], v6mask[mask], 8); - - crc.process_bytes((char const*)&b1[0], 16); - crc.process_bytes((char const*)&b2[0], 16); - } -#endif - else - { - const static boost::uint8_t v4mask[][4] = { - { 0xff, 0xff, 0x55, 0x55 }, - { 0xff, 0xff, 0xff, 0x55 }, - { 0xff, 0xff, 0xff, 0xff } - }; - - if (e2 < e1) swap(e1, e2); - address_v4::bytes_type b1 = e1.address().to_v4().to_bytes(); - address_v4::bytes_type b2 = e2.address().to_v4().to_bytes(); - int mask = memcmp(&b1[0], &b2[0], 2) ? 0 - : memcmp(&b1[0], &b2[0], 3) ? 1 : 2; - apply_mask(&b1[0], v4mask[mask], 4); - apply_mask(&b2[0], v4mask[mask], 4); - - crc.process_bytes((char const*)&b1[0], 4); - crc.process_bytes((char const*)&b2[0], 4); - } - - return crc.checksum(); - } - - // returns the rank of a peer's source. We have an affinity - // to connecting to peers with higher rank. This is to avoid - // problems when our peer list is diluted by stale peers from - // the resume data for instance - int source_rank(int source_bitmask) - { - int ret = 0; - if (source_bitmask & peer_info::tracker) ret |= 1 << 5; - if (source_bitmask & peer_info::lsd) ret |= 1 << 4; - if (source_bitmask & peer_info::dht) ret |= 1 << 3; - if (source_bitmask & peer_info::pex) ret |= 1 << 2; - return ret; - } - - // the case where ignore_peer is motivated is if two peers - // have only one piece that we don't have, and it's the - // same piece for both peers. Then they might get into an - // infinite loop, fighting to request the same blocks. - void request_a_block(torrent& t, peer_connection& c) - { - if (t.is_seed()) return; - if (c.no_download()) return; - if (t.upload_mode()) return; - if (c.is_disconnecting()) return; - - // don't request pieces before we have the metadata - if (!t.valid_metadata()) return; - - // don't request pieces before the peer is properly - // initialized after we have the metadata - if (!t.are_files_checked()) return; - - TORRENT_ASSERT(t.valid_metadata()); - TORRENT_ASSERT(c.peer_info_struct() != 0 || c.type() != peer_connection::bittorrent_connection); - - bool time_critical_mode = t.num_time_critical_pieces() > 0; - - int desired_queue_size = c.desired_queue_size(); - - // in time critical mode, only have 1 outstanding request at a time - // via normal requests - if (time_critical_mode) - desired_queue_size = (std::min)(1, desired_queue_size); - - int num_requests = desired_queue_size - - (int)c.download_queue().size() - - (int)c.request_queue().size(); - -#ifdef TORRENT_VERBOSE_LOGGING - c.peer_log("*** PIECE_PICKER [ req: %d engame: %d ]", num_requests, c.endgame()); -#endif - TORRENT_ASSERT(c.desired_queue_size() > 0); - // if our request queue is already full, we - // don't have to make any new requests yet - if (num_requests <= 0) return; - - piece_picker& p = t.picker(); - std::vector interesting_pieces; - interesting_pieces.reserve(100); - - int prefer_whole_pieces = c.prefer_whole_pieces(); - - if (prefer_whole_pieces == 0 && !time_critical_mode) - { - prefer_whole_pieces = c.statistics().download_payload_rate() - * t.settings().whole_pieces_threshold - > t.torrent_file().piece_length() ? 1 : 0; - } - - // if we prefer whole pieces, the piece picker will pick at least - // the number of blocks we want, but it will try to make the picked - // blocks be from whole pieces, possibly by returning more blocks - // than we requested. -#ifdef TORRENT_DEBUG - error_code ec; - TORRENT_ASSERT(c.remote() == c.get_socket()->remote_endpoint(ec) || ec); -#endif - - aux::session_impl& ses = t.session(); - - std::vector const& dq = c.download_queue(); - std::vector const& rq = c.request_queue(); - - std::vector const& suggested = c.suggested_pieces(); - bitfield const* bits = &c.get_bitfield(); - bitfield fast_mask; - - if (c.has_peer_choked()) - { - // if we are choked we can only pick pieces from the - // allowed fast set. The allowed fast set is sorted - // in ascending priority order - std::vector const& allowed_fast = c.allowed_fast(); - - // build a bitmask with only the allowed pieces in it - fast_mask.resize(c.get_bitfield().size(), false); - for (std::vector::const_iterator i = allowed_fast.begin() - , end(allowed_fast.end()); i != end; ++i) - if ((*bits)[*i]) fast_mask.set_bit(*i); - bits = &fast_mask; - } - - piece_picker::piece_state_t state; - peer_connection::peer_speed_t speed = c.peer_speed(); - if (speed == peer_connection::fast) state = piece_picker::fast; - else if (speed == peer_connection::medium) state = piece_picker::medium; - else state = piece_picker::slow; - - // picks the interesting pieces from this peer - // the integer is the number of pieces that - // should be guaranteed to be available for download - // (if num_requests is too big, too many pieces are - // picked and cpu-time is wasted) - // the last argument is if we should prefer whole pieces - // for this peer. If we're downloading one piece in 20 seconds - // then use this mode. - p.pick_pieces(*bits, interesting_pieces - , num_requests, prefer_whole_pieces, c.peer_info_struct() - , state, c.picker_options(), suggested, t.num_peers()); - -#ifdef TORRENT_VERBOSE_LOGGING - c.peer_log("*** PIECE_PICKER [ prefer_whole: %d picked: %d ]" - , prefer_whole_pieces, int(interesting_pieces.size())); -#endif - - // if the number of pieces we have + the number of pieces - // we're requesting from is less than the number of pieces - // in the torrent, there are still some unrequested pieces - // and we're not strictly speaking in end-game mode yet - // also, if we already have at least one outstanding - // request, we shouldn't pick any busy pieces either - // in time critical mode, it's OK to request busy blocks - bool dont_pick_busy_blocks = ((ses.m_settings.strict_end_game_mode - && p.num_downloading_pieces() < p.num_want_left()) - || dq.size() + rq.size() > 0) - && !time_critical_mode; - - // this is filled with an interesting piece - // that some other peer is currently downloading - piece_block busy_block = piece_block::invalid; - - for (std::vector::iterator i = interesting_pieces.begin(); - i != interesting_pieces.end(); ++i) - { -#ifdef TORRENT_STATS - ++ses.m_piece_picker_blocks; -#endif - - if (prefer_whole_pieces == 0 && num_requests <= 0) break; - - if (time_critical_mode && p.piece_priority(i->piece_index) != 7) - { - // assume the subsequent pieces are not prio 7 and - // be done - break; - } - - int num_block_requests = p.num_peers(*i); - if (num_block_requests > 0) - { - // have we picked enough pieces? - if (num_requests <= 0) break; - - // this block is busy. This means all the following blocks - // in the interesting_pieces list are busy as well, we might - // as well just exit the loop - if (dont_pick_busy_blocks) break; - - TORRENT_ASSERT(p.num_peers(*i) > 0); - busy_block = *i; - continue; - } - - TORRENT_ASSERT(p.num_peers(*i) == 0); - - // don't request pieces we already have in our request queue - // This happens when pieces time out or the peer sends us - // pieces we didn't request. Those aren't marked in the - // piece picker, but we still keep track of them in the - // download queue - if (std::find_if(dq.begin(), dq.end(), has_block(*i)) != dq.end() - || std::find_if(rq.begin(), rq.end(), has_block(*i)) != rq.end()) - { -#ifdef TORRENT_DEBUG - std::vector::const_iterator j - = std::find_if(dq.begin(), dq.end(), has_block(*i)); - if (j != dq.end()) TORRENT_ASSERT(j->timed_out || j->not_wanted); -#endif - continue; - } - - // ok, we found a piece that's not being downloaded - // by somebody else. request it from this peer - // and return - if (!c.add_request(*i, 0)) continue; - TORRENT_ASSERT(p.num_peers(*i) == 1); - TORRENT_ASSERT(p.is_requested(*i)); - num_requests--; - } - - // we have picked as many blocks as we should - // we're done! - if (num_requests <= 0) - { - // since we could pick as many blocks as we - // requested without having to resort to picking - // busy ones, we're not in end-game mode - c.set_endgame(false); - return; - } - - // we did not pick as many pieces as we wanted, because - // there aren't enough. This means we're in end-game mode - // as long as we have at least one request outstanding, - // we shouldn't pick another piece - // if we are attempting to download 'allowed' pieces - // and can't find any, that doesn't count as end-game - if (!c.has_peer_choked()) - c.set_endgame(true); - - // if we don't have any potential busy blocks to request - // or if we already have outstanding requests, don't - // pick a busy piece - if (busy_block == piece_block::invalid - || dq.size() + rq.size() > 0) - { - return; - } - -#ifdef TORRENT_STATS - ++ses.m_end_game_piece_picker_blocks; -#endif - -#ifdef TORRENT_DEBUG - piece_picker::downloading_piece st; - p.piece_info(busy_block.piece_index, st); - TORRENT_ASSERT(st.requested + st.finished + st.writing - == p.blocks_in_piece(busy_block.piece_index)); -#endif - TORRENT_ASSERT(p.is_requested(busy_block)); - TORRENT_ASSERT(!p.is_downloaded(busy_block)); - TORRENT_ASSERT(!p.is_finished(busy_block)); - TORRENT_ASSERT(p.num_peers(busy_block) > 0); - - c.add_request(busy_block, peer_connection::req_busy); - } - - policy::policy(torrent* t) - : m_torrent(t) - , m_locked_peer(NULL) + policy::policy() + : m_locked_peer(NULL) + , m_num_seeds(0) + , m_finished(0) , m_round_robin(0) , m_num_connect_candidates(0) - , m_num_seeds(0) - , m_finished(false) - { TORRENT_ASSERT(t); } - - void policy::clear_peer_prio() { - for (peers_t::iterator i = m_peers.begin() - , end(m_peers.end()); i != end; ++i) - (*i)->peer_rank = 0; + thread_started(); } // disconnects and removes all peers that are now filtered - void policy::ip_filter_updated() + // fills in 'erased' with torrent_peer pointers that were removed + // from the peer list. Any references to these peers must be cleared + // immediately after this call returns. For instance, in the piece picker. + void policy::apply_ip_filter(ip_filter const& filter, torrent_state* state, std::vector
& banned) { + TORRENT_ASSERT(is_single_thread()); INVARIANT_CHECK; - aux::session_impl& ses = m_torrent->session(); - if (!m_torrent->apply_ip_filter()) return; - for (iterator i = m_peers.begin(); i != m_peers.end();) { - if ((ses.m_ip_filter.access((*i)->address()) & ip_filter::blocked) == 0) + if ((filter.access((*i)->address()) & ip_filter::blocked) == 0) { ++i; continue; } - if (*i == m_locked_peer) { ++i; continue; } - if (ses.m_alerts.should_post()) - ses.m_alerts.post_alert(peer_blocked_alert(m_torrent->get_handle() - , (*i)->address(), peer_blocked_alert::ip_filter)); - int current = i - m_peers.begin(); TORRENT_ASSERT(current >= 0); TORRENT_ASSERT(m_peers.size() > 0); @@ -507,9 +165,70 @@ namespace libtorrent // disconnecting the peer here may also delete the // peer_info_struct. If that is the case, just continue int count = m_peers.size(); - peer_connection* p = (*i)->connection; + peer_connection_interface* p = (*i)->connection; - p->disconnect(errors::banned_by_ip_filter); + banned.push_back(p->remote().address()); + + p->disconnect(errors::banned_by_ip_filter, peer_connection_interface::op_bittorrent); + // what *i refers to has changed, i.e. cur was deleted + if (m_peers.size() < count) + { + i = m_peers.begin() + current; + continue; + } + TORRENT_ASSERT((*i)->connection == 0 + || (*i)->connection->peer_info_struct() == 0); + } + + erase_peer(i, state); + i = m_peers.begin() + current; + } + } + + void policy::clear_peer_prio() + { + for (peers_t::iterator i = m_peers.begin() + , end(m_peers.end()); i != end; ++i) + (*i)->peer_rank = 0; + } + + // disconnects and removes all peers that are now filtered + // fills in 'erased' with torrent_peer pointers that were removed + // from the peer list. Any references to these peers must be cleared + // immediately after this call returns. For instance, in the piece picker. + void policy::apply_port_filter(port_filter const& filter, torrent_state* state, std::vector
& banned) + { + TORRENT_ASSERT(is_single_thread()); + INVARIANT_CHECK; + + for (iterator i = m_peers.begin(); i != m_peers.end();) + { + if ((filter.access((*i)->port) & port_filter::blocked) == 0) + { + ++i; + continue; + } + if (*i == m_locked_peer) + { + ++i; + continue; + } + + int current = i - m_peers.begin(); + TORRENT_ASSERT(current >= 0); + TORRENT_ASSERT(m_peers.size() > 0); + TORRENT_ASSERT(i != m_peers.end()); + + if ((*i)->connection) + { + // disconnecting the peer here may also delete the + // peer_info_struct. If that is the case, just continue + int count = m_peers.size(); + peer_connection_interface* p = (*i)->connection; + + banned.push_back(p->remote().address()); + + p->disconnect(errors::banned_by_port_filter, peer_connection_interface::op_bittorrent); // what *i refers to has changed, i.e. cur was deleted if (int(m_peers.size()) < count) { @@ -520,118 +239,100 @@ namespace libtorrent || (*i)->connection->peer_info_struct() == 0); } - erase_peer(i); + erase_peer(i, state); i = m_peers.begin() + current; } } - void policy::erase_peer(policy::peer* p) + void policy::erase_peer(torrent_peer* p, torrent_state* state) { + TORRENT_ASSERT(is_single_thread()); INVARIANT_CHECK; TORRENT_ASSERT(p->in_use); + TORRENT_ASSERT(m_locked_peer != p); std::pair range = find_peers(p->address()); iterator iter = std::find_if(range.first, range.second, match_peer_endpoint(p->ip())); if (iter == range.second) return; - erase_peer(iter); + erase_peer(iter, state); } // any peer that is erased from m_peers will be // erased through this function. This way we can make // sure that any references to the peer are removed // as well, such as in the piece picker. - void policy::erase_peer(iterator i) + void policy::erase_peer(iterator i, torrent_state* state) { + TORRENT_ASSERT(is_single_thread()); INVARIANT_CHECK; TORRENT_ASSERT(i != m_peers.end()); TORRENT_ASSERT(m_locked_peer != *i); - if (m_torrent->has_picker()) - m_torrent->picker().clear_peer(*i); + state->erased.push_back(*i); if ((*i)->seed) --m_num_seeds; - if (is_connect_candidate(**i, m_finished)) - { - TORRENT_ASSERT(m_num_connect_candidates > 0); - --m_num_connect_candidates; - } + if (is_connect_candidate(**i)) + update_connect_candidates(-1); TORRENT_ASSERT(m_num_connect_candidates < int(m_peers.size())); if (m_round_robin > i - m_peers.begin()) --m_round_robin; if (m_round_robin >= int(m_peers.size())) m_round_robin = 0; + // if this peer is in the connect candidate + // cache, erase it from there as well + std::vector::iterator ci = std::find(m_candidate_cache.begin(), m_candidate_cache.end(), *i); + if (ci != m_candidate_cache.end()) m_candidate_cache.erase(ci); + #if TORRENT_USE_ASSERTS TORRENT_ASSERT((*i)->in_use); (*i)->in_use = false; #endif -#if TORRENT_USE_IPV6 - if ((*i)->is_v6_addr) - { - TORRENT_ASSERT(m_torrent->session().m_ipv6_peer_pool.is_from( - static_cast(*i))); - m_torrent->session().m_ipv6_peer_pool.destroy( - static_cast(*i)); - } - else -#endif -#if TORRENT_USE_I2P - if ((*i)->is_i2p_addr) - { - TORRENT_ASSERT(m_torrent->session().m_i2p_peer_pool.is_from( - static_cast(*i))); - m_torrent->session().m_i2p_peer_pool.destroy( - static_cast(*i)); - } - else -#endif - { - TORRENT_ASSERT(m_torrent->session().m_ipv4_peer_pool.is_from( - static_cast(*i))); - m_torrent->session().m_ipv4_peer_pool.destroy( - static_cast(*i)); - } + state->peer_allocator->free_peer_entry(*i); m_peers.erase(i); } - bool policy::should_erase_immediately(peer const& p) const + bool policy::should_erase_immediately(torrent_peer const& p) const { + TORRENT_ASSERT(is_single_thread()); TORRENT_ASSERT(p.in_use); if (&p == m_locked_peer) return false; return p.source == peer_info::resume_data; } - bool policy::is_erase_candidate(peer const& pe, bool finished) const + bool policy::is_erase_candidate(torrent_peer const& pe) const { + TORRENT_ASSERT(is_single_thread()); TORRENT_ASSERT(pe.in_use); if (&pe == m_locked_peer) return false; if (pe.connection) return false; - if (is_connect_candidate(pe, finished)) return false; + if (is_connect_candidate(pe)) return false; return (pe.failcount > 0) || (pe.source == peer_info::resume_data); } - bool policy::is_force_erase_candidate(peer const& pe) const + bool policy::is_force_erase_candidate(torrent_peer const& pe) const { + TORRENT_ASSERT(is_single_thread()); TORRENT_ASSERT(pe.in_use); if (&pe == m_locked_peer) return false; return pe.connection == 0; } - void policy::erase_peers(int flags) + void policy::erase_peers(torrent_state* state, int flags) { + TORRENT_ASSERT(is_single_thread()); INVARIANT_CHECK; - int max_peerlist_size = m_torrent->is_paused() - ? m_torrent->settings().max_paused_peerlist_size - : m_torrent->settings().max_peerlist_size; + int max_peerlist_size = state->max_peerlist_size; if (max_peerlist_size == 0 || m_peers.empty()) return; int erase_candidate = -1; int force_erase_candidate = -1; - TORRENT_ASSERT(m_finished == m_torrent->is_finished()); + if (state->is_finished != m_finished) + recalculate_connect_candidates(state); int round_robin = random() % m_peers.size(); @@ -646,11 +347,11 @@ namespace libtorrent if (round_robin == int(m_peers.size())) round_robin = 0; - peer& pe = *m_peers[round_robin]; + torrent_peer& pe = *m_peers[round_robin]; TORRENT_ASSERT(pe.in_use); int current = round_robin; - if (is_erase_candidate(pe, m_finished) + if (is_erase_candidate(pe) && (erase_candidate == -1 || !compare_peer_erase(*m_peers[erase_candidate], pe))) { @@ -659,7 +360,7 @@ namespace libtorrent if (erase_candidate > current) --erase_candidate; if (force_erase_candidate > current) --force_erase_candidate; TORRENT_ASSERT(current >= 0 && current < int(m_peers.size())); - erase_peer(m_peers.begin() + current); + erase_peer(m_peers.begin() + current, state); continue; } else @@ -680,147 +381,129 @@ namespace libtorrent if (erase_candidate > -1) { TORRENT_ASSERT(erase_candidate >= 0 && erase_candidate < int(m_peers.size())); - erase_peer(m_peers.begin() + erase_candidate); + erase_peer(m_peers.begin() + erase_candidate, state); } else if ((flags & force_erase) && force_erase_candidate > -1) { TORRENT_ASSERT(force_erase_candidate >= 0 && force_erase_candidate < int(m_peers.size())); - erase_peer(m_peers.begin() + force_erase_candidate); + erase_peer(m_peers.begin() + force_erase_candidate, state); } } - void policy::ban_peer(policy::peer* p) + // returns true if the peer was actually banned + bool policy::ban_peer(torrent_peer* p) { + TORRENT_ASSERT(is_single_thread()); INVARIANT_CHECK; TORRENT_ASSERT(p->in_use); - if (!m_torrent->settings().ban_web_seeds && p->web_seed) - return; - - if (is_connect_candidate(*p, m_finished)) - --m_num_connect_candidates; - -#ifdef TORRENT_STATS - aux::session_impl& ses = m_torrent->session(); - ++ses.m_num_banned_peers; -#endif + if (is_connect_candidate(*p)) + update_connect_candidates(-1); p->banned = true; - TORRENT_ASSERT(!is_connect_candidate(*p, m_finished)); + TORRENT_ASSERT(!is_connect_candidate(*p)); + return true; } - void policy::set_connection(policy::peer* p, peer_connection* c) + void policy::set_connection(torrent_peer* p, peer_connection_interface* c) { + TORRENT_ASSERT(is_single_thread()); INVARIANT_CHECK; TORRENT_ASSERT(p->in_use); TORRENT_ASSERT(c); - const bool was_conn_cand = is_connect_candidate(*p, m_finished); + const bool was_conn_cand = is_connect_candidate(*p); p->connection = c; - if (was_conn_cand) --m_num_connect_candidates; + if (was_conn_cand) update_connect_candidates(-1); } - void policy::set_failcount(policy::peer* p, int f) + void policy::inc_failcount(torrent_peer* p) { + // failcount is a 5 bit value + if (p->failcount == 31) return; + + const bool was_conn_cand = is_connect_candidate(*p); + ++p->failcount; + if (was_conn_cand && !is_connect_candidate(*p)) + update_connect_candidates(-1); + } + + void policy::set_failcount(torrent_peer* p, int f) + { + TORRENT_ASSERT(is_single_thread()); INVARIANT_CHECK; TORRENT_ASSERT(p->in_use); - const bool was_conn_cand = is_connect_candidate(*p, m_finished); + const bool was_conn_cand = is_connect_candidate(*p); p->failcount = f; - if (was_conn_cand != is_connect_candidate(*p, m_finished)) + if (was_conn_cand != is_connect_candidate(*p)) { - if (was_conn_cand) --m_num_connect_candidates; - else ++m_num_connect_candidates; + update_connect_candidates(was_conn_cand ? -1 : 1); } } - bool policy::is_connect_candidate(peer const& p, bool finished) const + bool policy::is_connect_candidate(torrent_peer const& p) const { + TORRENT_ASSERT(is_single_thread()); TORRENT_ASSERT(p.in_use); if (p.connection || p.banned || p.web_seed || !p.connectable - || (p.seed && finished) - || int(p.failcount) >= m_torrent->settings().max_failcount) + || (p.seed && m_finished) + || int(p.failcount) >= 3) return false; - aux::session_impl const& ses = m_torrent->session(); - if (ses.m_port_filter.access(p.port) & port_filter::blocked) - return false; - - // only apply this to peers we've only heard - // about from the DHT - if (ses.m_settings.no_connect_privileged_ports - && p.port < 1024 - && p.source == peer_info::dht) - return false; - return true; } - policy::iterator policy::find_connect_candidate(int session_time) + void policy::find_connect_candidates(std::vector& peers, int session_time, torrent_state* state) { + TORRENT_ASSERT(is_single_thread()); INVARIANT_CHECK; - int candidate = -1; + const int candidate_count = 10; + peers.reserve(candidate_count); + int erase_candidate = -1; - TORRENT_ASSERT(m_finished == m_torrent->is_finished()); + if (m_finished != state->is_finished) + recalculate_connect_candidates(state); - int min_reconnect_time = m_torrent->settings().min_reconnect_time; - external_ip const& external = m_torrent->session().external_address(); - int external_port = m_torrent->session().listen_port(); + external_ip const& external = *state->ip; + int external_port = state->port; if (m_round_robin >= int(m_peers.size())) m_round_robin = 0; -#ifndef TORRENT_DISABLE_DHT - bool pinged = false; -#endif - - int max_peerlist_size = m_torrent->is_paused() - ?m_torrent->settings().max_paused_peerlist_size - :m_torrent->settings().max_peerlist_size; + int max_peerlist_size = state->max_peerlist_size; for (int iterations = (std::min)(int(m_peers.size()), 300); iterations > 0; --iterations) { + ++state->loop_counter; + if (m_round_robin >= int(m_peers.size())) m_round_robin = 0; - peer& pe = *m_peers[m_round_robin]; + torrent_peer& pe = *m_peers[m_round_robin]; TORRENT_ASSERT(pe.in_use); int current = m_round_robin; -#ifndef TORRENT_DISABLE_DHT - // try to send a DHT ping to this peer - // as well, to figure out if it supports - // DHT (uTorrent and BitComet doesn't - // advertise support) - if (!pinged && !pe.added_to_dht) - { - udp::endpoint node(pe.address(), pe.port); - m_torrent->session().add_dht_node(node); - pe.added_to_dht = true; - pinged = true; - } -#endif // if the number of peers is growing large // we need to start weeding. if (int(m_peers.size()) >= max_peerlist_size * 0.95 && max_peerlist_size > 0) { - if (is_erase_candidate(pe, m_finished) + if (is_erase_candidate(pe) && (erase_candidate == -1 || !compare_peer_erase(*m_peers[erase_candidate], pe))) { if (should_erase_immediately(pe)) { if (erase_candidate > current) --erase_candidate; - if (candidate > current) --candidate; - erase_peer(m_peers.begin() + current); + erase_peer(m_peers.begin() + current, state); continue; } else @@ -832,75 +515,51 @@ namespace libtorrent ++m_round_robin; - if (!is_connect_candidate(pe, m_finished)) continue; + if (!is_connect_candidate(pe)) continue; + + if (pe.last_connected + && session_time - pe.last_connected < + (int(pe.failcount) + 1) * state->min_reconnect_time) + continue; // compare peer returns true if lhs is better than rhs. In this // case, it returns true if the current candidate is better than // pe, which is the peer m_round_robin points to. If it is, just // keep looking. - if (candidate != -1 - && compare_peer(*m_peers[candidate], pe, external, external_port)) continue; + if (peers.size() == candidate_count + && compare_peer(peers.back(), &pe, external, external_port)) continue; - if (pe.last_connected - && session_time - pe.last_connected < - (int(pe.failcount) + 1) * min_reconnect_time) - continue; + if (peers.size() >= candidate_count) + peers.resize(candidate_count - 1); - candidate = current; + // insert this candidate sorted into peers + std::vector::iterator i = std::lower_bound(peers.begin(), peers.end() + , &pe, boost::bind(&policy::compare_peer, this, _1, _2, boost::cref(external), external_port)); + + peers.insert(i, &pe); } if (erase_candidate > -1) { - if (candidate > erase_candidate) --candidate; - erase_peer(m_peers.begin() + erase_candidate); + erase_peer(m_peers.begin() + erase_candidate, state); } - -#if defined TORRENT_LOGGING || defined TORRENT_VERBOSE_LOGGING - if (candidate != -1) - { - (*m_torrent->session().m_logger) << time_now_string() - << " *** FOUND CONNECTION CANDIDATE [" - " ip: " << m_peers[candidate]->ip() << - " d: " << cidr_distance(external.external_address(m_peers[candidate]->address()), m_peers[candidate]->address()) << - " rank: " << m_peers[candidate]->rank(external, external_port) << - " external: " << external.external_address(m_peers[candidate]->address()) << - " t: " << (session_time - m_peers[candidate]->last_connected) << - " ]\n"; - } -#endif - - if (candidate == -1) return m_peers.end(); - return m_peers.begin() + candidate; } - bool policy::new_connection(peer_connection& c, int session_time) + bool policy::new_connection(peer_connection_interface& c, int session_time, torrent_state* state) { - TORRENT_ASSERT(!c.is_outgoing()); + TORRENT_ASSERT(is_single_thread()); +// TORRENT_ASSERT(!c.is_outgoing()); INVARIANT_CHECK; - // if the connection comes from the tracker, - // it's probably just a NAT-check. Ignore the - // num connections constraint then. - - // TODO: only allow _one_ connection to use this - // override at a time error_code ec; - TORRENT_ASSERT(c.remote() == c.get_socket()->remote_endpoint(ec) || ec); - TORRENT_ASSERT(!m_torrent->is_paused()); - -#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING - if (c.remote().address() == m_torrent->current_tracker().address()) - { - m_torrent->debug_log("overriding connection limit for tracker NAT-check"); - } -#endif + TORRENT_ASSERT(!state->is_paused); iterator iter; - peer* i = 0; + torrent_peer* i = 0; bool found = false; - if (m_torrent->settings().allow_multiple_connections_per_ip) + if (state->allow_multiple_connections_per_ip) { tcp::endpoint remote = c.remote(); std::pair range = find_peers(remote.address()); @@ -933,10 +592,6 @@ namespace libtorrent // || (iter != m_peers.end() && c.remote().address() < (*iter)->address()) // || (iter != m_peers.end() && iter != m_peers.begin() && (*(iter-1))->address() < c.remote().address())); -#if !defined TORRENT_DISABLE_GEO_IP || TORRENT_LOGGING || defined TORRENT_VERBOSE_LOGGING - aux::session_impl& ses = m_torrent->session(); -#endif - if (found) { i = *iter; @@ -951,33 +606,20 @@ namespace libtorrent #endif if (i->banned) { - c.disconnect(errors::peer_banned); + c.disconnect(errors::peer_banned, peer_connection_interface::op_bittorrent); return false; } if (i->connection != 0) { - boost::shared_ptr other_socket - = i->connection->get_socket(); - boost::shared_ptr this_socket - = c.get_socket(); - - error_code ec1; - error_code ec2; bool self_connection = - other_socket->remote_endpoint(ec2) == this_socket->local_endpoint(ec1) - || other_socket->local_endpoint(ec2) == this_socket->remote_endpoint(ec1); - - if (ec1) - { - c.disconnect(ec1); - return false; - } + i->connection->remote() == c.local_endpoint() + || i->connection->local_endpoint() == c.remote(); if (self_connection) { - c.disconnect(errors::self_connection, 1); - i->connection->disconnect(errors::self_connection, 1); + c.disconnect(errors::self_connection, peer_connection_interface::op_bittorrent, 1); + i->connection->disconnect(errors::self_connection, peer_connection_interface::op_bittorrent, 1); TORRENT_ASSERT(i->connection == 0); return false; } @@ -985,19 +627,11 @@ namespace libtorrent TORRENT_ASSERT(i->connection != &c); // the new connection is a local (outgoing) connection // or the current one is already connected - if (ec2) - { - TORRENT_ASSERT(m_locked_peer == NULL); - m_locked_peer = i; - i->connection->disconnect(ec2); - TORRENT_ASSERT(i->connection == 0); - m_locked_peer = NULL; - } - else if (i->connection->is_outgoing() == c.is_outgoing()) + if (i->connection->is_outgoing() == c.is_outgoing()) { // if the other end connected to us both times, just drop // the second one. Or if we made both connections. - c.disconnect(errors::duplicate_peer_id); + c.disconnect(errors::duplicate_peer_id, peer_connection_interface::op_bittorrent); return false; } else @@ -1017,8 +651,8 @@ namespace libtorrent // to be careful to only look at the target end of a // connection for the endpoint. - int our_port = outgoing1 ? other_socket->local_endpoint(ec1).port() : this_socket->local_endpoint(ec1).port(); - int other_port = outgoing1 ? this_socket->remote_endpoint(ec1).port() : other_socket->remote_endpoint(ec1).port(); + int our_port = outgoing1 ? i->connection->local_endpoint().port() : c.local_endpoint().port(); + int other_port= outgoing1 ? c.remote().port() : i->connection->remote().port(); if (our_port < other_port) { @@ -1030,12 +664,12 @@ namespace libtorrent // we should keep our outgoing connection if (!outgoing1) { - c.disconnect(errors::duplicate_peer_id); + c.disconnect(errors::duplicate_peer_id, peer_connection_interface::op_bittorrent); return false; } TORRENT_ASSERT(m_locked_peer == NULL); m_locked_peer = i; - i->connection->disconnect(errors::duplicate_peer_id); + i->connection->disconnect(errors::duplicate_peer_id, peer_connection_interface::op_bittorrent); m_locked_peer = NULL; } else @@ -1047,50 +681,33 @@ namespace libtorrent // they should keep their outgoing connection if (outgoing1) { - c.disconnect(errors::duplicate_peer_id); + c.disconnect(errors::duplicate_peer_id, peer_connection_interface::op_bittorrent); return false; } TORRENT_ASSERT(m_locked_peer == NULL); m_locked_peer = i; - i->connection->disconnect(errors::duplicate_peer_id); + i->connection->disconnect(errors::duplicate_peer_id, peer_connection_interface::op_bittorrent); m_locked_peer = NULL; } } } - if (is_connect_candidate(*i, m_finished)) - { - m_num_connect_candidates--; - TORRENT_ASSERT(m_num_connect_candidates >= 0); - if (m_num_connect_candidates < 0) m_num_connect_candidates = 0; - } + if (is_connect_candidate(*i)) + update_connect_candidates(-1); } else { // we don't have any info about this peer. // add a new entry error_code ec; - TORRENT_ASSERT(c.remote() == c.get_socket()->remote_endpoint(ec) || ec); - if (int(m_peers.size()) >= m_torrent->settings().max_peerlist_size) + if (int(m_peers.size()) >= state->max_peerlist_size) { // this may invalidate our iterator! - erase_peers(force_erase); - if (int(m_peers.size()) >= m_torrent->settings().max_peerlist_size) + erase_peers(state, force_erase); + if (int(m_peers.size()) >= state->max_peerlist_size) { -#if defined TORRENT_LOGGING || defined TORRENT_VERBOSE_LOGGING - (*m_torrent->session().m_logger) << time_now_string() - << " *** TOO MANY CONNECTIONS [" - " torrent: " << m_torrent->name() << - " torrent peers: " << m_torrent->num_peers() << - " torrent limit: " << m_torrent->max_connections() << - " global peers: " << ses.num_connections() << - " global limit: " << ses.settings().connections_limit << - " global list peers " << int(m_peers.size()) << - " global list limit: " << m_torrent->settings().max_peerlist_size << - " ]\n"; -#endif - c.disconnect(errors::too_many_connections); + c.disconnect(errors::too_many_connections, peer_connection_interface::op_bittorrent); return false; } // restore it @@ -1102,21 +719,14 @@ namespace libtorrent #if TORRENT_USE_IPV6 bool is_v6 = c.remote().address().is_v6(); +#else + bool is_v6 = false; #endif - peer* p = -#if TORRENT_USE_IPV6 - is_v6 ? (peer*)m_torrent->session().m_ipv6_peer_pool.malloc() : -#endif - (peer*)m_torrent->session().m_ipv4_peer_pool.malloc(); + torrent_peer* p = state->peer_allocator->allocate_peer_entry( + is_v6 ? torrent_peer_allocator_interface::ipv6_peer_type + : torrent_peer_allocator_interface::ipv4_peer_type); if (p == 0) return false; -#if TORRENT_USE_IPV6 - if (is_v6) - m_torrent->session().m_ipv6_peer_pool.set_next_size(500); - else -#endif - m_torrent->session().m_ipv4_peer_pool.set_next_size(500); - #if TORRENT_USE_IPV6 if (is_v6) new (p) ipv6_peer(c.remote(), false, 0); @@ -1133,13 +743,7 @@ namespace libtorrent if (m_round_robin >= iter - m_peers.begin()) ++m_round_robin; i = *iter; -#ifndef TORRENT_DISABLE_GEO_IP - int as = ses.as_for_ip(c.remote().address()); -#ifdef TORRENT_DEBUG - i->inet_as_num = as; -#endif - i->inet_as = ses.lookup_as(as); -#endif + i->source = peer_info::incoming; } @@ -1148,13 +752,6 @@ namespace libtorrent TORRENT_ASSERT(i->connection == 0); c.add_stat(size_type(i->prev_amount_download) << 10, size_type(i->prev_amount_upload) << 10); - // restore transfer rate limits - int rate_limit; - rate_limit = i->upload_rate_limit; - if (rate_limit) c.set_upload_limit(rate_limit); - rate_limit = i->download_rate_limit; - if (rate_limit) c.set_download_limit(rate_limit); - i->prev_amount_download = 0; i->prev_amount_upload = 0; i->connection = &c; @@ -1163,23 +760,23 @@ namespace libtorrent i->last_connected = session_time; // this cannot be a connect candidate anymore, since i->connection is set - TORRENT_ASSERT(!is_connect_candidate(*i, m_finished)); + TORRENT_ASSERT(!is_connect_candidate(*i)); TORRENT_ASSERT(has_connection(&c)); - m_torrent->state_updated(); return true; } - bool policy::update_peer_port(int port, policy::peer* p, int src) + bool policy::update_peer_port(int port, torrent_peer* p, int src, torrent_state* state) { TORRENT_ASSERT(p != 0); TORRENT_ASSERT(p->connection); TORRENT_ASSERT(p->in_use); + TORRENT_ASSERT(is_single_thread()); INVARIANT_CHECK; if (p->port == port) return true; - if (m_torrent->settings().allow_multiple_connections_per_ip) + if (state->allow_multiple_connections_per_ip) { tcp::endpoint remote(p->address(), port); std::pair range = find_peers(remote.address()); @@ -1187,33 +784,31 @@ namespace libtorrent , match_peer_endpoint(remote)); if (i != range.second) { - policy::peer& pp = **i; + torrent_peer& pp = **i; TORRENT_ASSERT(pp.in_use); if (pp.connection) { - bool was_conn_cand = is_connect_candidate(pp, m_finished); + bool was_conn_cand = is_connect_candidate(pp); // if we already have an entry with this // new endpoint, disconnect this one pp.connectable = true; pp.source |= src; - if (!was_conn_cand && is_connect_candidate(pp, m_finished)) - ++m_num_connect_candidates; + if (!was_conn_cand && is_connect_candidate(pp)) + update_connect_candidates(1); // calling disconnect() on a peer, may actually end - // up "garbage collecting" its policy::peer entry + // up "garbage collecting" its torrent_peer entry // as well, if it's considered useless (which this specific) // case will, since it was an incoming peer that just disconnected // and we allow multiple connections per IP. Because of that, - // we need to make sure we don't let it do that, by unlinking - // the peer_connection from the policy::peer first. - p->connection->set_peer_info(0); + // we need to make sure we don't let it do that, locking i TORRENT_ASSERT(m_locked_peer == NULL); m_locked_peer = p; - p->connection->disconnect(errors::duplicate_peer_id); + p->connection->disconnect(errors::duplicate_peer_id, peer_connection_interface::op_bittorrent); m_locked_peer = NULL; - erase_peer(p); + erase_peer(p, state); return false; } - erase_peer(i); + erase_peer(i, state); } } #ifdef TORRENT_DEBUG @@ -1224,31 +819,27 @@ namespace libtorrent #endif { std::pair range = find_peers(p->address()); - TORRENT_ASSERT(range.second - range.first == 1); + TORRENT_ASSERT(std::distance(range.first, range.second) == 1); } } #endif - bool was_conn_cand = is_connect_candidate(*p, m_finished); + bool was_conn_cand = is_connect_candidate(*p); p->port = port; p->source |= src; p->connectable = true; - if (was_conn_cand != is_connect_candidate(*p, m_finished)) - { - m_num_connect_candidates += was_conn_cand ? -1 : 1; - TORRENT_ASSERT(m_num_connect_candidates >= 0); - if (m_num_connect_candidates < 0) m_num_connect_candidates = 0; - } + if (was_conn_cand != is_connect_candidate(*p)) + update_connect_candidates(was_conn_cand ? -1 : 1); return true; } // it's important that we don't dereference // p here, since it is allowed to be a dangling // pointer. see smart_ban.cpp - bool policy::has_peer(policy::peer const* p) const + bool policy::has_peer(torrent_peer const* p) const { - TORRENT_ASSERT(p->in_use); + TORRENT_ASSERT(is_single_thread()); // find p in m_peers for (const_iterator i = m_peers.begin() , end(m_peers.end()); i != end; ++i) @@ -1258,19 +849,16 @@ namespace libtorrent return false; } - void policy::set_seed(policy::peer* p, bool s) + void policy::set_seed(torrent_peer* p, bool s) { + TORRENT_ASSERT(is_single_thread()); if (p == 0) return; TORRENT_ASSERT(p->in_use); if (p->seed == s) return; - bool was_conn_cand = is_connect_candidate(*p, m_finished); + bool was_conn_cand = is_connect_candidate(*p); p->seed = s; - if (was_conn_cand && !is_connect_candidate(*p, m_finished)) - { - --m_num_connect_candidates; - TORRENT_ASSERT(m_num_connect_candidates >= 0); - if (m_num_connect_candidates < 0) m_num_connect_candidates = 0; - } + if (was_conn_cand && !is_connect_candidate(*p)) + update_connect_candidates(-1); if (p->web_seed) return; if (s) ++m_num_seeds; @@ -1279,23 +867,23 @@ namespace libtorrent TORRENT_ASSERT(m_num_seeds <= int(m_peers.size())); } - bool policy::insert_peer(policy::peer* p, iterator iter, int flags) + // this is an internal function + bool policy::insert_peer(torrent_peer* p, iterator iter, int flags, torrent_state* state) { + TORRENT_ASSERT(is_single_thread()); TORRENT_ASSERT(p); TORRENT_ASSERT(p->in_use); - int max_peerlist_size = m_torrent->is_paused() - ?m_torrent->settings().max_paused_peerlist_size - :m_torrent->settings().max_peerlist_size; + int max_peerlist_size = state->max_peerlist_size; if (max_peerlist_size && int(m_peers.size()) >= max_peerlist_size) { if (p->source == peer_info::resume_data) return false; - erase_peers(); + erase_peers(state); if (int(m_peers.size()) >= max_peerlist_size) - return 0; + return false; // since some peers were removed, we need to // update the iterator to make it valid again @@ -1318,37 +906,28 @@ namespace libtorrent if (m_round_robin >= iter - m_peers.begin()) ++m_round_robin; #ifndef TORRENT_DISABLE_ENCRYPTION - if (flags & 0x01) p->pe_support = true; + if (flags & flag_encryption) p->pe_support = true; #endif - if (flags & 0x02) + if (flags & flag_seed) { p->seed = true; ++m_num_seeds; } - if (flags & 0x04) + if (flags & flag_utp) p->supports_utp = true; - if (flags & 0x08) + if (flags & flag_holepunch) p->supports_holepunch = true; - -#ifndef TORRENT_DISABLE_GEO_IP - int as = m_torrent->session().as_for_ip(p->address()); -#ifdef TORRENT_DEBUG - p->inet_as_num = as; -#endif - p->inet_as = m_torrent->session().lookup_as(as); -#endif - if (is_connect_candidate(*p, m_finished)) - ++m_num_connect_candidates; - - m_torrent->state_updated(); + if (is_connect_candidate(*p)) + update_connect_candidates(1); return true; } - void policy::update_peer(policy::peer* p, int src, int flags + void policy::update_peer(torrent_peer* p, int src, int flags , tcp::endpoint const& remote, char const* destination) { - bool was_conn_cand = is_connect_candidate(*p, m_finished); + TORRENT_ASSERT(is_single_thread()); + bool was_conn_cand = is_connect_candidate(*p); TORRENT_ASSERT(p->in_use); p->connectable = true; @@ -1367,45 +946,38 @@ namespace libtorrent // if we're connected to this peer // we already know if it's a seed or not // so we don't have to trust this source - if ((flags & 0x02) && !p->connection) + if ((flags & flag_seed) && !p->connection) { if (!p->seed) ++m_num_seeds; p->seed = true; } - if (flags & 0x04) + if (flags & flag_utp) p->supports_utp = true; - if (flags & 0x08) + if (flags & flag_holepunch) p->supports_holepunch = true; -#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING - if (p->connection) + if (was_conn_cand != is_connect_candidate(*p)) { - // this means we're already connected - // to this peer. don't connect to - // it again. - - error_code ec; - char hex_pid[41]; - to_hex((char*)&p->connection->pid()[0], 20, hex_pid); - char msg[200]; - snprintf(msg, 200, "already connected to peer: %s %s" - , print_endpoint(remote).c_str(), hex_pid); - m_torrent->debug_log(msg); - - TORRENT_ASSERT(p->connection->associated_torrent().lock().get() == m_torrent); + update_connect_candidates(was_conn_cand ? -1 : 1); } -#endif + } - if (was_conn_cand != is_connect_candidate(*p, m_finished)) + void policy::update_connect_candidates(int delta) + { + TORRENT_ASSERT(is_single_thread()); + if (delta == 0) return; + m_num_connect_candidates += delta; + if (delta < 0) { - m_num_connect_candidates += was_conn_cand ? -1 : 1; + TORRENT_ASSERT(m_num_connect_candidates >= 0); if (m_num_connect_candidates < 0) m_num_connect_candidates = 0; } } #if TORRENT_USE_I2P - policy::peer* policy::add_i2p_peer(char const* destination, int src, char flags) + torrent_peer* policy::add_i2p_peer(char const* destination, int src, char flags, torrent_state* state) { + TORRENT_ASSERT(is_single_thread()); INVARIANT_CHECK; bool found = false; @@ -1417,28 +989,27 @@ namespace libtorrent if (iter != m_peers.end() && strcmp((*iter)->dest(), destination) == 0) found = true; - peer* p = 0; + torrent_peer* p = 0; if (!found) { // we don't have any info about this peer. // add a new entry - p = (peer*)m_torrent->session().m_i2p_peer_pool.malloc(); - if (p == 0) return 0; - m_torrent->session().m_i2p_peer_pool.set_next_size(500); + p = state->peer_allocator->allocate_peer_entry(torrent_peer_allocator_interface::i2p_peer_type); + if (p == NULL) return NULL; new (p) i2p_peer(destination, true, src); #if TORRENT_USE_ASSERTS p->in_use = true; #endif - if (!insert_peer(p, iter, flags)) + if (!insert_peer(p, iter, flags, state)) { #if TORRENT_USE_ASSERTS p->in_use = false; #endif - m_torrent->session().m_i2p_peer_pool.destroy((i2p_peer*)p); + state->peer_allocator->free_peer_entry(p); return 0; } } @@ -1447,14 +1018,15 @@ namespace libtorrent p = *iter; update_peer(p, src, flags, tcp::endpoint(), destination); } - m_torrent->state_updated(); return p; } #endif // TORRENT_USE_I2P - policy::peer* policy::add_peer(tcp::endpoint const& remote, peer_id const& pid - , int src, char flags) + // if this returns non-NULL, the torrent need to post status update + torrent_peer* policy::add_peer(tcp::endpoint const& remote, int src, char flags + , torrent_state* state) { + TORRENT_ASSERT(is_single_thread()); INVARIANT_CHECK; // just ignore the obviously invalid entries @@ -1469,59 +1041,11 @@ namespace libtorrent return 0; #endif - aux::session_impl& ses = m_torrent->session(); - - // if this is an i2p torrent, and we don't allow mixed mode - // no regular peers should ever be added! - if (!ses.m_settings.allow_i2p_mixed && m_torrent->torrent_file().is_i2p()) - { - if (ses.m_alerts.should_post()) - ses.m_alerts.post_alert(peer_blocked_alert(m_torrent->get_handle() - , remote.address(), peer_blocked_alert::ip_filter)); - return 0; - } - - port_filter const& pf = ses.m_port_filter; - if (pf.access(remote.port()) & port_filter::blocked) - { - if (ses.m_alerts.should_post()) - ses.m_alerts.post_alert(peer_blocked_alert(m_torrent->get_handle() - , remote.address(), peer_blocked_alert::port_filter)); -#ifndef TORRENT_DISABLE_EXTENSIONS - m_torrent->notify_extension_add_peer(remote, src, torrent_plugin::filtered); -#endif - return 0; - } - - if (ses.m_settings.no_connect_privileged_ports && remote.port() < 1024) - { - if (ses.m_alerts.should_post()) - ses.m_alerts.post_alert(peer_blocked_alert(m_torrent->get_handle() - , remote.address(), peer_blocked_alert::privileged_ports)); -#ifndef TORRENT_DISABLE_EXTENSIONS - m_torrent->notify_extension_add_peer(remote, src, torrent_plugin::filtered); -#endif - return 0; - } - - // if the IP is blocked, don't add it - if (m_torrent->apply_ip_filter() - && (ses.m_ip_filter.access(remote.address()) & ip_filter::blocked)) - { - if (ses.m_alerts.should_post()) - ses.m_alerts.post_alert(peer_blocked_alert(m_torrent->get_handle() - , remote.address(), peer_blocked_alert::ip_filter)); -#ifndef TORRENT_DISABLE_EXTENSIONS - m_torrent->notify_extension_add_peer(remote, src, torrent_plugin::filtered); -#endif - return 0; - } - iterator iter; - peer* p = 0; + torrent_peer* p = 0; bool found = false; - if (m_torrent->settings().allow_multiple_connections_per_ip) + if (state->allow_multiple_connections_per_ip) { std::pair range = find_peers(remote.address()); iter = std::find_if(range.first, range.second, match_peer_endpoint(remote)); @@ -1544,19 +1068,13 @@ namespace libtorrent #if TORRENT_USE_IPV6 bool is_v6 = remote.address().is_v6(); +#else + bool is_v6 = false; #endif - p = -#if TORRENT_USE_IPV6 - is_v6 ? (peer*)m_torrent->session().m_ipv6_peer_pool.malloc() : -#endif - (peer*)m_torrent->session().m_ipv4_peer_pool.malloc(); - if (p == 0) return 0; -#if TORRENT_USE_IPV6 - if (is_v6) - m_torrent->session().m_ipv6_peer_pool.set_next_size(500); - else -#endif - m_torrent->session().m_ipv4_peer_pool.set_next_size(500); + p = state->peer_allocator->allocate_peer_entry( + is_v6 ? torrent_peer_allocator_interface::ipv6_peer_type + : torrent_peer_allocator_interface::ipv4_peer_type); + if (p == NULL) return NULL; #if TORRENT_USE_IPV6 if (is_v6) @@ -1569,78 +1087,82 @@ namespace libtorrent p->in_use = true; #endif - if (!insert_peer(p, iter, flags)) + if (!insert_peer(p, iter, flags, state)) { #if TORRENT_USE_ASSERTS p->in_use = false; #endif -#if TORRENT_USE_IPV6 - if (is_v6) m_torrent->session().m_ipv6_peer_pool.destroy((ipv6_peer*)p); - else -#endif - m_torrent->session().m_ipv4_peer_pool.destroy((ipv4_peer*)p); + state->peer_allocator->free_peer_entry(p); return 0; } -#ifndef TORRENT_DISABLE_EXTENSIONS - m_torrent->notify_extension_add_peer(remote, src, torrent_plugin::first_time); -#endif + state->first_time_seen = true; } else { p = *iter; TORRENT_ASSERT(p->in_use); update_peer(p, src, flags, remote, 0); -#ifndef TORRENT_DISABLE_EXTENSIONS - m_torrent->notify_extension_add_peer(remote, src, 0); -#endif + state->first_time_seen = false; } return p; } - bool policy::connect_one_peer(int session_time) + torrent_peer* policy::connect_one_peer(int session_time, torrent_state* state) { + TORRENT_ASSERT(is_single_thread()); INVARIANT_CHECK; - TORRENT_ASSERT(m_torrent->want_more_peers()); - - iterator i = find_connect_candidate(session_time); - if (i == m_peers.end()) return false; - peer& p = **i; - TORRENT_ASSERT(p.in_use); + if (m_finished != state->is_finished) + recalculate_connect_candidates(state); - TORRENT_ASSERT(!p.banned); - TORRENT_ASSERT(!p.connection); - TORRENT_ASSERT(p.connectable); - - TORRENT_ASSERT(m_finished == m_torrent->is_finished()); - TORRENT_ASSERT(is_connect_candidate(p, m_finished)); - if (!m_torrent->connect_to_peer(&p)) + // clear out any peers from the cache that no longer + // are connection candidates + for (std::vector::iterator i = m_candidate_cache.begin(); + i != m_candidate_cache.end();) { - // failcount is a 5 bit value - const bool was_conn_cand = is_connect_candidate(p, m_finished); - if (p.failcount < 31) ++p.failcount; - if (was_conn_cand && !is_connect_candidate(p, m_finished)) - --m_num_connect_candidates; - return false; + if (!is_connect_candidate(**i)) + i = m_candidate_cache.erase(i); + else + ++i; } - TORRENT_ASSERT(p.connection); - TORRENT_ASSERT(!is_connect_candidate(p, m_finished)); - return true; + + if (m_candidate_cache.empty()) + { + find_connect_candidates(m_candidate_cache, session_time, state); + if (m_candidate_cache.empty()) return NULL; + } + + torrent_peer* p = m_candidate_cache.front(); + m_candidate_cache.erase(m_candidate_cache.begin()); + + TORRENT_ASSERT(p->in_use); + + TORRENT_ASSERT(!p->banned); + TORRENT_ASSERT(!p->connection); + TORRENT_ASSERT(p->connectable); + + // this should hold because find_connect_candidates should have done this + TORRENT_ASSERT(m_finished == state->is_finished); + + TORRENT_ASSERT(is_connect_candidate(*p)); + return p; } // this is called whenever a peer connection is closed - void policy::connection_closed(const peer_connection& c, int session_time) + void policy::connection_closed(const peer_connection_interface& c, int session_time, torrent_state* state) { + TORRENT_ASSERT(is_single_thread()); INVARIANT_CHECK; - peer* p = c.peer_info_struct(); + torrent_peer* p = c.peer_info_struct(); // if we couldn't find the connection in our list, just ignore it. if (p == 0) return; TORRENT_ASSERT(p->in_use); +#ifndef TORRENT_DISABLE_INVARIANT_CHECKS // web seeds are special, they're not connected via the peer list // so they're not kept in m_peers TORRENT_ASSERT(p->web_seed @@ -1649,13 +1171,10 @@ namespace libtorrent , m_peers.end() , match_peer_connection(c)) != m_peers.end()); +#endif TORRENT_ASSERT(p->connection == &c); - TORRENT_ASSERT(!is_connect_candidate(*p, m_finished)); - - // save transfer rate limits - p->upload_rate_limit = c.upload_limit(); - p->download_rate_limit = c.download_limit(); + TORRENT_ASSERT(!is_connect_candidate(*p)); p->connection = 0; p->optimistically_unchoked = false; @@ -1672,8 +1191,8 @@ namespace libtorrent if (p->failcount < 31) ++p->failcount; } - if (is_connect_candidate(*p, m_finished)) - ++m_num_connect_candidates; + if (is_connect_candidate(*p)) + update_connect_candidates(1); // if we're already a seed, it's not as important // to keep all the possibly stale peers @@ -1683,7 +1202,7 @@ namespace libtorrent // at this point it may be tempting to erase peers // from the peer list, but keep in mind that we might // have gotten to this point through new_connection, just - // disconnecting an old peer, relying on this policy::peer + // disconnecting an old peer, relying on this torrent_peer // to still exist when we get back there, to assign the new // peer connection pointer to it. The peer list must // be left intact. @@ -1693,60 +1212,45 @@ namespace libtorrent // port, we don't really know which peer it was. In order // to avoid adding one entry for every single connection // the peer makes to us, don't save this entry - if (m_torrent->settings().allow_multiple_connections_per_ip + if (state->allow_multiple_connections_per_ip && !p->connectable && p != m_locked_peer) { - erase_peer(p); + erase_peer(p, state); } } - void policy::peer_is_interesting(peer_connection& c) + void policy::recalculate_connect_candidates(torrent_state* state) { + TORRENT_ASSERT(is_single_thread()); INVARIANT_CHECK; - // no peer should be interesting if we're finished - TORRENT_ASSERT(!m_torrent->is_finished()); - - if (c.in_handshake()) return; - c.send_interested(); - if (c.has_peer_choked() - && c.allowed_fast().empty()) - return; - request_a_block(*m_torrent, c); - c.send_block_requests(); - } - - void policy::recalculate_connect_candidates() - { - INVARIANT_CHECK; - - const bool is_finished = m_torrent->is_finished(); - if (is_finished == m_finished) return; + if (state->is_finished == m_finished) return; m_num_connect_candidates = 0; - m_finished = is_finished; + m_finished = state->is_finished; + for (const_iterator i = m_peers.begin(); i != m_peers.end(); ++i) { - m_num_connect_candidates += is_connect_candidate(**i, m_finished); + m_num_connect_candidates += is_connect_candidate(**i); } } #if TORRENT_USE_ASSERTS - bool policy::has_connection(const peer_connection* c) + bool policy::has_connection(const peer_connection_interface* c) { + TORRENT_ASSERT(is_single_thread()); INVARIANT_CHECK; TORRENT_ASSERT(c); - error_code ec; - if (c->remote() != c->get_socket()->remote_endpoint(ec) && !ec) - { - fprintf(stderr, "c->remote: %s\nc->get_socket()->remote_endpoint: %s\n" - , print_endpoint(c->remote()).c_str() - , print_endpoint(c->get_socket()->remote_endpoint(ec)).c_str()); - TORRENT_ASSERT(false); - } + + iterator iter = std::lower_bound( + m_peers.begin(), m_peers.end() + , c->remote().address(), peer_address_compare()); + + if (iter != m_peers.end() && (*iter)->address() == c->remote().address()) + return true; return std::find_if( m_peers.begin() @@ -1758,18 +1262,15 @@ namespace libtorrent #if TORRENT_USE_INVARIANT_CHECKS void policy::check_invariant() const { + TORRENT_ASSERT(is_single_thread()); TORRENT_ASSERT(m_num_connect_candidates >= 0); TORRENT_ASSERT(m_num_connect_candidates <= int(m_peers.size())); - if (m_torrent->is_aborted()) return; #ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS - int connected_peers = 0; - int total_connections = 0; int nonempty_connections = 0; int connect_candidates = 0; - std::set unique_test; const_iterator prev = m_peers.end(); for (const_iterator i = m_peers.begin(); i != m_peers.end(); ++i) @@ -1778,28 +1279,14 @@ namespace libtorrent if (i == m_peers.begin() + 1) prev = m_peers.begin(); if (prev != m_peers.end()) { - if (m_torrent->settings().allow_multiple_connections_per_ip) - TORRENT_ASSERT(!((*i)->address() < (*prev)->address())); - else - TORRENT_ASSERT((*prev)->address() < (*i)->address()); + TORRENT_ASSERT(!((*i)->address() < (*prev)->address())); } - peer const& p = **i; + torrent_peer const& p = **i; TORRENT_ASSERT(p.in_use); - if (is_connect_candidate(p, m_finished)) ++connect_candidates; + if (is_connect_candidate(p)) ++connect_candidates; #ifndef TORRENT_DISABLE_GEO_IP TORRENT_ASSERT(p.inet_as == 0 || p.inet_as->first == p.inet_as_num); #endif - if (!m_torrent->settings().allow_multiple_connections_per_ip) - { - std::pair range = find_peers(p.address()); - TORRENT_ASSERT(range.second - range.first == 1); - } - else - { - TORRENT_ASSERT(unique_test.count(p.ip()) == 0); - unique_test.insert(p.ip()); -// TORRENT_ASSERT(p.connection == 0 || p.ip() == p.connection->remote()); - } ++total_connections; if (!p.connection) { @@ -1813,161 +1300,18 @@ namespace libtorrent TORRENT_ASSERT(p.connection->peer_info_struct() == 0 || p.connection->peer_info_struct() == &p); ++nonempty_connections; - if (!p.connection->is_disconnecting()) - ++connected_peers; } TORRENT_ASSERT(m_num_connect_candidates == connect_candidates); - - int num_torrent_peers = 0; - for (torrent::const_peer_iterator i = m_torrent->begin(); - i != m_torrent->end(); ++i) - { - if ((*i)->is_disconnecting()) continue; - // ignore web_peer_connections since they are not managed - // by the policy class - if ((*i)->type() != peer_connection::bittorrent_connection) continue; - ++num_torrent_peers; - } - - if (m_torrent->has_picker()) - { - piece_picker& p = m_torrent->picker(); - std::vector downloaders = p.get_download_queue(); - - std::set peer_set; - std::vector peers; - for (std::vector::iterator i = downloaders.begin() - , end(downloaders.end()); i != end; ++i) - { - p.get_downloaders(peers, i->index); - std::copy(peers.begin(), peers.end() - , std::insert_iterator >(peer_set, peer_set.begin())); - } - - for (std::set::iterator i = peer_set.begin() - , end(peer_set.end()); i != end; ++i) - { - policy::peer* p = static_cast(*i); - if (p == 0) continue; - TORRENT_ASSERT(p->in_use); - if (p->connection == 0) continue; - // web seeds are special, they're not connected via the peer list - // so they're not kept in m_peers - if (p->connection->type() != peer_connection::bittorrent_connection) continue; - TORRENT_ASSERT(std::find_if(m_peers.begin(), m_peers.end() - , match_peer_connection_or_endpoint(*p->connection)) != m_peers.end()); - } - } #endif // TORRENT_EXPENSIVE_INVARIANT_CHECKS - // this invariant is a bit complicated. - // the usual case should be that connected_peers - // == num_torrent_peers. But when there's an incoming - // connection, it will first be added to the policy - // and then be added to the torrent. - // When there's an outgoing connection, it will first - // be added to the torrent and then to the policy. - // that's why the two second cases are in there. -/* - TORRENT_ASSERT(connected_peers == num_torrent_peers - || (connected_peers == num_torrent_peers + 1 - && connected_peers > 0) - || (connected_peers + 1 == num_torrent_peers - && num_torrent_peers > 0)); -*/ } #endif // TORRENT_DEBUG - policy::peer::peer(boost::uint16_t port, bool conn, int src) - : prev_amount_upload(0) - , prev_amount_download(0) - , connection(0) - , peer_rank(0) -#ifndef TORRENT_DISABLE_GEO_IP - , inet_as(0) -#endif - , last_optimistically_unchoked(0) - , last_connected(0) - , port(port) - , upload_rate_limit(0) - , download_rate_limit(0) - , hashfails(0) - , failcount(0) - , connectable(conn) - , optimistically_unchoked(false) - , seed(false) - , fast_reconnects(0) - , trust_points(0) - , source(src) -#ifndef TORRENT_DISABLE_ENCRYPTION - // assume no support in order to - // prefer opening non-encrypyed - // connections. If it fails, we'll - // retry with encryption - , pe_support(false) -#endif -#if TORRENT_USE_IPV6 - , is_v6_addr(false) -#endif -#if TORRENT_USE_I2P - , is_i2p_addr(false) -#endif - , on_parole(false) - , banned(false) -#ifndef TORRENT_DISABLE_DHT - , added_to_dht(false) -#endif - , supports_utp(true) // assume peers support utp - , confirmed_supports_utp(false) - , supports_holepunch(false) - , web_seed(false) -#if TORRENT_USE_ASSERTS - , in_use(false) -#endif - { - TORRENT_ASSERT((src & 0xff) == src); - } - - // TOOD: pass in both an IPv6 and IPv4 address here - boost::uint32_t policy::peer::rank(external_ip const& external, int external_port) const - { - if (peer_rank == 0) - peer_rank = peer_priority( - tcp::endpoint(external.external_address(this->address()), external_port) - , tcp::endpoint(this->address(), this->port)); - return peer_rank; - } - - size_type policy::peer::total_download() const - { - if (connection != 0) - { - TORRENT_ASSERT(prev_amount_download == 0); - return connection->statistics().total_payload_download(); - } - else - { - return size_type(prev_amount_download) << 10; - } - } - - size_type policy::peer::total_upload() const - { - if (connection != 0) - { - TORRENT_ASSERT(prev_amount_upload == 0); - return connection->statistics().total_payload_upload(); - } - else - { - return size_type(prev_amount_upload) << 10; - } - } - // this returns true if lhs is a better erase candidate than rhs - bool policy::compare_peer_erase(policy::peer const& lhs, policy::peer const& rhs) const + bool policy::compare_peer_erase(torrent_peer const& lhs, torrent_peer const& rhs) const { + TORRENT_ASSERT(is_single_thread()); TORRENT_ASSERT(lhs.connection == 0); TORRENT_ASSERT(rhs.connection == 0); @@ -1989,36 +1333,28 @@ namespace libtorrent } // this returns true if lhs is a better connect candidate than rhs - bool policy::compare_peer(policy::peer const& lhs, policy::peer const& rhs + bool policy::compare_peer(torrent_peer const* lhs, torrent_peer const* rhs , external_ip const& external, int external_port) const { + TORRENT_ASSERT(is_single_thread()); // prefer peers with lower failcount - if (lhs.failcount != rhs.failcount) - return lhs.failcount < rhs.failcount; + if (lhs->failcount != rhs->failcount) + return lhs->failcount < rhs->failcount; // Local peers should always be tried first - bool lhs_local = is_local(lhs.address()); - bool rhs_local = is_local(rhs.address()); + bool lhs_local = is_local(lhs->address()); + bool rhs_local = is_local(rhs->address()); if (lhs_local != rhs_local) return lhs_local > rhs_local; - if (lhs.last_connected != rhs.last_connected) - return lhs.last_connected < rhs.last_connected; + if (lhs->last_connected != rhs->last_connected) + return lhs->last_connected < rhs->last_connected; - int lhs_rank = source_rank(lhs.source); - int rhs_rank = source_rank(rhs.source); + int lhs_rank = source_rank(lhs->source); + int rhs_rank = source_rank(rhs->source); if (lhs_rank != rhs_rank) return lhs_rank > rhs_rank; -#ifndef TORRENT_DISABLE_GEO_IP - // don't bias fast peers when seeding - if (!m_finished && m_torrent->session().has_asnum_db()) - { - int lhs_as = lhs.inet_as ? lhs.inet_as->second : 0; - int rhs_as = rhs.inet_as ? rhs.inet_as->second : 0; - if (lhs_as != rhs_as) return lhs_as > rhs_as; - } -#endif - boost::uint32_t lhs_peer_rank = lhs.rank(external, external_port); - boost::uint32_t rhs_peer_rank = rhs.rank(external, external_port); + boost::uint32_t lhs_peer_rank = lhs->rank(external, external_port); + boost::uint32_t rhs_peer_rank = rhs->rank(external, external_port); if (lhs_peer_rank > rhs_peer_rank) return true; return false; } diff --git a/src/proxy_base.cpp b/src/proxy_base.cpp new file mode 100644 index 000000000..c8bff9e46 --- /dev/null +++ b/src/proxy_base.cpp @@ -0,0 +1,55 @@ +/* + +Copyright (c) 2012-2013, 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. + +*/ + +#include "libtorrent/proxy_base.hpp" + +namespace libtorrent +{ + + proxy_base::proxy_base(io_service& io_service) + : m_sock(io_service) + , m_port(0) + , m_resolver(io_service) + {} + + proxy_base::~proxy_base() {} + + bool proxy_base::handle_error(error_code const& e, boost::shared_ptr const& h) + { + if (!e) return false; + (*h)(e); + error_code ec; + close(ec); + return true; + } +} + diff --git a/src/request_blocks.cpp b/src/request_blocks.cpp new file mode 100644 index 000000000..49e48d66a --- /dev/null +++ b/src/request_blocks.cpp @@ -0,0 +1,296 @@ +/* + +Copyright (c) 2003-2013, 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. + +*/ + +#include "libtorrent/bitfield.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/torrent.hpp" +#include "libtorrent/socket_type.hpp" +#include "libtorrent/peer_info.hpp" // for peer_info flags +#include "libtorrent/performance_counters.hpp" // for counters + +#include + +namespace libtorrent +{ + // returns the rank of a peer's source. We have an affinity + // to connecting to peers with higher rank. This is to avoid + // problems when our peer list is diluted by stale peers from + // the resume data for instance + int source_rank(int source_bitmask) + { + int ret = 0; + if (source_bitmask & peer_info::tracker) ret |= 1 << 5; + if (source_bitmask & peer_info::lsd) ret |= 1 << 4; + if (source_bitmask & peer_info::dht) ret |= 1 << 3; + if (source_bitmask & peer_info::pex) ret |= 1 << 2; + return ret; + } + + // the case where ignore_peer is motivated is if two peers + // have only one piece that we don't have, and it's the + // same piece for both peers. Then they might get into an + // infinite loop, fighting to request the same blocks. + // returns false if the function is aborted by an early-exit + // condition. + bool request_a_block(torrent& t, peer_connection& c) + { + if (t.is_seed()) return false; + if (c.no_download()) return false; + if (t.upload_mode()) return false; + if (c.is_disconnecting()) return false; + + // don't request pieces before we have the metadata + if (!t.valid_metadata()) return false; + + // don't request pieces before the peer is properly + // initialized after we have the metadata + if (!t.are_files_checked()) return false; + + TORRENT_ASSERT(c.peer_info_struct() != 0 || c.type() != peer_connection::bittorrent_connection); + + bool time_critical_mode = t.num_time_critical_pieces() > 0; + + int desired_queue_size = c.desired_queue_size(); + + // in time critical mode, only have 1 outstanding request at a time + // via normal requests + if (time_critical_mode) + desired_queue_size = (std::min)(1, desired_queue_size); + + int num_requests = desired_queue_size + - (int)c.download_queue().size() + - (int)c.request_queue().size(); + +#ifdef TORRENT_VERBOSE_LOGGING + c.peer_log("*** PIECE_PICKER [ dlq: %d rqq: %d target: %d req: %d engame: %d ]" + , int(c.download_queue().size()), int(c.request_queue().size()) + , c.desired_queue_size(), num_requests, c.endgame()); +#endif + TORRENT_ASSERT(c.desired_queue_size() > 0); + // if our request queue is already full, we + // don't have to make any new requests yet + if (num_requests <= 0) return false; + + t.need_picker(); + + piece_picker& p = t.picker(); + std::vector interesting_pieces; + interesting_pieces.reserve(100); + + int prefer_whole_pieces = c.prefer_whole_pieces(); + + if (prefer_whole_pieces == 0 && !time_critical_mode) + { + prefer_whole_pieces = c.statistics().download_payload_rate() + * t.settings().get_int(settings_pack::whole_pieces_threshold) + > t.torrent_file().piece_length() ? 1 : 0; + } + + // if we prefer whole pieces, the piece picker will pick at least + // the number of blocks we want, but it will try to make the picked + // blocks be from whole pieces, possibly by returning more blocks + // than we requested. +#ifdef TORRENT_DEBUG + error_code ec; + TORRENT_ASSERT(c.remote() == c.get_socket()->remote_endpoint(ec) || ec); +#endif + + aux::session_interface& ses = t.session(); + + std::vector const& dq = c.download_queue(); + std::vector const& rq = c.request_queue(); + + std::vector const& suggested = c.suggested_pieces(); + bitfield const* bits = &c.get_bitfield(); + bitfield fast_mask; + + if (c.has_peer_choked()) + { + // if we are choked we can only pick pieces from the + // allowed fast set. The allowed fast set is sorted + // in ascending priority order + std::vector const& allowed_fast = c.allowed_fast(); + + // build a bitmask with only the allowed pieces in it + fast_mask.resize(c.get_bitfield().size(), false); + for (std::vector::const_iterator i = allowed_fast.begin() + , end(allowed_fast.end()); i != end; ++i) + if ((*bits)[*i]) fast_mask.set_bit(*i); + bits = &fast_mask; + } + + piece_picker::piece_state_t state; + peer_connection::peer_speed_t speed = c.peer_speed(); + if (speed == peer_connection::fast) state = piece_picker::fast; + else if (speed == peer_connection::medium) state = piece_picker::medium; + else state = piece_picker::slow; + + // picks the interesting pieces from this peer + // the integer is the number of pieces that + // should be guaranteed to be available for download + // (if num_requests is too big, too many pieces are + // picked and cpu-time is wasted) + // the last argument is if we should prefer whole pieces + // for this peer. If we're downloading one piece in 20 seconds + // then use this mode. + p.pick_pieces(*bits, interesting_pieces + , num_requests, prefer_whole_pieces, c.peer_info_struct() + , state, c.picker_options(), suggested, t.num_peers() + , ses.stats_counters()); + +#ifdef TORRENT_VERBOSE_LOGGING + c.peer_log("*** PIECE_PICKER [ prefer_whole: %d picked: %d ]" + , prefer_whole_pieces, int(interesting_pieces.size())); +#endif + + // if the number of pieces we have + the number of pieces + // we're requesting from is less than the number of pieces + // in the torrent, there are still some unrequested pieces + // and we're not strictly speaking in end-game mode yet + // also, if we already have at least one outstanding + // request, we shouldn't pick any busy pieces either + // in time critical mode, it's OK to request busy blocks + bool dont_pick_busy_blocks + = ((ses.settings().get_bool(settings_pack::strict_end_game_mode) + && p.get_download_queue_size() < p.num_want_left()) + || dq.size() + rq.size() > 0) + && !time_critical_mode; + + // this is filled with an interesting piece + // that some other peer is currently downloading + piece_block busy_block = piece_block::invalid; + + for (std::vector::iterator i = interesting_pieces.begin(); + i != interesting_pieces.end(); ++i) + { + if (prefer_whole_pieces == 0 && num_requests <= 0) break; + + if (time_critical_mode && p.piece_priority(i->piece_index) != 7) + { + // assume the subsequent pieces are not prio 7 and + // be done + break; + } + + int num_block_requests = p.num_peers(*i); + if (num_block_requests > 0) + { + // have we picked enough pieces? + if (num_requests <= 0) break; + + // this block is busy. This means all the following blocks + // in the interesting_pieces list are busy as well, we might + // as well just exit the loop + if (dont_pick_busy_blocks) break; + + TORRENT_ASSERT(p.num_peers(*i) > 0); + busy_block = *i; + continue; + } + + TORRENT_ASSERT(p.num_peers(*i) == 0); + + // don't request pieces we already have in our request queue + // This happens when pieces time out or the peer sends us + // pieces we didn't request. Those aren't marked in the + // piece picker, but we still keep track of them in the + // download queue + if (std::find_if(dq.begin(), dq.end(), has_block(*i)) != dq.end() + || std::find_if(rq.begin(), rq.end(), has_block(*i)) != rq.end()) + { +#ifdef TORRENT_DEBUG + std::vector::const_iterator j + = std::find_if(dq.begin(), dq.end(), has_block(*i)); + if (j != dq.end()) TORRENT_ASSERT(j->timed_out || j->not_wanted); +#endif +#ifdef TORRENT_VERBOSE_LOGGING + c.peer_log("*** PIECE_PICKER [ not_picking: %d,%d already in queue ]" + , i->piece_index, i->block_index); +#endif + continue; + } + + // ok, we found a piece that's not being downloaded + // by somebody else. request it from this peer + // and return + if (!c.add_request(*i, 0)) continue; + TORRENT_ASSERT(p.num_peers(*i) == 1); + TORRENT_ASSERT(p.is_requested(*i)); + num_requests--; + } + + // we have picked as many blocks as we should + // we're done! + if (num_requests <= 0) + { + // since we could pick as many blocks as we + // requested without having to resort to picking + // busy ones, we're not in end-game mode + c.set_endgame(false); + return true; + } + + // we did not pick as many pieces as we wanted, because + // there aren't enough. This means we're in end-game mode + // as long as we have at least one request outstanding, + // we shouldn't pick another piece + // if we are attempting to download 'allowed' pieces + // and can't find any, that doesn't count as end-game + if (!c.has_peer_choked()) + c.set_endgame(true); + + // if we don't have any potential busy blocks to request + // or if we already have outstanding requests, don't + // pick a busy piece + if (busy_block == piece_block::invalid + || dq.size() + rq.size() > 0) + { + return true; + } + +#ifdef TORRENT_DEBUG + piece_picker::downloading_piece st; + p.piece_info(busy_block.piece_index, st); + TORRENT_ASSERT(st.requested + st.finished + st.writing + == p.blocks_in_piece(busy_block.piece_index)); +#endif + TORRENT_ASSERT(p.is_requested(busy_block)); + TORRENT_ASSERT(!p.is_downloaded(busy_block)); + TORRENT_ASSERT(!p.is_finished(busy_block)); + TORRENT_ASSERT(p.num_peers(busy_block) > 0); + + c.add_request(busy_block, peer_connection::req_busy); + return true; + } + +} + diff --git a/src/resolver.cpp b/src/resolver.cpp new file mode 100644 index 000000000..ee6fc954e --- /dev/null +++ b/src/resolver.cpp @@ -0,0 +1,119 @@ +/* + +Copyright (c) 2013, 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. + +*/ + +#include "libtorrent/resolver.hpp" +#include +#include "libtorrent/debug.hpp" + +namespace libtorrent +{ +// #error the first places to use this resolver is the http_connection/http_tracker_connection and udp_tracker_connection. make sure to prefer cache on shutdown + + resolver::resolver(io_service& ios) + : m_ios(ios) + , m_resolver(ios) + , m_max_size(700) + , m_timeout(1200) + {} + + void resolver::on_lookup(error_code const& ec, tcp::resolver::iterator i + , resolver_interface::callback_t h, std::string hostname) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("resolver::on_lookup"); +#endif + if (ec) + { + std::vector
empty; + h(ec, empty); + return; + } + + dns_cache_entry& ce = m_cache[hostname]; + time_t now = time(NULL); + ce.last_seen = now; + ce.addresses.clear(); + while (i != tcp::resolver::iterator()) + { + ce.addresses.push_back(i->endpoint().address()); + ++i; + } + + h(ec, ce.addresses); + + // if m_cache grows too big, weed out the + // oldest entries + if (m_cache.size() > m_max_size) + { + cache_t::iterator oldest = m_cache.begin(); + for (cache_t::iterator i = m_cache.begin(); + i != m_cache.end(); ++i) + { + cache_t::iterator e = i; + ++i; + if (i->second.last_seen < oldest->second.last_seen) + oldest = i; + } + + // remove the oldest entry + m_cache.erase(oldest); + } + } + + + void resolver::async_resolve(std::string const& host, int flags + , resolver_interface::callback_t const& h) + { + cache_t::iterator i = m_cache.find(host); + if (i != m_cache.end()) + { + // keep cache entries valid for m_timeout seconds + if ((flags & resolver_interface::prefer_cache) + || i->second.last_seen + m_timeout >= time(NULL)) + { + error_code ec; + m_ios.post(boost::bind(h, ec, i->second.addresses)); + return; + } + } + + // the port is ignored + tcp::resolver::query q(host, "80"); + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("resolver::on_lookup"); +#endif + m_resolver.async_resolve(q, boost::bind(&resolver::on_lookup, this, _1, _2 + , h, host)); + } +} + diff --git a/src/rss.cpp b/src/rss.cpp index 700cacd6d..d0a7ca713 100644 --- a/src/rss.cpp +++ b/src/rss.cpp @@ -36,7 +36,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/http_connection.hpp" #include "libtorrent/aux_/session_impl.hpp" #include "libtorrent/session.hpp" -#include "libtorrent/settings.hpp" #include "libtorrent/alert_types.hpp" // for rss_alert #include @@ -398,86 +397,52 @@ void feed::on_feed(error_code const& ec m_ses.update_rss_feeds(); } -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Winvalid-offsetof" -#endif - -#define TORRENT_SETTING(t, x) {#x, offsetof(feed_settings,x), t}, - bencode_map_entry feed_settings_map[] = - { - TORRENT_SETTING(std_string, url) - TORRENT_SETTING(boolean, auto_download) - TORRENT_SETTING(boolean, auto_map_handles) - TORRENT_SETTING(integer, default_ttl) - }; -#undef TORRENT_SETTING - -#define TORRENT_SETTING(t, x) {#x, offsetof(feed_item,x), t}, - bencode_map_entry feed_item_map[] = - { - TORRENT_SETTING(std_string, url) - TORRENT_SETTING(std_string, uuid) - TORRENT_SETTING(std_string, title) - TORRENT_SETTING(std_string, description) - TORRENT_SETTING(std_string, comment) - TORRENT_SETTING(std_string, category) - TORRENT_SETTING(size_integer, size) - }; -#undef TORRENT_SETTING - -#define TORRENT_SETTING(t, x) {#x, offsetof(feed,x), t}, - bencode_map_entry feed_map[] = - { - TORRENT_SETTING(std_string, m_title) - TORRENT_SETTING(std_string, m_description) - TORRENT_SETTING(time_integer, m_last_attempt) - TORRENT_SETTING(time_integer, m_last_update) - }; -#undef TORRENT_SETTING - -#define TORRENT_SETTING(t, x) {#x, offsetof(add_torrent_params,x), t}, - bencode_map_entry add_torrent_map[] = - { - TORRENT_SETTING(std_string, save_path) - TORRENT_SETTING(size_integer, flags) - }; -#undef TORRENT_SETTING - -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - void feed::load_state(lazy_entry const& rd) { - load_struct(rd, this, feed_map, sizeof(feed_map)/sizeof(feed_map[0])); + m_title = rd.dict_find_string_value("m_title"); + m_description = rd.dict_find_string_value("m_description"); + m_last_attempt = rd.dict_find_int_value("m_last_attempt"); + m_last_update = rd.dict_find_int_value("m_last_update"); + lazy_entry const* e = rd.dict_find_list("items"); if (e) { m_items.reserve(e->list_size()); for (int i = 0; i < e->list_size(); ++i) { - if (e->list_at(i)->type() != lazy_entry::dict_t) continue; + lazy_entry const* entry = e->list_at(i); + if (entry->type() != lazy_entry::dict_t) continue; + m_items.push_back(feed_item()); - load_struct(*e->list_at(i), &m_items.back(), feed_item_map - , sizeof(feed_item_map)/sizeof(feed_item_map[0])); + feed_item& item = m_items.back(); + item.url = entry->dict_find_string_value("url"); + item.uuid = entry->dict_find_string_value("uuid"); + item.title = entry->dict_find_string_value("title"); + item.description = entry->dict_find_string_value("description"); + item.comment = entry->dict_find_string_value("comment"); + item.category = entry->dict_find_string_value("category"); + item.size = entry->dict_find_int_value("size"); // don't load duplicates - if (m_urls.find(m_items.back().url) != m_urls.end()) + if (m_urls.find(item.url) != m_urls.end()) { m_items.pop_back(); continue; } - m_urls.insert(m_items.back().url); + m_urls.insert(item.url); } } - load_struct(rd, &m_settings, feed_settings_map - , sizeof(feed_settings_map)/sizeof(feed_settings_map[0])); + + m_settings.url = rd.dict_find_string_value("url"); + m_settings.auto_download = rd.dict_find_int_value("auto_download"); + m_settings.auto_map_handles = rd.dict_find_int_value("auto_map_handles"); + m_settings.default_ttl = rd.dict_find_int_value("default_ttl"); + e = rd.dict_find_dict("add_params"); if (e) { - load_struct(*e, &m_settings.add_args, add_torrent_map - , sizeof(add_torrent_map)/sizeof(add_torrent_map[0])); + m_settings.add_args.save_path = e->dict_find_string_value("save_path"); + m_settings.add_args.flags = e->dict_find_int_value("flags"); } e = rd.dict_find_list("history"); @@ -504,7 +469,10 @@ void feed::load_state(lazy_entry const& rd) void feed::save_state(entry& rd) const { // feed properties - save_struct(rd, this, feed_map, sizeof(feed_map)/sizeof(feed_map[0])); + rd["m_title"] = m_title; + rd["m_description"] = m_description; + rd["m_last_attempt"] = m_last_attempt; + rd["m_last_update"] = m_last_update; // items entry::list_type& items = rd["items"].list(); @@ -513,17 +481,36 @@ void feed::save_state(entry& rd) const { items.push_back(entry()); entry& item = items.back(); - save_struct(item, &*i, feed_item_map, sizeof(feed_item_map)/sizeof(feed_item_map[0])); + item["url"] = i->url; + item["uuid"] = i->uuid; + item["title"] = i->title; + item["description"] = i->description; + item["comment"] = i->comment; + item["category"] = i->category; + item["size"] = i->size; } // settings feed_settings sett_def; - save_struct(rd, &m_settings, feed_settings_map - , sizeof(feed_settings_map)/sizeof(feed_settings_map[0]), &sett_def); +#define TORRENT_WRITE_SETTING(name) \ + if (m_settings.name != sett_def.name) rd[#name] = m_settings.name + + TORRENT_WRITE_SETTING(url); + TORRENT_WRITE_SETTING(auto_download); + TORRENT_WRITE_SETTING(auto_map_handles); + TORRENT_WRITE_SETTING(default_ttl); + +#undef TORRENT_WRITE_SETTING + entry& add = rd["add_params"]; add_torrent_params add_def; - save_struct(add, &m_settings.add_args, add_torrent_map - , sizeof(add_torrent_map)/sizeof(add_torrent_map[0]), &add_def); +#define TORRENT_WRITE_SETTING(name) \ + if (m_settings.add_args.name != add_def.name) add[#name] = m_settings.add_args.name; + + TORRENT_WRITE_SETTING(save_path); + TORRENT_WRITE_SETTING(flags); + +#undef TORRENT_WRITE_SETTING entry::list_type& history = rd["history"].list(); for (std::map::const_iterator i = m_added.begin() @@ -597,11 +584,13 @@ int feed::update_feed() boost::shared_ptr feed( new http_connection(m_ses.m_io_service, m_ses.m_half_open + , m_ses.m_host_resolver , boost::bind(&feed::on_feed, shared_from_this() , _1, _2, _3, _4))); m_updating = true; - feed->get(m_settings.url, seconds(30), 0, 0, 5, m_ses.m_settings.user_agent); + feed->get(m_settings.url, seconds(30), 0, 0, 5 + , m_ses.m_settings.get_str(settings_pack::user_agent)); return 60 + m_failures * m_failures * 60; } diff --git a/src/session.cpp b/src/session.cpp index 46d4491c2..f97bf8fcd 100644 --- a/src/session.cpp +++ b/src/session.cpp @@ -73,6 +73,10 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/upnp.hpp" #include "libtorrent/magnet_uri.hpp" +#ifdef TORRENT_PROFILE_CALLS +#include +#endif + using boost::shared_ptr; using boost::weak_ptr; using libtorrent::aux::session_impl; @@ -82,226 +86,257 @@ namespace libtorrent TORRENT_EXPORT void TORRENT_LINK_TEST_NAME() {} - // this function returns a session_settings object - // which will optimize libtorrent for minimum memory - // usage, with no consideration of performance. - TORRENT_EXPORT session_settings min_memory_usage() + TORRENT_EXPORT void min_memory_usage(settings_pack& set) { - session_settings set; + // receive data directly into disk buffers + // this yields more system calls to read() and + // kqueue(), but saves RAM. + set.set_bool(settings_pack::contiguous_recv_buffer, false); - set.alert_queue_size = 100; + set.set_int(settings_pack::disk_io_write_mode, settings_pack::disable_os_cache); + set.set_int(settings_pack::disk_io_read_mode, settings_pack::disable_os_cache); - set.max_allowed_in_request_queue = 100; + // keep 2 blocks outstanding when hashing + set.set_int(settings_pack::checking_mem_usage, 2); + + // don't use any extra threads to do SHA-1 hashing + set.set_int(settings_pack::hashing_threads, 0); + set.set_int(settings_pack::network_threads, 0); + set.set_int(settings_pack::aio_threads, 1); + + set.set_int(settings_pack::alert_queue_size, 100); + + set.set_int(settings_pack::max_out_request_queue, 300); + set.set_int(settings_pack::max_allowed_in_request_queue, 100); // setting this to a low limit, means more // peers are more likely to request from the // same piece. Which means fewer partial // pieces and fewer entries in the partial // piece list - set.whole_pieces_threshold = 2; - set.use_parole_mode = false; - set.prioritize_partial_pieces = true; + set.set_int(settings_pack::whole_pieces_threshold, 2); + set.set_bool(settings_pack::use_parole_mode, false); + set.set_bool(settings_pack::prioritize_partial_pieces, true); // connect to 5 peers per second - set.connection_speed = 5; + set.set_int(settings_pack::connection_speed, 5); // be extra nice on the hard drive when running // on embedded devices. This might slow down // torrent checking - set.file_checks_delay_per_block = 5; + set.set_int(settings_pack::file_checks_delay_per_block, 5); // only have 4 files open at a time - set.file_pool_size = 4; + set.set_int(settings_pack::file_pool_size, 4); // we want to keep the peer list as small as possible - set.allow_multiple_connections_per_ip = false; - set.max_failcount = 2; - set.inactivity_timeout = 120; + set.set_bool(settings_pack::allow_multiple_connections_per_ip, false); + set.set_int(settings_pack::max_failcount, 2); + set.set_int(settings_pack::inactivity_timeout, 120); // whenever a peer has downloaded one block, write // it to disk, and don't read anything from the // socket until the disk write is complete - set.max_queued_disk_bytes = 1; + set.set_int(settings_pack::max_queued_disk_bytes, 1); // don't keep track of all upnp devices, keep // the device list small - set.upnp_ignore_nonrouters = true; + set.set_bool(settings_pack::upnp_ignore_nonrouters, true); // never keep more than one 16kB block in // the send buffer - set.send_buffer_watermark = 9; + set.set_int(settings_pack::send_buffer_watermark, 9); // don't use any disk cache - set.cache_size = 0; - set.cache_buffer_chunk_size = 1; - set.use_read_cache = false; - set.use_disk_read_ahead = false; + set.set_int(settings_pack::cache_size, 0); + set.set_int(settings_pack::cache_buffer_chunk_size, 1); + set.set_bool(settings_pack::use_read_cache, false); + set.set_bool(settings_pack::use_disk_read_ahead, false); - set.close_redundant_connections = true; + set.set_bool(settings_pack::close_redundant_connections, true); - set.max_peerlist_size = 500; - set.max_paused_peerlist_size = 50; + set.set_int(settings_pack::max_peerlist_size, 500); + set.set_int(settings_pack::max_paused_peerlist_size, 50); // udp trackers are cheaper to talk to - set.prefer_udp_trackers = true; + set.set_bool(settings_pack::prefer_udp_trackers, true); - set.max_rejects = 10; + set.set_int(settings_pack::max_rejects, 10); - set.recv_socket_buffer_size = 16 * 1024; - set.send_socket_buffer_size = 16 * 1024; - - // use less memory when checking pieces - set.optimize_hashing_for_speed = false; + set.set_int(settings_pack::recv_socket_buffer_size, 16 * 1024); + set.set_int(settings_pack::send_socket_buffer_size, 16 * 1024); // use less memory when reading and writing // whole pieces - set.coalesce_reads = false; - set.coalesce_writes = false; + set.set_bool(settings_pack::coalesce_reads, false); + set.set_bool(settings_pack::coalesce_writes, false); // disallow the buffer size to grow for the uTP socket - set.utp_dynamic_sock_buf = false; - - // max 'bottled' http receive buffer/url torrent size - set.max_http_recv_buffer_size = 1024 * 1024; - - return set; + set.set_bool(settings_pack::utp_dynamic_sock_buf, false); } - TORRENT_EXPORT session_settings high_performance_seed() + TORRENT_EXPORT void high_performance_seed(settings_pack& set) { - session_settings set; - - // allow peers to request a lot of blocks at a time, - // to be more likely to saturate the bandwidth-delay- - // product. - set.max_out_request_queue = 1500; - set.max_allowed_in_request_queue = 2000; - // don't throttle TCP, assume there is // plenty of bandwidth - set.mixed_mode_algorithm = session_settings::prefer_tcp; + set.set_int(settings_pack::mixed_mode_algorithm, settings_pack::prefer_tcp); - set.max_allowed_in_request_queue = 2000; - set.max_out_request_queue = 1000; + set.set_int(settings_pack::max_out_request_queue, 1500); + set.set_int(settings_pack::max_allowed_in_request_queue, 2000); // we will probably see a high rate of alerts, make it less // likely to loose alerts - set.alert_queue_size = 50000; + set.set_int(settings_pack::alert_queue_size, 10000); // allow 500 files open at a time - set.file_pool_size = 500; + set.set_int(settings_pack::file_pool_size, 500); // don't update access time for each read/write - set.no_atime_storage = true; + set.set_bool(settings_pack::no_atime_storage, true); // as a seed box, we must accept multiple peers behind // the same NAT - set.allow_multiple_connections_per_ip = true; +// set.set_bool(settings_pack::allow_multiple_connections_per_ip, true); // connect to 50 peers per second - set.connection_speed = 50; + set.set_int(settings_pack::connection_speed, 500); // allow 8000 peer connections - set.connections_limit = 8000; + set.set_int(settings_pack::connections_limit, 8000); // allow lots of peers to try to connect simultaneously - set.listen_queue_size = 200; + set.set_int(settings_pack::listen_queue_size, 3000); // unchoke many peers - set.unchoke_slots_limit = 500; + set.set_int(settings_pack::unchoke_slots_limit, 2000); // we need more DHT capacity to ping more peers // candidates before trying to connect - set.dht_upload_rate_limit = 20000; - - // we're more interested in downloading than seeding - // only service a read job every 1000 write job (when - // disk is congested). Presumably on a big box, writes - // are extremely cheap and reads are relatively expensive - // so that's the main reason this ratio should be adjusted - set.read_job_every = 100; + set.set_int(settings_pack::dht_upload_rate_limit, 20000); // use 1 GB of cache - set.cache_size = 32768 * 2; - set.use_read_cache = true; - set.cache_buffer_chunk_size = 128; - set.read_cache_line_size = 32; - set.write_cache_line_size = 32; - set.low_prio_disk = false; - // one hour expiration - set.cache_expiry = 60 * 60; + set.set_int(settings_pack::cache_size, 32768 * 2); + set.set_bool(settings_pack::use_read_cache, true); + set.set_int(settings_pack::cache_buffer_chunk_size, 0); + set.set_int(settings_pack::read_cache_line_size, 32); + set.set_int(settings_pack::write_cache_line_size, 256); + set.set_bool(settings_pack::low_prio_disk, false); + // 30 seconds expiration to save cache + // space for active pieces + set.set_int(settings_pack::cache_expiry, 30); // this is expensive and could add significant // delays when freeing a large number of buffers - set.lock_disk_cache = false; + set.set_bool(settings_pack::lock_disk_cache, false); + + // in case the OS we're running on doesn't support + // readv/writev, allocate contiguous buffers for + // reads and writes + // disable, since it uses a lot more RAM and a significant + // amount of CPU to copy it around + set.set_bool(settings_pack::coalesce_reads, false); + set.set_bool(settings_pack::coalesce_writes, false); // the max number of bytes pending write before we throttle // download rate - set.max_queued_disk_bytes = 10 * 1024 * 1024; - // flush write cache in a way to minimize the amount we need to - // read back once we want to hash-check the piece. i.e. try to - // flush all blocks in-order - set.disk_cache_algorithm = session_settings::avoid_readback; + set.set_int(settings_pack::max_queued_disk_bytes, 7 * 1024 * 1024); - set.explicit_read_cache = false; + set.set_bool(settings_pack::explicit_read_cache, false); // prevent fast pieces to interfere with suggested pieces // since we unchoke everyone, we don't need fast pieces anyway - set.allowed_fast_set_size = 0; + set.set_int(settings_pack::allowed_fast_set_size, 0); + // suggest pieces in the read cache for higher cache hit rate - set.suggest_mode = session_settings::suggest_read_cache; + set.set_int(settings_pack::suggest_mode, settings_pack::suggest_read_cache); - set.close_redundant_connections = true; + set.set_bool(settings_pack::close_redundant_connections, true); - set.max_rejects = 10; + set.set_int(settings_pack::max_rejects, 10); - set.recv_socket_buffer_size = 1024 * 1024; - set.send_socket_buffer_size = 1024 * 1024; - - set.optimize_hashing_for_speed = true; + set.set_int(settings_pack::recv_socket_buffer_size, 1024 * 1024); + set.set_int(settings_pack::send_socket_buffer_size, 1024 * 1024); // don't let connections linger for too long - set.request_timeout = 10; - set.peer_timeout = 20; - set.inactivity_timeout = 20; + set.set_int(settings_pack::request_timeout, 10); + set.set_int(settings_pack::peer_timeout, 20); + set.set_int(settings_pack::inactivity_timeout, 20); - set.active_limit = 2000; - set.active_tracker_limit = 2000; - set.active_dht_limit = 600; - set.active_seeds = 2000; + set.set_int(settings_pack::active_limit, 2000); + set.set_int(settings_pack::active_tracker_limit, 2000); + set.set_int(settings_pack::active_dht_limit, 600); + set.set_int(settings_pack::active_seeds, 2000); - set.choking_algorithm = session_settings::fixed_slots_choker; + set.set_int(settings_pack::choking_algorithm, settings_pack::fixed_slots_choker); - // in order to be able to deliver very high - // upload rates, this should be able to cover - // the bandwidth delay product. Assuming an RTT - // of 500 ms, and a send rate of 20 MB/s, the upper - // limit should be 10 MB - set.send_buffer_watermark = 3 * 1024 * 1024; + // of 500 ms, and a send rate of 4 MB/s, the upper + // limit should be 2 MB + set.set_int(settings_pack::send_buffer_watermark, 3 * 1024 * 1024); // put 1.5 seconds worth of data in the send buffer // this gives the disk I/O more heads-up on disk // reads, and can maximize throughput - set.send_buffer_watermark_factor = 150; + set.set_int(settings_pack::send_buffer_watermark_factor, 150); // always stuff at least 1 MiB down each peer // pipe, to quickly ramp up send rates - set.send_buffer_low_watermark = 1 * 1024 * 1024; + set.set_int(settings_pack::send_buffer_low_watermark, 1 * 1024 * 1024); // don't retry peers if they fail once. Let them // connect to us if they want to - set.max_failcount = 1; + set.set_int(settings_pack::max_failcount, 1); // allow the buffer size to grow for the uTP socket - set.utp_dynamic_sock_buf = true; + set.set_bool(settings_pack::utp_dynamic_sock_buf, true); - // max 'bottled' http receive buffer/url torrent size - set.max_http_recv_buffer_size = 6 * 1024 * 1024; + // we're likely to have more than 4 cores on a high + // performance machine. One core is needed for the + // network thread + set.set_int(settings_pack::hashing_threads, 4); + + // the number of threads to use to call async_write_some + // and read_some on peer sockets + // this doesn't work. See comment in settings_pack.cpp + set.set_int(settings_pack::network_threads, 0); + + // number of disk threads for low level file operations + set.set_int(settings_pack::aio_threads, 8); + + // keep 5 MiB outstanding when checking hashes + // of a resumed file + set.set_int(settings_pack::checking_mem_usage, 320); // the disk cache performs better with the pool allocator - set.use_disk_cache_pool = true; - - return set; + set.set_bool(settings_pack::use_disk_cache_pool, true); } +#ifndef TORRENT_NO_DEPRECATE + // this function returns a session_settings object + // which will optimize libtorrent for minimum memory + // usage, with no consideration of performance. + TORRENT_EXPORT session_settings min_memory_usage() + { + aux::session_settings def; + initialize_default_settings(def); + settings_pack pack; + min_memory_usage(pack); + apply_pack(&pack, def, 0); + session_settings ret; + load_struct_from_settings(def, ret); + return ret; + } + + TORRENT_EXPORT session_settings high_performance_seed() + { + aux::session_settings def; + initialize_default_settings(def); + settings_pack pack; + high_performance_seed(pack); + apply_pack(&pack, def, 0); + session_settings ret; + load_struct_from_settings(def, ret); + return ret; + } +#endif + // wrapper around a function that's executed in the network thread // ans synchronized in the client thread template @@ -321,6 +356,44 @@ namespace libtorrent e->notify_all(); } +#ifdef TORRENT_PROFILE_CALLS + + static mutex g_calls_mutex; + static boost::unordered_map g_blocking_calls; + + void blocking_call() + { + char stack[2048]; + print_backtrace(stack, sizeof(stack), 20); + mutex::scoped_lock l(g_calls_mutex); + g_blocking_calls[stack] += 1; + } + + void dump_call_profile() + { + FILE* out = fopen("blocking_calls.txt", "w+"); + + std::map profile; + + mutex::scoped_lock l(g_calls_mutex); + for (boost::unordered_map::const_iterator i = g_blocking_calls.begin() + , end(g_blocking_calls.end()); i != end; ++i) + { + profile[i->second] = i->first; + } + for (std::map::const_reverse_iterator i = profile.rbegin() + , end(profile.rend()); i != end; ++i) + { + fprintf(out, "\n\n%d\n%s\n", i->first, i->second.c_str()); + } + fclose(out); + } + +#define TORRENT_RECORD_BLOCKING_CALL blocking_call(); +#else +#define TORRENT_RECORD_BLOCKING_CALL +#endif + #define TORRENT_ASYNC_CALL(x) \ m_impl->m_io_service.dispatch(boost::bind(&session_impl:: x, m_impl.get())) @@ -334,6 +407,7 @@ namespace libtorrent m_impl->m_io_service.dispatch(boost::bind(&session_impl:: x, m_impl.get(), a1, a2, a3)) #define TORRENT_WAIT \ + TORRENT_RECORD_BLOCKING_CALL \ mutex::scoped_lock l(m_impl->mut); \ while (!done) { m_impl->cond.wait(l); }; @@ -401,8 +475,7 @@ namespace libtorrent { throw; } #endif - void session::init(std::pair listen_range, char const* listen_interface - , fingerprint const& id, boost::uint32_t alert_mask) + void session::init(fingerprint const& id) { #if defined _MSC_VER && defined TORRENT_DEBUG // workaround for microsofts @@ -411,7 +484,7 @@ namespace libtorrent ::_set_se_translator(straight_to_debugger); #endif - m_impl.reset(new session_impl(listen_range, id, listen_interface, alert_mask)); + m_impl.reset(new session_impl(id)); } void session::set_log_path(std::string const& p) @@ -422,7 +495,7 @@ namespace libtorrent #endif } - void session::start(int flags) + void session::start(int flags, settings_pack const& pack) { #ifndef TORRENT_DISABLE_EXTENSIONS if (flags & add_default_plugins) @@ -434,21 +507,16 @@ namespace libtorrent } #endif - m_impl->start_session(); - - if (flags & start_default_features) - { - start_upnp(); - start_natpmp(); -#ifndef TORRENT_DISABLE_DHT - start_dht(); -#endif - start_lsd(); - } + m_impl->start_session(pack); } session::~session() { + +#ifdef TORRENT_PROFILE_CALLS + dump_call_profile(); +#endif + TORRENT_ASSERT(m_impl); // if there is at least one destruction-proxy // abort the session and let the destructor @@ -490,6 +558,11 @@ namespace libtorrent TORRENT_SYNC_CALL1(get_feeds, &f); } + void session::set_load_function(user_load_function_t fun) + { + TORRENT_ASYNC_CALL1(set_load_function, fun); + } + void session::add_extension(boost::function(torrent*, void*)> ext) { #ifndef TORRENT_DISABLE_EXTENSIONS @@ -628,6 +701,20 @@ namespace libtorrent TORRENT_ASYNC_CALL(post_torrent_updates); } + std::vector session_stats_metrics() + { + std::vector ret; + // defined in session_stats.cpp + extern void get_stats_metric_map(std::vector& stats); + get_stats_metric_map(ret); + return ret; + } + + void session::post_session_stats() + { + TORRENT_ASYNC_CALL(post_session_stats); + } + std::vector session::get_torrents() const { TORRENT_SYNC_CALL_RET(std::vector, get_torrents); @@ -681,7 +768,7 @@ namespace libtorrent , bool paused , storage_constructor_type sc) { - boost::intrusive_ptr tip(new torrent_info(ti)); + boost::shared_ptr tip(boost::make_shared(ti)); add_torrent_params p(sc); p.ti = tip; p.save_path = save_path; @@ -694,28 +781,6 @@ namespace libtorrent return add_torrent(p); } - torrent_handle session::add_torrent( - boost::intrusive_ptr ti - , std::string const& save_path - , entry const& resume_data - , storage_mode_t storage_mode - , bool paused - , storage_constructor_type sc - , void* userdata) - { - add_torrent_params p(sc); - p.ti = ti; - p.save_path = save_path; - if (resume_data.type() != entry::undefined_t) - { - bencode(std::back_inserter(p.resume_data), resume_data); - } - p.storage_mode = storage_mode; - p.paused = paused; - p.userdata = userdata; - return add_torrent(p); - } - torrent_handle session::add_torrent( char const* tracker_url , sha1_hash const& info_hash @@ -750,24 +815,33 @@ namespace libtorrent } #ifndef TORRENT_NO_DEPRECATE - bool session::listen_on( - std::pair const& port_range - , const char* net_interface, int flags) - { - error_code ec; - TORRENT_SYNC_CALL4(listen_on, port_range, boost::ref(ec), net_interface, flags); - return !!ec; - } -#endif - void session::listen_on( std::pair const& port_range , error_code& ec , const char* net_interface, int flags) { - TORRENT_SYNC_CALL4(listen_on, port_range, boost::ref(ec), net_interface, flags); + settings_pack p; + std::string interfaces_str; + if (net_interface == NULL || strlen(net_interface) == 0) + net_interface = "0.0.0.0"; + + interfaces_str = print_endpoint(tcp::endpoint(address::from_string(net_interface, ec), port_range.first)); + if (ec) return; + + p.set_str(settings_pack::listen_interfaces, interfaces_str); + p.set_int(settings_pack::max_retry_port_bind, port_range.second - port_range.first); + p.set_bool(settings_pack::listen_system_port_fallback, (flags & session::listen_no_system_port) == 0); + apply_settings(p); } + void session::use_interfaces(char const* interfaces) + { + settings_pack pack; + pack.set_str(settings_pack::outgoing_interfaces, interfaces); + apply_settings(pack); + } +#endif + unsigned short session::listen_port() const { TORRENT_SYNC_CALL_RET(unsigned short, listen_port); @@ -802,31 +876,53 @@ namespace libtorrent return r; } +#ifndef TORRENT_NO_DEPRECATE void session::get_cache_info(sha1_hash const& ih , std::vector& ret) const { - m_impl->m_disk_thread.get_cache_info(ih, ret); + cache_status st; + get_cache_info(&st, find_torrent(ih)); + ret.swap(st.pieces); } cache_status session::get_cache_status() const { - return m_impl->m_disk_thread.status(); + cache_status st; + get_cache_info(&st); + return st; + } +#endif + + void session::get_cache_info(cache_status* ret + , torrent_handle h, int flags) const + { + piece_manager* st = 0; + boost::shared_ptr t = h.m_torrent.lock(); + if (t) + { + if (t->has_storage()) + st = &t->storage(); + else + flags = session::disk_cache_no_pieces; + } + m_impl->m_disk_thread.get_cache_info(ret, flags & session::disk_cache_no_pieces, st); } +#ifndef TORRENT_NO_DEPRECATE void session::start_dht() { -#ifndef TORRENT_DISABLE_DHT - // the state is loaded in load_state() - TORRENT_ASYNC_CALL(start_dht); -#endif + settings_pack p; + p.set_bool(settings_pack::enable_dht, true); + apply_settings(p); } void session::stop_dht() { -#ifndef TORRENT_DISABLE_DHT - TORRENT_ASYNC_CALL(stop_dht); -#endif + settings_pack p; + p.set_bool(settings_pack::enable_dht, false); + apply_settings(p); } +#endif void session::set_dht_settings(dht_settings const& settings) { @@ -915,22 +1011,62 @@ namespace libtorrent #endif } - void session::set_pe_settings(pe_settings const& settings) +#ifndef TORRENT_NO_DEPRECATE + void session::set_pe_settings(pe_settings const& r) { -#ifndef TORRENT_DISABLE_ENCRYPTION - TORRENT_ASYNC_CALL1(set_pe_settings, settings); -#endif + settings_pack pack; + pack.set_bool(settings_pack::prefer_rc4, r.prefer_rc4); + pack.set_int(settings_pack::out_enc_policy, r.out_enc_policy); + pack.set_int(settings_pack::in_enc_policy, r.in_enc_policy); + pack.set_int(settings_pack::allowed_enc_level, r.allowed_enc_level); + + apply_settings(pack); } pe_settings session::get_pe_settings() const { -#ifndef TORRENT_DISABLE_ENCRYPTION - TORRENT_SYNC_CALL_RET(pe_settings, get_pe_settings); -#else + aux::session_settings sett = get_settings(); + pe_settings r; -#endif + r.prefer_rc4 = sett.get_bool(settings_pack::prefer_rc4); + r.out_enc_policy = sett.get_int(settings_pack::out_enc_policy); + r.in_enc_policy = sett.get_int(settings_pack::in_enc_policy); + r.allowed_enc_level = sett.get_int(settings_pack::allowed_enc_level); return r; } +#endif // TORRENT_NO_DEPRECATE + + void session::set_peer_class_filter(ip_filter const& f) + { + TORRENT_ASYNC_CALL1(set_peer_class_filter, f); + } + + void session::set_peer_class_type_filter(peer_class_type_filter const& f) + { + TORRENT_ASYNC_CALL1(set_peer_class_type_filter, f); + } + + int session::create_peer_class(char const* name) + { + TORRENT_SYNC_CALL_RET1(int, create_peer_class, name); + return r; + } + + void session::delete_peer_class(int cid) + { + TORRENT_ASYNC_CALL1(delete_peer_class, cid); + } + + peer_class_info session::get_peer_class(int cid) + { + TORRENT_SYNC_CALL_RET1(peer_class_info, get_peer_class, cid); + return r; + } + + void session::set_peer_class(int cid, peer_class_info const& pci) + { + TORRENT_ASYNC_CALL2(set_peer_class, cid, pci); + } bool session::is_listening() const { @@ -938,6 +1074,7 @@ namespace libtorrent return r; } +#ifndef TORRENT_NO_DEPRECATE void session::set_settings(session_settings const& s) { TORRENT_ASYNC_CALL1(set_settings, s); @@ -945,90 +1082,113 @@ namespace libtorrent session_settings session::settings() const { - TORRENT_SYNC_CALL_RET(session_settings, settings); + TORRENT_SYNC_CALL_RET(session_settings, deprecated_settings); return r; } +#endif - void session::set_proxy(proxy_settings const& s) + void session::apply_settings(settings_pack const& s) { - TORRENT_ASYNC_CALL1(set_proxy, s); + settings_pack* copy = new settings_pack(s); + TORRENT_ASYNC_CALL1(apply_settings_pack, copy); } - proxy_settings session::proxy() const + aux::session_settings session::get_settings() const { - TORRENT_SYNC_CALL_RET(proxy_settings, proxy); + TORRENT_SYNC_CALL_RET(aux::session_settings, settings); return r; } #ifndef TORRENT_NO_DEPRECATE + + void session::set_proxy(proxy_settings const& s) + { + settings_pack pack; + pack.set_str(settings_pack::proxy_hostname, s.hostname); + pack.set_str(settings_pack::proxy_username, s.username); + pack.set_str(settings_pack::proxy_password, s.password); + pack.set_int(settings_pack::proxy_type, s.type); + pack.set_int(settings_pack::proxy_port, s.port); + pack.set_bool(settings_pack::proxy_hostnames,s.proxy_hostnames); + pack.set_bool(settings_pack::proxy_peer_connections, s.proxy_peer_connections); + + apply_settings(pack); + } + + proxy_settings session::proxy() const + { + aux::session_settings sett = get_settings(); + + proxy_settings ret; + ret.hostname = sett.get_str(settings_pack::proxy_hostname); + ret.username = sett.get_str(settings_pack::proxy_username); + ret.password = sett.get_str(settings_pack::proxy_password); + ret.type = sett.get_int(settings_pack::proxy_type); + ret.port = sett.get_int(settings_pack::proxy_port); + ret.proxy_hostnames = sett.get_bool(settings_pack::proxy_hostnames); + ret.proxy_peer_connections = sett.get_bool( + settings_pack::proxy_peer_connections); + return ret; + } + void session::set_peer_proxy(proxy_settings const& s) { - TORRENT_ASYNC_CALL1(set_peer_proxy, s); + set_proxy(s); } void session::set_web_seed_proxy(proxy_settings const& s) { - TORRENT_ASYNC_CALL1(set_web_seed_proxy, s); + set_proxy(s); } void session::set_tracker_proxy(proxy_settings const& s) { - TORRENT_ASYNC_CALL1(set_tracker_proxy, s); + set_proxy(s); } proxy_settings session::peer_proxy() const { - TORRENT_SYNC_CALL_RET(proxy_settings, peer_proxy); - return r; + return proxy(); } proxy_settings session::web_seed_proxy() const { - TORRENT_SYNC_CALL_RET(proxy_settings, web_seed_proxy); - return r; + return proxy(); } proxy_settings session::tracker_proxy() const { - TORRENT_SYNC_CALL_RET(proxy_settings, tracker_proxy); - return r; + return proxy(); } - void session::set_dht_proxy(proxy_settings const& s) { -#ifndef TORRENT_DISABLE_DHT - TORRENT_ASYNC_CALL1(set_dht_proxy, s); -#endif + set_proxy(s); } proxy_settings session::dht_proxy() const { -#ifndef TORRENT_DISABLE_DHT - TORRENT_SYNC_CALL_RET(proxy_settings, dht_proxy); - return r; -#else - return proxy_settings(); -#endif + return proxy(); } -#endif // TORRENT_NO_DEPRECATE void session::set_i2p_proxy(proxy_settings const& s) { -#if TORRENT_USE_I2P - TORRENT_ASYNC_CALL1(set_i2p_proxy, s); -#endif + settings_pack pack; + pack.set_str(settings_pack::i2p_hostname, s.hostname); + pack.set_int(settings_pack::i2p_port, s.port); + + apply_settings(pack); } proxy_settings session::i2p_proxy() const { -#if TORRENT_USE_I2P - TORRENT_SYNC_CALL_RET(proxy_settings, i2p_proxy); -#else - proxy_settings r; -#endif - return r; + proxy_settings ret; + aux::session_settings sett = get_settings(); + ret.hostname = sett.get_str(settings_pack::i2p_hostname); + ret.port = sett.get_int(settings_pack::i2p_port); + return ret; } +#endif // TORRENT_NO_DEPRECATE #ifdef TORRENT_STATS void session::enable_stats_logging(bool s) @@ -1152,12 +1312,19 @@ namespace libtorrent return m_impl->wait_for_alert(max_wait); } +#ifndef TORRENT_NO_DEPRECATE void session::set_alert_mask(boost::uint32_t m) { - TORRENT_ASYNC_CALL1(set_alert_mask, m); + settings_pack p; + p.set_int(settings_pack::alert_mask, m); + apply_settings(p); + } + + boost::uint32_t session::get_alert_mask() const + { + return get_settings().get_int(settings_pack::alert_mask); } -#ifndef TORRENT_NO_DEPRECATE size_t session::set_alert_queue_size_limit(size_t queue_size_limit_) { TORRENT_SYNC_CALL_RET1(size_t, set_alert_queue_size_limit, queue_size_limit_); @@ -1180,25 +1347,54 @@ namespace libtorrent default: break; } - TORRENT_ASYNC_CALL1(set_alert_mask, m); + settings_pack p; + p.set_int(settings_pack::alert_mask, m); + apply_settings(p); } -#endif void session::start_lsd() { - TORRENT_ASYNC_CALL(start_lsd); + settings_pack p; + p.set_bool(settings_pack::enable_lsd, true); + apply_settings(p); } void session::start_natpmp() { - TORRENT_ASYNC_CALL(start_natpmp); + settings_pack p; + p.set_bool(settings_pack::enable_natpmp, true); + apply_settings(p); } void session::start_upnp() { - TORRENT_ASYNC_CALL(start_upnp); + settings_pack p; + p.set_bool(settings_pack::enable_upnp, true); + apply_settings(p); } + void session::stop_lsd() + { + settings_pack p; + p.set_bool(settings_pack::enable_lsd, false); + apply_settings(p); + } + + void session::stop_natpmp() + { + settings_pack p; + p.set_bool(settings_pack::enable_natpmp, false); + apply_settings(p); + } + + void session::stop_upnp() + { + settings_pack p; + p.set_bool(settings_pack::enable_upnp, false); + apply_settings(p); + } +#endif + int session::add_port_mapping(protocol_type t, int external_port, int local_port) { TORRENT_SYNC_CALL_RET3(int, add_port_mapping, int(t), external_port, local_port); @@ -1210,213 +1406,22 @@ namespace libtorrent TORRENT_ASYNC_CALL1(delete_port_mapping, handle); } - void session::stop_lsd() - { - TORRENT_ASYNC_CALL(stop_lsd); - } - - void session::stop_natpmp() - { - TORRENT_ASYNC_CALL(stop_natpmp); - } - - void session::stop_upnp() - { - TORRENT_ASYNC_CALL(stop_upnp); - } - connection_queue& session::get_connection_queue() { return m_impl->m_half_open; } +#ifndef TORRENT_NO_DEPRECATE session_settings::session_settings(std::string const& user_agent_) - : version(LIBTORRENT_VERSION_NUM) - , user_agent(user_agent_) - , tracker_completion_timeout(60) - , tracker_receive_timeout(40) - , stop_tracker_timeout(5) - , tracker_maximum_response_length(1024*1024) - , piece_timeout(20) - , request_timeout(50) - , request_queue_time(3) - , max_allowed_in_request_queue(500) - , max_out_request_queue(500) - , whole_pieces_threshold(20) - , peer_timeout(120) - , urlseed_timeout(20) - , urlseed_pipeline_size(5) - , urlseed_wait_retry(30) - , file_pool_size(40) - , allow_multiple_connections_per_ip(false) - , max_failcount(3) - , min_reconnect_time(60) - , peer_connect_timeout(15) - , ignore_limits_on_local_network(true) - , connection_speed(6) - , send_redundant_have(true) - , lazy_bitfields(true) - , inactivity_timeout(600) - , unchoke_interval(15) - , optimistic_unchoke_interval(30) - , num_want(200) - , initial_picker_threshold(4) - , allowed_fast_set_size(10) - , suggest_mode(no_piece_suggestions) - , max_queued_disk_bytes(1024 * 1024) - , max_queued_disk_bytes_low_watermark(0) - , handshake_timeout(10) - , use_dht_as_fallback(false) - , free_torrent_hashes(true) - , upnp_ignore_nonrouters(false) - , send_buffer_low_watermark(512) - , send_buffer_watermark(500 * 1024) - , send_buffer_watermark_factor(50) -#ifndef TORRENT_NO_DEPRECATE - // deprecated in 0.16 - , auto_upload_slots(true) - , auto_upload_slots_rate_based(true) -#endif - , choking_algorithm(fixed_slots_choker) - , seed_choking_algorithm(round_robin) - , use_parole_mode(true) - , cache_size(1024) - , cache_buffer_chunk_size(16) - , cache_expiry(300) - , use_read_cache(true) - , explicit_read_cache(0) - , explicit_cache_interval(30) - , disk_io_write_mode(0) - , disk_io_read_mode(0) - , coalesce_reads(false) - , coalesce_writes(false) - , outgoing_ports(0,0) - , peer_tos(0) - , active_downloads(3) - , active_seeds(5) - , active_dht_limit(88) // don't announce more than once every 40 seconds - , active_tracker_limit(1600) // don't announce to trackers more than once every 1.125 seconds - , active_lsd_limit(60) // don't announce to local network more than once every 5 seconds - , active_limit(15) - , auto_manage_prefer_seeds(false) - , dont_count_slow_torrents(true) - , auto_manage_interval(30) - , share_ratio_limit(2.f) - , seed_time_ratio_limit(7.f) - , seed_time_limit(24 * 60 * 60) // 24 hours - , peer_turnover_interval(300) - , peer_turnover(2 / 50.f) - , peer_turnover_cutoff(.9f) - , close_redundant_connections(true) - , auto_scrape_interval(1800) - , auto_scrape_min_interval(300) - , max_peerlist_size(4000) - , max_paused_peerlist_size(4000) - , min_announce_interval(5 * 60) - , prioritize_partial_pieces(false) - , auto_manage_startup(60) - , rate_limit_ip_overhead(true) - , announce_to_all_trackers(false) - , announce_to_all_tiers(false) - , prefer_udp_trackers(true) - , strict_super_seeding(false) - , seeding_piece_quota(20) -#ifdef TORRENT_WINDOWS - , max_sparse_regions(30000) -#else - , max_sparse_regions(0) -#endif - , lock_disk_cache(false) - , max_rejects(50) - , recv_socket_buffer_size(0) - , send_socket_buffer_size(0) - , optimize_hashing_for_speed(true) - , file_checks_delay_per_block(0) - , disk_cache_algorithm(avoid_readback) - , read_cache_line_size(32) - , write_cache_line_size(32) - , optimistic_disk_retry(10 * 60) - , disable_hash_checks(false) - , allow_reordered_disk_operations(true) - , allow_i2p_mixed(false) - , max_suggest_pieces(10) - , drop_skipped_requests(false) - , low_prio_disk(true) - , local_service_announce_interval(5 * 60) - , dht_announce_interval(15 * 60) - , udp_tracker_token_expiry(60) - , volatile_read_cache(false) - , guided_read_cache(false) - , default_cache_min_age(1) - , num_optimistic_unchoke_slots(0) - , no_atime_storage(true) - , default_est_reciprocation_rate(16000) - , increase_est_reciprocation_rate(20) - , decrease_est_reciprocation_rate(3) - , incoming_starts_queued_torrents(false) - , report_true_downloaded(false) - , strict_end_game_mode(true) - , broadcast_lsd(true) - , enable_outgoing_utp(true) - , enable_incoming_utp(true) - , enable_outgoing_tcp(true) - , enable_incoming_tcp(true) - , max_pex_peers(50) - , ignore_resume_timestamps(false) - , no_recheck_incomplete_resume(false) - , anonymous_mode(true) - , force_proxy(false) - , tick_interval(500) - , report_web_seed_downloads(true) - , share_mode_target(3) - , upload_rate_limit(0) - , download_rate_limit(0) - , local_upload_rate_limit(0) - , local_download_rate_limit(0) - , dht_upload_rate_limit(4000) - , unchoke_slots_limit(8) - , half_open_limit(0) - , connections_limit(200) - , connections_slack(10) - , utp_target_delay(100) // milliseconds - , utp_gain_factor(1500) // bytes per rtt - , utp_min_timeout(500) // milliseconds - , utp_syn_resends(2) - , utp_fin_resends(2) - , utp_num_resends(6) - , utp_connect_timeout(3000) // milliseconds -#ifndef TORRENT_NO_DEPRECATE - , utp_delayed_ack(0) // milliseconds -#endif - , utp_dynamic_sock_buf(false) // this doesn't seem quite reliable yet - , utp_loss_multiplier(50) // specified in percent - , mixed_mode_algorithm(peer_proportional) - , rate_limit_utp(true) - , listen_queue_size(5) - , announce_double_nat(false) - , torrent_connect_boost(10) - , seeding_outgoing_connections(true) - , no_connect_privileged_ports(true) - , alert_queue_size(6000) - , max_metadata_size(3*1024*1024) - , smooth_connects(true) - , always_send_user_agent(false) - , apply_ip_filter_to_trackers(true) - , read_job_every(10) - , use_disk_read_ahead(true) - , lock_files(false) - , ssl_listen(4433) - , tracker_backoff(250) - , ban_web_seeds(true) - , max_http_recv_buffer_size(4*1024*1024) - , support_share_mode(true) - , support_merkle_torrents(false) - , report_redundant_bytes(true) - , use_disk_cache_pool(false) - , inactive_down_rate(2048) - , inactive_up_rate(2048) - {} + { + aux::session_settings def; + initialize_default_settings(def); + def.set_str(settings_pack::user_agent, user_agent_); + load_struct_from_settings(def, *this); + } session_settings::~session_settings() {} +#endif // TORRENT_NO_DEPRECATE + } diff --git a/src/session_impl.cpp b/src/session_impl.cpp index 562dc8bb9..de30dc38a 100644 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -32,10 +32,17 @@ POSSIBILITY OF SUCH DAMAGE. #include #include -#include #include #include +#if defined TORRENT_DEBUG && !defined TORRENT_DISABLE_INVARIANT_CHECKS +#if TORRENT_HAS_BOOST_UNORDERED +#include +#else +#include +#endif +#endif // TORRENT_DEBUG && !TORRENT_DISABLE_INVARIANT_CHECKS + #ifdef _MSC_VER #pragma warning(push, 1) #endif @@ -43,6 +50,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #ifdef TORRENT_USE_VALGRIND #include @@ -79,11 +87,12 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/lsd.hpp" #include "libtorrent/instantiate_connection.hpp" #include "libtorrent/peer_info.hpp" -#include "libtorrent/settings.hpp" #include "libtorrent/build_config.hpp" #include "libtorrent/extensions.hpp" #include "libtorrent/random.hpp" #include "libtorrent/magnet_uri.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/torrent_peer.hpp" #if defined TORRENT_STATS && defined __MACH__ #include @@ -181,13 +190,41 @@ namespace libtorrent { #if defined TORRENT_ASIO_DEBUGGING std::map _async_ops; + std::deque _wakeups; int _async_ops_nthreads = 0; mutex _async_ops_mutex; #endif +socket_job::~socket_job() {} + +void network_thread_pool::process_job(socket_job const& j, bool post) +{ + if (j.type == socket_job::write_job) + { + TORRENT_ASSERT(j.peer->m_socket_is_writing); + j.peer->get_socket()->async_write_some( + *j.vec, j.peer->make_write_handler(boost::bind( + &peer_connection::on_send_data, j.peer, _1, _2))); + } + else + { + if (j.recv_buf) + { + j.peer->get_socket()->async_read_some(asio::buffer(j.recv_buf, j.buf_size) + , j.peer->make_read_handler(boost::bind( + &peer_connection::on_receive_data, j.peer, _1, _2))); + } + else + { + j.peer->get_socket()->async_read_some(j.read_vec + , j.peer->make_read_handler(boost::bind( + &peer_connection::on_receive_data, j.peer, _1, _2))); + } + } +} + namespace detail { - std::string generate_auth_string(std::string const& user , std::string const& passwd) { @@ -199,21 +236,27 @@ namespace detail namespace aux { #ifdef TORRENT_STATS - void get_vm_stats(vm_statistics_data_t* vm_stat) + void get_vm_stats(vm_statistics_data_t* vm_stat, error_code& ec) { memset(vm_stat, 0, sizeof(*vm_stat)); #if defined __MACH__ + ec.clear(); mach_port_t host_port = mach_host_self(); mach_msg_type_number_t host_count = HOST_VM_INFO_COUNT; kern_return_t error = host_statistics(host_port, HOST_VM_INFO, (host_info_t)vm_stat, &host_count); TORRENT_ASSERT_VAL(error == KERN_SUCCESS, error); #elif defined TORRENT_LINUX - char buffer[4096]; - char string[1024]; + ec.clear(); + char string[4096]; boost::uint32_t value; FILE* f = fopen("/proc/vmstat", "r"); int ret = 0; + if (f == 0) + { + ec.assign(errno, boost::system::system_category()); + return; + } while ((ret = fscanf(f, "%s %u\n", string, &value)) != EOF) { if (ret != 2) continue; @@ -228,6 +271,8 @@ namespace aux { else if (strcmp(string, "pgfault") == 0) vm_stat->faults = value; } fclose(f); +#else + ec = asio::error::operation_not_supported; #endif // TOOD: windows? } @@ -239,21 +284,17 @@ namespace aux { mach_msg_type_number_t t_info_count = TASK_THREAD_TIMES_INFO_COUNT; task_info(mach_task_self(), TASK_THREAD_TIMES_INFO, (task_info_t)&t_info, &t_info_count); - tu->user_time = min_time() - + seconds(t_info.user_time.seconds) - + microsec(t_info.user_time.microseconds); - tu->system_time = min_time() - + seconds(t_info.system_time.seconds) - + microsec(t_info.system_time.microseconds); + tu->user_time = seconds(t_info.user_time.seconds) + + microseconds(t_info.user_time.microseconds); + tu->system_time = seconds(t_info.system_time.seconds) + + microseconds(t_info.system_time.microseconds); #elif defined TORRENT_LINUX struct rusage ru; getrusage(RUSAGE_THREAD, &ru); - tu->user_time = min_time() - + seconds(ru.ru_utime.tv_sec) - + microsec(ru.ru_utime.tv_usec); - tu->system_time = min_time() - + seconds(ru.ru_stime.tv_sec) - + microsec(ru.ru_stime.tv_usec); + tu->user_time = seconds(ru.ru_utime.tv_sec) + + microseconds(ru.ru_utime.tv_usec); + tu->system_time = seconds(ru.ru_stime.tv_sec) + + microseconds(ru.ru_stime.tv_usec); #elif defined TORRENT_WINDOWS FILETIME system_time; FILETIME user_time; @@ -266,307 +307,85 @@ namespace aux { boost::uint64_t stime = (boost::uint64_t(system_time.dwHighDateTime) << 32) + system_time.dwLowDateTime; - tu->user_time = min_time() + microsec(utime / 10); - tu->system_time = min_time() + microsec(stime / 10); + tu->user_time = microseconds(utime / 10); + tu->system_time = microseconds(stime / 10); #endif } -#endif //TORRENT_STATS +#endif // TORRENT_STATS struct seed_random_generator { seed_random_generator() { - random_seed((unsigned int)((total_microseconds( - time_now_hires() - min_time())) & 0xffffffff)); + random_seed((unsigned int)((time_now().time_since_epoch().count()) & 0xffffffff)); } }; -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Winvalid-offsetof" -#endif - -#define TORRENT_SETTING(t, x) {#x, offsetof(session_settings,x), t}, - - bencode_map_entry session_settings_map[] = + void session_impl::init_peer_class_filter(bool unlimited_local) { - TORRENT_SETTING(std_string, user_agent) - TORRENT_SETTING(integer, tracker_completion_timeout) - TORRENT_SETTING(integer, tracker_receive_timeout) - TORRENT_SETTING(integer, stop_tracker_timeout) - TORRENT_SETTING(integer, tracker_maximum_response_length) - TORRENT_SETTING(integer, piece_timeout) - TORRENT_SETTING(integer, request_timeout) - TORRENT_SETTING(integer, request_queue_time) - TORRENT_SETTING(integer, max_allowed_in_request_queue) - TORRENT_SETTING(integer, max_out_request_queue) - TORRENT_SETTING(integer, whole_pieces_threshold) - TORRENT_SETTING(integer, peer_timeout) - TORRENT_SETTING(integer, urlseed_timeout) - TORRENT_SETTING(integer, urlseed_pipeline_size) - TORRENT_SETTING(integer, urlseed_wait_retry) - TORRENT_SETTING(integer, file_pool_size) - TORRENT_SETTING(boolean, allow_multiple_connections_per_ip) - TORRENT_SETTING(integer, max_failcount) - TORRENT_SETTING(integer, min_reconnect_time) - TORRENT_SETTING(integer, peer_connect_timeout) - TORRENT_SETTING(boolean, ignore_limits_on_local_network) - TORRENT_SETTING(integer, connection_speed) - TORRENT_SETTING(boolean, send_redundant_have) - TORRENT_SETTING(boolean, lazy_bitfields) - TORRENT_SETTING(integer, inactivity_timeout) - TORRENT_SETTING(integer, unchoke_interval) - TORRENT_SETTING(integer, optimistic_unchoke_interval) - TORRENT_SETTING(std_string, announce_ip) - TORRENT_SETTING(integer, num_want) - TORRENT_SETTING(integer, initial_picker_threshold) - TORRENT_SETTING(integer, allowed_fast_set_size) - TORRENT_SETTING(integer, suggest_mode) - TORRENT_SETTING(integer, max_queued_disk_bytes) - TORRENT_SETTING(integer, max_queued_disk_bytes_low_watermark) - TORRENT_SETTING(integer, handshake_timeout) -#ifndef TORRENT_DISABLE_DHT - TORRENT_SETTING(boolean, use_dht_as_fallback) -#endif - TORRENT_SETTING(boolean, free_torrent_hashes) - TORRENT_SETTING(boolean, upnp_ignore_nonrouters) - TORRENT_SETTING(integer, send_buffer_low_watermark) - TORRENT_SETTING(integer, send_buffer_watermark) - TORRENT_SETTING(integer, send_buffer_watermark_factor) -#ifndef TORRENT_NO_DEPRECATE - TORRENT_SETTING(boolean, auto_upload_slots) - TORRENT_SETTING(boolean, auto_upload_slots_rate_based) -#endif - TORRENT_SETTING(integer, choking_algorithm) - TORRENT_SETTING(integer, seed_choking_algorithm) - TORRENT_SETTING(boolean, use_parole_mode) - TORRENT_SETTING(integer, cache_size) - TORRENT_SETTING(integer, cache_buffer_chunk_size) - TORRENT_SETTING(integer, cache_expiry) - TORRENT_SETTING(boolean, use_read_cache) - TORRENT_SETTING(boolean, explicit_read_cache) - TORRENT_SETTING(integer, disk_io_write_mode) - TORRENT_SETTING(integer, disk_io_read_mode) - TORRENT_SETTING(boolean, coalesce_reads) - TORRENT_SETTING(boolean, coalesce_writes) - TORRENT_SETTING(character, peer_tos) - TORRENT_SETTING(integer, active_downloads) - TORRENT_SETTING(integer, active_seeds) - TORRENT_SETTING(integer, active_dht_limit) - TORRENT_SETTING(integer, active_tracker_limit) - TORRENT_SETTING(integer, active_lsd_limit) - TORRENT_SETTING(integer, active_limit) - TORRENT_SETTING(boolean, auto_manage_prefer_seeds) - TORRENT_SETTING(boolean, dont_count_slow_torrents) - TORRENT_SETTING(integer, auto_manage_interval) - TORRENT_SETTING(floating_point, share_ratio_limit) - TORRENT_SETTING(floating_point, seed_time_ratio_limit) - TORRENT_SETTING(integer, seed_time_limit) - TORRENT_SETTING(floating_point, peer_turnover) - TORRENT_SETTING(floating_point, peer_turnover_cutoff) - TORRENT_SETTING(boolean, close_redundant_connections) - TORRENT_SETTING(integer, auto_scrape_interval) - TORRENT_SETTING(integer, auto_scrape_min_interval) - TORRENT_SETTING(integer, max_peerlist_size) - TORRENT_SETTING(integer, max_paused_peerlist_size) - TORRENT_SETTING(integer, min_announce_interval) - TORRENT_SETTING(boolean, prioritize_partial_pieces) - TORRENT_SETTING(integer, auto_manage_startup) - TORRENT_SETTING(boolean, rate_limit_ip_overhead) - TORRENT_SETTING(boolean, announce_to_all_trackers) - TORRENT_SETTING(boolean, announce_to_all_tiers) - TORRENT_SETTING(boolean, prefer_udp_trackers) - TORRENT_SETTING(boolean, strict_super_seeding) - TORRENT_SETTING(integer, seeding_piece_quota) - TORRENT_SETTING(integer, max_sparse_regions) -#ifndef TORRENT_DISABLE_MLOCK - TORRENT_SETTING(boolean, lock_disk_cache) -#endif - TORRENT_SETTING(integer, max_rejects) - TORRENT_SETTING(integer, recv_socket_buffer_size) - TORRENT_SETTING(integer, send_socket_buffer_size) - TORRENT_SETTING(boolean, optimize_hashing_for_speed) - TORRENT_SETTING(integer, file_checks_delay_per_block) - TORRENT_SETTING(integer, disk_cache_algorithm) - TORRENT_SETTING(integer, read_cache_line_size) - TORRENT_SETTING(integer, write_cache_line_size) - TORRENT_SETTING(integer, optimistic_disk_retry) - TORRENT_SETTING(boolean, disable_hash_checks) - TORRENT_SETTING(boolean, allow_reordered_disk_operations) - TORRENT_SETTING(boolean, allow_i2p_mixed) - TORRENT_SETTING(integer, max_suggest_pieces) - TORRENT_SETTING(boolean, drop_skipped_requests) - TORRENT_SETTING(boolean, low_prio_disk) - TORRENT_SETTING(integer, local_service_announce_interval) - TORRENT_SETTING(integer, dht_announce_interval) - TORRENT_SETTING(integer, udp_tracker_token_expiry) - TORRENT_SETTING(boolean, volatile_read_cache) - TORRENT_SETTING(boolean, guided_read_cache) - TORRENT_SETTING(integer, default_cache_min_age) - TORRENT_SETTING(integer, num_optimistic_unchoke_slots) - TORRENT_SETTING(boolean, no_atime_storage) - TORRENT_SETTING(integer, default_est_reciprocation_rate) - TORRENT_SETTING(integer, increase_est_reciprocation_rate) - TORRENT_SETTING(integer, decrease_est_reciprocation_rate) - TORRENT_SETTING(boolean, incoming_starts_queued_torrents) - TORRENT_SETTING(boolean, report_true_downloaded) - TORRENT_SETTING(boolean, strict_end_game_mode) - TORRENT_SETTING(boolean, broadcast_lsd) - TORRENT_SETTING(boolean, enable_outgoing_utp) - TORRENT_SETTING(boolean, enable_incoming_utp) - TORRENT_SETTING(boolean, enable_outgoing_tcp) - TORRENT_SETTING(boolean, enable_incoming_tcp) - TORRENT_SETTING(integer, max_pex_peers) - TORRENT_SETTING(boolean, ignore_resume_timestamps) - TORRENT_SETTING(boolean, no_recheck_incomplete_resume) - TORRENT_SETTING(boolean, anonymous_mode) - TORRENT_SETTING(boolean, force_proxy) - TORRENT_SETTING(integer, tick_interval) - TORRENT_SETTING(boolean, report_web_seed_downloads) - TORRENT_SETTING(integer, share_mode_target) - TORRENT_SETTING(integer, upload_rate_limit) - TORRENT_SETTING(integer, download_rate_limit) - TORRENT_SETTING(integer, local_upload_rate_limit) - TORRENT_SETTING(integer, local_download_rate_limit) - TORRENT_SETTING(integer, dht_upload_rate_limit) - TORRENT_SETTING(integer, unchoke_slots_limit) - TORRENT_SETTING(integer, half_open_limit) - TORRENT_SETTING(integer, connections_limit) - TORRENT_SETTING(integer, utp_target_delay) - TORRENT_SETTING(integer, utp_gain_factor) - TORRENT_SETTING(integer, utp_syn_resends) - TORRENT_SETTING(integer, utp_fin_resends) - TORRENT_SETTING(integer, utp_num_resends) - TORRENT_SETTING(integer, utp_connect_timeout) -#ifndef TORRENT_NO_DEPRECATE - TORRENT_SETTING(integer, utp_delayed_ack) -#endif - TORRENT_SETTING(boolean, utp_dynamic_sock_buf) - TORRENT_SETTING(integer, mixed_mode_algorithm) - TORRENT_SETTING(boolean, rate_limit_utp) - TORRENT_SETTING(integer, listen_queue_size) - TORRENT_SETTING(boolean, announce_double_nat) - TORRENT_SETTING(integer, torrent_connect_boost) - TORRENT_SETTING(boolean, seeding_outgoing_connections) - TORRENT_SETTING(boolean, no_connect_privileged_ports) - TORRENT_SETTING(integer, alert_queue_size) - TORRENT_SETTING(integer, max_metadata_size) - TORRENT_SETTING(boolean, smooth_connects) - TORRENT_SETTING(boolean, always_send_user_agent) - TORRENT_SETTING(boolean, apply_ip_filter_to_trackers) - TORRENT_SETTING(integer, read_job_every) - TORRENT_SETTING(boolean, use_disk_read_ahead) - TORRENT_SETTING(boolean, lock_files) - TORRENT_SETTING(integer, ssl_listen) - TORRENT_SETTING(integer, tracker_backoff) - TORRENT_SETTING(boolean, ban_web_seeds) - TORRENT_SETTING(integer, max_http_recv_buffer_size) - }; + // set the default peer_class_filter to use the local peer class + // for peers on local networks + boost::uint32_t lfilter = 1 << m_local_peer_class; + boost::uint32_t gfilter = 1 << m_global_class; -#undef TORRENT_SETTING -#define TORRENT_SETTING(t, x) {#x, offsetof(proxy_settings,x), t}, + struct class_mapping + { + char const* first; + char const* last; + boost::uint32_t filter; + }; - bencode_map_entry proxy_settings_map[] = - { - TORRENT_SETTING(std_string, hostname) - TORRENT_SETTING(integer16, port) - TORRENT_SETTING(std_string, username) - TORRENT_SETTING(std_string, password) - TORRENT_SETTING(character, type) - TORRENT_SETTING(boolean, proxy_hostnames) - TORRENT_SETTING(boolean, proxy_peer_connections) - }; -#undef TORRENT_SETTING + const static class_mapping v4_classes[] = + { + // everything + {"0.0.0.0", "255.255.255.255", gfilter}, + // local networks + {"10.0.0.0", "10.255.255.255", lfilter}, + {"172.16.0.0", "172.16.255.255", lfilter}, + {"192.168.0.0", "192.168.255.255", lfilter}, + // link-local + {"169.254.0.0", "169.254.255.255", lfilter}, + // loop-back + {"127.0.0.0", "127.255.255.255", lfilter}, + }; -#ifndef TORRENT_DISABLE_DHT -#define TORRENT_SETTING(t, x) {#x, offsetof(dht_settings,x), t}, - bencode_map_entry dht_settings_map[] = - { - TORRENT_SETTING(integer, max_peers_reply) - TORRENT_SETTING(integer, search_branching) -#ifndef TORRENT_NO_DEPRECATE - TORRENT_SETTING(integer, service_port) -#endif - TORRENT_SETTING(integer, max_fail_count) - TORRENT_SETTING(integer, max_torrents) - TORRENT_SETTING(integer, max_dht_items) - TORRENT_SETTING(integer, max_torrent_search_reply) - TORRENT_SETTING(boolean, restrict_routing_ips) - TORRENT_SETTING(boolean, restrict_search_ips) - TORRENT_SETTING(boolean, extended_routing_table) - }; -#undef TORRENT_SETTING +#if TORRENT_USE_IPV6 + const static class_mapping v6_classes[] = + { + // everything + {"::0", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", gfilter}, + // link-local + {"fe80::", "febf::ffff:ffff:ffff:ffff:ffff:ffff:ffff", lfilter}, + // loop-back + {"::1", "::1", lfilter}, + }; #endif -#ifndef TORRENT_DISABLE_ENCRYPTION -#define TORRENT_SETTING(t, x) {#x, offsetof(pe_settings,x), t}, - bencode_map_entry pe_settings_map[] = - { - TORRENT_SETTING(character, out_enc_policy) - TORRENT_SETTING(character, in_enc_policy) - TORRENT_SETTING(character, allowed_enc_level) - TORRENT_SETTING(boolean, prefer_rc4) - }; -#undef TORRENT_SETTING + class_mapping const* p = v4_classes; + int len = sizeof(v4_classes) / sizeof(v4_classes[0]); + if (!unlimited_local) len = 1; + for (int i = 0; i < len; ++i) + { + error_code ec; + address_v4 begin = address_v4::from_string(p[i].first, ec); + address_v4 end = address_v4::from_string(p[i].last, ec); + if (ec) continue; + m_peer_class_filter.add_rule(begin, end, p[i].filter); + } +#if TORRENT_USE_IPV6 + p = v6_classes; + len = sizeof(v6_classes) / sizeof(v6_classes[0]); + if (!unlimited_local) len = 1; + for (int i = 0; i < len; ++i) + { + error_code ec; + address_v6 begin = address_v6::from_string(p[i].first, ec); + address_v6 end = address_v6::from_string(p[i].last, ec); + if (ec) continue; + m_peer_class_filter.add_rule(begin, end, p[i].filter); + } #endif - - struct session_category - { - char const* name; - bencode_map_entry const* map; - int num_entries; - int flag; - int offset; - int default_offset; - }; - - // the names in here need to match the names in session_impl - // to make the macro simpler - struct all_default_values - { - session_settings m_settings; - proxy_settings m_proxy; -#ifndef TORRENT_DISABLE_ENCRYPTION - pe_settings m_pe_settings; -#endif -#ifndef TORRENT_DISABLE_DHT - dht_settings m_dht_settings; -#endif - }; - -#define lenof(x) sizeof(x)/sizeof(x[0]) -#define TORRENT_CATEGORY(name, flag, member, map) \ - { name, map, lenof(map), session:: flag , offsetof(session_impl, member), offsetof(all_default_values, member) }, - - session_category all_settings[] = - { - TORRENT_CATEGORY("settings", save_settings, m_settings, session_settings_map) -#ifndef TORRENT_DISABLE_DHT - TORRENT_CATEGORY("dht", save_dht_settings, m_dht_settings, dht_settings_map) -#endif - TORRENT_CATEGORY("proxy", save_proxy, m_proxy, proxy_settings_map) -#if TORRENT_USE_I2P -// TORRENT_CATEGORY("i2p", save_i2p_proxy, m_i2p_proxy, proxy_settings_map) -#endif -#ifndef TORRENT_DISABLE_ENCRYPTION - TORRENT_CATEGORY("encryption", save_encryption_settings, m_pe_settings, pe_settings_map) -#endif - }; - - std::pair settings_map() - { - return std::make_pair(session_settings_map, lenof(session_settings_map)); } -#undef lenof - -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - -#ifdef TORRENT_STATS - int session_impl::logging_allocator::allocations = 0; - int session_impl::logging_allocator::allocated_bytes = 0; -#endif #if defined TORRENT_USE_OPENSSL && BOOST_VERSION >= 104700 && OPENSSL_VERSION_NUMBER >= 0x90812f // when running bittorrent over SSL, the SNI (server name indication) @@ -611,26 +430,18 @@ namespace aux { } #endif - session_impl::session_impl( - std::pair listen_port_range - , fingerprint const& cl_fprint - , char const* listen_interface - , boost::uint32_t alert_mask - ) - : m_ipv4_peer_pool(500) -#if TORRENT_USE_IPV6 - , m_ipv6_peer_pool(500) -#endif + session_impl::session_impl(fingerprint const& cl_fprint) + : #ifndef TORRENT_DISABLE_POOL_ALLOCATOR - , m_send_buffers(send_buffer_size) + m_send_buffers(send_buffer_size()) + , #endif - , m_files(40) - , m_io_service() + m_io_service() #ifdef TORRENT_USE_OPENSSL , m_ssl_ctx(m_io_service, asio::ssl::context::sslv23) #endif - , m_alerts(m_settings.alert_queue_size, alert_mask) - , m_disk_thread(m_io_service, boost::bind(&session_impl::on_disk_queue, this), m_files) + , m_alerts(m_settings.get_int(settings_pack::alert_queue_size), alert::all_categories) + , m_disk_thread(m_io_service, this, (uncork_interface*)this) , m_half_open(m_io_service) , m_download_rate(peer_connection::download_channel) #ifdef TORRENT_VERBOSE_BANDWIDTH_LIMIT @@ -638,14 +449,18 @@ namespace aux { #else , m_upload_rate(peer_connection::upload_channel) #endif - , m_tracker_manager(*this, m_proxy) - , m_num_active_downloading(0) - , m_num_active_finished(0) + , m_tracker_manager(*this) + , m_num_save_resume(0) + , m_num_queued_resume(0) + , m_work(io_service::work(m_io_service)) + , m_max_queue_pos(-1) , m_key(0) - , m_listen_port_retries(listen_port_range.second - listen_port_range.first) + , m_listen_port_retries(10) #if TORRENT_USE_I2P , m_i2p_conn(m_io_service) #endif + , m_socks_listen_port(0) + , m_interface_index(0) , m_allowed_upload_slots(8) , m_num_unchoked(0) , m_unchoke_time_scaler(0) @@ -655,13 +470,13 @@ namespace aux { , m_auto_scrape_time_scaler(180) , m_next_explicit_cache_torrent(0) , m_cache_rotation_timer(0) + , m_next_suggest_torrent(0) + , m_suggest_timer(0) , m_peak_up_rate(0) , m_peak_down_rate(0) , m_created(time_now_hires()) , m_last_tick(m_created) , m_last_second_tick(m_created - milliseconds(900)) - , m_last_disk_performance_warning(min_time()) - , m_last_disk_queue_performance_warning(min_time()) , m_last_choke(m_created) , m_next_rss_update(min_time()) #ifndef TORRENT_DISABLE_DHT @@ -675,15 +490,14 @@ namespace aux { // peek into the first few bytes the payload stream of a socket to determine // whether or not it's an SSL connection. (The former is simpler but won't // do as well with NATs) - , m_utp_socket_manager(m_settings, m_udp_socket + , m_utp_socket_manager(m_settings, m_udp_socket, m_stats_counters , boost::bind(&session_impl::incoming_connection, this, _1)) , m_boost_connections(0) , m_timer(m_io_service) , m_lsd_announce_timer(m_io_service) , m_host_resolver(m_io_service) - , m_current_connect_attempts(0) + , m_download_connect_attempts(0) , m_tick_residual(0) - , m_non_filtered_torrents(0) #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING , m_logpath(".") #endif @@ -691,13 +505,11 @@ namespace aux { , m_asnum_db(0) , m_country_db(0) #endif - , m_total_failed_bytes(0) - , m_total_redundant_bytes(0) + , m_deferred_submit_disk_jobs(false) , m_pending_auto_manage(false) , m_need_auto_manage(false) , m_abort(false) , m_paused(false) - , m_incoming_connection(false) #if TORRENT_USE_ASSERTS && defined BOOST_HAS_PTHREADS , m_network_thread(0) #endif @@ -705,17 +517,13 @@ namespace aux { #if TORRENT_USE_ASSERTS m_posting_torrent_updates = false; #endif - memset(m_redundant_bytes, 0, sizeof(m_redundant_bytes)); - m_udp_socket.set_rate_limit(m_settings.dht_upload_rate_limit); + m_udp_socket.set_rate_limit(m_settings.get_int(settings_pack::dht_upload_rate_limit)); m_udp_socket.subscribe(&m_tracker_manager); m_udp_socket.subscribe(&m_utp_socket_manager); m_udp_socket.subscribe(this); - m_disk_queues[0] = 0; - m_disk_queues[1] = 0; - #ifdef TORRENT_REQUEST_LOGGING char log_filename[200]; #ifdef TORRENT_WINDOWS @@ -732,8 +540,7 @@ namespace aux { #endif error_code ec; - if (!listen_interface) listen_interface = "0.0.0.0"; - m_listen_interface = tcp::endpoint(address::from_string(listen_interface, ec), listen_port_range.first); + m_listen_interface = tcp::endpoint(address_v4::any(), 0); TORRENT_ASSERT_VAL(!ec, ec); // ---- generate a peer id ---- @@ -749,13 +556,9 @@ namespace aux { , m_peer_id.begin()); url_random((char*)&m_peer_id[print.length()], (char*)&m_peer_id[0] + 20); - - update_rate_settings(); - update_connections_limit(); - update_unchoke_limit(); } - void session_impl::start_session() + void session_impl::start_session(settings_pack const& pack) { #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING m_logger = create_log("main_session", listen_port(), false); @@ -777,7 +580,9 @@ namespace aux { m_next_dht_torrent = m_torrents.begin(); #endif m_next_lsd_torrent = m_torrents.begin(); - m_next_connect_torrent = m_torrents.begin(); + m_next_downloading_connect_torrent = 0; + m_next_finished_connect_torrent = 0; + m_next_scrape_torrent = 0; m_next_disk_peer = m_connections.begin(); m_tcp_mapping[0] = -1; @@ -859,11 +664,31 @@ namespace aux { // before XP SP2, there was no limit m_half_open.limit(0); } - m_settings.half_open_limit = m_half_open.limit(); + m_settings.set_int(settings_pack::half_open_limit, m_half_open.limit()); #endif - m_bandwidth_channel[peer_connection::download_channel] = &m_download_channel; - m_bandwidth_channel[peer_connection::upload_channel] = &m_upload_channel; + m_global_class = m_classes.new_peer_class("global"); + m_tcp_peer_class = m_classes.new_peer_class("tcp"); + m_local_peer_class = m_classes.new_peer_class("local"); + // local peers are always unchoked + m_classes.at(m_local_peer_class)->ignore_unchoke_slots = true; + // local peers are allowed to exceed the normal connection + // limit by 50% + m_classes.at(m_local_peer_class)->connection_limit_factor = 150; + + TORRENT_ASSERT(m_global_class == session::global_peer_class_id); + TORRENT_ASSERT(m_tcp_peer_class == session::tcp_peer_class_id); + TORRENT_ASSERT(m_local_peer_class == session::local_peer_class_id); + + init_peer_class_filter(true); + + // TCP, SSL/TCP and I2P connections should be assigned the TCP peer class + m_peer_class_type_filter.add(peer_class_type_filter::tcp_socket, m_tcp_peer_class); + m_peer_class_type_filter.add(peer_class_type_filter::ssl_tcp_socket, m_tcp_peer_class); + m_peer_class_type_filter.add(peer_class_type_filter::i2p_socket, m_tcp_peer_class); + + // TODO: there's no rule here to make uTP connections not have the global or + // local rate limits apply to it. This used to be the default. #ifdef TORRENT_UPNP_LOGGING m_upnp_log.open("upnp.log", std::ios::in | std::ios::out | std::ios::trunc); @@ -889,7 +714,9 @@ namespace aux { m_stats_logging_enabled = true; memset(&m_last_cache_status, 0, sizeof(m_last_cache_status)); - get_vm_stats(&m_last_vm_stat); + vm_statistics_data_t vst; + get_vm_stats(&vst, ec); + if (!ec) m_last_vm_stat = vst; m_last_failed = 0; m_last_redundant = 0; @@ -897,16 +724,15 @@ namespace aux { m_last_downloaded = 0; get_thread_cpu_usage(&m_network_thread_cpu_usage); - reset_stat_counters(); rotate_stats_log(); #endif -#ifdef TORRENT_DISK_STATS +#ifdef TORRENT_BUFFER_STATS m_buffer_usage_logger.open("buffer_stats.log", std::ios::trunc); m_buffer_allocations = 0; #endif -#if defined TORRENT_BSD || defined TORRENT_LINUX - // ---- auto-cap open files ---- +#if TORRENT_USE_RLIMIT + // ---- auto-cap max connections ---- struct rlimit rl; if (getrlimit(RLIMIT_NOFILE, &rl) == 0) @@ -914,34 +740,62 @@ namespace aux { #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING session_log(" max number of open files: %d", rl.rlim_cur); #endif - // deduct some margin for epoll/kqueue, log files, // futexes, shared objects etc. rl.rlim_cur -= 20; - // 80% of the available file descriptors should go - m_settings.connections_limit = (std::min)(m_settings.connections_limit - , int(rl.rlim_cur * 8 / 10)); - // 20% goes towards regular files - m_files.resize((std::min)(m_files.size_limit(), int(rl.rlim_cur * 2 / 10))); + // 80% of the available file descriptors should go to connections + m_settings.set_int(settings_pack::connections_limit, (std::min)( + m_settings.get_int(settings_pack::connections_limit) + , int(rl.rlim_cur * 8 / 10))); + // 20% goes towards regular files (see disk_io_thread) #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING - (*m_logger) << time_now_string() << " max connections: " << m_settings.connections_limit << "\n"; - (*m_logger) << time_now_string() << " max files: " << m_files.size_limit() << "\n"; + session_log(" max connections: %d", m_settings.get_int(settings_pack::connections_limit)); + session_log(" max files: %d", int(rl.rlim_cur * 2 / 10)); #endif } -#endif // TORRENT_BSD || TORRENT_LINUX +#endif // TORRENT_USE_RLIMIT #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING session_log(" generated peer ID: %s", m_peer_id.to_string().c_str()); #endif + update_half_open(); +#ifndef TORRENT_NO_DEPRECATE + update_local_download_rate(); + update_local_upload_rate(); +#endif + update_download_rate(); + update_upload_rate(); + update_connections_limit(); + update_choking_algorithm(); + update_disk_threads(); + update_network_threads(); + update_upnp(); + update_natpmp(); + update_lsd(); + update_dht(); + + settings_pack* copy = new settings_pack(pack); + m_io_service.post(boost::bind(&session_impl::apply_settings_pack, this, copy)); + m_io_service.post(boost::bind(&session_impl::maybe_open_listen_port, this)); + #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING session_log(" spawning network thread"); #endif m_thread.reset(new thread(boost::bind(&session_impl::main_thread, this))); } + void session_impl::maybe_open_listen_port() + { + if (m_listen_sockets.empty()) + { + update_listen_interfaces(); + open_listen_port(); + } + } + #ifdef TORRENT_STATS void session_impl::rotate_stats_log() { @@ -951,36 +805,6 @@ namespace aux { fclose(m_stats_logger); } - // make these cumulative for easier reading of graphs - // reset them every time the log is rotated though, - // to make them cumulative per one-hour graph - m_error_peers = 0; - m_disconnected_peers = 0; - m_eof_peers = 0; - m_connreset_peers = 0; - m_connrefused_peers = 0; - m_connaborted_peers = 0; - m_perm_peers = 0; - m_buffer_peers = 0; - m_unreachable_peers = 0; - m_broken_pipe_peers = 0; - m_addrinuse_peers = 0; - m_no_access_peers = 0; - m_invalid_arg_peers = 0; - m_aborted_peers = 0; - m_error_incoming_peers = 0; - m_error_outgoing_peers = 0; - m_error_rc4_peers = 0; - m_error_encrypted_peers = 0; - m_error_tcp_peers = 0; - m_error_utp_peers = 0; - m_connect_timeouts = 0; - m_uninteresting_peers = 0; - m_transport_timeout_peers = 0; - m_timeout_peers = 0; - m_no_memory_peers = 0; - m_too_many_peers = 0; - error_code ec; char filename[100]; create_directory("session_stats", ec); @@ -991,42 +815,72 @@ namespace aux { #endif snprintf(filename, sizeof(filename), "session_stats/%d.%04d.log", pid, m_log_seq); m_stats_logger = fopen(filename, "w+"); + m_last_log_rotation = time_now(); if (m_stats_logger == 0) { fprintf(stderr, "Failed to create session stats log file \"%s\": (%d) %s\n" , filename, errno, strerror(errno)); return; } - m_last_log_rotation = time_now(); - fputs("second:uploaded bytes:downloaded bytes:downloading torrents:seeding torrents" - ":peers:connecting peers:disk block buffers:num list peers" - ":peer allocations:peer storage bytes" + fputs("second" + ":uploaded bytes" + ":downloaded bytes" + ":downloading torrents" + ":seeding torrents" + ":peers" + ":connecting peers" + ":disk block buffers" + ":num list peers" + ":peer allocations" + ":peer storage bytes" ":checking torrents" ":stopped torrents" ":upload-only torrents" ":queued seed torrents" ":queued download torrents" - ":peers bw-up:peers bw-down:peers disk-up:peers disk-down" - ":upload rate:download rate:disk write queued bytes" - ":peers down 0:peers down 0-2:peers down 2-5:peers down 5-10:peers down 10-50" - ":peers down 50-100:peers down 100-" - ":peers up 0:peers up 0-2:peers up 2-5:peers up 5-10:peers up 10-50:peers up 50-100" - ":peers up 100-:error peers" - ":peers down interesting:peers down unchoked:peers down requests" - ":peers up interested:peers up unchoked:peers up requests" - ":peer disconnects:peers eof:peers connection reset" - ":outstanding requests:outstanding end-game requests" + ":peers bw-up" + ":peers bw-down" + ":peers disk-up" + ":peers disk-down" + ":upload rate" + ":download rate" + ":disk write queued bytes" + ":peers down 0" + ":peers down 0-2" + ":peers down 2-5" + ":peers down 5-10" + ":peers down 10-50" + ":peers down 50-100" + ":peers down 100-" + ":peers up 0" + ":peers up 0-2" + ":peers up 2-5" + ":peers up 5-10" + ":peers up 10-50" + ":peers up 50-100" + ":peers up 100-" + ":error peers" + ":peers down interesting" + ":peers down unchoked" + ":peers down requests" + ":peers up interested" + ":peers up unchoked" + ":peers up requests" + ":peer disconnects" + ":peers eof" + ":peers connection reset" + ":outstanding requests" + ":outstanding end-game requests" ":outstanding writing blocks" - ":end game piece picker blocks" - ":piece picker blocks" - ":piece picks" ":reject piece picks" ":unchoke piece picks" ":incoming redundant piece picks" ":incoming piece picks" ":end game piece picks" ":snubbed piece picks" + ":interesting piece picks" + ":hash fail piece picks" ":connect timeouts" ":uninteresting peers disconnect" ":timeout peers" @@ -1035,9 +889,8 @@ namespace aux { ":% protocol bytes" ":disk read time" ":disk write time" - ":disk queue time" ":disk queue size" - ":disk queued bytes" + ":queued disk bytes" ":read cache hits" ":disk block read" ":disk block written" @@ -1048,27 +901,22 @@ namespace aux { ":disk cache size" ":disk buffer allocations" ":disk hash time" - ":disk job time" - ":disk sort time" ":connection attempts" ":banned peers" ":banned for hash failure" ":cache size" ":max connections" ":connect candidates" - ":disk queue limit" - ":disk queue low watermark" + ":cache trim low watermark" ":% read time" ":% write time" ":% hash time" - ":% sort time" ":disk read back" ":% read back" ":disk read queue size" ":tick interval" ":tick residual" ":max unchoked" - ":read job queue size limit" ":smooth upload rate" ":smooth download rate" ":num end-game peers" @@ -1093,6 +941,15 @@ namespace aux { ":page faults" ":smooth read ops/s" ":smooth write ops/s" + ":pinned blocks" + ":num partial pieces" + ":num downloading partial pieces" + ":num full partial pieces" + ":num finished partial pieces" + ":num 0-priority partial pieces" + ":allocated jobs" + ":allocated read jobs" + ":allocated write jobs" ":pending reading bytes" ":read_counter" ":write_counter" @@ -1102,8 +959,7 @@ namespace aux { ":udp_counter" ":accept_counter" ":disk_queue_counter" - ":disk_read_counter" - ":disk_write_counter" + ":disk_counter" ":up 8:up 16:up 32:up 64:up 128:up 256:up 512:up 1024:up 2048:up 4096:up 8192:up 16384:up 32768:up 65536:up 131072:up 262144:up 524288:up 1048576" ":down 8:down 16:down 32:down 64:down 128:down 256:down 512:down 1024:down 2048:down 4096:down 8192:down 16384:down 32768:down 65536:down 131072:down 262144:down 524288:down 1048576" ":network thread system time" @@ -1118,6 +974,14 @@ namespace aux { ":no memory peer errors" ":too many peers" ":transport timeout peers" + + ":arc LRU write pieces" + ":arc LRU volatile pieces" + ":arc LRU pieces" + ":arc LRU ghost pieces" + ":arc LFU pieces" + ":arc LFU ghost pieces" + ":uTP idle" ":uTP syn-sent" ":uTP connected" @@ -1159,6 +1023,11 @@ namespace aux { ":cancelled piece requests" ":piece rejects" + ":total pieces" + ":pieces flushed" + ":pieces passed" + ":pieces failed" + ":peers up send buffer" ":packet_loss" @@ -1174,46 +1043,139 @@ namespace aux { ":invalid_pkts_in" ":redundant_pkts_in" + ":loaded torrents" + ":pinned torrents" + ":loaded torrent churn" + + ":num_incoming_choke" + ":num_incoming_unchoke" + ":num_incoming_interested" + ":num_incoming_not_interested" + ":num_incoming_have" + ":num_incoming_bitfield" + ":num_incoming_request" + ":num_incoming_piece" + ":num_incoming_cancel" + ":num_incoming_dht_port" + ":num_incoming_suggest" + ":num_incoming_have_all" + ":num_incoming_have_none" + ":num_incoming_reject" + ":num_incoming_allowed_fast" + ":num_incoming_ext_handshake" + ":num_incoming_pex" + ":num_incoming_metadata" + ":num_incoming_extended" + + ":num_outgoing_choke" + ":num_outgoing_unchoke" + ":num_outgoing_interested" + ":num_outgoing_not_interested" + ":num_outgoing_have" + ":num_outgoing_bitfield" + ":num_outgoing_request" + ":num_outgoing_piece" + ":num_outgoing_cancel" + ":num_outgoing_dht_port" + ":num_outgoing_suggest" + ":num_outgoing_have_all" + ":num_outgoing_have_none" + ":num_outgoing_reject" + ":num_outgoing_allowed_fast" + ":num_outgoing_ext_handshake" + ":num_outgoing_pex" + ":num_outgoing_metadata" + ":num_outgoing_extended" + + ":blocked jobs" + ":num writing threads" + ":num running threads" + ":incoming connections" + + ":move_storage" + ":release_files" + ":delete_files" + ":check_fastresume" + ":save_resume_data" + ":rename_file" + ":stop_torrent" + ":file_priority" + ":clear_piece" + + ":piece_picker_partial_loops" + ":piece_picker_suggest_loops" + ":piece_picker_sequential_loops" + ":piece_picker_reverse_rare_loops" + ":piece_picker_rare_loops" + ":piece_picker_rand_start_loops" + ":piece_picker_rand_loops" + ":piece_picker_busy_loops" + + ":connection attempt loops" + "\n\n", m_stats_logger); } #endif - void session_impl::trigger_auto_manage() - { - if (m_pending_auto_manage || m_abort) return; - - m_pending_auto_manage = true; - m_need_auto_manage = true; - m_io_service.post(boost::bind(&session_impl::on_trigger_auto_manage, this)); - } - - void session_impl::on_trigger_auto_manage() + void session_impl::queue_async_resume_data(boost::shared_ptr const& t) { INVARIANT_CHECK; - assert(m_pending_auto_manage); - m_pending_auto_manage = false; - if (!m_need_auto_manage) return; - recalculate_auto_managed_torrents(); + int loaded_limit = m_settings.get_int(settings_pack::active_loaded_limit); + + if (m_num_save_resume + m_num_queued_resume >= loaded_limit + && m_user_load_torrent + && loaded_limit > 0) + { + TORRENT_ASSERT(t); + // do loaded torrents first, otherwise they'll just be + // evicted and have to be loaded again + if (t->is_loaded()) + m_save_resume_queue.push_front(t); + else + m_save_resume_queue.push_back(t); + return; + } + + if (t->do_async_save_resume_data()) + ++m_num_save_resume; } - void session_impl::update_dht_announce_interval() + // this is called whenever a save_resume_data comes back + // from the disk thread + void session_impl::done_async_resume() { -#ifndef TORRENT_DISABLE_DHT - if (!m_dht) return; + TORRENT_ASSERT(m_num_save_resume > 0); + --m_num_save_resume; + ++m_num_queued_resume; + } -#if defined TORRENT_ASIO_DEBUGGING - add_outstanding_async("session_impl::on_dht_announce"); -#endif - m_dht_interval_update_torrents = m_torrents.size(); - error_code ec; - int delay = (std::max)(m_settings.dht_announce_interval - / (std::max)(int(m_torrents.size()), 1), 1); - m_dht_announce_timer.expires_from_now(seconds(delay), ec); - m_dht_announce_timer.async_wait( - boost::bind(&session_impl::on_dht_announce, this, _1)); - TORRENT_ASSERT(!ec); -#endif + // this is called when one or all save resume alerts are + // popped off the alert queue + void session_impl::async_resume_dispatched(bool all) + { + INVARIANT_CHECK; + + if (all) + { + m_num_queued_resume = 0; + } + else + { + TORRENT_ASSERT(m_num_queued_resume > 0); + --m_num_queued_resume; + } + + int loaded_limit = m_settings.get_int(settings_pack::active_loaded_limit); + while (!m_save_resume_queue.empty() + && (m_num_save_resume + m_num_queued_resume < loaded_limit + || loaded_limit == 0)) + { + boost::shared_ptr t = m_save_resume_queue.front(); + m_save_resume_queue.erase(m_save_resume_queue.begin()); + if (t->do_async_save_resume_data()) + ++m_num_save_resume; + } } void session_impl::init() @@ -1236,7 +1198,7 @@ namespace aux { #if defined TORRENT_ASIO_DEBUGGING add_outstanding_async("session_impl::on_lsd_announce"); #endif - int delay = (std::max)(m_settings.local_service_announce_interval + int delay = (std::max)(m_settings.get_int(settings_pack::local_service_announce_interval) / (std::max)(int(m_torrents.size()), 1), 1); m_lsd_announce_timer.expires_from_now(seconds(delay), ec); m_lsd_announce_timer.async_wait( @@ -1247,11 +1209,6 @@ namespace aux { update_dht_announce_interval(); #endif -#if defined TORRENT_LOGGING || defined TORRENT_VERBOSE_LOGGING - session_log(" open listen port"); -#endif - // no reuse_address and allow system defined port - open_listen_port(0, ec); #if defined TORRENT_LOGGING || defined TORRENT_VERBOSE_LOGGING session_log(" done starting session"); #endif @@ -1259,34 +1216,34 @@ namespace aux { void session_impl::save_state(entry* eptr, boost::uint32_t flags) const { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); entry& e = *eptr; - all_default_values def; + entry::dictionary_type& sett = e["settings"].dict(); + save_settings_to_dict(m_settings, sett); - for (int i = 0; i < int(sizeof(all_settings)/sizeof(all_settings[0])); ++i) - { - session_category const& c = all_settings[i]; - if ((flags & c.flag) == 0) continue; - save_struct(e[c.name], reinterpret_cast(this) + c.offset - , c.map, c.num_entries, reinterpret_cast(&def) + c.default_offset); - } #ifndef TORRENT_DISABLE_DHT + if (flags & session::save_dht_settings) + { + entry::dictionary_type& dht_sett = e["dht"].dict(); + + dht_sett["max_peers_reply"] = m_dht_settings.max_peers_reply; + dht_sett["search_branching"] = m_dht_settings.search_branching; + dht_sett["max_fail_count"] = m_dht_settings.max_fail_count; + dht_sett["max_torrents"] = m_dht_settings.max_torrents; + dht_sett["max_dht_items"] = m_dht_settings.max_dht_items; + dht_sett["max_torrent_search_reply"] = m_dht_settings.max_torrent_search_reply; + dht_sett["restrict_routing_ips"] = m_dht_settings.restrict_routing_ips; + dht_sett["extended_routing_table"] = m_dht_settings.extended_routing_table; + } + if (m_dht && (flags & session::save_dht_state)) { e["dht state"] = m_dht->state(); } #endif -#if TORRENT_USE_I2P - if (flags & session::save_i2p_proxy) - { - save_struct(e["i2p"], &i2p_proxy(), proxy_settings_map - , sizeof(proxy_settings_map)/sizeof(proxy_settings_map[0]) - , &def.m_proxy); - } -#endif #ifndef TORRENT_DISABLE_GEO_IP if (flags & session::save_as_map) { @@ -1323,43 +1280,101 @@ namespace aux { } #endif } - - void session_impl::set_proxy(proxy_settings const& s) + + proxy_settings session_impl::proxy() const { - TORRENT_ASSERT(is_network_thread()); + proxy_settings ret; - m_proxy = s; - // in case we just set a socks proxy, we might have to - // open the socks incoming connection - if (!m_socks_listen_socket) open_new_incoming_socks_connection(); - m_udp_socket.set_proxy_settings(m_proxy); + ret.hostname = m_settings.get_str(settings_pack::proxy_hostname); + ret.username = m_settings.get_str(settings_pack::proxy_username); + ret.password = m_settings.get_str(settings_pack::proxy_password); + ret.type = m_settings.get_int(settings_pack::proxy_type); + ret.port = m_settings.get_int(settings_pack::proxy_port); + ret.proxy_hostnames = m_settings.get_bool(settings_pack::proxy_hostnames); + ret.proxy_peer_connections = m_settings.get_bool( + settings_pack::proxy_peer_connections); + return ret; } - + void session_impl::load_state(lazy_entry const* e) { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); lazy_entry const* settings; - if (e->type() != lazy_entry::dict_t) return; - for (int i = 0; i < int(sizeof(all_settings)/sizeof(all_settings[0])); ++i) +#ifndef TORRENT_DISABLE_DHT + // load from the old settings names + settings = e->dict_find_dict("dht"); + if (settings) { - session_category const& c = all_settings[i]; - settings = e->dict_find_dict(c.name); - if (!settings) continue; - load_struct(*settings, reinterpret_cast(this) + c.offset, c.map, c.num_entries); + lazy_entry const* val; + val = settings->dict_find_int("max_peers_reply"); + if (val) m_dht_settings.max_peers_reply = val->int_value(); + val = settings->dict_find_int("search_branching"); + if (val) m_dht_settings.search_branching = val->int_value(); + val = settings->dict_find_int("max_fail_count"); + if (val) m_dht_settings.max_fail_count = val->int_value(); + val = settings->dict_find_int("max_torrents"); + if (val) m_dht_settings.max_torrents = val->int_value(); + val = settings->dict_find_int("max_dht_items"); + if (val) m_dht_settings.max_dht_items = val->int_value(); + val = settings->dict_find_int("max_torrent_search_reply"); + if (val) m_dht_settings.max_torrent_search_reply = val->int_value(); + val = settings->dict_find_int("restrict_routing_ips"); + if (val) m_dht_settings.restrict_routing_ips = val->int_value(); + val = settings->dict_find_int("extended_routing_table"); + if (val) m_dht_settings.extended_routing_table = val->int_value(); } +#endif + +#ifndef TORRENT_NO_DEPRECATE + settings = e->dict_find_dict("proxy"); + if (settings) + { + lazy_entry const* val; + val = settings->dict_find_int("port"); + if (val) m_settings.set_int(settings_pack::proxy_port, val->int_value()); + val = settings->dict_find_int("type"); + if (val) m_settings.set_int(settings_pack::proxy_type, val->int_value()); + val = settings->dict_find_int("proxy_hostnames"); + if (val) m_settings.set_bool(settings_pack::proxy_hostnames, val->int_value()); + val = settings->dict_find_int("proxy_peer_connections"); + if (val) m_settings.set_bool(settings_pack::proxy_peer_connections, val->int_value()); + val = settings->dict_find_string("hostname"); + if (val) m_settings.set_str(settings_pack::proxy_hostname, val->string_value()); + val = settings->dict_find_string("password"); + if (val) m_settings.set_str(settings_pack::proxy_password, val->string_value()); + val = settings->dict_find_string("username"); + if (val) m_settings.set_str(settings_pack::proxy_username, val->string_value()); + } + + settings = e->dict_find_dict("encryption"); + if (settings) + { + lazy_entry const* val; + val = settings->dict_find_int("prefer_rc4"); + if (val) m_settings.set_bool(settings_pack::prefer_rc4, val->int_value()); + val = settings->dict_find_int("out_enc_policy"); + if (val) m_settings.set_int(settings_pack::out_enc_policy, val->int_value()); + val = settings->dict_find_int("in_enc_policy"); + if (val) m_settings.set_int(settings_pack::in_enc_policy, val->int_value()); + val = settings->dict_find_int("allowed_enc_level"); + if (val) m_settings.set_int(settings_pack::allowed_enc_level, val->int_value()); + } +#endif - update_rate_settings(); - update_connections_limit(); - update_unchoke_limit(); - m_alerts.set_alert_queue_size_limit(m_settings.alert_queue_size); + settings = e->dict_find_dict("settings"); + if (settings) + { + settings_pack* pack = load_pack_from_dict(settings); + apply_settings_pack(pack); + } // in case we just set a socks proxy, we might have to // open the socks incoming connection if (!m_socks_listen_socket) open_new_incoming_socks_connection(); - m_udp_socket.set_proxy_settings(m_proxy); + m_udp_socket.set_proxy_settings(proxy()); #ifndef TORRENT_DISABLE_DHT settings = e->dict_find_dict("dht state"); @@ -1369,16 +1384,6 @@ namespace aux { } #endif -#if TORRENT_USE_I2P - settings = e->dict_find_dict("i2p"); - if (settings) - { - proxy_settings s; - load_struct(*settings, &s, proxy_settings_map - , sizeof(proxy_settings_map)/sizeof(proxy_settings_map[0])); - set_i2p_proxy(s); - } -#endif #ifndef TORRENT_DISABLE_GEO_IP settings = e->dict_find_dict("AS map"); if (settings) @@ -1394,10 +1399,6 @@ namespace aux { } #endif - if (m_settings.connection_speed < 0) m_settings.connection_speed = 200; - - update_disk_thread_settings(); - settings = e->dict_find_list("feeds"); if (settings) { @@ -1437,7 +1438,7 @@ namespace aux { char const* session_impl::country_for_ip(address const& a) { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); if (!a.is_v4() || m_country_db == 0) return 0; return GeoIP_country_code_by_ipnum(m_country_db, a.to_v4().to_ulong()); @@ -1445,7 +1446,7 @@ namespace aux { int session_impl::as_for_ip(address const& a) { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); if (!a.is_v4() || m_asnum_db == 0) return 0; char* name = GeoIP_name_by_ipnum(m_asnum_db, a.to_v4().to_ulong()); @@ -1457,7 +1458,7 @@ namespace aux { std::string session_impl::as_name_for_ip(address const& a) { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); if (!a.is_v4() || m_asnum_db == 0) return std::string(); char* name = GeoIP_name_by_ipnum(m_asnum_db, a.to_v4().to_ulong()); @@ -1470,7 +1471,7 @@ namespace aux { std::pair* session_impl::lookup_as(int as) { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); std::map::iterator i = m_as_peak.lower_bound(as); @@ -1484,7 +1485,7 @@ namespace aux { void session_impl::load_asnum_db(std::string file) { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); if (m_asnum_db) GeoIP_delete(m_asnum_db); m_asnum_db = GeoIP_open(file.c_str(), GEOIP_STANDARD); @@ -1495,7 +1496,7 @@ namespace aux { #ifndef TORRENT_NO_DEPRECATE void session_impl::load_asnum_dbw(std::wstring file) { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); if (m_asnum_db) GeoIP_delete(m_asnum_db); std::string utf8; @@ -1506,7 +1507,7 @@ namespace aux { void session_impl::load_country_dbw(std::wstring file) { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); if (m_country_db) GeoIP_delete(m_country_db); std::string utf8; @@ -1519,7 +1520,7 @@ namespace aux { void session_impl::load_country_db(std::string file) { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); if (m_country_db) GeoIP_delete(m_country_db); m_country_db = GeoIP_open(file.c_str(), GEOIP_STANDARD); @@ -1543,7 +1544,7 @@ namespace aux { void session_impl::add_extension(ext_function_t ext) { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); TORRENT_ASSERT_VAL(ext, ext); boost::shared_ptr p(new session_plugin_wrapper(ext)); @@ -1553,7 +1554,7 @@ namespace aux { void session_impl::add_ses_extension(boost::shared_ptr ext) { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); TORRENT_ASSERT_VAL(ext, ext); m_ses_extensions.push_back(ext); @@ -1564,7 +1565,7 @@ namespace aux { feed_handle session_impl::add_feed(feed_settings const& sett) { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); // look for duplicates. If we already have a feed with this // URL, return a handle to the existing one @@ -1583,7 +1584,7 @@ namespace aux { void session_impl::remove_feed(feed_handle h) { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); boost::shared_ptr f = h.m_feed_ptr.lock(); if (!f) return; @@ -1598,7 +1599,7 @@ namespace aux { void session_impl::get_feeds(std::vector* ret) const { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); ret->clear(); ret->reserve(m_feeds.size()); @@ -1609,7 +1610,7 @@ namespace aux { void session_impl::pause() { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); if (m_paused) return; #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING @@ -1626,7 +1627,7 @@ namespace aux { void session_impl::resume() { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); if (!m_paused) return; m_paused = false; @@ -1635,13 +1636,13 @@ namespace aux { { torrent& t = *i->second; t.do_resume(); - if (t.should_check_files()) t.queue_torrent_check(); + if (t.should_check_files()) t.start_checking(); } } void session_impl::abort() { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); if (m_abort) return; #if defined TORRENT_LOGGING @@ -1653,7 +1654,6 @@ namespace aux { #if TORRENT_USE_I2P m_i2p_conn.close(ec); #endif - m_queued_for_checking.clear(); stop_lsd(); stop_upnp(); stop_natpmp(); @@ -1661,7 +1661,6 @@ namespace aux { stop_dht(); m_dht_announce_timer.cancel(ec); #endif - m_timer.cancel(ec); m_lsd_announce_timer.cancel(ec); for (std::set >::iterator i = m_incoming_sockets.begin() @@ -1705,22 +1704,13 @@ namespace aux { { i->second->abort(); } + m_torrents.clear(); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) session_log(" aborting all tracker requests"); #endif m_tracker_manager.abort_all_requests(); -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - session_log(" sending event=stopped to trackers"); -#endif - for (torrent_map::iterator i = m_torrents.begin(); - i != m_torrents.end(); ++i) - { - torrent& t = *i->second; - t.abort(); - } - #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) session_log(" aborting all connections (%d)", m_connections.size()); #endif @@ -1736,7 +1726,7 @@ namespace aux { #if TORRENT_USE_ASSERTS int conn = m_connections.size(); #endif - (*m_connections.begin())->disconnect(errors::stopping_torrent); + (*m_connections.begin())->disconnect(errors::stopping_torrent, peer_connection::op_bittorrent); TORRENT_ASSERT_VAL(conn == int(m_connections.size()) + 1, conn); } @@ -1765,13 +1755,33 @@ namespace aux { m_country_db = 0; #endif - m_disk_thread.abort(); + // it's OK to detach the threads here. The disk_io_thread + // has an internal counter and won't release the network + // thread until they're all dead (via m_work). + m_disk_thread.set_num_threads(0, false); } + bool session_impl::has_connection(peer_connection* p) const + { + return m_connections.find(p->self()) != m_connections.end(); + } + + void session_impl::insert_peer(boost::shared_ptr const& c) + { + TORRENT_ASSERT(!c->m_in_constructor); + m_connections.insert(c); + } + void session_impl::set_port_filter(port_filter const& f) { m_port_filter = f; - // TODO: recalculate all connect candidates for all torrents + if (m_settings.get_bool(settings_pack::no_connect_privileged_ports)) + m_port_filter.add_rule(0, 1024, port_filter::blocked); + // Close connections whose endpoint is filtered + // by the new ip-filter + for (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + i->second->ip_filter_updated(); } void session_impl::set_ip_filter(ip_filter const& f) @@ -1784,7 +1794,7 @@ namespace aux { // by the new ip-filter for (torrent_map::iterator i = m_torrents.begin() , end(m_torrents.end()); i != end; ++i) - i->second->ip_filter_updated(); + i->second->port_filter_updated(); } ip_filter const& session_impl::get_ip_filter() const @@ -1792,25 +1802,22 @@ namespace aux { return m_ip_filter; } - void session_impl::update_disk_thread_settings() + port_filter const& session_impl::get_port_filter() const { - disk_io_job j; - j.buffer = (char*)new session_settings(m_settings); - j.action = disk_io_job::update_settings; - m_disk_thread.add_job(j); + return m_port_filter; } template void static set_socket_buffer_size(Socket& s, session_settings const& sett, error_code& ec) { - if (sett.send_socket_buffer_size) + int snd_size = sett.get_int(settings_pack::send_socket_buffer_size); + if (snd_size) { stream_socket::send_buffer_size prev_option; s.get_option(prev_option, ec); - if (!ec) + if (!ec && prev_option.value() != snd_size) { - stream_socket::send_buffer_size option( - sett.send_socket_buffer_size); + stream_socket::send_buffer_size option(snd_size); s.set_option(option, ec); if (ec) { @@ -1820,14 +1827,14 @@ namespace aux { } } } - if (sett.recv_socket_buffer_size) + int recv_size = sett.get_int(settings_pack::recv_socket_buffer_size); + if (recv_size) { stream_socket::receive_buffer_size prev_option; s.get_option(prev_option, ec); - if (!ec) + if (!ec && prev_option.value() != recv_size) { - stream_socket::receive_buffer_size option( - sett.recv_socket_buffer_size); + stream_socket::receive_buffer_size option(recv_size); s.set_option(option, ec); if (ec) { @@ -1839,229 +1846,387 @@ namespace aux { } } - void session_impl::set_settings(session_settings const& s) + int session_impl::create_peer_class(char const* name) + { + return m_classes.new_peer_class(name); + } + + void session_impl::delete_peer_class(int cid) + { + // if you hit this assert, you're deleting a non-existent peer class + TORRENT_ASSERT(m_classes.at(cid)); + if (m_classes.at(cid) == 0) return; + m_classes.decref(cid); + } + + peer_class_info session_impl::get_peer_class(int cid) + { + peer_class_info ret; + peer_class* pc = m_classes.at(cid); + // if you hit this assert, you're passing in an invalid cid + TORRENT_ASSERT(pc); + if (pc == 0) + { +#ifdef TORRENT_DEBUG + // make it obvious that the return value is undefined + ret.upload_limit = rand(); + ret.download_limit = rand(); + ret.label.resize(20); + url_random(&ret.label[0], &ret.label[0] + 20); + ret.ignore_unchoke_slots = false; +#endif + return ret; + } + + pc->get_info(&ret); + return ret; + } + + void session_impl::queue_tracker_request(tracker_request& req + , std::string login, boost::weak_ptr c, boost::uint32_t key) + { + req.listen_port = listen_port(); + if (m_key) + req.key = m_key; + else + req.key = key; + +#ifdef TORRENT_USE_OPENSSL + // SSL torrents use the SSL listen port + if (req.ssl_ctx) req.listen_port = ssl_listen_port(); + req.ssl_ctx = &m_ssl_ctx; +#endif + if (is_any(req.bind_ip)) req.bind_ip = m_listen_interface.address(); + m_tracker_manager.queue_request(get_io_service(), m_half_open, req + , login, c); + } + + void session_impl::set_peer_class(int cid, peer_class_info const& pci) + { + peer_class* pc = m_classes.at(cid); + // if you hit this assert, you're passing in an invalid cid + TORRENT_ASSERT(pc); + if (pc == 0) return; + + pc->set_info(&pci); + } + + void session_impl::set_peer_class_filter(ip_filter const& f) { INVARIANT_CHECK; - TORRENT_ASSERT(is_network_thread()); + m_peer_class_filter = f; + } - TORRENT_ASSERT_VAL(s.file_pool_size > 0, s.file_pool_size); + ip_filter const& session_impl::get_peer_class_filter() const + { + return m_peer_class_filter; + } - // less than 5 seconds unchoke interval is insane - TORRENT_ASSERT_VAL(s.unchoke_interval >= 5, s.unchoke_interval); + void session_impl::set_peer_class_type_filter(peer_class_type_filter f) + { + m_peer_class_type_filter = f; + } - // if disk io thread settings were changed - // post a notification to that thread - bool update_disk_io_thread = false; - if (m_settings.cache_size != s.cache_size - || m_settings.cache_expiry != s.cache_expiry - || m_settings.optimize_hashing_for_speed != s.optimize_hashing_for_speed - || m_settings.file_checks_delay_per_block != s.file_checks_delay_per_block - || m_settings.disk_cache_algorithm != s.disk_cache_algorithm - || m_settings.read_cache_line_size != s.read_cache_line_size - || m_settings.write_cache_line_size != s.write_cache_line_size - || m_settings.coalesce_writes != s.coalesce_writes - || m_settings.coalesce_reads != s.coalesce_reads - || m_settings.max_queued_disk_bytes != s.max_queued_disk_bytes - || m_settings.max_queued_disk_bytes_low_watermark != s.max_queued_disk_bytes_low_watermark - || m_settings.disable_hash_checks != s.disable_hash_checks - || m_settings.explicit_read_cache != s.explicit_read_cache -#ifndef TORRENT_DISABLE_MLOCK - || m_settings.lock_disk_cache != s.lock_disk_cache + peer_class_type_filter session_impl::get_peer_class_type_filter() + { + return m_peer_class_type_filter; + } + + void session_impl::set_peer_classes(peer_class_set* s, address const& a, int st) + { + boost::uint32_t peer_class_mask = m_peer_class_filter.access(a); + + // assign peer class based on socket type + const static int mapping[] = { 0, 0, 0, 0, 1, 4, 2, 2, 2, 3}; + int socket_type = mapping[st]; + // filter peer classes based on type + peer_class_mask = m_peer_class_type_filter.apply(socket_type, peer_class_mask); + + for (peer_class_t i = 0; peer_class_mask; peer_class_mask >>= 1, ++i) + { + if ((peer_class_mask & 1) == 0) continue; + + // if you hit this assert, your peer class filter contains + // a bitmask referencing a non-existent peer class + TORRENT_ASSERT_PRECOND(m_classes.at(i)); + + if (m_classes.at(i) == 0) continue; + s->add_class(m_classes, i); + } + } + + bool session_impl::ignore_unchoke_slots_set(peer_class_set const& set) const + { + int num = set.num_classes(); + for (int i = 0; i < num; ++i) + { + peer_class const* pc = m_classes.at(set.class_at(i)); + if (pc == 0) continue; + if (pc->ignore_unchoke_slots) return true; + } + return false; + } + + bandwidth_manager* session_impl::get_bandwidth_manager(int channel) + { + return (channel == peer_connection::download_channel) + ? &m_download_rate : &m_upload_rate; + } + + // the back argument determines whether this bump causes the torrent + // to be the most recently used or the least recently used. Putting + // the torrent at the back of the queue makes it the most recently + // used and the least likely to be evicted. This is the default. + // if back is false, the torrent is moved to the front of the queue, + // and made the most likely to be evicted. This is used for torrents + // that are paused, to give up their slot among the loaded torrents + void session_impl::bump_torrent(torrent* t, bool back) + { + if (t->is_aborted()) return; + + bool new_torrent = false; + + // if t is the only torrent in the LRU list, both + // its prev and next links will be NULL, even though + // it's already in the list. Cover this case by also + // checking to see if it's the first item + if (t->next != NULL || t->prev != NULL || m_torrent_lru.front() == t) + { +#ifdef TORRENT_DEBUG + torrent* i = (torrent*)m_torrent_lru.front(); + while (i != NULL && i != t) i = (torrent*)i->next; + TORRENT_ASSERT(i == t); #endif - || m_settings.use_read_cache != s.use_read_cache - || m_settings.disk_io_write_mode != s.disk_io_write_mode - || m_settings.disk_io_read_mode != s.disk_io_read_mode - || m_settings.allow_reordered_disk_operations != s.allow_reordered_disk_operations - || m_settings.file_pool_size != s.file_pool_size - || m_settings.volatile_read_cache != s.volatile_read_cache - || m_settings.no_atime_storage!= s.no_atime_storage - || m_settings.ignore_resume_timestamps != s.ignore_resume_timestamps - || m_settings.no_recheck_incomplete_resume != s.no_recheck_incomplete_resume - || m_settings.low_prio_disk != s.low_prio_disk - || m_settings.lock_files != s.lock_files - || m_settings.use_disk_cache_pool != s.use_disk_cache_pool) - update_disk_io_thread = true; - - bool connections_limit_changed = m_settings.connections_limit != s.connections_limit; - bool unchoke_limit_changed = m_settings.unchoke_slots_limit != s.unchoke_slots_limit; - -#ifndef TORRENT_NO_DEPRECATE - // support deprecated choker settings - if (s.choking_algorithm == session_settings::rate_based_choker) - { - if (s.auto_upload_slots && !s.auto_upload_slots_rate_based) - m_settings.choking_algorithm = session_settings::auto_expand_choker; - else if (!s.auto_upload_slots) - m_settings.choking_algorithm = session_settings::fixed_slots_choker; - } -#endif - - // safety check - if (m_settings.volatile_read_cache - && (m_settings.suggest_mode == session_settings::suggest_read_cache - || m_settings.explicit_read_cache)) - { - // If you hit this assert, you're trying to set your cache to be - // volatile and to suggest pieces out of it (or to make the cache - // explicit) at the same time this is a bad configuration, don't do it - TORRENT_ASSERT_PRECOND(false); - m_settings.volatile_read_cache = false; - } - - if (m_settings.choking_algorithm != s.choking_algorithm) - { - // trigger recalculation of the unchoked peers - m_unchoke_time_scaler = 0; - } - -#ifndef TORRENT_DISABLE_DHT - if (m_settings.dht_announce_interval != s.dht_announce_interval) - { -#if defined TORRENT_ASIO_DEBUGGING - add_outstanding_async("session_impl::on_dht_announce"); -#endif - error_code ec; - int delay = (std::max)(s.dht_announce_interval - / (std::max)(int(m_torrents.size()), 1), 1); - m_dht_announce_timer.expires_from_now(seconds(delay), ec); - m_dht_announce_timer.async_wait( - boost::bind(&session_impl::on_dht_announce, this, _1)); - } -#endif - - if (m_settings.local_service_announce_interval != s.local_service_announce_interval) - { -#if defined TORRENT_ASIO_DEBUGGING - add_outstanding_async("session_impl::on_lsd_announce"); -#endif - error_code ec; - int delay = (std::max)(s.local_service_announce_interval - / (std::max)(int(m_torrents.size()), 1), 1); - m_lsd_announce_timer.expires_from_now(seconds(delay), ec); - m_lsd_announce_timer.async_wait( - boost::bind(&session_impl::on_lsd_announce, this, _1)); - } - - // if queuing settings were changed, recalculate - // queued torrents sooner - if ((m_settings.active_downloads != s.active_downloads - || m_settings.active_seeds != s.active_seeds - || m_settings.active_limit != s.active_limit)) - m_auto_manage_time_scaler = 2; - - if (m_settings.report_web_seed_downloads != s.report_web_seed_downloads) - { - // if this flag changed, update all web seed connections - for (connection_map::iterator i = m_connections.begin() - , end(m_connections.end()); i != end; ++i) - { - int type = (*i)->type(); - if (type == peer_connection::url_seed_connection - || type == peer_connection::http_seed_connection) - (*i)->ignore_stats(!s.report_web_seed_downloads); - } - } - - if (m_settings.alert_queue_size != s.alert_queue_size) - m_alerts.set_alert_queue_size_limit(s.alert_queue_size); - - if (m_settings.dht_upload_rate_limit != s.dht_upload_rate_limit) - m_udp_socket.set_rate_limit(s.dht_upload_rate_limit); - - if (m_settings.peer_tos != s.peer_tos && s.peer_tos != 0) - { - error_code ec; - m_udp_socket.set_option(type_of_service(s.peer_tos), ec); -#if defined TORRENT_VERBOSE_LOGGING - (*m_logger) << ">>> SET_TOS[ udp_socket tos: " << s.peer_tos << " e: " << ec.message() << " ]\n"; -#endif - } - - { - error_code ec; - set_socket_buffer_size(m_udp_socket, m_settings, ec); - if (ec) - { - if (m_alerts.should_post()) - m_alerts.post_alert(udp_error_alert(udp::endpoint(), ec)); - } - } - - bool reopen_listen_port = false; - if (m_settings.ssl_listen != s.ssl_listen) - reopen_listen_port = true; - - m_settings = s; - - if (m_settings.cache_buffer_chunk_size <= 0) - m_settings.cache_buffer_chunk_size = 1; - - update_rate_settings(); - - if (connections_limit_changed) update_connections_limit(); - if (unchoke_limit_changed) update_unchoke_limit(); - bool anonymous_mode = (m_settings.anonymous_mode != s.anonymous_mode && s.anonymous_mode); - if (anonymous_mode) + // this torrent is in the list already. + // first remove it + m_torrent_lru.erase(t); + } + else { - m_settings.user_agent.clear(); - url_random((char*)&m_peer_id[0], (char*)&m_peer_id[0] + 20); + new_torrent = true; + } + + // pinned torrents should not be part of the LRU, since + // the LRU is only used to evict torrents + if (t->is_pinned()) return; + + if (back) + m_torrent_lru.push_back(t); + else + m_torrent_lru.push_front(t); + + if (new_torrent) evict_torrents_except(t); + } + + void session_impl::evict_torrent(torrent* t) + { + TORRENT_ASSERT(!t->is_pinned()); + + // if there's no user-load function set, we cannot evict + // torrents. The feature is not enabled + if (!m_user_load_torrent) return; + + // if it's already evicted, there's nothing to do + if (!t->is_loaded() || !t->should_be_loaded()) return; + + TORRENT_ASSERT(t->next != NULL || t->prev != NULL || m_torrent_lru.front() == t); + +#if defined TORRENT_DEBUG && defined TORRENT_EXPENSIVE_INVARIANT_CHECKS + torrent* i = (torrent*)m_torrent_lru.front(); + while (i != NULL && i != t) i = (torrent*)i->next; + TORRENT_ASSERT(i == t); +#endif + + int loaded_limit = m_settings.get_int(settings_pack::active_loaded_limit); + + // 0 means unlimited, never evict enything + if (loaded_limit == 0) return; + + if (m_torrent_lru.size() > loaded_limit) + { + // just evict the torrent + inc_stats_counter(counters::torrent_evicted_counter); + TORRENT_ASSERT(t->is_pinned() == false); + t->unload(); + m_torrent_lru.erase(t); + return; } - bool force_proxy = (m_settings.force_proxy != s.force_proxy && s.force_proxy); + // move this torrent to be the first to be evicted whenever + // another torrent need its slot + bump_torrent(t, false); + } - m_udp_socket.set_force_proxy(s.force_proxy); + void session_impl::evict_torrents_except(torrent* ignore) + { + if (!m_user_load_torrent) return; - // in force_proxy mode, we don't want to accept any incoming - // connections, except through a proxy. - if (force_proxy) + int loaded_limit = m_settings.get_int(settings_pack::active_loaded_limit); + + // 0 means unlimited, never evict enything + if (loaded_limit == 0) return; + + // if the torrent we're ignoring (i.e. making room for), allow + // one more torrent in the list. + if (ignore->next != NULL || ignore->prev != NULL || m_torrent_lru.front() == ignore) { - stop_lsd(); - stop_upnp(); - stop_natpmp(); -#ifndef TORRENT_DISABLE_DHT - stop_dht(); +#ifdef TORRENT_DEBUG + torrent* i = (torrent*)m_torrent_lru.front(); + while (i != NULL && i != ignore) i = (torrent*)i->next; + TORRENT_ASSERT(i == ignore); #endif - // close the listen sockets - error_code ec; - for (std::list::iterator i = m_listen_sockets.begin() - , end(m_listen_sockets.end()); i != end; ++i) - i->sock->close(ec); - m_listen_sockets.clear(); + ++loaded_limit; } - if (m_settings.connection_speed < 0) m_settings.connection_speed = 200; - - if (update_disk_io_thread) - update_disk_thread_settings(); - if (m_settings.num_optimistic_unchoke_slots >= m_allowed_upload_slots / 2) + while (m_torrent_lru.size() >= loaded_limit) { - if (m_alerts.should_post()) - m_alerts.post_alert(performance_alert(torrent_handle() - , performance_alert::too_many_optimistic_unchoke_slots)); + // we're at the limit of loaded torrents. Find the least important + // torrent and unload it. This is done with an LRU. + torrent* i = (torrent*)m_torrent_lru.front(); + + if (i == ignore) + { + i = (torrent*)i->next; + if (i == NULL) break; + } + inc_stats_counter(counters::torrent_evicted_counter); + TORRENT_ASSERT(i->is_pinned() == false); + i->unload(); + m_torrent_lru.erase(i); } + } - if (s.choking_algorithm == session_settings::fixed_slots_choker) - m_allowed_upload_slots = m_settings.unchoke_slots_limit; - else if (s.choking_algorithm == session_settings::auto_expand_choker - && m_allowed_upload_slots < m_settings.unchoke_slots_limit) - m_allowed_upload_slots = m_settings.unchoke_slots_limit; - if (m_allowed_upload_slots < 0) - m_allowed_upload_slots = (std::numeric_limits::max)(); + bool session_impl::load_torrent(torrent* t) + { + TORRENT_ASSERT(is_single_thread()); + evict_torrents_except(t); - // replace all occurances of '\n' with ' '. - std::string::iterator i = m_settings.user_agent.begin(); - while ((i = std::find(i, m_settings.user_agent.end(), '\n')) - != m_settings.user_agent.end()) - *i = ' '; + // we wouldn't be loading the torrent if it was already + // in the LRU (and loaded) + TORRENT_ASSERT(t->next == NULL && t->prev == NULL && m_torrent_lru.front() != t); + + // now, load t into RAM + std::vector buffer; + error_code ec; + m_user_load_torrent(t->info_hash(), buffer, ec); + if (ec) + { + t->set_error(ec, torrent::error_file_metadata); + t->pause(false); + return false; + } + bool ret = t->load(buffer); + if (ret) bump_torrent(t); + return ret; + } + + void session_impl::deferred_submit_jobs() + { + if (m_deferred_submit_disk_jobs) return; + m_deferred_submit_disk_jobs = true; + m_io_service.post(boost::bind(&session_impl::submit_disk_jobs, this)); + } + + void session_impl::submit_disk_jobs() + { + TORRENT_ASSERT(m_deferred_submit_disk_jobs); + m_deferred_submit_disk_jobs = false; + if (m_abort) return; + m_disk_thread.submit_jobs(); + } + + // copies pointers to bandwidth channels from the peer classes + // into the array. Only bandwidth channels with a bandwidth limit + // is considered pertinent and copied + // returns the number of pointers copied + // channel is upload_channel or download_channel + int session_impl::copy_pertinent_channels(peer_class_set const& set + , int channel, bandwidth_channel** dst, int max) + { + int num_channels = set.num_classes(); + int num_copied = 0; + for (int i = 0; i < num_channels; ++i) + { + peer_class* pc = m_classes.at(set.class_at(i)); + TORRENT_ASSERT(pc); + if (pc == 0) continue; + bandwidth_channel* chan = &pc->channel[channel]; + // no need to include channels that don't have any bandwidth limits + if (chan->throttle() == 0) continue; + dst[num_copied] = chan; + ++num_copied; + if (num_copied == max) break; + } + return num_copied; + } + + bool session_impl::use_quota_overhead(bandwidth_channel* ch, int channel, int amount) + { + ch->use_quota(amount); + return (ch->throttle() > 0 && ch->throttle() < amount); + } + + int session_impl::use_quota_overhead(peer_class_set& set, int amount_down, int amount_up) + { + int ret = 0; + int num = set.num_classes(); + for (int i = 0; i < num; ++i) + { + peer_class* p = m_classes.at(set.class_at(i)); + if (p == 0) continue; + bandwidth_channel* ch = &p->channel[peer_connection::download_channel]; + if (use_quota_overhead(ch, peer_connection::download_channel, amount_down)) + ret |= 1 << peer_connection::download_channel; + ch = &p->channel[peer_connection::upload_channel]; + if (use_quota_overhead(ch, peer_connection::upload_channel, amount_up)) + ret |= 1 << peer_connection::upload_channel; + } + return ret; + } + + // session_impl is responsible for deleting 'pack', but it + // will pass it on to the disk io thread, which will take + // over ownership of it + void session_impl::apply_settings_pack(settings_pack* pack) + { + bool reopen_listen_port = + (pack->has_val(settings_pack::ssl_listen) + && pack->get_int(settings_pack::ssl_listen) + != m_settings.get_int(settings_pack::ssl_listen)) + || (pack->has_val(settings_pack::listen_interfaces) + && pack->get_str(settings_pack::listen_interfaces) + != m_settings.get_str(settings_pack::listen_interfaces)); + + apply_pack(pack, m_settings, this); + m_disk_thread.set_settings(pack); + delete pack; if (reopen_listen_port) { error_code ec; - open_listen_port(0, ec); + open_listen_port(); } } +#ifndef TORRENT_NO_DEPRECATE + void session_impl::set_settings(libtorrent::session_settings const& s) + { + INVARIANT_CHECK; + TORRENT_ASSERT(is_single_thread()); + settings_pack* p = load_pack_from_struct(m_settings, s); + apply_settings_pack(p); + } + + libtorrent::session_settings session_impl::deprecated_settings() const + { + libtorrent::session_settings ret; + + load_struct_from_settings(m_settings, ret); + return ret; + } +#endif + tcp::endpoint session_impl::get_ipv6_interface() const { return m_ipv6_interface; @@ -2072,21 +2237,24 @@ namespace aux { return m_ipv4_interface; } - void session_impl::setup_listener(listen_socket_t* s, tcp::endpoint ep - , int& retries, bool v6_only, int flags, error_code& ec) + enum { listen_no_system_port = 0x02 }; + + void session_impl::setup_listener(listen_socket_t* s, std::string const& device + , bool ipv4, int port, int& retries, int flags, error_code& ec) { int last_op = 0; listen_failed_alert::socket_type_t sock_type = s->ssl ? listen_failed_alert::tcp_ssl : listen_failed_alert::tcp; s->sock.reset(new socket_acceptor(m_io_service)); - s->sock->open(ep.protocol(), ec); + s->sock->open(ipv4 ? tcp::v4() : tcp::v6(), ec); last_op = listen_failed_alert::open; if (ec) { if (m_alerts.should_post()) - m_alerts.post_alert(listen_failed_alert(ep, last_op, ec, sock_type)); + m_alerts.post_alert(listen_failed_alert(device, last_op, ec, sock_type)); + #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING session_log("failed to open socket: %s: %s" - , print_endpoint(ep).c_str(), ec.message().c_str()); + , device.c_str(), ec.message().c_str()); #endif return; } @@ -2101,11 +2269,11 @@ namespace aux { #endif #if TORRENT_USE_IPV6 - if (ep.protocol() == tcp::v6()) + if (!ipv4) { error_code err; // ignore errors here #ifdef IPV6_V6ONLY - s->sock->set_option(v6only(v6_only), err); + s->sock->set_option(v6only(true), err); #endif #ifdef TORRENT_WINDOWS @@ -2116,93 +2284,114 @@ namespace aux { s->sock->set_option(v6_protection_level(PROTECTION_LEVEL_UNRESTRICTED), err); #endif } -#endif - s->sock->bind(ep, ec); +#endif // TORRENT_USE_IPV6 + + address bind_ip = bind_to_device(m_io_service, *s->sock, ipv4 + , device.c_str(), port, ec); + + if (ec == error_code(boost::system::errc::no_such_device, generic_category())) + return; + while (ec && retries > 0) { #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING - session_log("failed to bind to interface \"%s\": %s" - , print_endpoint(ep).c_str(), ec.message().c_str()); + session_log("failed to bind to interface [%s] \"%s\": %s" + , device.c_str(), bind_ip.to_string(ec).c_str() + , ec.message().c_str()); #endif ec.clear(); TORRENT_ASSERT_VAL(!ec, ec); --retries; - ep.port(ep.port() + 1); - s->sock->bind(ep, ec); + port += 1; + bind_ip = bind_to_device(m_io_service, *s->sock, ipv4 + , device.c_str(), port, ec); last_op = listen_failed_alert::bind; } - if (ec && !(flags & session::listen_no_system_port)) + if (ec && !(flags & listen_no_system_port)) { // instead of giving up, trying // let the OS pick a port - ep.port(0); - ec = error_code(); - s->sock->bind(ep, ec); + port = 0; + ec.clear(); + bind_ip = bind_to_device(m_io_service, *s->sock, ipv4 + , device.c_str(), port, ec); last_op = listen_failed_alert::bind; } if (ec) { // not even that worked, give up if (m_alerts.should_post()) - m_alerts.post_alert(listen_failed_alert(ep, last_op, ec, sock_type)); + m_alerts.post_alert(listen_failed_alert(device, last_op, ec, sock_type)); #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING session_log("cannot bind to interface \"%s\": %s" - , print_endpoint(ep).c_str(), ec.message().c_str()); + , device.c_str(), ec.message().c_str()); #endif return; } s->external_port = s->sock->local_endpoint(ec).port(); - TORRENT_ASSERT(s->external_port == ep.port() || ep.port() == 0); + TORRENT_ASSERT(s->external_port == port || port == 0); last_op = listen_failed_alert::get_peer_name; if (!ec) { - s->sock->listen(m_settings.listen_queue_size, ec); + s->sock->listen(m_settings.get_int(settings_pack::listen_queue_size), ec); last_op = listen_failed_alert::listen; } if (ec) { if (m_alerts.should_post()) - m_alerts.post_alert(listen_failed_alert(ep, last_op, ec, sock_type)); + m_alerts.post_alert(listen_failed_alert(device, last_op, ec, sock_type)); #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING session_log("cannot listen on interface \"%s\": %s" - , print_endpoint(ep).c_str(), ec.message().c_str()); + , device.c_str(), ec.message().c_str()); #endif return; } // if we asked the system to listen on port 0, which // socket did it end up choosing? - if (ep.port() == 0) + if (port == 0) { - ep.port(s->sock->local_endpoint(ec).port()); + port = s->sock->local_endpoint(ec).port(); last_op = listen_failed_alert::get_peer_name; if (ec) { if (m_alerts.should_post()) - m_alerts.post_alert(listen_failed_alert(ep, last_op, ec, sock_type)); + m_alerts.post_alert(listen_failed_alert(device, last_op, ec, sock_type)); #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING char msg[200]; snprintf(msg, 200, "failed to get peer name \"%s\": %s" - , print_endpoint(ep).c_str(), ec.message().c_str()); + , device.c_str(), ec.message().c_str()); (*m_logger) << time_now_string() << msg << "\n"; #endif } } if (m_alerts.should_post()) - m_alerts.post_alert(listen_succeeded_alert(ep, s->ssl ? listen_succeeded_alert::tcp_ssl : listen_succeeded_alert::tcp)); + m_alerts.post_alert(listen_succeeded_alert(tcp::endpoint(bind_ip, port) + , s->ssl ? listen_succeeded_alert::tcp_ssl : listen_succeeded_alert::tcp)); #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING session_log(" listening on: %s external port: %d" - , print_endpoint(ep).c_str(), s->external_port); + , print_endpoint(tcp::endpoint(bind_ip, port)).c_str(), s->external_port); #endif } - void session_impl::open_listen_port(int flags, error_code& ec) + void session_impl::open_listen_port() { - TORRENT_ASSERT(is_network_thread()); +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + m_logger = create_log("main_session", listen_port(), false); + session_log("log created"); +#endif + + TORRENT_ASSERT(is_single_thread()); TORRENT_ASSERT(!m_abort); + int flags = m_settings.get_bool(settings_pack::listen_system_port_fallback) ? 0 : listen_no_system_port; + error_code ec; + + // reset the retry counter + m_listen_port_retries = m_settings.get_int(settings_pack::max_retry_port_bind); + retry: // close the open listen sockets @@ -2211,7 +2400,7 @@ retry: , end(m_listen_sockets.end()); i != end; ++i) i->sock->close(ec); m_listen_sockets.clear(); - m_incoming_connection = false; + m_stats_counters.set_value(counters::has_incoming_connections, 0); ec.clear(); if (m_abort) return; @@ -2219,19 +2408,17 @@ retry: m_ipv6_interface = tcp::endpoint(); m_ipv4_interface = tcp::endpoint(); -#ifdef TORRENT_USE_OPENSSL - tcp::endpoint ssl_interface = m_listen_interface; - ssl_interface.port(m_settings.ssl_listen); -#endif - - if (is_any(m_listen_interface.address())) + // TODO: instead of having a special case for this, just make the + // default listen interfaces be "0.0.0.0:6881,[::1]:6881" and use + // the generic path. That would even allow for not listening at all. + if (m_listen_interfaces.empty()) { // this means we should open two listen sockets // one for IPv4 and one for IPv6 listen_socket_t s; - setup_listener(&s, tcp::endpoint(address_v4::any(), m_listen_interface.port()) - , m_listen_port_retries, false, flags, ec); + setup_listener(&s, "0.0.0.0", true, m_listen_interface.port() + , m_listen_port_retries, flags, ec); if (s.sock) { @@ -2245,12 +2432,13 @@ retry: } #ifdef TORRENT_USE_OPENSSL - if (m_settings.ssl_listen) + if (m_settings.get_int(settings_pack::ssl_listen)) { listen_socket_t s; s.ssl = true; int retries = 10; - setup_listener(&s, ssl_interface, retries, false, flags, ec); + setup_listener(&s, "0.0.0.0", true, m_settings.get_int(settings_pack::ssl_listen) + , retries, flags, ec); if (s.sock) { @@ -2264,8 +2452,8 @@ retry: // only try to open the IPv6 port if IPv6 is installed if (supports_ipv6()) { - setup_listener(&s, tcp::endpoint(address_v6::any(), m_listen_interface.port()) - , m_listen_port_retries, true, flags, ec); + setup_listener(&s, "::1", false, m_listen_interface.port() + , m_listen_port_retries, flags, ec); if (s.sock) { @@ -2274,13 +2462,13 @@ retry: } #ifdef TORRENT_USE_OPENSSL - if (m_settings.ssl_listen) + if (m_settings.get_int(settings_pack::ssl_listen)) { listen_socket_t s; s.ssl = true; int retries = 10; - setup_listener(&s, tcp::endpoint(address_v6::any(), ssl_interface.port()) - , retries, false, flags, ec); + setup_listener(&s, "::1", false, m_settings.get_int(settings_pack::ssl_listen) + , retries, flags, ec); if (s.sock) { @@ -2307,40 +2495,84 @@ retry: } else { - // we should only open a single listen socket, that - // binds to the given interface - - listen_socket_t s; - setup_listener(&s, m_listen_interface, m_listen_port_retries, false, flags, ec); - - if (s.sock) + // we should open a one listen socket for each entry in the + // listen_interfaces list + for (int i = 0; i < m_listen_interfaces.size(); ++i) { - TORRENT_ASSERT(!m_abort); - m_listen_sockets.push_back(s); + std::string const& device = m_listen_interfaces[i].first; + int port = m_listen_interfaces[i].second; - if (m_listen_interface.address().is_v6()) - m_ipv6_interface = m_listen_interface; - else - m_ipv4_interface = m_listen_interface; - } + int num_device_fails = 0; + +#if TORRENT_USE_IPV6 + const int first_family = 0; +#else + const int first_family = 1; +#endif + for (int address_family = first_family; address_family < 2; ++address_family) + { + error_code err; + address test_family = address::from_string(device.c_str(), err); + if (!err && test_family.is_v4() != address_family) + continue; + + listen_socket_t s; + setup_listener(&s, device, address_family, port + , m_listen_port_retries, flags, ec); + + if (ec == error_code(boost::system::errc::no_such_device, generic_category())) + { + ++num_device_fails; + continue; + } + + if (s.sock) + { + TORRENT_ASSERT(!m_abort); + m_listen_sockets.push_back(s); + + tcp::endpoint bind_ep = s.sock->local_endpoint(ec); +#if TORRENT_USE_IPV6 + if (bind_ep.address().is_v6()) + m_ipv6_interface = bind_ep; + else +#endif + m_ipv4_interface = bind_ep; + } #ifdef TORRENT_USE_OPENSSL - if (m_settings.ssl_listen) - { - listen_socket_t s; - s.ssl = true; - int retries = 10; - setup_listener(&s, ssl_interface, retries, false, flags, ec); + if (m_settings.get_int(settings_pack::ssl_listen)) + { + listen_socket_t s; + s.ssl = true; + int retries = 10; - if (s.sock) + setup_listener(&s, device, address_family + , m_settings.get_int(settings_pack::ssl_listen) + , m_listen_port_retries, flags, ec); + + if (s.sock) + { + TORRENT_ASSERT(!m_abort); + m_listen_sockets.push_back(s); + } + } +#endif + } + + if (num_device_fails == 2) { - TORRENT_ASSERT(!m_abort); - m_listen_sockets.push_back(s); + // only report this if both IPv4 and IPv6 fails for a device + if (m_alerts.should_post()) + m_alerts.post_alert(listen_failed_alert(device + , listen_failed_alert::bind + , error_code(boost::system::errc::no_such_device, generic_category()) + , listen_failed_alert::tcp)); } } -#endif } + // TODO: 2 use bind_to_device in udp_socket m_udp_socket.bind(udp::endpoint(m_listen_interface.address(), m_listen_interface.port()), ec); if (ec) { @@ -2355,8 +2587,11 @@ retry: goto retry; } if (m_alerts.should_post()) - m_alerts.post_alert(listen_failed_alert(m_listen_interface + { + error_code err; + m_alerts.post_alert(listen_failed_alert(print_endpoint(m_listen_interface) , listen_failed_alert::bind, ec, listen_failed_alert::udp)); + } } else { @@ -2367,10 +2602,11 @@ retry: m_alerts.post_alert(listen_succeeded_alert(m_listen_interface, listen_succeeded_alert::udp)); } - if (m_settings.peer_tos != 0) { - m_udp_socket.set_option(type_of_service(m_settings.peer_tos), ec); + if (m_settings.get_int(settings_pack::peer_tos) != 0) { + m_udp_socket.set_option(type_of_service(m_settings.get_int(settings_pack::peer_tos)), ec); #if defined TORRENT_VERBOSE_LOGGING - (*m_logger) << ">>> SET_TOS[ udp_socket tos: " << m_settings.peer_tos << " e: " << ec.message() << " ]\n"; + session_log(">>> SET_TOS[ udp_socket tos: %x e: %s ]" + , m_settings.get_int(settings_pack::peer_tos), ec.message().c_str()); #endif } ec.clear(); @@ -2429,15 +2665,17 @@ retry: void session_impl::open_new_incoming_socks_connection() { - if (m_proxy.type != proxy_settings::socks5 - && m_proxy.type != proxy_settings::socks5_pw - && m_proxy.type != proxy_settings::socks4) + int proxy_type = m_settings.get_int(settings_pack::proxy_type); + + if (proxy_type != settings_pack::socks5 + && proxy_type != settings_pack::socks5_pw + && proxy_type != settings_pack::socks4) return; if (m_socks_listen_socket) return; m_socks_listen_socket = boost::shared_ptr(new socket_type(m_io_service)); - bool ret = instantiate_connection(m_io_service, m_proxy + bool ret = instantiate_connection(m_io_service, proxy() , *m_socks_listen_socket); TORRENT_ASSERT_VAL(ret, ret); @@ -2452,15 +2690,31 @@ retry: , boost::bind(&session_impl::on_socks_accept, this, m_socks_listen_socket, _1)); } -#if TORRENT_USE_I2P - void session_impl::set_i2p_proxy(proxy_settings const& s) + void session_impl::update_i2p_bridge() { // we need this socket to be open before we // can make name lookups for trackers for instance. // pause the session now and resume it once we've // established the i2p SAM connection - m_i2p_conn.open(s, boost::bind(&session_impl::on_i2p_open, this, _1)); +#if TORRENT_USE_I2P + m_i2p_conn.open(m_settings.get_str(settings_pack::i2p_hostname) + , m_settings.get_int(settings_pack::i2p_port) + , boost::bind(&session_impl::on_i2p_open, this, _1)); + open_new_incoming_i2p_connection(); +#endif + } + +#if TORRENT_USE_I2P + + proxy_settings session_impl::i2p_proxy() const + { + proxy_settings ret; + + ret.hostname = m_settings.get_str(settings_pack::i2p_hostname); + ret.type = settings_pack::i2p_proxy; + ret.port = m_settings.get_int(settings_pack::i2p_port); + return ret; } void session_impl::on_i2p_open(error_code const& ec) @@ -2515,8 +2769,7 @@ retry: if (e) { if (m_alerts.should_post()) - m_alerts.post_alert(listen_failed_alert(tcp::endpoint( - address_v4::any(), m_listen_interface.port()), listen_failed_alert::accept + m_alerts.post_alert(listen_failed_alert("i2p", listen_failed_alert::accept , e, listen_failed_alert::i2p)); #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING session_log("cannot bind to port %d: %s" @@ -2532,9 +2785,7 @@ retry: bool session_impl::incoming_packet(error_code const& ec , udp::endpoint const& ep, char const* buf, int size) { -#ifdef TORRENT_STATS - ++m_num_messages[on_udp_counter]; -#endif + inc_stats_counter(counters::on_udp_counter); if (ec) { @@ -2587,10 +2838,8 @@ retry: #if defined TORRENT_ASIO_DEBUGGING complete_async("session_impl::on_accept_connection"); #endif -#ifdef TORRENT_STATS - ++m_num_messages[on_accept_counter]; -#endif - TORRENT_ASSERT(is_network_thread()); + inc_stats_counter(counters::on_accept_counter); + TORRENT_ASSERT(is_single_thread()); boost::shared_ptr listener = listen_socket.lock(); if (!listener) return; @@ -2630,7 +2879,7 @@ retry: // because we have too many files open, try again // and lower the number of file descriptors used // elsewere. - if (m_settings.connections_limit > 10) + if (m_settings.get_int(settings_pack::connections_limit) > 10) { // now, disconnect a random peer torrent_map::iterator i = std::max_element(m_torrents.begin() @@ -2646,14 +2895,17 @@ retry: i->second->disconnect_peers(1, e); } - m_settings.connections_limit = m_connections.size(); + m_settings.set_int(settings_pack::connections_limit, m_connections.size()); } // try again, but still alert the user of the problem async_accept(listener, ssl); } if (m_alerts.should_post()) - m_alerts.post_alert(listen_failed_alert(ep, listen_failed_alert::accept, e + { + error_code err; + m_alerts.post_alert(listen_failed_alert(print_endpoint(ep), listen_failed_alert::accept, e , ssl ? listen_failed_alert::tcp_ssl : listen_failed_alert::tcp)); + } return; } async_accept(listener, ssl); @@ -2706,7 +2958,7 @@ retry: if (m_alerts.should_post()) { m_alerts.post_alert(peer_error_alert(torrent_handle(), endp - , peer_id(), ec)); + , peer_id(), peer_connection::op_ssl_handshake, ec)); } return; } @@ -2718,11 +2970,11 @@ retry: void session_impl::incoming_connection(boost::shared_ptr const& s) { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); #ifdef TORRENT_USE_OPENSSL // add the current time to the PRNG, to add more unpredictability - boost::uint64_t now = total_microseconds(time_now_hires() - min_time()); + boost::uint64_t now = time_now_hires().time_since_epoch().count(); // assume 12 bits of entropy (i.e. about 8 milliseconds) RAND_add(&now, 8, 1.5); #endif @@ -2754,13 +3006,8 @@ retry: , print_endpoint(endp).c_str(), s->type_name()); #endif - if (m_alerts.should_post()) - { - m_alerts.post_alert(incoming_connection_alert(s->type(), endp)); - } - - if (!m_settings.enable_incoming_utp - && s->get()) + if (!m_settings.get_bool(settings_pack::enable_incoming_utp) + && is_utp(*s)) { #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) session_log(" rejected uTP connection"); @@ -2771,7 +3018,7 @@ retry: return; } - if (!m_settings.enable_incoming_tcp + if (!m_settings.get_bool(settings_pack::enable_incoming_tcp) && s->get()) { #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) @@ -2783,17 +3030,54 @@ retry: return; } + // if there are outgoing interfaces specified, verify this + // peer is correctly bound to on of them + if (!m_settings.get_str(settings_pack::outgoing_interfaces).empty()) + { + error_code ec; + tcp::endpoint local = s->local_endpoint(ec); + if (ec) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + session_log(" rejected connection: (%d) %s", ec.value() + , ec.message().c_str()); +#endif + return; + } + if (!verify_bound_address(local.address() + , is_utp(*s), ec)) + { + if (ec) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + session_log(" rejected connection, not allowed local interface: (%d) %s" + , ec.value(), ec.message().c_str()); +#endif + return; + } + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + session_log(" rejected connection, not allowed local interface: %s" + , local.address().to_string(ec).c_str()); +#endif + if (m_alerts.should_post()) + m_alerts.post_alert(peer_blocked_alert(torrent_handle() + , endp.address(), peer_blocked_alert::invalid_local_interface)); + return; + } + } + // local addresses do not count, since it's likely // coming from our own client through local service discovery // and it does not reflect whether or not a router is open // for incoming connections or not. if (!is_local(endp.address())) - m_incoming_connection = true; + m_stats_counters.set_value(counters::has_incoming_connections, 1); // this filter is ignored if a single torrent // is set to ignore the filter, since this peer might be // for that torrent - if (m_non_filtered_torrents == 0 + if (m_stats_counters[counters::non_filter_torrents] == 0 && (m_ip_filter.access(endp.address()) & ip_filter::blocked)) { #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) @@ -2815,13 +3099,26 @@ retry: return; } + // figure out which peer classes this is connections has, + // to get connection_limit_factor + peer_class_set pcs; + set_peer_classes(&pcs, endp.address(), s->type()); + int connection_limit_factor = 0; + for (int i = 0; i < pcs.num_classes(); ++i) + { + int pc = pcs.class_at(i); + if (m_classes.at(pc) == NULL) continue; + int f = m_classes.at(pc)->connection_limit_factor; + if (connection_limit_factor < f) connection_limit_factor = f; + } + if (connection_limit_factor == 0) connection_limit_factor = 100; + + boost::uint64_t limit = m_settings.get_int(settings_pack::connections_limit); + limit = limit * 100 / connection_limit_factor; + // don't allow more connections than the max setting - bool reject = false; - if (m_settings.ignore_limits_on_local_network && is_local(endp.address())) - reject = m_settings.connections_limit < INT_MAX / 12 - && num_connections() >= m_settings.connections_limit * 12 / 10; - else - reject = num_connections() >= m_settings.connections_limit + m_settings.connections_slack; + // weighed by the peer class' setting + bool reject = num_connections() >= limit + m_settings.get_int(settings_pack::connections_slack); if (reject) { @@ -2829,12 +3126,13 @@ retry: { m_alerts.post_alert( peer_disconnected_alert(torrent_handle(), endp, peer_id() + , peer_connection::op_bittorrent , error_code(errors::too_many_connections, get_libtorrent_category()))); } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - session_log("number of connections limit exceeded (conns: %d" - ", limit: %d slack: %d), connection rejected\n" - , num_connections(), m_settings.connections_limit, m_settings.connections_slack); + session_log("number of connections limit exceeded (conns: %d, limit: %d, slack: %d), connection rejected" + , num_connections(), m_settings.get_int(settings_pack::connections_limit) + , m_settings.get_int(settings_pack::connections_slack)); #endif return; } @@ -2844,7 +3142,7 @@ retry: // the setting to start up queued torrents when they // get an incoming connection is enabled, we cannot // perform this check. - if (!m_settings.incoming_starts_queued_torrents) + if (!m_settings.get_bool(settings_pack::incoming_starts_queued_torrents)) { bool has_active_torrent = false; for (torrent_map::iterator i = m_torrents.begin() @@ -2865,10 +3163,17 @@ retry: } } + m_stats_counters.inc_stats_counter(counters::incoming_connections); + + if (m_alerts.should_post()) + m_alerts.post_alert(incoming_connection_alert(s->type(), endp)); + setup_socket_buffers(*s); - boost::intrusive_ptr c( - new bt_peer_connection(*this, s, endp, 0, get_peer_id())); + boost::shared_ptr c + = boost::make_shared(boost::ref(*this), m_settings + , boost::ref(*this), boost::ref(m_disk_thread), s, endp, (torrent_peer*)0 + , get_peer_id()); #if TORRENT_USE_ASSERTS c->m_in_constructor = false; #endif @@ -2878,7 +3183,7 @@ retry: // in case we've exceeded the limit, let this peer know that // as soon as it's received the handshake, it needs to either // disconnect or pick another peer to disconnect - if (num_connections() >= m_settings.connections_limit) + if (num_connections() >= limit) c->peer_exceeds_limit(); TORRENT_ASSERT(!c->m_in_constructor); @@ -2906,8 +3211,7 @@ retry: if (e) { if (m_alerts.should_post()) - m_alerts.post_alert(listen_failed_alert(tcp::endpoint( - address_v4::any(), m_listen_interface.port()), listen_failed_alert::accept, e + m_alerts.post_alert(listen_failed_alert("socks5", listen_failed_alert::accept, e , listen_failed_alert::socks5)); return; } @@ -2915,16 +3219,23 @@ retry: incoming_connection(s); } - void session_impl::close_connection(peer_connection const* p - , error_code const& ec) + // if cancel_with_cq is set, the peer connection is + // currently expected to be scheduled for a connection + // with the connection queue, and should be cancelled + // TODO: should this function take a shared_ptr instead? + void session_impl::close_connection(peer_connection* p + , error_code const& ec, bool cancel_with_cq) { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); + boost::shared_ptr sp(p->self()); + + if (cancel_with_cq) m_half_open.cancel(p); // someone else is holding a reference, it's important that // it's destructed from the network thread. Make sure the // last reference is held by the network thread. - if (p->refcount() != 1) - m_undead_peers.push_back((peer_connection*)p); + if (!sp.unique()) + m_undead_peers.push_back(sp); // too expensive // INVARIANT_CHECK; @@ -2943,9 +3254,8 @@ retry: TORRENT_ASSERT(p->is_disconnecting()); if (!p->is_choked() && !p->ignore_unchoke_slots()) --m_num_unchoked; - TORRENT_ASSERT(p->refcount() > 0); + TORRENT_ASSERT(sp.use_count() > 0); - boost::intrusive_ptr sp((peer_connection*)p); connection_map::iterator i = m_connections.find(sp); // make sure the next disk peer round-robin cursor stays valid if (m_next_disk_peer == i) ++m_next_disk_peer; @@ -2989,9 +3299,11 @@ retry: --m_num_unchoked; } - int session_impl::next_port() + int session_impl::next_port() const { - std::pair const& out_ports = m_settings.outgoing_ports; + int start = m_settings.get_int(settings_pack::outgoing_port); + int num = m_settings.get_int(settings_pack::num_outgoing_ports); + std::pair out_ports(start, start + num); if (m_next_port < out_ports.first || m_next_port > out_ports.second) m_next_port = out_ports.first; @@ -3004,40 +3316,6 @@ retry: return port; } - // this function is called from the disk-io thread - // when the disk queue is low enough to post new - // write jobs to it. It will go through all peer - // connections that are blocked on the disk and - // wake them up - void session_impl::on_disk_queue() - { -#ifdef TORRENT_STATS - ++m_num_messages[on_disk_queue_counter]; -#endif - TORRENT_ASSERT(is_network_thread()); - - // just to play it safe - if (m_next_disk_peer == m_connections.end()) m_next_disk_peer = m_connections.begin(); - - // never loop more times than there are connections - // keep in mind that connections may disconnect - // while we're looping, that's why this is a reliable - // way of limiting it - int limit = m_connections.size(); - - while (m_next_disk_peer != m_connections.end() && limit > 0 && can_write_to_disk()) - { - --limit; - peer_connection* p = m_next_disk_peer->get(); - ++m_next_disk_peer; - if (m_next_disk_peer == m_connections.end()) m_next_disk_peer = m_connections.begin(); - if ((p->m_channel_state[peer_connection::download_channel] - & peer_info::bw_disk) == 0) continue; - p->on_disk(); - } - - } - // used to cache the current time // every 100 ms. This is cheaper // than a system call and can be @@ -3050,16 +3328,104 @@ retry: g_current_time = time_now_hires(); } + int session_impl::rate_limit(peer_class_t c, int channel) const + { + TORRENT_ASSERT(channel >= 0 && channel <= 1); + if (channel < 0 || channel > 1) return 0; + + peer_class const* pc = m_classes.at(c); + if (pc == 0) return 0; + return pc->channel[channel].throttle(); + } + + int session_impl::upload_rate_limit(peer_class_t c) const + { + return rate_limit(c, peer_connection::upload_channel); + } + + int session_impl::download_rate_limit(peer_class_t c) const + { + return rate_limit(c, peer_connection::download_channel); + } + + void session_impl::set_rate_limit(peer_class_t c, int channel, int limit) + { + TORRENT_ASSERT(is_single_thread()); + TORRENT_ASSERT(limit >= -1); + TORRENT_ASSERT(channel >= 0 && channel <= 1); + + if (channel < 0 || channel > 1) return; + + peer_class* pc = m_classes.at(c); + if (pc == 0) return; + if (limit <= 0) limit = 0; + pc->channel[channel].throttle(limit); + } + + void session_impl::set_upload_rate_limit(peer_class_t c, int limit) + { + set_rate_limit(c, peer_connection::upload_channel, limit); + } + + void session_impl::set_download_rate_limit(peer_class_t c, int limit) + { + set_rate_limit(c, peer_connection::download_channel, limit); + } + +#if TORRENT_USE_ASSERTS + bool session_impl::has_peer(peer_connection const* p) const + { + TORRENT_ASSERT(is_single_thread()); + return std::find_if(m_connections.begin(), m_connections.end() + , boost::bind(&boost::shared_ptr::get, _1) == p) + != m_connections.end(); + } + + bool session_impl::any_torrent_has_peer(peer_connection const* p) const + { + for (aux::session_impl::torrent_map::const_iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + if (i->second->has_peer(p)) return true; + return false; + } +#endif + + void session_impl::sent_bytes(int bytes_payload, int bytes_protocol) + { + m_stat.sent_bytes(bytes_payload, bytes_protocol); + } + + void session_impl::received_bytes(int bytes_payload, int bytes_protocol) + { + m_stat.received_bytes(bytes_payload, bytes_protocol); + } + + void session_impl::trancieve_ip_packet(int bytes, bool ipv6) + { + m_stat.trancieve_ip_packet(bytes, ipv6); + } + + void session_impl::sent_syn(bool ipv6) + { + m_stat.sent_syn(ipv6); + } + + void session_impl::received_synack(bool ipv6) + { + m_stat.received_synack(ipv6); + } + void session_impl::on_tick(error_code const& e) { #if defined TORRENT_ASIO_DEBUGGING complete_async("session_impl::on_tick"); #endif -#ifdef TORRENT_STATS - ++m_num_messages[on_tick_counter]; -#endif + inc_stats_counter(counters::on_tick_counter); - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); + + // submit all disk jobs when we leave this function + deferred_submit_jobs(); ptime now = time_now_hires(); aux::g_current_time = now; @@ -3068,12 +3434,13 @@ retry: // we have to keep ticking the utp socket manager // until they're all closed - if (m_abort && m_utp_socket_manager.num_sockets() == 0) + if (m_abort) { + if (m_utp_socket_manager.num_sockets() == 0) + return; #if defined TORRENT_ASIO_DEBUGGING fprintf(stderr, "uTP sockets left: %d\n", m_utp_socket_manager.num_sockets()); #endif - return; } if (e == asio::error::operation_aborted) return; @@ -3091,7 +3458,7 @@ retry: add_outstanding_async("session_impl::on_tick"); #endif error_code ec; - m_timer.expires_at(now + milliseconds(m_settings.tick_interval), ec); + m_timer.expires_at(now + milliseconds(m_settings.get_int(settings_pack::tick_interval)), ec); m_timer.async_wait(bind(&session_impl::on_tick, this, _1)); m_download_rate.update_quotas(now - m_last_tick); @@ -3111,9 +3478,9 @@ retry: #endif // remove undead peers that only have this list as their reference keeping them alive - std::vector >::iterator i = std::remove_if( + std::vector >::iterator i = std::remove_if( m_undead_peers.begin(), m_undead_peers.end() - , boost::bind(&peer_connection::refcount, _1) == 1); + , boost::bind(&boost::shared_ptr::unique, _1)); m_undead_peers.erase(i, m_undead_peers.end()); int tick_interval_ms = int(total_milliseconds(now - m_last_second_tick)); @@ -3124,7 +3491,7 @@ retry: if (session_time > 65000) { // we're getting close to the point where our timestamps - // in policy::peer are wrapping. We need to step all counters back + // in torrent_peer are wrapping. We need to step all counters back // four hours. This means that any timestamp that refers to a time // more than 18.2 - 4 = 14.2 hours ago, will be incremented to refer to // 14.2 hours ago. @@ -3135,22 +3502,7 @@ retry: for (torrent_map::iterator i = m_torrents.begin() , end(m_torrents.end()); i != end; ++i) { - policy& p = i->second->get_policy(); - for (policy::iterator j = p.begin_peer() - , end(p.end_peer()); j != end; ++j) - { - policy::peer* pe = *j; - - if (pe->last_optimistically_unchoked < four_hours) - pe->last_optimistically_unchoked = 0; - else - pe->last_optimistically_unchoked -= four_hours; - - if (pe->last_connected < four_hours) - pe->last_connected = 0; - else - pe->last_connected -= four_hours; - } + i->second->step_session_time(four_hours); } } @@ -3173,13 +3525,13 @@ retry: if (now > m_next_rss_update) update_rss_feeds(); - switch (m_settings.mixed_mode_algorithm) + switch (m_settings.get_int(settings_pack::mixed_mode_algorithm)) { - case session_settings::prefer_tcp: - m_tcp_upload_channel.throttle(0); - m_tcp_download_channel.throttle(0); + case settings_pack::prefer_tcp: + set_upload_rate_limit(m_tcp_peer_class, 0); + set_download_rate_limit(m_tcp_peer_class, 0); break; - case session_settings::peer_proportional: + case settings_pack::peer_proportional: { int num_peers[2][2] = {{0, 0}, {0, 0}}; for (connection_map::iterator i = m_connections.begin() @@ -3196,7 +3548,8 @@ retry: ++num_peers[protocol][peer_connection::upload_channel]; } - bandwidth_channel* tcp_channel[] = { &m_tcp_upload_channel, &m_tcp_download_channel }; + peer_class* pc = m_classes.at(m_tcp_peer_class); + bandwidth_channel* tcp_channel = pc->channel; int stat_rate[] = {m_stat.upload_rate(), m_stat.download_rate() }; // never throttle below this int lower_limit[] = {5000, 30000}; @@ -3206,7 +3559,7 @@ retry: // if there are no uploading uTP peers, don't throttle TCP up if (num_peers[1][i] == 0) { - tcp_channel[i]->throttle(0); + tcp_channel[i].throttle(0); } else { @@ -3215,7 +3568,7 @@ retry: // this are 64 bits since it's multiplied by the number // of peers, which otherwise might overflow an int boost::uint64_t rate = stat_rate[i]; - tcp_channel[i]->throttle((std::max)(int(rate * num_peers[0][i] / total_peers), lower_limit[i])); + tcp_channel[i].throttle((std::max)(int(rate * num_peers[0][i] / total_peers), lower_limit[i])); } } } @@ -3228,7 +3581,8 @@ retry: if (!m_paused) m_auto_manage_time_scaler--; if (m_auto_manage_time_scaler < 0) { - m_auto_manage_time_scaler = settings().auto_manage_interval; + INVARIANT_CHECK; + m_auto_manage_time_scaler = settings().get_int(settings_pack::auto_manage_interval); recalculate_auto_managed_torrents(); } @@ -3244,89 +3598,35 @@ retry: // ignore connections that already have a torrent, since they // are ticked through the torrents' second_tick if (!p->associated_torrent().expired()) continue; + // TODO: have a separate list for these connections, instead of having to loop through all of them - if (m_last_tick - p->connected_time() > seconds(m_settings.handshake_timeout)) - p->disconnect(errors::timed_out); + if (m_last_tick - p->connected_time() + > seconds(m_settings.get_int(settings_pack::handshake_timeout))) + p->disconnect(errors::timed_out, peer_connection::op_bittorrent); } // -------------------------------------------------------------- - // second_tick every torrent + // second_tick every torrent (that wants it) // -------------------------------------------------------------- - int congested_torrents = 0; - int uncongested_torrents = 0; - - // count the number of seeding torrents vs. downloading - // torrents we are running - int num_seeds = 0; - int num_downloads = 0; - - // count the number of peers of downloading torrents - int num_downloads_peers = 0; - - torrent_map::iterator least_recently_scraped = m_torrents.end(); - int num_paused_auto_managed = 0; - - int num_checking = 0; - int num_queued = 0; - #if TORRENT_DEBUG_STREAMING > 0 printf("\033[2J\033[0;0H"); #endif - for (torrent_map::iterator i = m_torrents.begin(); - i != m_torrents.end();) + std::vector& want_tick = m_torrent_lists[torrent_want_tick]; + for (int i = 0; i < int(want_tick.size()); ++i) { - torrent& t = *i->second; + torrent& t = *want_tick[i]; + TORRENT_ASSERT(t.want_tick()); TORRENT_ASSERT(!t.is_aborted()); - if (t.statistics().upload_rate() * 11 / 10 > t.upload_limit()) - ++congested_torrents; - else - ++uncongested_torrents; - if (t.state() == torrent_status::checking_files) ++num_checking; - else if (t.state() == torrent_status::queued_for_checking && !t.is_paused()) ++num_queued; + t.second_tick(tick_interval_ms, m_tick_residual / 1000); - if (t.is_auto_managed() && t.is_paused() && !t.has_error()) - { - ++num_paused_auto_managed; - if (least_recently_scraped == m_torrents.end() - || least_recently_scraped->second->seconds_since_last_scrape() - < t.seconds_since_last_scrape()) - { - least_recently_scraped = i; - } - } - - if (t.is_finished()) - { - ++num_seeds; - } - else - { - ++num_downloads; - num_downloads_peers += t.num_peers(); - } - - ++i; - t.second_tick(m_stat, tick_interval_ms); - } - - // some people claim that there sometimes can be cases where - // there is no torrent being checked, but there are torrents - // waiting to be checked. I have never seen this, and I can't - // see a way for it to happen. But, if it does, start one of - // the queued torrents - if (num_checking == 0 && num_queued > 0 && !m_paused) - { - TORRENT_ASSERT(false); - check_queue_t::iterator i = std::min_element(m_queued_for_checking.begin() - , m_queued_for_checking.end(), boost::bind(&torrent::queue_position, _1) - < boost::bind(&torrent::queue_position, _2)); - if (i != m_queued_for_checking.end()) - { - (*i)->start_checking(); - } + // if the call to second_tick caused the torrent + // to no longer want to be ticked (i.e. it was + // removed from the list) we need to back up the counter + // to not miss the torrent after it + if (!t.want_tick()) --i; } #ifndef TORRENT_DISABLE_DHT @@ -3340,22 +3640,25 @@ retry: } #endif - if (m_settings.rate_limit_ip_overhead) + // TODO: this should apply to all bandwidth channels + if (m_settings.get_bool(settings_pack::rate_limit_ip_overhead)) { - m_download_channel.use_quota( + peer_class* gpc = m_classes.at(m_global_class); + + gpc->channel[peer_connection::download_channel].use_quota( #ifndef TORRENT_DISABLE_DHT m_stat.download_dht() + #endif m_stat.download_tracker()); - m_upload_channel.use_quota( + gpc->channel[peer_connection::upload_channel].use_quota( #ifndef TORRENT_DISABLE_DHT m_stat.upload_dht() + #endif m_stat.upload_tracker()); - int up_limit = m_upload_channel.throttle(); - int down_limit = m_download_channel.throttle(); + int up_limit = upload_rate_limit(m_global_class); + int down_limit = download_rate_limit(m_global_class); if (down_limit > 0 && m_stat.download_ip_overhead() >= down_limit @@ -3379,12 +3682,7 @@ retry: m_stat.second_tick(tick_interval_ms); - TORRENT_ASSERT(least_recently_scraped == m_torrents.end() - || (least_recently_scraped->second->is_paused() - && least_recently_scraped->second->is_auto_managed())); - #ifdef TORRENT_STATS - if (m_stats_logging_enabled) { print_log_line(tick_interval_ms, now); @@ -3397,29 +3695,64 @@ retry: // -------------------------------------------------------------- if (!is_paused()) { + INVARIANT_CHECK; --m_auto_scrape_time_scaler; if (m_auto_scrape_time_scaler <= 0) { - m_auto_scrape_time_scaler = m_settings.auto_scrape_interval - / (std::max)(1, num_paused_auto_managed); - if (m_auto_scrape_time_scaler < m_settings.auto_scrape_min_interval) - m_auto_scrape_time_scaler = m_settings.auto_scrape_min_interval; + std::vector& want_scrape = m_torrent_lists[torrent_want_scrape]; + m_auto_scrape_time_scaler = m_settings.get_int(settings_pack::auto_scrape_interval) + / (std::max)(1, int(want_scrape.size())); + if (m_auto_scrape_time_scaler < m_settings.get_int(settings_pack::auto_scrape_min_interval)) + m_auto_scrape_time_scaler = m_settings.get_int(settings_pack::auto_scrape_min_interval); - if (least_recently_scraped != m_torrents.end()) + if (!want_scrape.empty() && !m_abort) { - least_recently_scraped->second->scrape_tracker(); + if (m_next_scrape_torrent >= int(want_scrape.size())) + m_next_scrape_torrent = 0; + + torrent& t = *want_scrape[m_next_scrape_torrent]; + TORRENT_ASSERT(t.is_paused() && t.is_auto_managed()); + + t.scrape_tracker(); + + ++m_next_scrape_torrent; + if (m_next_scrape_torrent >= int(want_scrape.size())) + m_next_scrape_torrent = 0; + } } } + // -------------------------------------------------------------- + // refresh torrent suggestions + // -------------------------------------------------------------- + --m_suggest_timer; + if (m_settings.get_int(settings_pack::suggest_mode) != settings_pack::no_piece_suggestions + && m_suggest_timer <= 0) + { + INVARIANT_CHECK; + m_suggest_timer = 10; + + torrent_map::iterator least_recently_refreshed = m_torrents.begin(); + if (m_next_suggest_torrent >= int(m_torrents.size())) + m_next_suggest_torrent = 0; + + std::advance(least_recently_refreshed, m_next_suggest_torrent); + + if (least_recently_refreshed != m_torrents.end()) + least_recently_refreshed->second->refresh_suggest_pieces(); + ++m_next_suggest_torrent; + } + // -------------------------------------------------------------- // refresh explicit disk read cache // -------------------------------------------------------------- --m_cache_rotation_timer; - if (m_settings.explicit_read_cache + if (m_settings.get_bool(settings_pack::explicit_read_cache) && m_cache_rotation_timer <= 0) { - m_cache_rotation_timer = m_settings.explicit_cache_interval; + INVARIANT_CHECK; + m_cache_rotation_timer = m_settings.get_int(settings_pack::explicit_cache_interval); torrent_map::iterator least_recently_refreshed = m_torrents.begin(); if (m_next_explicit_cache_torrent >= int(m_torrents.size())) @@ -3428,7 +3761,7 @@ retry: std::advance(least_recently_refreshed, m_next_explicit_cache_torrent); // how many blocks does this torrent get? - int cache_size = (std::max)(0, m_settings.cache_size * 9 / 10); + int cache_size = (std::max)(0, m_settings.get_int(settings_pack::cache_size) * 9 / 10); if (m_connections.empty()) { @@ -3451,7 +3784,7 @@ retry: // connect new peers // -------------------------------------------------------------- - try_connect_more_peers(num_downloads, num_downloads_peers); + try_connect_more_peers(); // -------------------------------------------------------------- // unchoke set calculations @@ -3459,9 +3792,8 @@ retry: m_unchoke_time_scaler--; if (m_unchoke_time_scaler <= 0 && !m_connections.empty()) { - m_unchoke_time_scaler = settings().unchoke_interval; - recalculate_unchoke_slots(congested_torrents - , uncongested_torrents); + m_unchoke_time_scaler = settings().get_int(settings_pack::unchoke_interval); + recalculate_unchoke_slots(); } // -------------------------------------------------------------- @@ -3471,7 +3803,7 @@ retry: if (m_optimistic_unchoke_time_scaler <= 0) { m_optimistic_unchoke_time_scaler - = settings().optimistic_unchoke_interval; + = settings().get_int(settings_pack::optimistic_unchoke_interval); recalculate_optimistic_unchoke_slots(); } @@ -3481,9 +3813,10 @@ retry: --m_disconnect_time_scaler; if (m_disconnect_time_scaler <= 0) { - m_disconnect_time_scaler = m_settings.peer_turnover_interval; + m_disconnect_time_scaler = m_settings.get_int(settings_pack::peer_turnover_interval); - if (num_connections() >= m_settings.connections_limit * m_settings.peer_turnover_cutoff + if (num_connections() >= m_settings.get_int(settings_pack::connections_limit) + * m_settings.get_int(settings_pack::peer_turnover_cutoff) / 100 && !m_torrents.empty()) { // every 90 seconds, disconnect the worst peers @@ -3494,8 +3827,8 @@ retry: TORRENT_ASSERT(i != m_torrents.end()); int peers_to_disconnect = (std::min)((std::max)( - int(i->second->num_peers() * m_settings.peer_turnover), 1) - , i->second->get_policy().num_connect_candidates()); + int(i->second->num_peers() * m_settings.get_int(settings_pack::peer_turnover) / 100), 1) + , i->second->num_connect_candidates()); i->second->disconnect_peers(peers_to_disconnect , error_code(errors::optimistic_disconnect, get_libtorrent_category())); } @@ -3507,31 +3840,61 @@ retry: , end(m_torrents.end()); i != end; ++i) { boost::shared_ptr t = i->second; - if (t->num_peers() < t->max_connections() * m_settings.peer_turnover_cutoff) + if (t->num_peers() < t->max_connections() * m_settings.get_int(settings_pack::peer_turnover_cutoff) / 100) continue; int peers_to_disconnect = (std::min)((std::max)(int(i->second->num_peers() - * m_settings.peer_turnover), 1) - , i->second->get_policy().num_connect_candidates()); + * m_settings.get_int(settings_pack::peer_turnover) / 100), 1) + , i->second->num_connect_candidates()); t->disconnect_peers(peers_to_disconnect , error_code(errors::optimistic_disconnect, get_libtorrent_category())); } } } - while (m_tick_residual >= 1000) m_tick_residual -= 1000; + m_tick_residual = m_tick_residual % 1000; // m_peer_pool.release_memory(); } + // returns the index of the first set bit. + int log2(boost::uint32_t v) + { +// http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn + static const int MultiplyDeBruijnBitPosition[32] = + { + 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 + }; + + v |= v >> 1; // first round down to one less than a power of 2 + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + + return MultiplyDeBruijnBitPosition[boost::uint32_t(v * 0x07C4ACDDU) >> 27]; + } + + void session_impl::received_buffer(int s) + { + int index = (std::min)(log2(s >> 3), 17); + m_stats_counters.inc_stats_counter(counters::socket_recv_size3 + index); + } + + void session_impl::sent_buffer(int s) + { + int index = (std::min)(log2(s >> 3), 17); + m_stats_counters.inc_stats_counter(counters::socket_send_size3 + index); + } + #ifdef TORRENT_STATS - + void session_impl::enable_stats_logging(bool s) { if (m_stats_logging_enabled == s) return; m_stats_logging_enabled = s; - reset_stat_counters(); if (!s) { if (m_stats_logger) fclose(m_stats_logger); @@ -3544,46 +3907,10 @@ retry: } } - void session_impl::reset_stat_counters() - { - m_end_game_piece_picker_blocks = 0; - m_piece_picker_blocks = 0; - m_piece_picks = 0; - m_reject_piece_picks = 0; - m_unchoke_piece_picks = 0; - m_incoming_redundant_piece_picks = 0; - m_incoming_piece_picks = 0; - m_end_game_piece_picks = 0; - m_snubbed_piece_picks = 0; - m_connection_attempts = 0; - m_num_banned_peers = 0; - m_banned_for_hash_failure = 0; - - m_piece_requests = 0; - m_max_piece_requests = 0; - m_invalid_piece_requests = 0; - m_choked_piece_requests = 0; - m_cancelled_piece_requests = 0; - m_piece_rejects = 0; - - memset(m_num_messages, 0, sizeof(m_num_messages)); - memset(m_send_buffer_sizes, 0, sizeof(m_send_buffer_sizes)); - memset(m_recv_buffer_sizes, 0, sizeof(m_recv_buffer_sizes)); - } - void session_impl::print_log_line(int tick_interval_ms, ptime now) { int connect_candidates = 0; - int checking_torrents = 0; - int stopped_torrents = 0; - int upload_only_torrents = 0; - int downloading_torrents = 0; - int seeding_torrents = 0; - int queued_seed_torrents = 0; - int queued_download_torrents = 0; - int error_torrents = 0; - int num_peers = 0; int peer_dl_rate_buckets[7]; int peer_ul_rate_buckets[7]; @@ -3593,14 +3920,17 @@ retry: int outstanding_end_game_requests = 0; int outstanding_write_blocks = 0; - int peers_up_interested = 0; - int peers_down_interesting = 0; - int peers_up_requests = 0; - int peers_down_requests = 0; int peers_up_send_buffer = 0; + int partial_pieces = 0; + int partial_downloading_pieces = 0; + int partial_full_pieces = 0; + int partial_finished_pieces = 0; + int partial_zero_prio_pieces = 0; + // number of torrents that want more peers - int num_want_more_peers = 0; + int num_want_more_peers = int(m_torrent_lists[torrent_want_peers_download].size() + + m_torrent_lists[torrent_want_peers_finished].size()); // number of peers among torrents with a peer limit int num_limited_peers = 0; @@ -3612,50 +3942,32 @@ retry: , end(m_torrents.end()); i != end; ++i) { torrent* t = i->second.get(); - int connection_slots = (std::max)(t->max_connections() - t->num_peers(), 0); - int candidates = t->get_policy().num_connect_candidates(); - connect_candidates += (std::min)(candidates, connection_slots); - num_peers += t->get_policy().num_peers(); - if (t->want_more_peers()) ++num_want_more_peers; + int connection_slots = (std::max)(t->max_connections() - t->num_peers(), 0); + int candidates = t->num_connect_candidates(); + connect_candidates += (std::min)(candidates, connection_slots); + num_peers += t->num_known_peers(); + if (t->max_connections() > 0) { num_limited_peers += t->num_peers(); total_peers_limit += t->max_connections(); } - if (t->has_error()) - ++error_torrents; - else + if (t->has_picker()) { - if (t->is_paused()) - { - if (!t->is_auto_managed()) - ++stopped_torrents; - else - { - if (t->is_seed()) - ++queued_seed_torrents; - else - ++queued_download_torrents; - } - } - else - { - if (i->second->state() == torrent_status::checking_files - || i->second->state() == torrent_status::queued_for_checking) - ++checking_torrents; - else if (i->second->is_seed()) - ++seeding_torrents; - else if (i->second->is_upload_only()) - ++upload_only_torrents; - else - ++downloading_torrents; - } + piece_picker& p = t->picker(); + partial_pieces += p.get_download_queue_size(); + int a, b, c, d; + p.get_download_queue_sizes(&a, &b, &c, &d); + partial_downloading_pieces += a; + partial_full_pieces += b; + partial_finished_pieces += c; + partial_zero_prio_pieces += d; } dq.clear(); - i->second->get_download_queue(&dq); + t->get_download_queue(&dq); for (std::vector::iterator j = dq.begin() , end(dq.end()); j != end; ++j) { @@ -3680,15 +3992,8 @@ retry: int utp_peak_recv_delay = 0; boost::uint64_t utp_send_delay_sum = 0; boost::uint64_t utp_recv_delay_sum = 0; - int num_utp_peers = 0; - int num_tcp_peers = 0; int utp_num_delay_sockets = 0; int utp_num_recv_delay_sockets = 0; - int num_complete_connections = 0; - int num_half_open = 0; - int peers_down_unchoked = 0; - int peers_up_unchoked = 0; - int num_end_game_peers = 0; int reading_bytes = 0; int pending_incoming_reqs = 0; @@ -3697,20 +4002,8 @@ retry: { peer_connection* p = i->get(); if (p->is_connecting()) - { - ++num_half_open; continue; - } - ++num_complete_connections; - if (!p->is_choked()) ++peers_up_unchoked; - if (!p->has_peer_choked()) ++peers_down_unchoked; - if (!p->download_queue().empty()) ++peers_down_requests; - if (p->is_peer_interested()) ++peers_up_interested; - if (p->is_interesting()) ++peers_down_interesting; - if (p->send_buffer_size() > 100 || !p->upload_queue().empty() || p->num_reading_bytes() > 0) - ++peers_up_requests; - if (p->endgame()) ++num_end_game_peers; reading_bytes += p->num_reading_bytes(); pending_incoming_reqs += int(p->upload_queue().size()); @@ -3740,11 +4033,11 @@ retry: boost::uint64_t upload_rate = int(p->statistics().upload_rate()); int buffer_size_watermark = upload_rate - * m_settings.send_buffer_watermark_factor / 100; - if (buffer_size_watermark < m_settings.send_buffer_low_watermark) - buffer_size_watermark = m_settings.send_buffer_low_watermark; - else if (buffer_size_watermark > m_settings.send_buffer_watermark) - buffer_size_watermark = m_settings.send_buffer_watermark; + * m_settings.get_int(settings_pack::send_buffer_watermark_factor) / 100; + if (buffer_size_watermark < m_settings.get_int(settings_pack::send_buffer_low_watermark)) + buffer_size_watermark = m_settings.get_int(settings_pack::send_buffer_low_watermark); + else if (buffer_size_watermark > m_settings.get_int(settings_pack::send_buffer_watermark)) + buffer_size_watermark = m_settings.get_int(settings_pack::send_buffer_watermark); if (p->send_buffer_size() + p->num_reading_bytes() >= buffer_size_watermark) ++peers_up_send_buffer; @@ -3774,46 +4067,39 @@ retry: utp_recv_delay_sum += recv_delay; ++utp_num_recv_delay_sockets; } - ++num_utp_peers; } else { tcp_up_rate += ul_rate; tcp_down_rate += dl_rate; - ++num_tcp_peers; } - } - int low_watermark = m_settings.max_queued_disk_bytes_low_watermark == 0 - || m_settings.max_queued_disk_bytes_low_watermark >= m_settings.max_queued_disk_bytes - ? size_type(m_settings.max_queued_disk_bytes) * 7 / 8 - : m_settings.max_queued_disk_bytes_low_watermark; - if (now - m_last_log_rotation > hours(1)) rotate_stats_log(); // system memory stats + error_code vm_ec; vm_statistics_data_t vm_stat; - get_vm_stats(&vm_stat); + get_vm_stats(&vm_stat, vm_ec); thread_cpu_usage cur_cpu_usage; get_thread_cpu_usage(&cur_cpu_usage); if (m_stats_logger) { - cache_status cs = m_disk_thread.status(); + cache_status cs; + m_disk_thread.get_cache_info(&cs); session_status sst = status(); - m_read_ops.add_sample((cs.reads - m_last_cache_status.reads) * 1000.0 / float(tick_interval_ms)); - m_write_ops.add_sample((cs.writes - m_last_cache_status.writes) * 1000.0 / float(tick_interval_ms)); - - int total_job_time = cs.cumulative_job_time == 0 ? 1 : cs.cumulative_job_time; + m_read_ops.add_sample((cs.reads - m_last_cache_status.reads) * 1000000.0 / float(tick_interval_ms)); + m_write_ops.add_sample((cs.writes - m_last_cache_status.writes) * 1000000.0 / float(tick_interval_ms)); #ifdef TORRENT_USE_VALGRIND #define STAT_LOGL(type, val) VALGRIND_CHECK_VALUE_IS_DEFINED(val); fprintf(m_stats_logger, "%" #type "\t", val) #else #define STAT_LOGL(type, val) fprintf(m_stats_logger, "%" #type "\t", val) #endif +#define STAT_COUNTER(cnt) fprintf(m_stats_logger, "%" PRId64 "\t", m_stats_counters[counters:: cnt]) #define STAT_LOG(type, val) fprintf(m_stats_logger, "%" #type "\t", val) STAT_LOG(f, total_milliseconds(now - m_last_log_rotation) / 1000.f); @@ -3821,26 +4107,26 @@ retry: STAT_LOG(d, int(uploaded)); size_type downloaded = m_stat.total_download() - m_last_downloaded; STAT_LOG(d, int(downloaded)); - STAT_LOGL(d, downloading_torrents); - STAT_LOGL(d, seeding_torrents); - STAT_LOGL(d, num_complete_connections); - STAT_LOGL(d, num_half_open); - STAT_LOG(d, m_disk_thread.disk_allocations()); - STAT_LOGL(d, num_peers); - STAT_LOGL(d, logging_allocator::allocations); - STAT_LOGL(d, logging_allocator::allocated_bytes); - STAT_LOGL(d, checking_torrents); - STAT_LOGL(d, stopped_torrents); - STAT_LOGL(d, upload_only_torrents); - STAT_LOGL(d, queued_seed_torrents); - STAT_LOGL(d, queued_download_torrents); + STAT_COUNTER(num_downloading_torrents); + STAT_COUNTER(num_seeding_torrents); + STAT_COUNTER(num_peers_connected); + STAT_COUNTER(num_peers_half_open); + STAT_COUNTER(disk_blocks_in_use); + STAT_LOGL(d, num_peers); // total number of known peers + STAT_LOG(d, m_peer_allocator.live_allocations()); + STAT_LOG(d, m_peer_allocator.live_bytes()); + STAT_COUNTER(num_checking_torrents); + STAT_COUNTER(num_stopped_torrents); + STAT_COUNTER(num_upload_only_torrents); + STAT_COUNTER(num_queued_seeding_torrents); + STAT_COUNTER(num_queued_download_torrents); STAT_LOG(d, m_upload_rate.queue_size()); STAT_LOG(d, m_download_rate.queue_size()); - STAT_LOGL(d, m_disk_queues[peer_connection::upload_channel]); - STAT_LOGL(d, m_disk_queues[peer_connection::download_channel]); + STAT_COUNTER(num_peers_up_disk); + STAT_COUNTER(num_peers_down_disk); STAT_LOG(d, m_stat.upload_rate()); STAT_LOG(d, m_stat.download_rate()); - STAT_LOG(d, int(m_disk_thread.queue_buffer_size())); + STAT_COUNTER(queued_write_bytes); STAT_LOGL(d, peer_dl_rate_buckets[0]); STAT_LOGL(d, peer_dl_rate_buckets[1]); STAT_LOGL(d, peer_dl_rate_buckets[2]); @@ -3855,77 +4141,80 @@ retry: STAT_LOGL(d, peer_ul_rate_buckets[4]); STAT_LOGL(d, peer_ul_rate_buckets[5]); STAT_LOGL(d, peer_ul_rate_buckets[6]); - STAT_LOGL(d, m_error_peers); - STAT_LOGL(d, peers_down_interesting); - STAT_LOGL(d, peers_down_unchoked); - STAT_LOGL(d, peers_down_requests); - STAT_LOGL(d, peers_up_interested); - STAT_LOGL(d, peers_up_unchoked); - STAT_LOGL(d, peers_up_requests); - STAT_LOGL(d, m_disconnected_peers); - STAT_LOGL(d, m_eof_peers); - STAT_LOGL(d, m_connreset_peers); + STAT_COUNTER(error_peers); + STAT_COUNTER(num_peers_down_interested); + STAT_COUNTER(num_peers_down_unchoked); + STAT_COUNTER(num_peers_down_requests); + STAT_COUNTER(num_peers_up_interested); + STAT_COUNTER(num_peers_up_unchoked); + STAT_COUNTER(num_peers_up_requests); + STAT_COUNTER(disconnected_peers); + STAT_COUNTER(eof_peers); + STAT_COUNTER(connreset_peers); STAT_LOGL(d, outstanding_requests); STAT_LOGL(d, outstanding_end_game_requests); STAT_LOGL(d, outstanding_write_blocks); - STAT_LOGL(d, m_end_game_piece_picker_blocks); - STAT_LOGL(d, m_piece_picker_blocks); - STAT_LOGL(d, m_piece_picks); - STAT_LOGL(d, m_reject_piece_picks); - STAT_LOGL(d, m_unchoke_piece_picks); - STAT_LOGL(d, m_incoming_redundant_piece_picks); - STAT_LOGL(d, m_incoming_piece_picks); - STAT_LOGL(d, m_end_game_piece_picks); - STAT_LOGL(d, m_snubbed_piece_picks); - STAT_LOGL(d, m_connect_timeouts); - STAT_LOGL(d, m_uninteresting_peers); - STAT_LOGL(d, m_timeout_peers); - STAT_LOG(f, (float(m_total_failed_bytes) * 100.f / (m_stat.total_payload_download() == 0 ? 1 : m_stat.total_payload_download()))); - STAT_LOG(f, (float(m_total_redundant_bytes) * 100.f / (m_stat.total_payload_download() == 0 ? 1 : m_stat.total_payload_download()))); - STAT_LOG(f, (float(m_stat.total_protocol_download()) * 100.f / (m_stat.total_download() == 0 ? 1 : m_stat.total_download()))); + STAT_COUNTER(reject_piece_picks); + STAT_COUNTER(unchoke_piece_picks); + STAT_COUNTER(incoming_redundant_piece_picks); + STAT_COUNTER(incoming_piece_picks); + STAT_COUNTER(end_game_piece_picks); + STAT_COUNTER(snubbed_piece_picks); + STAT_COUNTER(interesting_piece_picks); + STAT_COUNTER(hash_fail_piece_picks); + STAT_COUNTER(connect_timeouts); + STAT_COUNTER(uninteresting_peers); + STAT_COUNTER(timeout_peers); + STAT_LOG(f, float(m_stats_counters[counters::recv_failed_bytes]) * 100.f + / (std::max)(m_stats_counters[counters::recv_bytes], boost::int64_t(1))); + STAT_LOG(f, float(m_stats_counters[counters::recv_redundant_bytes]) * 100.f + / (std::max)(m_stats_counters[counters::recv_bytes], boost::int64_t(1))); + STAT_LOG(f, float(m_stats_counters[counters::recv_bytes] + - m_stats_counters[counters::recv_payload_bytes]) * 100.f + / (std::max)(m_stats_counters[counters::recv_bytes], boost::int64_t(1))); STAT_LOG(f, float(cs.average_read_time) / 1000000.f); STAT_LOG(f, float(cs.average_write_time) / 1000000.f); - STAT_LOG(f, float(cs.average_queue_time) / 1000000.f); - STAT_LOG(d, int(cs.job_queue_length)); - STAT_LOG(d, int(cs.queued_bytes)); + STAT_LOG(d, int(cs.pending_jobs + cs.queued_jobs)); + STAT_COUNTER(queued_write_bytes); STAT_LOG(d, int(cs.blocks_read_hit - m_last_cache_status.blocks_read_hit)); STAT_LOG(d, int(cs.blocks_read - m_last_cache_status.blocks_read)); STAT_LOG(d, int(cs.blocks_written - m_last_cache_status.blocks_written)); - STAT_LOG(d, int(m_total_failed_bytes - m_last_failed)); - STAT_LOG(d, int(m_total_redundant_bytes - m_last_redundant)); - STAT_LOGL(d, error_torrents); + STAT_LOG(d, int(m_stats_counters[counters::recv_failed_bytes] + - m_last_failed)); + STAT_LOG(d, int(m_stats_counters[counters::recv_redundant_bytes] + - m_last_redundant)); + STAT_COUNTER(num_error_torrents); STAT_LOGL(d, cs.read_cache_size); - STAT_LOGL(d, cs.cache_size); - STAT_LOGL(d, cs.total_used_buffers); + STAT_LOG(d, cs.write_cache_size + cs.read_cache_size); + STAT_COUNTER(disk_blocks_in_use); STAT_LOG(f, float(cs.average_hash_time) / 1000000.f); - STAT_LOG(f, float(cs.average_job_time) / 1000000.f); - STAT_LOG(f, float(cs.average_sort_time) / 1000000.f); - STAT_LOGL(d, m_connection_attempts); - STAT_LOGL(d, m_num_banned_peers); - STAT_LOGL(d, m_banned_for_hash_failure); - STAT_LOGL(d, m_settings.cache_size); - STAT_LOGL(d, m_settings.connections_limit); + STAT_COUNTER(connection_attempts); + STAT_COUNTER(num_banned_peers); + STAT_COUNTER(banned_for_hash_failure); + STAT_LOG(d, m_settings.get_int(settings_pack::cache_size)); + STAT_LOG(d, m_settings.get_int(settings_pack::connections_limit)); STAT_LOGL(d, connect_candidates); - STAT_LOG(d, int(m_settings.max_queued_disk_bytes)); - STAT_LOGL(d, low_watermark); - STAT_LOG(f, float(cs.cumulative_read_time * 100.f / total_job_time)); - STAT_LOG(f, float(cs.cumulative_write_time * 100.f / total_job_time)); - STAT_LOG(f, float(cs.cumulative_hash_time * 100.f / total_job_time)); - STAT_LOG(f, float(cs.cumulative_sort_time * 100.f / total_job_time)); + STAT_LOG(d, int(m_settings.get_int(settings_pack::cache_size) + - m_settings.get_int(settings_pack::max_queued_disk_bytes) / 0x4000)); + STAT_LOG(f, float(m_stats_counters[counters::disk_read_time] * 100.f + / (std::max)(m_stats_counters[counters::disk_job_time], boost::int64_t(1)))); + STAT_LOG(f, float(m_stats_counters[counters::disk_write_time] * 100.f + / (std::max)(m_stats_counters[counters::disk_job_time], boost::int64_t(1)))); + STAT_LOG(f, float(m_stats_counters[counters::disk_hash_time] * 100.f + / (std::max)(m_stats_counters[counters::disk_job_time], boost::int64_t(1)))); STAT_LOG(d, int(cs.total_read_back - m_last_cache_status.total_read_back)); - STAT_LOG(f, float(cs.total_read_back * 100.f / (cs.blocks_written == 0 ? 1: cs.blocks_written))); - STAT_LOGL(d, cs.read_queue_size); + STAT_LOG(f, float(cs.total_read_back * 100.f / (std::max)(1, int(cs.blocks_written)))); + STAT_COUNTER(num_read_jobs); STAT_LOG(f, float(tick_interval_ms) / 1000.f); STAT_LOG(f, float(m_tick_residual) / 1000.f); STAT_LOGL(d, m_allowed_upload_slots); - STAT_LOG(d, m_settings.unchoke_slots_limit * 2); STAT_LOG(d, m_stat.low_pass_upload_rate()); STAT_LOG(d, m_stat.low_pass_download_rate()); - STAT_LOGL(d, num_end_game_peers); + STAT_COUNTER(num_peers_end_game); STAT_LOGL(d, tcp_up_rate); STAT_LOGL(d, tcp_down_rate); - STAT_LOG(d, int(m_tcp_upload_channel.throttle())); - STAT_LOG(d, int(m_tcp_download_channel.throttle())); + STAT_LOG(d, int(rate_limit(m_tcp_peer_class, peer_connection::upload_channel))); + STAT_LOG(d, int(rate_limit(m_tcp_peer_class, peer_connection::download_channel))); STAT_LOGL(d, utp_up_rate); STAT_LOGL(d, utp_down_rate); STAT_LOG(f, float(utp_peak_send_delay) / 1000000.f); @@ -3943,24 +4232,34 @@ retry: STAT_LOG(d, int(vm_stat.pageouts - m_last_vm_stat.pageouts)); STAT_LOG(d, int(vm_stat.faults - m_last_vm_stat.faults)); - STAT_LOG(d, m_read_ops.mean()); - STAT_LOG(d, m_write_ops.mean()); + STAT_LOG(f, m_read_ops.mean() / 1000.f); + STAT_LOG(f, m_write_ops.mean() / 1000.f); + STAT_COUNTER(pinned_blocks); + + STAT_LOGL(d, partial_pieces); + STAT_LOGL(d, partial_downloading_pieces); + STAT_LOGL(d, partial_full_pieces); + STAT_LOGL(d, partial_finished_pieces); + STAT_LOGL(d, partial_zero_prio_pieces); + + STAT_COUNTER(num_jobs); + STAT_COUNTER(num_read_jobs); + STAT_COUNTER(num_write_jobs); STAT_LOGL(d, reading_bytes); - for (int i = 0; i < max_messages; ++i) + for (int i = counters::on_read_counter; i <= counters::on_disk_counter; ++i) { - STAT_LOGL(d, m_num_messages[i]); + STAT_LOG(d, int(m_stats_counters[i])); } - int num_max = sizeof(m_send_buffer_sizes)/sizeof(m_send_buffer_sizes[0]); - for (int i = 0; i < num_max; ++i) + + for (int i = counters::socket_send_size3; i <= counters::socket_send_size20; ++i) { - STAT_LOGL(d, m_send_buffer_sizes[i]); + STAT_LOG(d, int(m_stats_counters[i])); } - num_max = sizeof(m_recv_buffer_sizes)/sizeof(m_recv_buffer_sizes[0]); - for (int i = 0; i < num_max; ++i) + for (int i = counters::socket_recv_size3; i <= counters::socket_recv_size20; ++i) { - STAT_LOGL(d, m_recv_buffer_sizes[i]); + STAT_LOG(d, int(m_stats_counters[i])); } STAT_LOG(f, total_microseconds(cur_cpu_usage.user_time @@ -3973,12 +4272,21 @@ retry: for (int i = 0; i < torrent::waste_reason_max; ++i) { - STAT_LOG(f, (m_redundant_bytes[i] * 100.) / double(m_total_redundant_bytes == 0 ? 1 : m_total_redundant_bytes)); + STAT_LOG(f, (m_redundant_bytes[i] * 100.) + / double(m_stats_counters[counters::recv_redundant_bytes] == 0 ? 1 + : m_stats_counters[counters::recv_redundant_bytes])); } - STAT_LOGL(d, m_no_memory_peers); - STAT_LOGL(d, m_too_many_peers); - STAT_LOGL(d, m_transport_timeout_peers); + STAT_COUNTER(no_memory_peers); + STAT_COUNTER(too_many_peers); + STAT_COUNTER(transport_timeout_peers); + + STAT_LOGL(d, cs.arc_write_size); + STAT_LOGL(d, cs.arc_volatile_size); + STAT_LOG(d, cs.arc_volatile_size + cs.arc_mru_size); + STAT_LOG(d, cs.arc_volatile_size + cs.arc_mru_size + cs.arc_mru_ghost_size); + STAT_LOG(d, -cs.arc_mfu_size); + STAT_LOG(d, -cs.arc_mfu_size - cs.arc_mfu_ghost_size); STAT_LOGL(d, sst.utp_stats.num_idle); STAT_LOGL(d, sst.utp_stats.num_syn_sent); @@ -3986,55 +4294,131 @@ retry: STAT_LOGL(d, sst.utp_stats.num_fin_sent); STAT_LOGL(d, sst.utp_stats.num_close_wait); - STAT_LOGL(d, num_tcp_peers); - STAT_LOGL(d, num_utp_peers); + STAT_COUNTER(num_tcp_peers); + STAT_COUNTER(num_utp_peers); - STAT_LOGL(d, m_connrefused_peers); - STAT_LOGL(d, m_connaborted_peers); - STAT_LOGL(d, m_perm_peers); - STAT_LOGL(d, m_buffer_peers); - STAT_LOGL(d, m_unreachable_peers); - STAT_LOGL(d, m_broken_pipe_peers); - STAT_LOGL(d, m_addrinuse_peers); - STAT_LOGL(d, m_no_access_peers); - STAT_LOGL(d, m_invalid_arg_peers); - STAT_LOGL(d, m_aborted_peers); + STAT_COUNTER(connrefused_peers); + STAT_COUNTER(connaborted_peers); + STAT_COUNTER(perm_peers); + STAT_COUNTER(buffer_peers); + STAT_COUNTER(unreachable_peers); + STAT_COUNTER(broken_pipe_peers); + STAT_COUNTER(addrinuse_peers); + STAT_COUNTER(no_access_peers); + STAT_COUNTER(invalid_arg_peers); + STAT_COUNTER(aborted_peers); - STAT_LOGL(d, m_error_incoming_peers); - STAT_LOGL(d, m_error_outgoing_peers); - STAT_LOGL(d, m_error_rc4_peers); - STAT_LOGL(d, m_error_encrypted_peers); - STAT_LOGL(d, m_error_tcp_peers); - STAT_LOGL(d, m_error_utp_peers); + STAT_COUNTER(error_incoming_peers); + STAT_COUNTER(error_outgoing_peers); + STAT_COUNTER(error_rc4_peers); + STAT_COUNTER(error_encrypted_peers); + STAT_COUNTER(error_tcp_peers); + STAT_COUNTER(error_utp_peers); STAT_LOG(d, int(m_connections.size())); STAT_LOGL(d, pending_incoming_reqs); - STAT_LOG(f, num_complete_connections == 0 ? 0.f : (float(pending_incoming_reqs) / num_complete_connections)); + STAT_LOG(f, m_stats_counters[counters::num_peers_connected] == 0 ? 0.f : (float(pending_incoming_reqs) / m_stats_counters[counters::num_peers_connected])); STAT_LOGL(d, num_want_more_peers); STAT_LOG(f, total_peers_limit == 0 ? 0 : float(num_limited_peers) / total_peers_limit); - STAT_LOGL(d, m_piece_requests); - STAT_LOGL(d, m_max_piece_requests); - STAT_LOGL(d, m_invalid_piece_requests); - STAT_LOGL(d, m_choked_piece_requests); - STAT_LOGL(d, m_cancelled_piece_requests); - STAT_LOGL(d, m_piece_rejects); + STAT_COUNTER(piece_requests); + STAT_COUNTER(max_piece_requests); + STAT_COUNTER(invalid_piece_requests); + STAT_COUNTER(choked_piece_requests); + STAT_COUNTER(cancelled_piece_requests); + STAT_COUNTER(piece_rejects); + + STAT_COUNTER(num_total_pieces_added); + STAT_COUNTER(num_have_pieces); + STAT_COUNTER(num_piece_passed); + STAT_COUNTER(num_piece_failed); STAT_LOGL(d, peers_up_send_buffer); - STAT_LOG(d, int(sst.utp_stats.packet_loss)); - STAT_LOG(d, int(sst.utp_stats.timeout)); - STAT_LOG(d, int(sst.utp_stats.packets_in)); - STAT_LOG(d, int(sst.utp_stats.packets_out)); - STAT_LOG(d, int(sst.utp_stats.fast_retransmit)); - STAT_LOG(d, int(sst.utp_stats.packet_resend)); - STAT_LOG(d, int(sst.utp_stats.samples_above_target)); - STAT_LOG(d, int(sst.utp_stats.samples_below_target)); - STAT_LOG(d, int(sst.utp_stats.payload_pkts_in)); - STAT_LOG(d, int(sst.utp_stats.payload_pkts_out)); - STAT_LOG(d, int(sst.utp_stats.invalid_pkts_in)); - STAT_LOG(d, int(sst.utp_stats.redundant_pkts_in)); + STAT_COUNTER(utp_packet_loss); + STAT_COUNTER(utp_timeout); + STAT_COUNTER(utp_packets_in); + STAT_COUNTER(utp_packets_out); + STAT_COUNTER(utp_fast_retransmit); + STAT_COUNTER(utp_packet_resend); + STAT_COUNTER(utp_samples_above_target); + STAT_COUNTER(utp_samples_below_target); + STAT_COUNTER(utp_payload_pkts_in); + STAT_COUNTER(utp_payload_pkts_out); + STAT_COUNTER(utp_invalid_pkts_in); + STAT_COUNTER(utp_redundant_pkts_in); + + // loaded torrents + STAT_COUNTER(num_loaded_torrents); + STAT_COUNTER(num_pinned_torrents); + STAT_COUNTER(torrent_evicted_counter); + + STAT_COUNTER(num_incoming_choke); + STAT_COUNTER(num_incoming_unchoke); + STAT_COUNTER(num_incoming_interested); + STAT_COUNTER(num_incoming_not_interested); + STAT_COUNTER(num_incoming_have); + STAT_COUNTER(num_incoming_bitfield); + STAT_COUNTER(num_incoming_request); + STAT_COUNTER(num_incoming_piece); + STAT_COUNTER(num_incoming_cancel); + STAT_COUNTER(num_incoming_dht_port); + STAT_COUNTER(num_incoming_suggest); + STAT_COUNTER(num_incoming_have_all); + STAT_COUNTER(num_incoming_have_none); + STAT_COUNTER(num_incoming_reject); + STAT_COUNTER(num_incoming_allowed_fast); + STAT_COUNTER(num_incoming_ext_handshake); + STAT_COUNTER(num_incoming_pex); + STAT_COUNTER(num_incoming_metadata); + STAT_COUNTER(num_incoming_extended); + + STAT_COUNTER(num_outgoing_choke); + STAT_COUNTER(num_outgoing_unchoke); + STAT_COUNTER(num_outgoing_interested); + STAT_COUNTER(num_outgoing_not_interested); + STAT_COUNTER(num_outgoing_have); + STAT_COUNTER(num_outgoing_bitfield); + STAT_COUNTER(num_outgoing_request); + STAT_COUNTER(num_outgoing_piece); + STAT_COUNTER(num_outgoing_cancel); + STAT_COUNTER(num_outgoing_dht_port); + STAT_COUNTER(num_outgoing_suggest); + STAT_COUNTER(num_outgoing_have_all); + STAT_COUNTER(num_outgoing_have_none); + STAT_COUNTER(num_outgoing_reject); + STAT_COUNTER(num_outgoing_allowed_fast); + STAT_COUNTER(num_outgoing_ext_handshake); + STAT_COUNTER(num_outgoing_pex); + STAT_COUNTER(num_outgoing_metadata); + STAT_COUNTER(num_outgoing_extended); + + STAT_LOG(d, cs.blocked_jobs); + STAT_COUNTER(num_writing_threads); + STAT_COUNTER(num_running_threads); + STAT_COUNTER(incoming_connections); + + STAT_LOG(d, cs.num_fence_jobs[disk_io_job::move_storage]); + STAT_LOG(d, cs.num_fence_jobs[disk_io_job::release_files]); + STAT_LOG(d, cs.num_fence_jobs[disk_io_job::delete_files]); + STAT_LOG(d, cs.num_fence_jobs[disk_io_job::check_fastresume]); + STAT_LOG(d, cs.num_fence_jobs[disk_io_job::save_resume_data]); + STAT_LOG(d, cs.num_fence_jobs[disk_io_job::rename_file]); + STAT_LOG(d, cs.num_fence_jobs[disk_io_job::stop_torrent]); + STAT_LOG(d, cs.num_fence_jobs[disk_io_job::file_priority]); + STAT_LOG(d, cs.num_fence_jobs[disk_io_job::clear_piece]); + + STAT_COUNTER(piece_picker_partial_loops); + STAT_COUNTER(piece_picker_suggest_loops); + STAT_COUNTER(piece_picker_sequential_loops); + STAT_COUNTER(piece_picker_reverse_rare_loops); + STAT_COUNTER(piece_picker_rare_loops); + STAT_COUNTER(piece_picker_rand_start_loops); + STAT_COUNTER(piece_picker_rand_loops); + STAT_COUNTER(piece_picker_busy_loops); + + STAT_COUNTER(connection_attempt_loops); fprintf(m_stats_logger, "\n"); @@ -4042,15 +4426,13 @@ retry: #undef STAT_LOGL m_last_cache_status = cs; - m_last_vm_stat = vm_stat; + if (!vm_ec) m_last_vm_stat = vm_stat; m_network_thread_cpu_usage = cur_cpu_usage; - m_last_failed = m_total_failed_bytes; - m_last_redundant = m_total_redundant_bytes; + m_last_failed = m_stats_counters[counters::recv_failed_bytes]; + m_last_redundant = m_stats_counters[counters::recv_redundant_bytes]; m_last_uploaded = m_stat.total_upload(); m_last_downloaded = m_stat.total_download(); } - - reset_stat_counters(); } #endif // TORRENT_STATS @@ -4082,15 +4464,28 @@ retry: void session_impl::add_dht_node(udp::endpoint n) { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); if (m_dht) m_dht->add_node(n); } + bool session_impl::has_dht() const + { + return m_dht.get(); + } + void session_impl::prioritize_dht(boost::weak_ptr t) { + TORRENT_ASSERT(!m_abort); + if (m_abort) return; + TORRENT_ASSERT(m_dht); m_dht_torrents.push_back(t); +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + boost::shared_ptr tor = t.lock(); + if (tor) + session_log("prioritizing DHT announce: \"%s\"", tor->name().c_str()); +#endif // trigger a DHT announce right away if we just // added a new torrent and there's no back-log if (m_dht_torrents.size() == 1) @@ -4110,18 +4505,34 @@ retry: #if defined TORRENT_ASIO_DEBUGGING complete_async("session_impl::on_dht_announce"); #endif - TORRENT_ASSERT(is_network_thread()); - if (e) return; + TORRENT_ASSERT(is_single_thread()); + if (e) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + session_log("aborting DHT announce timer (%d): %s" + , e.value(), e.message().c_str()); +#endif + return; + } - if (m_abort) return; + if (m_abort) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + session_log("aborting DHT announce timer: m_abort set"); +#endif + return; + } + + if (!m_dht) + { + m_dht_torrents.clear(); + return; + } TORRENT_ASSERT(m_dht); -#if defined TORRENT_ASIO_DEBUGGING - add_outstanding_async("session_impl::on_dht_announce"); -#endif // announce to DHT every 15 minutes - int delay = (std::max)(m_settings.dht_announce_interval + int delay = (std::max)(m_settings.get_int(settings_pack::dht_announce_interval) / (std::max)(int(m_torrents.size()), 1), 1); if (!m_dht_torrents.empty()) @@ -4132,6 +4543,9 @@ retry: delay = (std::min)(4, delay); } +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("session_impl::on_dht_announce"); +#endif error_code ec; m_dht_announce_timer.expires_from_now(seconds(delay), ec); m_dht_announce_timer.async_wait( @@ -4144,8 +4558,8 @@ retry: { t = m_dht_torrents.front().lock(); m_dht_torrents.pop_front(); - } - while (!t && !m_dht_torrents.empty()); + } while (!t && !m_dht_torrents.empty()); + if (t) { t->dht_announce(); @@ -4157,6 +4571,8 @@ retry: if (m_next_dht_torrent == m_torrents.end()) m_next_dht_torrent = m_torrents.begin(); m_next_dht_torrent->second->dht_announce(); + // TODO: 2 make a list for torrents that want to be announced on the DHT so we + // don't have to loop over all torrents, just to find the ones that want to announce ++m_next_dht_torrent; if (m_next_dht_torrent == m_torrents.end()) m_next_dht_torrent = m_torrents.begin(); @@ -4168,10 +4584,8 @@ retry: #if defined TORRENT_ASIO_DEBUGGING complete_async("session_impl::on_lsd_announce"); #endif -#ifdef TORRENT_STATS - ++m_num_messages[on_lsd_counter]; -#endif - TORRENT_ASSERT(is_network_thread()); + inc_stats_counter(counters::on_lsd_counter); + TORRENT_ASSERT(is_single_thread()); if (e) return; if (m_abort) return; @@ -4180,7 +4594,7 @@ retry: add_outstanding_async("session_impl::on_lsd_announce"); #endif // announce on local network every 5 minutes - int delay = (std::max)(m_settings.local_service_announce_interval + int delay = (std::max)(m_settings.get_int(settings_pack::local_service_announce_interval) / (std::max)(int(m_torrents.size()), 1), 1); error_code ec; m_lsd_announce_timer.expires_from_now(seconds(delay), ec); @@ -4198,17 +4612,25 @@ retry: } void session_impl::auto_manage_torrents(std::vector& list - , int& dht_limit, int& tracker_limit, int& lsd_limit - , int& hard_limit, int type_limit) + , int& checking_limit, int& dht_limit, int& tracker_limit + , int& lsd_limit, int& hard_limit, int type_limit) { for (std::vector::iterator i = list.begin() , end(list.end()); i != end; ++i) { torrent* t = *i; - if ((t->state() == torrent_status::checking_files - || t->state() == torrent_status::queued_for_checking)) + if (t->state() == torrent_status::checking_files) + { + if (checking_limit <= 0) t->pause(); + else + { + t->resume(); + t->start_checking(); + --checking_limit; + } continue; + } --dht_limit; --lsd_limit; @@ -4230,14 +4652,16 @@ retry: --hard_limit; --type_limit; #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING - t->log_to_all_peers("AUTO MANAGER STARTING TORRENT"); + if (!t->allows_peers()) + t->log_to_all_peers("AUTO MANAGER STARTING TORRENT"); #endif t->set_allow_peers(true); } else { #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING - t->log_to_all_peers("AUTO MANAGER PAUSING TORRENT"); + if (t->allows_peers()) + t->log_to_all_peers("AUTO MANAGER PAUSING TORRENT"); #endif // use graceful pause for auto-managed torrents t->set_allow_peers(false, true); @@ -4251,7 +4675,20 @@ retry: m_need_auto_manage = false; + if (is_paused()) return; + // these vectors are filled with auto managed torrents + + // TODO: these vectors could be copied from m_torrent_lists, + // if we would maintain them. That way the first pass over + // all torrents could be avoided. It would be especially + // efficient if most torrents are not auto-managed + // whenever we receive a scrape response (or anything + // that may change the rank of a torrent) that one torrent + // could re-sort itself in a list that's kept sorted at all + // times. That way, this pass over all torrents could be + // avoided alltogether. + std::vector checking; std::vector downloaders; downloaders.reserve(m_torrents.size()); std::vector seeds; @@ -4259,12 +4696,13 @@ retry: // these counters are set to the number of torrents // of each kind we're allowed to have active - int num_downloaders = settings().active_downloads; - int num_seeds = settings().active_seeds; - int dht_limit = settings().active_dht_limit; - int tracker_limit = settings().active_tracker_limit; - int lsd_limit = settings().active_lsd_limit; - int hard_limit = settings().active_limit; + int num_downloaders = settings().get_int(settings_pack::active_downloads); + int num_seeds = settings().get_int(settings_pack::active_seeds); + int checking_limit = 1; + int dht_limit = settings().get_int(settings_pack::active_dht_limit); + int tracker_limit = settings().get_int(settings_pack::active_tracker_limit); + int lsd_limit = settings().get_int(settings_pack::active_lsd_limit); + int hard_limit = settings().get_int(settings_pack::active_limit); if (num_downloaders == -1) num_downloaders = (std::numeric_limits::max)(); @@ -4285,15 +4723,14 @@ retry: torrent* t = i->second.get(); TORRENT_ASSERT(t); - // checking torrents are not subject to auto-management - if (t->state() == torrent_status::checking_files - || t->state() == torrent_status::queued_for_checking) - { - if (t->is_auto_managed() && t->is_paused()) t->resume(); - continue; - } if (t->is_auto_managed() && !t->has_error()) { + if (t->state() == torrent_status::checking_files) + { + checking.push_back(t); + continue; + } + TORRENT_ASSERT(t->m_resume_data_loaded || !t->valid_metadata()); // this torrent is auto managed, add it to // the list (depending on if it's a seed or not) @@ -4304,6 +4741,11 @@ retry: } else if (!t->is_paused()) { + if (t->state() == torrent_status::checking_files) + { + if (checking_limit > 0) --checking_limit; + continue; + } TORRENT_ASSERT(t->m_resume_data_loaded || !t->valid_metadata()); --hard_limit; } @@ -4317,6 +4759,9 @@ retry: if (!handled_by_extension) { + std::sort(checking.begin(), checking.end() + , boost::bind(&torrent::sequence_number, _1) < boost::bind(&torrent::sequence_number, _2)); + std::sort(downloaders.begin(), downloaders.end() , boost::bind(&torrent::sequence_number, _1) < boost::bind(&torrent::sequence_number, _2)); @@ -4325,35 +4770,38 @@ retry: > boost::bind(&torrent::seed_rank, _2, boost::ref(m_settings))); } - if (settings().auto_manage_prefer_seeds) + auto_manage_torrents(checking, checking_limit, dht_limit, tracker_limit, lsd_limit + , hard_limit, num_downloaders); + + if (settings().get_bool(settings_pack::auto_manage_prefer_seeds)) { - auto_manage_torrents(seeds, dht_limit, tracker_limit, lsd_limit + auto_manage_torrents(seeds, checking_limit, dht_limit, tracker_limit, lsd_limit , hard_limit, num_seeds); - auto_manage_torrents(downloaders, dht_limit, tracker_limit, lsd_limit + auto_manage_torrents(downloaders, checking_limit, dht_limit, tracker_limit, lsd_limit , hard_limit, num_downloaders); } else { - auto_manage_torrents(downloaders, dht_limit, tracker_limit, lsd_limit + auto_manage_torrents(downloaders, checking_limit, dht_limit, tracker_limit, lsd_limit , hard_limit, num_downloaders); - auto_manage_torrents(seeds, dht_limit, tracker_limit, lsd_limit + auto_manage_torrents(seeds, checking_limit, dht_limit, tracker_limit, lsd_limit , hard_limit, num_seeds); } } void session_impl::recalculate_optimistic_unchoke_slots() { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); if (m_allowed_upload_slots == 0) return; - std::vector opt_unchoke; + std::vector opt_unchoke; for (connection_map::iterator i = m_connections.begin() , end(m_connections.end()); i != end; ++i) { peer_connection* p = i->get(); TORRENT_ASSERT(p); - policy::peer* pi = p->peer_info_struct(); + torrent_peer* pi = p->peer_info_struct(); if (!pi) continue; if (pi->web_seed) continue; torrent* t = p->associated_torrent().lock().get(); @@ -4387,8 +4835,8 @@ retry: // sort all candidates based on when they were last optimistically // unchoked. std::sort(opt_unchoke.begin(), opt_unchoke.end() - , boost::bind(&policy::peer::last_optimistically_unchoked, _1) - < boost::bind(&policy::peer::last_optimistically_unchoked, _2)); + , boost::bind(&torrent_peer::last_optimistically_unchoked, _1) + < boost::bind(&torrent_peer::last_optimistically_unchoked, _2)); #ifndef TORRENT_DISABLE_EXTENSIONS for (ses_extension_list_t::iterator i = m_ses_extensions.begin() @@ -4399,22 +4847,23 @@ retry: } #endif - int num_opt_unchoke = m_settings.num_optimistic_unchoke_slots; + int num_opt_unchoke = m_settings.get_int(settings_pack::num_optimistic_unchoke_slots); if (num_opt_unchoke == 0) num_opt_unchoke = (std::max)(1, m_allowed_upload_slots / 5); // unchoke the first num_opt_unchoke peers in the candidate set // and make sure that the others are choked - for (std::vector::iterator i = opt_unchoke.begin() + for (std::vector::iterator i = opt_unchoke.begin() , end(opt_unchoke.end()); i != end; ++i) { - policy::peer* pi = *i; + torrent_peer* pi = *i; if (num_opt_unchoke > 0) { --num_opt_unchoke; if (!pi->optimistically_unchoked) { - torrent* t = pi->connection->associated_torrent().lock().get(); - bool ret = t->unchoke_peer(*pi->connection, true); + peer_connection* p = static_cast(pi->connection); + torrent* t = p->associated_torrent().lock().get(); + bool ret = t->unchoke_peer(*p, true); if (ret) { pi->optimistically_unchoked = true; @@ -4432,19 +4881,29 @@ retry: { if (pi->optimistically_unchoked) { - torrent* t = pi->connection->associated_torrent().lock().get(); + peer_connection* p = static_cast(pi->connection); + torrent* t = p->associated_torrent().lock().get(); pi->optimistically_unchoked = false; - t->choke_peer(*pi->connection); + t->choke_peer(*p); --m_num_unchoked; } } } } - void session_impl::try_connect_more_peers(int num_downloads, int num_downloads_peers) + void session_impl::try_connect_more_peers() { - // let torrents connect to peers if they want to - // if there are any torrents and any free slots + if (m_abort) return; + + if (num_connections() >= m_settings.get_int(settings_pack::connections_limit)) + return; + + // this is the maximum number of connections we will + // attempt this tick + int max_connections = m_settings.get_int(settings_pack::connection_speed); + + // zero connections speeds are allowed, we just won't make any connections + if (max_connections <= 0) return; // this loop will "hand out" max(connection_speed // , half_open.free_slots()) to the torrents, in a @@ -4452,7 +4911,10 @@ retry: // equally likely to connect to a peer int free_slots = m_half_open.free_slots(); - int max_connections = m_settings.connection_speed; + + // if we don't have any free slots, return + if (free_slots <= -m_half_open.limit()) return; + // boost connections are connections made by torrent connection // boost, which are done immediately on a tracker response. These // connections needs to be deducted from this second @@ -4470,106 +4932,116 @@ retry: } } - // this logic is here to smooth out the number of new connection - // attempts over time, to prevent connecting a large number of - // sockets, wait 10 seconds, and then try again - int limit = (std::min)(m_settings.connections_limit - num_connections(), free_slots); - if (m_settings.smooth_connects && max_connections > (limit+1) / 2) - max_connections = (limit+1) / 2; - // TODO: use a lower limit than m_settings.connections_limit // to allocate the to 10% or so of connection slots for incoming // connections - if (!m_torrents.empty() - && free_slots > -m_half_open.limit() - && num_connections() < m_settings.connections_limit - && !m_abort - && m_settings.connection_speed > 0 - && max_connections > 0) + int limit = (std::min)(m_settings.get_int(settings_pack::connections_limit) + - num_connections(), free_slots); + + // this logic is here to smooth out the number of new connection + // attempts over time, to prevent connecting a large number of + // sockets, wait 10 seconds, and then try again + if (m_settings.get_bool(settings_pack::smooth_connects) && max_connections > (limit+1) / 2) + max_connections = (limit+1) / 2; + + std::vector& want_peers_download = m_torrent_lists[torrent_want_peers_download]; + std::vector& want_peers_finished = m_torrent_lists[torrent_want_peers_finished]; + + // if no torrent want any peers, just return + if (want_peers_download.empty() && want_peers_finished.empty()) return; + + // if we don't have any connection attempt quota, return + if (max_connections <= 0) return; + + INVARIANT_CHECK; + + int steps_since_last_connect = 0; + int num_torrents = int(want_peers_finished.size() + want_peers_download.size()); + for (;;) { - // this is the maximum number of connections we will - // attempt this tick -// int average_peers = 0; -// if (num_downloads > 0) -// average_peers = num_downloads_peers / num_downloads; + if (m_next_downloading_connect_torrent >= int(want_peers_download.size())) + m_next_downloading_connect_torrent = 0; - if (m_next_connect_torrent == m_torrents.end()) - m_next_connect_torrent = m_torrents.begin(); + if (m_next_finished_connect_torrent >= int(want_peers_finished.size())) + m_next_finished_connect_torrent = 0; - int steps_since_last_connect = 0; - int num_torrents = int(m_torrents.size()); - for (;;) + torrent* t = NULL; + // there are prioritized torrents. Pick one of those + while (!m_prio_torrents.empty()) { - torrent& t = *m_next_connect_torrent->second; - if (t.want_more_peers()) - { - TORRENT_ASSERT(t.allows_peers()); - // have a bias to give more connection attempts - // to downloading torrents than seed, and even - // more to downloading torrents with less than - // average number of connections - int num_attempts = 1; - if (!t.is_finished()) - { - // TODO: make this bias configurable - // TODO: also take average_peers into account, to create a bias for downloading torrents with < average peers - TORRENT_ASSERT(m_num_active_downloading > 0); - num_attempts += m_num_active_finished / m_num_active_downloading; - } - while (m_current_connect_attempts < num_attempts) - { - TORRENT_TRY - { - ++m_current_connect_attempts; - if (t.try_connect_peer()) - { - --max_connections; - --free_slots; - steps_since_last_connect = 0; -#ifdef TORRENT_STATS - ++m_connection_attempts; -#endif - } - } - TORRENT_CATCH(std::bad_alloc&) - { - // we ran out of memory trying to connect to a peer - // lower the global limit to the number of peers - // we already have - m_settings.connections_limit = num_connections(); - if (m_settings.connections_limit < 2) m_settings.connections_limit = 2; - } - if (!t.want_more_peers()) break; - if (free_slots <= -m_half_open.limit()) return; - if (max_connections == 0) return; - if (num_connections() >= m_settings.connections_limit) return; - } - } - - ++m_next_connect_torrent; - m_current_connect_attempts = 0; - ++steps_since_last_connect; - if (m_next_connect_torrent == m_torrents.end()) - m_next_connect_torrent = m_torrents.begin(); - - // if we have gone a whole loop without - // handing out a single connection, break - if (steps_since_last_connect > num_torrents + 1) break; - // if there are no more free connection slots, abort - if (free_slots <= -m_half_open.limit()) break; - // if we should not make any more connections - // attempts this tick, abort - if (max_connections == 0) break; - // maintain the global limit on number of connections - if (num_connections() >= m_settings.connections_limit) break; + t = m_prio_torrents.front().first.lock().get(); + --m_prio_torrents.front().second; + if (m_prio_torrents.front().second > 0 + && t != NULL + && t->want_peers()) break; + m_prio_torrents.pop_front(); + t = NULL; } + + if (t == NULL) + { + if ((m_download_connect_attempts >= m_settings.get_int( + settings_pack::connect_seed_every_n_download) + && want_peers_finished.size()) + || want_peers_download.empty()) + { + // pick a finished torrent to give a peer to + t = want_peers_finished[m_next_finished_connect_torrent]; + TORRENT_ASSERT(t->want_peers_finished()); + m_download_connect_attempts = 0; + ++m_next_finished_connect_torrent; + } + else + { + // pick a downloading torrent to give a peer to + t = want_peers_download[m_next_downloading_connect_torrent]; + TORRENT_ASSERT(t->want_peers_download()); + ++m_download_connect_attempts; + ++m_next_downloading_connect_torrent; + } + } + + TORRENT_ASSERT(t->want_peers()); + TORRENT_ASSERT(t->allows_peers()); + + TORRENT_TRY + { + if (t->try_connect_peer()) + { + --max_connections; + --free_slots; + steps_since_last_connect = 0; + inc_stats_counter(counters::connection_attempts); + } + } + TORRENT_CATCH(std::bad_alloc&) + { + // we ran out of memory trying to connect to a peer + // lower the global limit to the number of peers + // we already have + m_settings.set_int(settings_pack::connections_limit, num_connections()); + if (m_settings.get_int(settings_pack::connections_limit) < 2) + m_settings.set_int(settings_pack::connections_limit, 2); + } + + ++steps_since_last_connect; + + // if there are no more free connection slots, abort + if (free_slots <= -m_half_open.limit()) break; + if (max_connections == 0) return; + // there are no more torrents that want peers + if (want_peers_download.empty() && want_peers_finished.empty()) break; + // if we have gone a whole loop without + // handing out a single connection, break + if (steps_since_last_connect > num_torrents + 1) break; + // maintain the global limit on number of connections + if (num_connections() >= m_settings.get_int(settings_pack::connections_limit)) break; } } - void session_impl::recalculate_unchoke_slots(int congested_torrents - , int uncongested_torrents) + void session_impl::recalculate_unchoke_slots() { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); INVARIANT_CHECK; ptime now = time_now(); @@ -4582,16 +5054,16 @@ retry: for (connection_map::iterator i = m_connections.begin(); i != m_connections.end();) { - boost::intrusive_ptr p = *i; + boost::shared_ptr p = *i; TORRENT_ASSERT(p); ++i; torrent* t = p->associated_torrent().lock().get(); - policy::peer* pi = p->peer_info_struct(); + torrent_peer* pi = p->peer_info_struct(); if (p->ignore_unchoke_slots() || t == 0 || pi == 0 || pi->web_seed || t->is_paused()) continue; - if (m_settings.choking_algorithm == session_settings::bittyrant_choker) + if (m_settings.get_int(settings_pack::choking_algorithm) == settings_pack::bittyrant_choker) { if (!p->is_choked() && p->is_interesting()) { @@ -4631,7 +5103,7 @@ retry: peers.push_back(p.get()); } - if (m_settings.choking_algorithm == session_settings::rate_based_choker) + if (m_settings.get_int(settings_pack::choking_algorithm) == settings_pack::rate_based_choker) { m_allowed_upload_slots = 0; std::sort(peers.begin(), peers.end() @@ -4677,7 +5149,7 @@ retry: ++m_allowed_upload_slots; } - if (m_settings.choking_algorithm == session_settings::bittyrant_choker) + if (m_settings.get_int(settings_pack::choking_algorithm) == settings_pack::bittyrant_choker) { // if we're using the bittyrant choker, sort peers by their return // on investment. i.e. download rate / upload rate @@ -4695,39 +5167,37 @@ retry: } // auto unchoke - int upload_limit = m_bandwidth_channel[peer_connection::upload_channel]->throttle(); - if (m_settings.choking_algorithm == session_settings::auto_expand_choker + peer_class* gpc = m_classes.at(m_global_class); + int upload_limit = gpc->channel[peer_connection::upload_channel].throttle(); + if (m_settings.get_int(settings_pack::choking_algorithm) == settings_pack::auto_expand_choker && upload_limit > 0) { // if our current upload rate is less than 90% of our - // limit AND most torrents are not "congested", i.e. - // they are not holding back because of a per-torrent // limit if (m_stat.upload_rate() < upload_limit * 0.9f && m_allowed_upload_slots <= m_num_unchoked + 1 - && congested_torrents < uncongested_torrents && m_upload_rate.queue_size() < 2) { ++m_allowed_upload_slots; } else if (m_upload_rate.queue_size() > 1 - && m_allowed_upload_slots > m_settings.unchoke_slots_limit - && m_settings.unchoke_slots_limit >= 0) + && m_allowed_upload_slots > m_settings.get_int(settings_pack::unchoke_slots_limit) + && m_settings.get_int(settings_pack::unchoke_slots_limit) >= 0) { --m_allowed_upload_slots; } } - int num_opt_unchoke = m_settings.num_optimistic_unchoke_slots; + int num_opt_unchoke = m_settings.get_int(settings_pack::num_optimistic_unchoke_slots); if (num_opt_unchoke == 0) num_opt_unchoke = (std::max)(1, m_allowed_upload_slots / 5); // reserve some upload slots for optimistic unchokes int unchoke_set_size = m_allowed_upload_slots - num_opt_unchoke; int upload_capacity_left = 0; - if (m_settings.choking_algorithm == session_settings::bittyrant_choker) + if (m_settings.get_int(settings_pack::choking_algorithm) == settings_pack::bittyrant_choker) { - upload_capacity_left = m_upload_channel.throttle(); + upload_capacity_left = upload_rate_limit(m_global_class); if (upload_capacity_left == 0) { // we don't know at what rate we can upload. If we have a @@ -4751,7 +5221,7 @@ retry: TORRENT_ASSERT(!p->ignore_unchoke_slots()); // this will update the m_uploaded_at_last_unchoke - // #error this should be called for all peers! + // TODO: this should be called for all peers! p->reset_choke_counters(); torrent* t = p->associated_torrent().lock().get(); @@ -4760,7 +5230,7 @@ retry: // if this peer should be unchoked depends on different things // in different unchoked schemes bool unchoke = false; - if (m_settings.choking_algorithm == session_settings::bittyrant_choker) + if (m_settings.get_int(settings_pack::choking_algorithm) == settings_pack::bittyrant_choker) { unchoke = p->est_reciprocation_rate() <= upload_capacity_left; } @@ -4795,7 +5265,7 @@ retry: } else { - // no, this peer should be shoked + // no, this peer should be choked TORRENT_ASSERT(p->peer_info_struct()); if (!p->is_choked() && !p->peer_info_struct()->optimistically_unchoked) t->choke_peer(*p); @@ -4805,6 +5275,26 @@ retry: } } + void session_impl::cork_burst(peer_connection* p) + { + TORRENT_ASSERT(is_single_thread()); + if (p->is_corked()) return; + p->cork_socket(); + m_delayed_uncorks.push_back(p); + } + + void session_impl::do_delayed_uncork() + { + inc_stats_counter(counters::on_disk_counter); + TORRENT_ASSERT(is_single_thread()); + for (std::vector::iterator i = m_delayed_uncorks.begin() + , end(m_delayed_uncorks.end()); i != end; ++i) + { + (*i)->uncork_socket(); + } + m_delayed_uncorks.clear(); + } + #if defined _MSC_VER && defined TORRENT_DEBUG static void straight_to_debugger(unsigned int, _EXCEPTION_POINTERS*) { throw; } @@ -4818,10 +5308,11 @@ retry: // it hard to debug stuff ::_set_se_translator(straight_to_debugger); #endif -#if TORRENT_USE_ASSERTS && defined BOOST_HAS_PTHREADS - m_network_thread = pthread_self(); -#endif - TORRENT_ASSERT(is_network_thread()); + // this is a debug facility + // see single_threaded in debug.hpp + thread_started(); + + TORRENT_ASSERT(is_single_thread()); // initialize async operations init(); @@ -4860,6 +5351,20 @@ retry: #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) session_log(" cleaning up torrents"); #endif + + // clear the torrent LRU (probably not strictly necessary) + list_node* i = m_torrent_lru.get_all(); +#if TORRENT_USE_ASSERTS + // clear the prev and next pointers in all torrents + // to avoid the assert when destructing them + while (i) + { + list_node* tmp = i; + i = i->next; + tmp->next = NULL; + tmp->prev= NULL; + } +#endif m_torrents.clear(); TORRENT_ASSERT(m_torrents.empty()); @@ -4870,15 +5375,34 @@ retry: #endif } + boost::shared_ptr session_impl::delay_load_torrent(sha1_hash const& info_hash + , peer_connection* pc) + { +#ifndef TORRENT_DISABLE_EXTENSIONS + for (ses_extension_list_t::iterator i = m_ses_extensions.begin() + , end(m_ses_extensions.end()); i != end; ++i) + { + add_torrent_params p; + if ((*i)->on_unknown_torrent(info_hash, pc, p)) + { + error_code ec; + torrent_handle handle = add_torrent(p, ec); + + return handle.native_handle(); + } + } +#endif + return boost::shared_ptr(); + } // the return value from this function is valid only as long as the // session is locked! boost::weak_ptr session_impl::find_torrent(sha1_hash const& info_hash) const { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); torrent_map::const_iterator i = m_torrents.find(info_hash); -#ifdef TORRENT_DEBUG +#if defined TORRENT_DEBUG && defined TORRENT_EXPENSIVE_INVARIANT_CHECKS for (torrent_map::const_iterator j = m_torrents.begin(); j != m_torrents.end(); ++j) { @@ -4890,9 +5414,110 @@ retry: return boost::weak_ptr(); } + void session_impl::insert_torrent(sha1_hash const& ih, boost::shared_ptr const& t + , std::string uuid) + { + m_torrents.insert(std::make_pair(ih, t)); + if (!uuid.empty()) m_uuids.insert(std::make_pair(uuid, t)); + + TORRENT_ASSERT(m_torrents.size() >= m_torrent_lru.size()); + } + + void session_impl::set_queue_position(torrent* me, int p) + { + if (p >= 0 && me->queue_position() == -1) + { + for (session_impl::torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + torrent* t = i->second.get(); + if (t->queue_position() >= p) + { + t->set_queue_position_impl(t->queue_position()+1); + t->state_updated(); + } + if (t->queue_position() >= p) t->set_queue_position_impl(t->queue_position()+1); + } + ++m_max_queue_pos; + me->set_queue_position_impl((std::min)(m_max_queue_pos, p)); + } + else if (p < 0) + { + TORRENT_ASSERT(me->queue_position() >= 0); + TORRENT_ASSERT(p == -1); + for (session_impl::torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + torrent* t = i->second.get(); + if (t == me) continue; + if (t->queue_position() == -1) continue; + if (t->queue_position() >= me->queue_position()) + { + t->set_queue_position_impl(t->queue_position()-1); + t->state_updated(); + } + } + --m_max_queue_pos; + me->set_queue_position_impl(p); + } + else if (p < me->queue_position()) + { + for (session_impl::torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + torrent* t = i->second.get(); + if (t == me) continue; + if (t->queue_position() == -1) continue; + if (t->queue_position() >= p + && t->queue_position() < me->queue_position()) + { + t->set_queue_position_impl(t->queue_position()+1); + t->state_updated(); + } + } + me->set_queue_position_impl(p); + } + else if (p > me->queue_position()) + { + for (session_impl::torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + torrent* t = i->second.get(); + int pos = t->queue_position(); + if (t == me) continue; + if (pos == -1) continue; + + if (pos <= p + && pos > me->queue_position() + && pos != -1) + { + t->set_queue_position_impl(t->queue_position()-1); + t->state_updated(); + } + + } + me->set_queue_position_impl((std::min)(m_max_queue_pos, p)); + } + + trigger_auto_manage(); + } + +#ifndef TORRENT_DISABLE_ENCRYPTION + torrent const* session_impl::find_encrypted_torrent(sha1_hash const& info_hash + , sha1_hash const& xor_mask) + { + sha1_hash obfuscated = info_hash; + obfuscated ^= xor_mask; + + torrent_map::iterator i = m_obfuscated_torrents.find(obfuscated); + if (i == m_obfuscated_torrents.end()) return NULL; + return i->second.get(); + } +#endif + boost::weak_ptr session_impl::find_torrent(std::string const& uuid) const { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); std::map >::const_iterator i = m_uuids.find(uuid); @@ -4941,9 +5566,14 @@ retry: { if (!m_logger) return; - va_list v; + va_list v; va_start(v, fmt); + session_vlog(fmt, v); + va_end(v); + } + void session_impl::session_vlog(char const* fmt, va_list& v) const + { char usr[400]; vsnprintf(usr, sizeof(usr), fmt, v); va_end(v); @@ -4951,6 +5581,17 @@ retry: snprintf(buf, sizeof(buf), "%s: %s\n", time_now_string(), usr); (*m_logger) << buf; } + +#if defined TORRENT_VERBOSE_LOGGING + void session_impl::log_all_torrents(peer_connection* p) + { + for (session_impl::torrent_map::const_iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + { + p->peer_log(" %s", to_hex(i->second->torrent_file().info_hash().to_string()).c_str()); + } + } +#endif #endif void session_impl::get_torrent_status(std::vector* ret @@ -4985,25 +5626,36 @@ retry: { INVARIANT_CHECK; - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); std::auto_ptr alert(new state_update_alert()); - alert->status.reserve(m_state_updates.size()); + std::vector& state_updates + = m_torrent_lists[aux::session_impl::torrent_state_updates]; + + alert->status.reserve(state_updates.size()); #if TORRENT_USE_ASSERTS m_posting_torrent_updates = true; #endif - for (std::vector >::iterator i = m_state_updates.begin() - , end(m_state_updates.end()); i != end; ++i) + // TODO: it might be a nice feature here to limit the number of torrents + // to send in a single update. By just posting the first n torrents, they + // would nicely be round-robined because the torrent lists are always + // pushed back + for (std::vector::iterator i = state_updates.begin() + , end(state_updates.end()); i != end; ++i) { - boost::shared_ptr t = i->lock(); - if (!t) continue; + torrent* t = *i; + TORRENT_ASSERT(t->m_links[aux::session_impl::torrent_state_updates].in_list()); alert->status.push_back(torrent_status()); - t->status(&alert->status.back(), 0xffffffff); + // querying accurate download counters may require + // the torrent to be loaded. Loading a torrent, and evicting another + // one will lead to calling state_updated(), which screws with + // this list while we're working on it, and break things + t->status(&alert->status.back(), ~torrent_handle::query_accurate_download_counters); t->clear_in_state_update(); } - m_state_updates.clear(); + state_updates.clear(); #if TORRENT_USE_ASSERTS m_posting_torrent_updates = false; @@ -5012,6 +5664,53 @@ retry: m_alerts.post_alert_ptr(alert.release()); } + void session_impl::post_session_stats() + { + std::auto_ptr alert(new session_stats_alert()); + std::vector& values = alert->values; + values.resize(counters::num_counters, 0); + + m_disk_thread.update_stats_counters(m_stats_counters); + + // TODO: 3 it would be really nice to update these counters + // as they are incremented. This depends on the session + // being ticked, which has a fairly coarse grained resolution + m_stats_counters.set_value(counters::sent_bytes + , m_stat.total_upload()); + m_stats_counters.set_value(counters::sent_payload_bytes + , m_stat.total_transfer(stat::upload_payload)); + m_stats_counters.set_value(counters::sent_ip_overhead_bytes + , m_stat.total_transfer(stat::upload_ip_protocol)); + m_stats_counters.set_value(counters::sent_tracker_bytes + , m_stat.total_transfer(stat::upload_tracker_protocol)); + + m_stats_counters.set_value(counters::recv_bytes + , m_stat.total_download()); + m_stats_counters.set_value(counters::recv_payload_bytes + , m_stat.total_transfer(stat::download_payload)); + m_stats_counters.set_value(counters::recv_ip_overhead_bytes + , m_stat.total_transfer(stat::download_ip_protocol)); + m_stats_counters.set_value(counters::recv_tracker_bytes + , m_stat.total_transfer(stat::download_tracker_protocol)); + + m_stats_counters.set_value(counters::limiter_up_queue + , m_upload_rate.queue_size()); + m_stats_counters.set_value(counters::limiter_down_queue + , m_download_rate.queue_size()); + + m_stats_counters.set_value(counters::limiter_up_bytes + , m_upload_rate.queued_bytes()); + m_stats_counters.set_value(counters::limiter_down_bytes + , m_download_rate.queued_bytes()); + + for (int i = 0; i < counters::num_counters; ++i) + values[i] = m_stats_counters[i]; + + alert->timestamp = total_microseconds(time_now_hires() - m_created); + + m_alerts.post_alert_ptr(alert.release()); + } + std::vector session_impl::get_torrents() const { std::vector ret; @@ -5033,11 +5732,51 @@ retry: void session_impl::async_add_torrent(add_torrent_params* params) { + if (string_begins_no_case("file://", params->url.c_str()) && !params->ti) + { + m_disk_thread.async_load_torrent(params + , boost::bind(&session_impl::on_async_load_torrent, this, _1)); + return; + } + error_code ec; torrent_handle handle = add_torrent(*params, ec); delete params; } + void session_impl::on_async_load_torrent(disk_io_job const* j) + { + add_torrent_params* params = (add_torrent_params*)j->requester; + error_code ec; + torrent_handle handle; + if (j->error.ec) + { + ec = j->error.ec; + m_alerts.post_alert(add_torrent_alert(handle, *params, ec)); + } + else + { + params->url.clear(); + params->ti = boost::shared_ptr((torrent_info*)j->buffer); + handle = add_torrent(*params, ec); + } + + delete params; + } + +#ifndef TORRENT_DISABLE_EXTENSIONS + void session_impl::add_extensions_to_torrent( + boost::shared_ptr const& torrent_ptr, void* userdata) + { + for (ses_extension_list_t::iterator i = m_ses_extensions.begin() + , end(m_ses_extensions.end()); i != end; ++i) + { + boost::shared_ptr tp((*i)->new_torrent(torrent_ptr.get(), userdata)); + if (tp) torrent_ptr->add_extension(tp); + } + } +#endif + torrent_handle session_impl::add_torrent(add_torrent_params const& p , error_code& ec) { @@ -5063,6 +5802,15 @@ retry: params.url.clear(); } + if (string_begins_no_case("file://", params.url.c_str()) && !params.ti) + { + std::string filename = resolve_file_url(params.url); + boost::shared_ptr t = boost::make_shared(filename, boost::ref(ec), 0); + if (ec) return torrent_handle(); + params.url.clear(); + params.ti = t; + } + if (params.ti && params.ti->is_valid() && params.ti->num_files() == 0) { ec = errors::no_files_in_torrent; @@ -5079,7 +5827,7 @@ retry: } #endif -// INVARIANT_CHECK; + INVARIANT_CHECK; if (is_aborted()) { @@ -5139,7 +5887,7 @@ retry: #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING session_log("info-hash matched"); #endif - params.ti = new torrent_info(resume_ih); + params.ti = boost::make_shared(resume_ih); if (params.ti->parse_info_section(*info, ec, 0)) { @@ -5179,7 +5927,7 @@ retry: // if we still can't find the torrent, look for it by url if (!torrent_ptr && !params.url.empty()) { - std::map >::iterator i = std::find_if(m_torrents.begin() + torrent_map::iterator i = std::find_if(m_torrents.begin() , m_torrents.end(), boost::bind(&torrent::url, boost::bind(&std::pair >::second, _1)) == params.url); if (i != m_torrents.end()) @@ -5203,15 +5951,9 @@ retry: return torrent_handle(); } - int queue_pos = 0; - for (torrent_map::const_iterator i = m_torrents.begin() - , end(m_torrents.end()); i != end; ++i) - { - int pos = i->second->queue_position(); - if (pos >= queue_pos) queue_pos = pos + 1; - } + int queue_pos = ++m_max_queue_pos; - torrent_ptr.reset(new torrent(*this, m_listen_interface + torrent_ptr.reset(new torrent(*this , 16 * 1024, queue_pos, params, *ih)); torrent_ptr->start(); @@ -5227,12 +5969,7 @@ retry: params.userdata)); } - for (ses_extension_list_t::iterator i = m_ses_extensions.begin() - , end(m_ses_extensions.end()); i != end; ++i) - { - boost::shared_ptr tp((*i)->new_torrent(torrent_ptr.get(), params.userdata)); - if (tp) torrent_ptr->add_extension(tp); - } + add_extensions_to_torrent(torrent_ptr, params.userdata); #endif #ifndef TORRENT_DISABLE_DHT @@ -5246,7 +5983,51 @@ retry: } #endif +#if TORRENT_HAS_BOOST_UNORDERED + sha1_hash next_lsd(0); + sha1_hash next_dht(0); + if (m_next_lsd_torrent != m_torrents.end()) + next_lsd = m_next_lsd_torrent->first; +#ifndef TORRENT_DISABLE_DHT + if (m_next_dht_torrent != m_torrents.end()) + next_dht = m_next_dht_torrent->first; +#endif + float load_factor = m_torrents.load_factor(); +#endif // TORRENT_HAS_BOOST_UNORDERED + m_torrents.insert(std::make_pair(*ih, torrent_ptr)); + + TORRENT_ASSERT(m_torrents.size() >= m_torrent_lru.size()); + +#ifndef TORRENT_DISABLE_ENCRYPTION + hasher h; + h.update("req2", 4); + h.update((char*)&(*ih)[0], 20); + // this is SHA1("req2" + info-hash), used for + // encrypted hand shakes + m_obfuscated_torrents.insert(std::make_pair(h.final(), torrent_ptr)); +#endif + + if (torrent_ptr->is_pinned() == false) + { + evict_torrents_except(torrent_ptr.get()); + bump_torrent(torrent_ptr.get()); + } + +#if TORRENT_HAS_BOOST_UNORDERED + // if this insert made the hash grow, the iterators became invalid + // we need to reset them + if (m_torrents.load_factor() < load_factor) + { + // this indicates the hash table re-hashed + if (!next_lsd.is_all_zeros()) + m_next_lsd_torrent = m_torrents.find(next_lsd); +#ifndef TORRENT_DISABLE_DHT + if (!next_dht.is_all_zeros()) + m_next_dht_torrent = m_torrents.find(next_dht); +#endif + } +#endif // TORRENT_HAS_BOOST_UNORDERED if (!params.uuid.empty() || !params.url.empty()) m_uuids.insert(std::make_pair(params.uuid.empty() ? params.url : params.uuid, torrent_ptr)); @@ -5265,54 +6046,98 @@ retry: return torrent_handle(torrent_ptr); } - void session_impl::queue_check_torrent(boost::shared_ptr const& t) - { - if (m_abort) return; - TORRENT_ASSERT(t->should_check_files()); - TORRENT_ASSERT(t->state() != torrent_status::checking_files); - if (m_queued_for_checking.empty()) t->start_checking(); - else t->set_state(torrent_status::queued_for_checking); - TORRENT_ASSERT(std::find(m_queued_for_checking.begin() - , m_queued_for_checking.end(), t) == m_queued_for_checking.end()); - m_queued_for_checking.push_back(t); - } - - void session_impl::dequeue_check_torrent(boost::shared_ptr const& t) + void session_impl::update_outgoing_interfaces() { INVARIANT_CHECK; - TORRENT_ASSERT(t->state() == torrent_status::checking_files - || t->state() == torrent_status::queued_for_checking); + std::string net_interfaces = m_settings.get_str(settings_pack::outgoing_interfaces); - if (m_queued_for_checking.empty()) return; + // declared in string_util.hpp + parse_comma_separated_string(net_interfaces, m_net_interfaces); + } - boost::shared_ptr next_check = *m_queued_for_checking.begin(); - check_queue_t::iterator done = m_queued_for_checking.end(); - for (check_queue_t::iterator i = m_queued_for_checking.begin() - , end(m_queued_for_checking.end()); i != end; ++i) + tcp::endpoint session_impl::bind_outgoing_socket(socket_type& s, address + const& remote_address, error_code& ec) const + { + tcp::endpoint bind_ep(address_v4(), 0); + if (m_settings.get_int(settings_pack::outgoing_port) > 0) { - // the reason m_paused is in there is because when the session - // is paused, all torrents that are queued ar all of a sudden - // not supposed to be queued anymore. The first torrent that gets - // removed from the queue will hence trigger this assert, without - // the m_paused exception - TORRENT_ASSERT(*i == t || (*i)->should_check_files() || m_paused); - if (*i == t) done = i; - else if (next_check == t || next_check->queue_position() > (*i)->queue_position()) - next_check = *i; - } - TORRENT_ASSERT(next_check != t || m_queued_for_checking.size() == 1); - // only start a new one if we removed the one that is checking - TORRENT_ASSERT(done != m_queued_for_checking.end()); - if (done == m_queued_for_checking.end()) return; - - if (next_check != t - && t->state() == torrent_status::checking_files - && !m_paused) - { - next_check->start_checking(); + s.set_option(socket_acceptor::reuse_address(true), ec); + // ignore errors because the underlying socket may not + // be opened yet. This happens when we're routing through + // a proxy. In that case, we don't yet know the address of + // the proxy server, and more importantly, we don't know + // the address family of its address. This means we can't + // open the socket yet. The socks abstraction layer defers + // opening it. + ec.clear(); + bind_ep.port(next_port()); } - m_queued_for_checking.erase(done); + if (!m_net_interfaces.empty()) + { + if (m_interface_index >= m_net_interfaces.size()) m_interface_index = 0; + std::string const& ifname = m_net_interfaces[m_interface_index++]; + + if (ec) return bind_ep; + + bind_ep.address(bind_to_device(m_io_service, s, remote_address.is_v4() + , ifname.c_str(), bind_ep.port(), ec)); + return bind_ep; + } + + // if we're not binding to a specific interface, bind + // to the same protocol family as the target endpoint + if (is_any(bind_ep.address())) + { +#if TORRENT_USE_IPV6 + if (remote_address.is_v6()) + bind_ep.address(address_v6::any()); + else +#endif + bind_ep.address(address_v4::any()); + } + + s.bind(bind_ep, ec); + return bind_ep; + } + + // verify that the given local address satisfies the requirements of + // the outgoing interfaces. i.e. that one of the allowed outgoing + // interfaces has this address. For uTP sockets, which are all backed + // by an unconnected udp socket, we won't be able to tell what local + // address is used for this peer's packets, in that case, just make + // sure one of the allowed interfaces exists and maybe that it's the + // default route. For systems that have SO_BINDTODEVICE, it should be + // enough to just know that one of the devices exist + bool session_impl::verify_bound_address(address const& addr, bool utp + , error_code& ec) + { + // we have specific outgoing interfaces specified. Make sure the + // local endpoint for this socket is bound to one of the allowed + // interfaces. the list can be a mixture of interfaces and IP + // addresses. first look for the address + for (int i = 0; i < int(m_net_interfaces.size()); ++i) + { + error_code err; + address ip = address::from_string(m_net_interfaces[i].c_str(), err); + if (err) continue; + if (ip == addr) return true; + } + + // we didn't find the address as an IP in the interface list. Now, + // resolve which device (if any) has this IP address. + std::string device = device_for_address(addr, m_io_service, ec); + if (ec) return false; + + // if no device was found to have this address, we fail + if (device.empty()) return false; + + for (int i = 0; i < int(m_net_interfaces.size()); ++i) + { + if (m_net_interfaces[i] == device) return true; + } + + return false; } void session_impl::remove_torrent(const torrent_handle& h, int options) @@ -5322,10 +6147,9 @@ retry: boost::shared_ptr tptr = h.m_torrent.lock(); if (!tptr) return; - remove_torrent_impl(tptr, options); + m_alerts.post_alert(torrent_removed_alert(tptr->get_handle(), tptr->info_hash())); - if (m_alerts.should_post()) - m_alerts.post_alert(torrent_removed_alert(tptr->get_handle(), tptr->info_hash())); + remove_torrent_impl(tptr, options); tptr->abort(); tptr->set_queue_position(-1); @@ -5365,7 +6189,13 @@ retry: } } - tptr->update_guage(); + if (m_torrent_lru.size() > 0 + && (t.prev != NULL || t.next != NULL || m_torrent_lru.front() == &t)) + m_torrent_lru.erase(&t); + + TORRENT_ASSERT(t.prev == NULL && t.next == NULL); + + tptr->update_gauge(); #if TORRENT_USE_ASSERTS sha1_hash i_hash = t.torrent_file().info_hash(); @@ -5376,70 +6206,167 @@ retry: #endif if (i == m_next_lsd_torrent) ++m_next_lsd_torrent; - if (i == m_next_connect_torrent) - ++m_next_connect_torrent; m_torrents.erase(i); + TORRENT_ASSERT(m_torrents.size() >= m_torrent_lru.size()); + +#ifndef TORRENT_DISABLE_ENCRYPTION + hasher h; + h.update("req2", 4); + h.update((char*)&tptr->info_hash()[0], 20); + m_obfuscated_torrents.erase(h.final()); +#endif + #ifndef TORRENT_DISABLE_DHT if (m_next_dht_torrent == m_torrents.end()) m_next_dht_torrent = m_torrents.begin(); #endif if (m_next_lsd_torrent == m_torrents.end()) m_next_lsd_torrent = m_torrents.begin(); - if (m_next_connect_torrent == m_torrents.end()) - m_next_connect_torrent = m_torrents.begin(); - std::list >::iterator k - = std::find(m_queued_for_checking.begin(), m_queued_for_checking.end(), tptr); - if (k != m_queued_for_checking.end()) m_queued_for_checking.erase(k); + // this torrent may open up a slot for a queued torrent + trigger_auto_manage(); + TORRENT_ASSERT(m_torrents.find(i_hash) == m_torrents.end()); } - void session_impl::listen_on( - std::pair const& port_range - , error_code& ec - , const char* net_interface, int flags) + void session_impl::update_listen_interfaces() { INVARIANT_CHECK; - tcp::endpoint new_interface; - if (net_interface && std::strlen(net_interface) > 0) - { - new_interface = tcp::endpoint(address::from_string(net_interface, ec), port_range.first); - if (ec) - { - if (m_alerts.should_post()) - m_alerts.post_alert(listen_failed_alert(new_interface, listen_failed_alert::parse_addr, ec - , listen_failed_alert::tcp)); + std::string net_interfaces = m_settings.get_str(settings_pack::listen_interfaces); + std::vector > new_listen_interfaces; + + // declared in string_util.hpp + parse_comma_separated_string_port(net_interfaces, new_listen_interfaces); -#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING - session_log("listen_on: %s failed: %s" - , net_interface, ec.message().c_str()); +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + session_log("update listen interfaces: %s", net_interfaces.c_str()); #endif - return; - } - } - else - { - new_interface = tcp::endpoint(address_v4::any(), port_range.first); - } - - m_listen_port_retries = port_range.second - port_range.first; // if the interface is the same and the socket is open // don't do anything - if (new_interface == m_listen_interface + if (new_listen_interfaces == m_listen_interfaces && !m_listen_sockets.empty()) return; - m_listen_interface = new_interface; + m_listen_interfaces = new_listen_interfaces; - open_listen_port(flags, ec); + // for backwards compatibility. Some components still only supports + // a single listen interface + m_listen_interface.address(address_v4::any()); + m_listen_interface.port(0); + if (m_listen_interfaces.size() > 0) + { + error_code ec; + m_listen_interface.port(m_listen_interfaces[0].second); + char const* device_name = m_listen_interfaces[0].first.c_str(); -#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING - m_logger = create_log("main_session", listen_port(), false); - session_log("log created"); + // if the first character is [, skip it since it may be an + // IPv6 address + m_listen_interface.address(address::from_string( + device_name[0] == '[' ? device_name + 1 : device_name, ec)); + if (ec) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + session_log("failed to treat %s as an IP address [ %s ]" + , device_name, ec.message().c_str()); +#endif + // it may have been a device name. + std::vector ifs = enum_net_interfaces(m_io_service, ec); + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + if (ec) + session_log("failed to enumerate interfaces [ %s ]" + , ec.message().c_str()); +#endif + + bool found = false; + for (int i = 0; i < int(ifs.size()); ++i) + { + // we're looking for a specific interface, and its address + // (which must be of the same family as the address we're + // connecting to) + if (strcmp(ifs[i].name, device_name) != 0) continue; + m_listen_interface.address(ifs[i].interface_address); +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + session_log("binding to %s" + , m_listen_interface.address().to_string(ec).c_str()); +#endif + found = true; + break; + } + + if (!found) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + session_log("failed to find device %s", device_name); +#endif + // effectively disable whatever socket decides to bind to this + m_listen_interface.address(address_v4::loopback()); + } + } + } + } + + void session_impl::update_privileged_ports() + { + if (m_settings.get_bool(settings_pack::no_connect_privileged_ports)) + { + m_port_filter.add_rule(0, 1024, port_filter::blocked); + + // Close connections whose endpoint is filtered + // by the new ip-filter + for (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) + i->second->ip_filter_updated(); + } + else + { + m_port_filter.add_rule(0, 1024, 0); + } + } + + void session_impl::update_proxy() + { + // in case we just set a socks proxy, we might have to + // open the socks incoming connection + if (!m_socks_listen_socket) open_new_incoming_socks_connection(); + m_udp_socket.set_proxy_settings(proxy()); + } + + void session_impl::update_upnp() + { + if (m_settings.get_bool(settings_pack::enable_upnp)) + start_upnp(); + else + stop_upnp(); + } + + void session_impl::update_natpmp() + { + if (m_settings.get_bool(settings_pack::enable_natpmp)) + start_natpmp(); + else + stop_natpmp(); + } + + void session_impl::update_lsd() + { + if (m_settings.get_bool(settings_pack::enable_lsd)) + start_lsd(); + else + stop_lsd(); + } + + void session_impl::update_dht() + { +#ifndef TORRENT_DISABLE_DHT + if (m_settings.get_bool(settings_pack::enable_dht)) + start_dht(); + else + stop_dht(); #endif } @@ -5464,7 +6391,7 @@ retry: // if not, don't tell the tracker anything if we're in force_proxy // mode. We don't want to leak our listen port since it can // potentially identify us if it is leaked elsewere - if (m_settings.force_proxy) return 0; + if (m_settings.get_bool(settings_pack::force_proxy)) return 0; if (m_listen_sockets.empty()) return 0; return m_listen_sockets.front().external_port; } @@ -5481,7 +6408,7 @@ retry: // if not, don't tell the tracker anything if we're in force_proxy // mode. We don't want to leak our listen port since it can // potentially identify us if it is leaked elsewere - if (m_settings.force_proxy) return 0; + if (m_settings.get_bool(settings_pack::force_proxy)) return 0; if (m_listen_sockets.empty()) return 0; for (std::list::const_iterator i = m_listen_sockets.begin() , end(m_listen_sockets.end()); i != end; ++i) @@ -5501,10 +6428,8 @@ retry: void session_impl::on_lsd_peer(tcp::endpoint peer, sha1_hash const& ih) { -#ifdef TORRENT_STATS - ++m_num_messages[on_lsd_peer_counter]; -#endif - TORRENT_ASSERT(is_network_thread()); + inc_stats_counter(counters::on_lsd_peer_counter); + TORRENT_ASSERT(is_single_thread()); INVARIANT_CHECK; @@ -5512,12 +6437,14 @@ retry: if (!t) return; // don't add peers from lsd to private torrents if (t->torrent_file().priv() || (t->torrent_file().is_i2p() - && !m_settings.allow_i2p_mixed)) return; + && !m_settings.get_bool(settings_pack::allow_i2p_mixed))) return; #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) session_log("added peer from local discovery: %s", print_endpoint(peer).c_str()); #endif - t->get_policy().add_peer(peer, peer_id(0), peer_info::lsd, 0); + t->add_peer(peer, peer_info::lsd); + t->do_connect_boost(); + if (m_alerts.should_post()) m_alerts.post_alert(lsd_peer_alert(t->get_handle(), peer)); } @@ -5539,7 +6466,7 @@ retry: void session_impl::on_port_mapping(int mapping, address const& ip, int port , error_code const& ec, int map_transport) { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); TORRENT_ASSERT(map_transport >= 0 && map_transport <= 1); @@ -5588,19 +6515,24 @@ retry: session_status session_impl::status() const { // INVARIANT_CHECK; - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); session_status s; s.optimistic_unchoke_counter = m_optimistic_unchoke_time_scaler; s.unchoke_counter = m_unchoke_time_scaler; - s.num_peers = (int)m_connections.size(); + s.num_peers = int(m_connections.size()); + s.num_dead_peers = int(m_undead_peers.size()); s.num_unchoked = m_num_unchoked; s.allowed_upload_slots = m_allowed_upload_slots; - s.total_redundant_bytes = m_total_redundant_bytes; - s.total_failed_bytes = m_total_failed_bytes; + s.num_torrents = m_torrents.size(); + // only non-paused torrents want tick + s.num_paused_torrents = m_torrents.size() - m_torrent_lists[torrent_want_tick].size(); + + s.total_redundant_bytes = m_stats_counters[counters::recv_redundant_bytes]; + s.total_failed_bytes = m_stats_counters[counters::recv_failed_bytes]; s.up_bandwidth_queue = m_upload_rate.queue_size(); s.down_bandwidth_queue = m_download_rate.queue_size(); @@ -5608,10 +6540,10 @@ retry: s.up_bandwidth_bytes_queue = int(m_upload_rate.queued_bytes()); s.down_bandwidth_bytes_queue = int(m_download_rate.queued_bytes()); - s.disk_write_queue = m_disk_queues[peer_connection::download_channel]; - s.disk_read_queue = m_disk_queues[peer_connection::upload_channel]; + s.disk_write_queue = m_stats_counters[counters::num_peers_down_disk]; + s.disk_read_queue = m_stats_counters[counters::num_peers_up_disk]; - s.has_incoming_connections = m_incoming_connection; + s.has_incoming_connections = m_stats_counters[counters::has_incoming_connections]; // total s.download_rate = m_stat.download_rate(); @@ -5687,11 +6619,13 @@ retry: m_utp_socket_manager.get_status(s.utp_stats); + // this loop is potentially expensive. It could be optimized by + // simply keeping a global counter int peerlist_size = 0; for (torrent_map::const_iterator i = m_torrents.begin() , end(m_torrents.end()); i != end; ++i) { - peerlist_size += i->second->get_policy().num_peers(); + peerlist_size += i->second->num_known_peers(); } s.peerlist_size = peerlist_size; @@ -5715,7 +6649,7 @@ retry: INVARIANT_CHECK; stop_dht(); - m_dht = new dht::dht_tracker(*this, m_udp_socket, m_dht_settings, &startup_state); + m_dht = new dht::dht_tracker(*this, m_udp_socket, m_dht_settings, m_stats_counters, &startup_state); for (std::list::iterator i = m_dht_router_nodes.begin() , end(m_dht_router_nodes.end()); i != end; ++i) @@ -5759,15 +6693,13 @@ retry: #if defined TORRENT_ASIO_DEBUGGING add_outstanding_async("session_impl::on_dht_router_name_lookup"); #endif - char port[7]; - snprintf(port, sizeof(port), "%d", node.second); - tcp::resolver::query q(node.first, port); - m_host_resolver.async_resolve(q, - boost::bind(&session_impl::on_dht_router_name_lookup, this, _1, _2)); + m_host_resolver.async_resolve(node.first, 0 + , boost::bind(&session_impl::on_dht_router_name_lookup + , this, _1, _2, node.second)); } void session_impl::on_dht_router_name_lookup(error_code const& e - , tcp::resolver::iterator host) + , std::vector
const& addresses, int port) { #if defined TORRENT_ASIO_DEBUGGING complete_async("session_impl::on_dht_router_name_lookup"); @@ -5780,13 +6712,14 @@ retry: return; } - while (host != tcp::resolver::iterator()) + + for (std::vector
::const_iterator i = addresses.begin() + , end(addresses.end()); i != end; ++i) { // router nodes should be added before the DHT is started (and bootstrapped) - udp::endpoint ep(host->endpoint().address(), host->endpoint().port()); + udp::endpoint ep(*i, port); if (m_dht) m_dht->add_router_node(ep); m_dht_router_nodes.push_back(ep); - ++host; } } @@ -5901,12 +6834,13 @@ retry: } } -#ifndef TORRENT_DISABLE_ENCRYPTION - void session_impl::set_pe_settings(pe_settings const& settings) +#if !defined TORRENT_DISABLE_ENCRYPTION + void session_impl::add_obfuscated_hash(sha1_hash const& obfuscated + , boost::weak_ptr const& t) { - m_pe_settings = settings; + m_obfuscated_torrents.insert(std::make_pair(obfuscated, t.lock())); } -#endif +#endif // TORRENT_DISABLE_ENCRYPTION bool session_impl::is_listening() const { @@ -5915,17 +6849,13 @@ retry: session_impl::~session_impl() { - TORRENT_ASSERT(is_not_network_thread()); + // this is not allowed to be the network thread! + TORRENT_ASSERT(is_not_thread()); m_io_service.post(boost::bind(&session_impl::abort, this)); - // we need to wait for the disk-io thread to - // die first, to make sure it won't post any - // more messages to the io_service containing references - // to disk_io_pool inside the disk_io_thread. Once - // the main thread has handled all the outstanding requests - // we know it's safe to destruct the disk thread. - m_disk_thread.join(); + // now it's OK for the network thread to exit + m_work.reset(); #if defined TORRENT_ASIO_DEBUGGING int counter = 0; @@ -5938,6 +6868,10 @@ retry: , m_half_open.max_timeout()); } async_dec_threads(); + + fprintf(stderr, "\n\nEXPECTS NO MORE ASYNC OPS\n\n\n"); + +// m_io_service.post(boost::bind(&io_service::stop, &m_io_service)); #endif if (m_thread) m_thread->join(); @@ -5957,101 +6891,160 @@ retry: #ifdef TORRENT_STATS if (m_stats_logger) fclose(m_stats_logger); #endif + +#if defined TORRENT_ASIO_DEBUGGING + FILE* f = fopen("wakeups.log", "w+"); + if (f != NULL) + { + ptime m = min_time(); + if (_wakeups.size() > 0) m = _wakeups[0].timestamp; + ptime prev = m; + boost::uint64_t prev_csw = 0; + if (_wakeups.size() > 0) prev_csw = _wakeups[0].context_switches; + fprintf(f, "abs. time\trel. time\tctx switch\tidle-wakeup\toperation\n"); + for (int i = 0; i < _wakeups.size(); ++i) + { + wakeup_t const& w = _wakeups[i]; + bool idle_wakeup = w.context_switches > prev_csw; + fprintf(f, "%" PRId64 "\t%" PRId64 "\t%" PRId64 "\t%c\t%s\n" + , total_microseconds(w.timestamp - m) + , total_microseconds(w.timestamp - prev) + , w.context_switches + , idle_wakeup ? '*' : '.' + , w.operation); + prev = w.timestamp; + prev_csw = w.context_switches; + } + fclose(f); + } +#endif } #ifndef TORRENT_NO_DEPRECATE int session_impl::max_connections() const { - return m_settings.connections_limit; + return m_settings.get_int(settings_pack::connections_limit); } int session_impl::max_uploads() const { - return m_settings.unchoke_slots_limit; + return m_settings.get_int(settings_pack::unchoke_slots_limit); } int session_impl::max_half_open_connections() const { - return m_settings.half_open_limit; + return m_settings.get_int(settings_pack::half_open_limit); } void session_impl::set_local_download_rate_limit(int bytes_per_second) { - session_settings s = m_settings; - s.local_download_rate_limit = bytes_per_second; - set_settings(s); + settings_pack* p = new settings_pack; + p->set_int(settings_pack::local_download_rate_limit, bytes_per_second); + apply_settings_pack(p); } void session_impl::set_local_upload_rate_limit(int bytes_per_second) { - session_settings s = m_settings; - s.local_upload_rate_limit = bytes_per_second; - set_settings(s); + settings_pack* p = new settings_pack; + p->set_int(settings_pack::local_upload_rate_limit, bytes_per_second); + apply_settings_pack(p); } void session_impl::set_download_rate_limit(int bytes_per_second) { - session_settings s = m_settings; - s.download_rate_limit = bytes_per_second; - set_settings(s); + settings_pack* p = new settings_pack; + p->set_int(settings_pack::download_rate_limit, bytes_per_second); + apply_settings_pack(p); } void session_impl::set_upload_rate_limit(int bytes_per_second) { - session_settings s = m_settings; - s.upload_rate_limit = bytes_per_second; - set_settings(s); + settings_pack* p = new settings_pack; + p->set_int(settings_pack::upload_rate_limit, bytes_per_second); + apply_settings_pack(p); } void session_impl::set_max_half_open_connections(int limit) { - session_settings s = m_settings; - s.half_open_limit = limit; - set_settings(s); + settings_pack* p = new settings_pack; + p->set_int(settings_pack::half_open_limit, limit); + apply_settings_pack(p); } void session_impl::set_max_connections(int limit) { - session_settings s = m_settings; - s.connections_limit = limit; - set_settings(s); + settings_pack* p = new settings_pack; + p->set_int(settings_pack::connections_limit, limit); + apply_settings_pack(p); } void session_impl::set_max_uploads(int limit) { - session_settings s = m_settings; - s.unchoke_slots_limit = limit; - set_settings(s); + settings_pack* p = new settings_pack; + p->set_int(settings_pack::unchoke_slots_limit, limit); + apply_settings_pack(p); } int session_impl::local_upload_rate_limit() const { - return m_local_upload_channel.throttle(); + return upload_rate_limit(m_local_peer_class); } int session_impl::local_download_rate_limit() const { - return m_local_download_channel.throttle(); + return download_rate_limit(m_local_peer_class); } int session_impl::upload_rate_limit() const { - return m_upload_channel.throttle(); + return upload_rate_limit(m_global_class); } int session_impl::download_rate_limit() const { - return m_download_channel.throttle(); + return download_rate_limit(m_global_class); } #endif - void session_impl::update_unchoke_limit() + void session_impl::update_peer_tos() { - m_allowed_upload_slots = m_settings.unchoke_slots_limit; + error_code ec; + m_udp_socket.set_option(type_of_service(m_settings.get_int(settings_pack::peer_tos)), ec); +#if defined TORRENT_VERBOSE_LOGGING + session_log(">>> SET_TOS[ udp_socket tos: %x e: %s ]" + , m_settings.get_int(settings_pack::peer_tos) + , ec.message().c_str()); +#endif + } + + void session_impl::update_user_agent() + { + // replace all occurances of '\n' with ' '. + std::string agent = m_settings.get_str(settings_pack::user_agent); + std::string::iterator i = agent.begin(); + while ((i = std::find(i, agent.end(), '\n')) + != agent.end()) + *i = ' '; + m_settings.set_str(settings_pack::user_agent, agent); + } + + void session_impl::update_choking_algorithm() + { + int algo = m_settings.get_int(settings_pack::choking_algorithm); + int unchoke_limit = m_settings.get_int(settings_pack::unchoke_slots_limit); + + if (algo == settings_pack::fixed_slots_choker) + m_allowed_upload_slots = unchoke_limit; + else if (algo == settings_pack::auto_expand_choker) + m_allowed_upload_slots = unchoke_limit; + if (m_allowed_upload_slots < 0) m_allowed_upload_slots = (std::numeric_limits::max)(); - if (m_settings.num_optimistic_unchoke_slots >= m_allowed_upload_slots / 2) + m_stats_counters.set_value(counters::num_unchoke_slots + , m_allowed_upload_slots); + + if (m_settings.get_int(settings_pack::num_optimistic_unchoke_slots) >= m_allowed_upload_slots / 2) { if (m_alerts.should_post()) m_alerts.post_alert(performance_alert(torrent_handle() @@ -6059,57 +7052,303 @@ retry: } } - void session_impl::update_rate_settings() + void session_impl::update_connection_speed() { - if (m_settings.half_open_limit <= 0) m_settings.half_open_limit - = (std::numeric_limits::max)(); - m_half_open.limit(m_settings.half_open_limit); + if (m_settings.get_int(settings_pack::connection_speed) < 0) + m_settings.set_int(settings_pack::connection_speed, 200); + } - if (m_settings.local_download_rate_limit < 0) - m_settings.local_download_rate_limit = 0; - m_local_download_channel.throttle(m_settings.local_download_rate_limit); + void session_impl::update_queued_disk_bytes() + { + boost::uint64_t cache_size = m_settings.get_int(settings_pack::cache_size); + if (m_settings.get_int(settings_pack::max_queued_disk_bytes) / 16 / 1024 + > cache_size / 2 + && cache_size > 5 + && m_alerts.should_post()) + { + m_alerts.post_alert(performance_alert(torrent_handle() + , performance_alert::too_high_disk_queue_limit)); + } + } - if (m_settings.local_upload_rate_limit < 0) - m_settings.local_upload_rate_limit = 0; - m_local_upload_channel.throttle(m_settings.local_upload_rate_limit); + void session_impl::update_alert_queue_size() + { + m_alerts.set_alert_queue_size_limit(m_settings.get_int(settings_pack::alert_queue_size)); + } - if (m_settings.download_rate_limit < 0) - m_settings.download_rate_limit = 0; - m_download_channel.throttle(m_settings.download_rate_limit); + bool session_impl::preemptive_unchoke() const + { + return m_num_unchoked < m_allowed_upload_slots; + } - if (m_settings.upload_rate_limit < 0) - m_settings.upload_rate_limit = 0; - m_upload_channel.throttle(m_settings.upload_rate_limit); + void session_impl::update_dht_upload_rate_limit() + { + m_udp_socket.set_rate_limit(m_settings.get_int(settings_pack::dht_upload_rate_limit)); + } + + void session_impl::update_disk_threads() + { + if (m_settings.get_int(settings_pack::aio_threads) < 1) + m_settings.set_int(settings_pack::aio_threads, 1); + +#if !TORRENT_USE_PREAD && !TORRENT_USE_PREADV + // if we don't have pread() nor preadv() there's no way + // to perform concurrent file operations on the same file + // handle, so we must limit the disk thread to a single one + + if (m_settings.get_int(settings_pack::aio_threads) > 1) + m_settings.set_int(settings_pack::aio_threads, 1); +#endif + + m_disk_thread.set_num_threads(m_settings.get_int(settings_pack::aio_threads)); + } + + void session_impl::update_network_threads() + { + int num_threads = m_settings.get_int(settings_pack::network_threads); + int num_pools = num_threads > 0 ? num_threads : 1; + while (num_pools > m_net_thread_pool.size()) + { + m_net_thread_pool.push_back(boost::make_shared()); + m_net_thread_pool.back()->set_num_threads(1); + } + + while (num_pools < m_net_thread_pool.size()) + { + m_net_thread_pool.erase(m_net_thread_pool.end() - 1); + } + + if (num_threads == 0 && m_net_thread_pool.size() > 0) + { + m_net_thread_pool[0]->set_num_threads(0); + } + } + + // TODO: 3 If socket jobs could be higher level, to include RC4 encryption and decryption, + // we would offload the main thread even more + void session_impl::post_socket_job(socket_job& j) + { + uintptr_t idx = 0; + if (m_net_thread_pool.size() > 1) + { + // each peer needs to be pinned to a specific thread + // since reading and writing simultaneously on the same + // socket from different threads is not supported by asio. + // as long as a specific socket is consistently used from + // the same thread, it's safe + idx = uintptr_t(j.peer.get()); + idx ^= idx >> 8; + idx %= m_net_thread_pool.size(); + } + m_net_thread_pool[idx]->post_job(j); + } + + void session_impl::update_cache_buffer_chunk_size() + { + if (m_settings.get_int(settings_pack::cache_buffer_chunk_size) <= 0) + m_settings.set_int(settings_pack::cache_buffer_chunk_size, 1); + } + + void session_impl::update_report_web_seed_downloads() + { + // if this flag changed, update all web seed connections + bool report = m_settings.get_bool(settings_pack::report_web_seed_downloads); + for (connection_map::iterator i = m_connections.begin() + , end(m_connections.end()); i != end; ++i) + { + int type = (*i)->type(); + if (type == peer_connection::url_seed_connection + || type == peer_connection::http_seed_connection) + (*i)->ignore_stats(!report); + } + } + + void session_impl::trigger_auto_manage() + { + if (m_pending_auto_manage || m_abort) return; + + m_pending_auto_manage = true; + m_need_auto_manage = true; + + // if we haven't started yet, don't actually trigger this + if (!m_thread) return; + + m_io_service.post(boost::bind(&session_impl::on_trigger_auto_manage, this)); + } + + void session_impl::on_trigger_auto_manage() + { + assert(m_pending_auto_manage); + if (!m_need_auto_manage || m_abort) + { + m_pending_auto_manage = false; + return; + } + // don't clear m_pending_auto_manage until after we've + // recalculated the auto managed torrents. The auto-managed + // logic may trigger another auto-managed event otherwise + recalculate_auto_managed_torrents(); + m_pending_auto_manage = false; + } + + void session_impl::update_socket_buffer_size() + { + error_code ec; + set_socket_buffer_size(m_udp_socket, m_settings, ec); + if (ec) + { + if (m_alerts.should_post()) + m_alerts.post_alert(udp_error_alert(udp::endpoint(), ec)); + } + } + + void session_impl::update_dht_announce_interval() + { +#ifndef TORRENT_DISABLE_DHT + if (!m_dht) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + session_log("not starting DHT announce timer: m_dht == NULL"); +#endif + return; + } + + m_dht_interval_update_torrents = m_torrents.size(); + + // if we haven't started yet, don't actually trigger this + if (!m_thread) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + session_log("not starting DHT announce timer: thread not running yet"); +#endif + return; + } + + if (m_abort) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + session_log("not starting DHT announce timer: m_abort set"); +#endif + return; + } + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("session_impl::on_dht_announce"); +#endif + error_code ec; + int delay = (std::max)(m_settings.get_int(settings_pack::dht_announce_interval) + / (std::max)(int(m_torrents.size()), 1), 1); + m_dht_announce_timer.expires_from_now(seconds(delay), ec); + m_dht_announce_timer.async_wait( + boost::bind(&session_impl::on_dht_announce, this, _1)); +#endif + } + + void session_impl::update_anonymous_mode() + { + if (!m_settings.get_bool(settings_pack::anonymous_mode)) return; + + m_settings.set_str(settings_pack::user_agent, ""); + url_random((char*)&m_peer_id[0], (char*)&m_peer_id[0] + 20); + } + + void session_impl::update_force_proxy() + { + m_udp_socket.set_force_proxy(m_settings.get_bool(settings_pack::force_proxy)); + + if (!m_settings.get_bool(settings_pack::force_proxy)) return; + + // if we haven't started yet, don't actually trigger this + if (!m_thread) return; + + // enable force_proxy mode. We don't want to accept any incoming + // connections, except through a proxy. + stop_lsd(); + stop_upnp(); + stop_natpmp(); +#ifndef TORRENT_DISABLE_DHT + stop_dht(); +#endif + // close the listen sockets + error_code ec; + for (std::list::iterator i = m_listen_sockets.begin() + , end(m_listen_sockets.end()); i != end; ++i) + i->sock->close(ec); + m_listen_sockets.clear(); + } + + void session_impl::update_half_open() + { + if (m_settings.get_int(settings_pack::half_open_limit) <= 0) + m_settings.set_int(settings_pack::half_open_limit, (std::numeric_limits::max)()); + m_half_open.limit(m_settings.get_int(settings_pack::half_open_limit)); + } + +#ifndef TORRENT_NO_DEPRECATE + void session_impl::update_local_download_rate() + { + if (m_settings.get_int(settings_pack::local_download_rate_limit) < 0) + m_settings.set_int(settings_pack::local_download_rate_limit, 0); + set_download_rate_limit(m_local_peer_class + , m_settings.get_int(settings_pack::local_download_rate_limit)); + } + + void session_impl::update_local_upload_rate() + { + if (m_settings.get_int(settings_pack::local_upload_rate_limit) < 0) + m_settings.set_int(settings_pack::local_upload_rate_limit, 0); + set_upload_rate_limit(m_local_peer_class + , m_settings.get_int(settings_pack::local_upload_rate_limit)); + } +#endif + + void session_impl::update_download_rate() + { + if (m_settings.get_int(settings_pack::download_rate_limit) < 0) + m_settings.set_int(settings_pack::download_rate_limit, 0); + set_download_rate_limit(m_global_class + , m_settings.get_int(settings_pack::download_rate_limit)); + } + + void session_impl::update_upload_rate() + { + if (m_settings.get_int(settings_pack::upload_rate_limit) < 0) + m_settings.set_int(settings_pack::upload_rate_limit, 0); + set_upload_rate_limit(m_global_class + , m_settings.get_int(settings_pack::upload_rate_limit)); } void session_impl::update_connections_limit() { - if (m_settings.connections_limit <= 0) + if (m_settings.get_int(settings_pack::connections_limit) <= 0) { - m_settings.connections_limit = (std::numeric_limits::max)(); + m_settings.set_int(settings_pack::connections_limit, (std::numeric_limits::max)()); #if TORRENT_USE_RLIMIT rlimit l; if (getrlimit(RLIMIT_NOFILE, &l) == 0 && l.rlim_cur != RLIM_INFINITY) { - m_settings.connections_limit = l.rlim_cur - m_settings.file_pool_size; - if (m_settings.connections_limit < 5) m_settings.connections_limit = 5; + m_settings.set_int(settings_pack::connections_limit + , l.rlim_cur - m_settings.get_int(settings_pack::file_pool_size)); + if (m_settings.get_int(settings_pack::connections_limit) < 5) + m_settings.set_int(settings_pack::connections_limit, 5); } #endif } - if (num_connections() > m_settings.connections_limit && !m_torrents.empty()) + if (num_connections() > m_settings.get_int(settings_pack::connections_limit) + && !m_torrents.empty()) { // if we have more connections that we're allowed, disconnect // peers from the torrents so that they are all as even as possible - int to_disconnect = num_connections() - m_settings.connections_limit; + int to_disconnect = num_connections() - m_settings.get_int(settings_pack::connections_limit); int last_average = 0; - int average = m_settings.connections_limit / m_torrents.size(); + int average = m_settings.get_int(settings_pack::connections_limit) / m_torrents.size(); // the number of slots that are unused by torrents - int extra = m_settings.connections_limit % m_torrents.size(); + int extra = m_settings.get_int(settings_pack::connections_limit) % m_torrents.size(); // run 3 iterations of this, then we're probably close enough for (int iter = 0; iter < 4; ++iter) @@ -6156,19 +7395,76 @@ retry: } } +#ifndef TORRENT_NO_DEPRECATE + void session_impl::update_rate_limit_utp() + { + if (m_settings.get_bool(settings_pack::rate_limit_utp)) + { + // allow the global or local peer class to limit uTP peers + m_peer_class_type_filter.add(peer_class_type_filter::utp_socket + , m_local_peer_class); + m_peer_class_type_filter.add(peer_class_type_filter::utp_socket + , m_global_class); + m_peer_class_type_filter.add(peer_class_type_filter::ssl_utp_socket + , m_local_peer_class); + m_peer_class_type_filter.add(peer_class_type_filter::ssl_utp_socket + , m_global_class); + } + else + { + // don't add the global or local peer class to limit uTP peers + m_peer_class_type_filter.remove(peer_class_type_filter::utp_socket + , m_local_peer_class); + m_peer_class_type_filter.remove(peer_class_type_filter::utp_socket + , m_global_class); + m_peer_class_type_filter.remove(peer_class_type_filter::ssl_utp_socket + , m_local_peer_class); + m_peer_class_type_filter.remove(peer_class_type_filter::ssl_utp_socket + , m_global_class); + } + } + + void session_impl::update_ignore_rate_limits_on_local_network() + { + init_peer_class_filter(m_settings.get_bool(settings_pack::ignore_limits_on_local_network)); + } +#endif + + void session_impl::update_alert_mask() + { + m_alerts.set_alert_mask(m_settings.get_int(settings_pack::alert_mask)); + } + void session_impl::set_alert_dispatch(boost::function)> const& fun) { m_alerts.set_dispatch_function(fun); } + // this function is called on the user's thread + // not the network thread std::auto_ptr session_impl::pop_alert() { - return m_alerts.get(); + std::auto_ptr ret = m_alerts.get(); + if (alert_cast(ret.get()) + || alert_cast(ret.get())) + { + // we can only issue more resume data jobs from + // the network thread + m_io_service.post(boost::bind(&session_impl::async_resume_dispatched + , this, false)); + } + return ret; } + // this function is called on the user's thread + // not the network thread void session_impl::pop_alerts(std::deque* alerts) { m_alerts.get_all(alerts); + // we can only issue more resume data jobs from + // the network thread + m_io_service.post(boost::bind(&session_impl::async_resume_dispatched + , this, true)); } alert const* session_impl::wait_for_alert(time_duration max_wait) @@ -6176,15 +7472,10 @@ retry: return m_alerts.wait_for_alert(max_wait); } - void session_impl::set_alert_mask(boost::uint32_t m) - { - m_alerts.set_alert_mask(m); - } - #ifndef TORRENT_NO_DEPRECATE size_t session_impl::set_alert_queue_size_limit(size_t queue_size_limit_) { - m_settings.alert_queue_size = queue_size_limit_; + m_settings.set_int(settings_pack::alert_queue_size, queue_size_limit_); return m_alerts.set_alert_queue_size_limit(queue_size_limit_); } #endif @@ -6240,12 +7531,12 @@ retry: upnp* u = new (std::nothrow) upnp(m_io_service , m_half_open , m_listen_interface.address() - , m_settings.user_agent + , m_settings.get_str(settings_pack::user_agent) , boost::bind(&session_impl::on_port_mapping , this, _1, _2, _3, _4, 1) , boost::bind(&session_impl::on_port_map_log , this, _1, 1) - , m_settings.upnp_ignore_nonrouters); + , m_settings.get_bool(settings_pack::upnp_ignore_nonrouters)); if (u == 0) return 0; @@ -6363,38 +7654,58 @@ retry: #endif } - void session_impl::free_disk_buffer(char* buf) + // decrement the refcount of the block in the disk cache + // since the network thread doesn't need it anymore + void session_impl::reclaim_block(block_cache_reference ref) { - m_disk_thread.free_buffer(buf); + m_disk_thread.reclaim_block(ref); } char* session_impl::allocate_disk_buffer(char const* category) { - return m_disk_thread.allocate_buffer(category); + return m_disk_thread.allocate_disk_buffer(category); + } + + char* session_impl::async_allocate_disk_buffer(char const* category + , boost::function const& handler) + { + return m_disk_thread.async_allocate_disk_buffer(category, handler); + } + + void session_impl::free_disk_buffer(char* buf) + { + m_disk_thread.free_disk_buffer(buf); + } + + char* session_impl::allocate_disk_buffer(bool& exceeded + , boost::shared_ptr o + , char const* category) + { + return m_disk_thread.allocate_disk_buffer(exceeded, o, category); } char* session_impl::allocate_buffer() { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); -#ifdef TORRENT_DISK_STATS +#ifdef TORRENT_BUFFER_STATS TORRENT_ASSERT(m_buffer_allocations >= 0); m_buffer_allocations++; m_buffer_usage_logger << log_time() << " protocol_buffer: " - << (m_buffer_allocations * send_buffer_size) << std::endl; + << (m_buffer_allocations * send_buffer_size()) << std::endl; #endif #ifdef TORRENT_DISABLE_POOL_ALLOCATOR - int num_bytes = send_buffer_size; + int num_bytes = send_buffer_size(); return (char*)malloc(num_bytes); #else return (char*)m_send_buffers.malloc(); #endif } -#ifdef TORRENT_DISK_STATS +#ifdef TORRENT_BUFFER_STATS void session_impl::log_buffer_usage() { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); int send_buffer_capacity = 0; int used_send_buffer = 0; @@ -6414,13 +7725,13 @@ retry: void session_impl::free_buffer(char* buf) { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); -#ifdef TORRENT_DISK_STATS +#ifdef TORRENT_BUFFER_STATS m_buffer_allocations--; TORRENT_ASSERT(m_buffer_allocations >= 0); m_buffer_usage_logger << log_time() << " protocol_buffer: " - << (m_buffer_allocations * send_buffer_size) << std::endl; + << (m_buffer_allocations * send_buffer_size()) << std::endl; #endif #ifdef TORRENT_DISABLE_POOL_ALLOCATOR free(buf); @@ -6432,29 +7743,55 @@ retry: #if TORRENT_USE_INVARIANT_CHECKS void session_impl::check_invariant() const { - TORRENT_ASSERT(is_network_thread()); + TORRENT_ASSERT(is_single_thread()); - if (m_settings.unchoke_slots_limit < 0 - && m_settings.choking_algorithm == session_settings::fixed_slots_choker) + int loaded_limit = m_settings.get_int(settings_pack::active_loaded_limit); + TORRENT_ASSERT(m_num_save_resume <= loaded_limit); + if (m_num_save_resume < loaded_limit) + TORRENT_ASSERT(m_save_resume_queue.empty()); + + TORRENT_ASSERT(m_torrents.size() >= m_torrent_lru.size()); + + if (m_settings.get_int(settings_pack::unchoke_slots_limit) < 0 + && m_settings.get_int(settings_pack::choking_algorithm) == settings_pack::fixed_slots_choker) TORRENT_ASSERT(m_allowed_upload_slots == (std::numeric_limits::max)()); - int num_checking = 0; - int num_queued_for_checking = 0; - for (check_queue_t::const_iterator i = m_queued_for_checking.begin() - , end(m_queued_for_checking.end()); i != end; ++i) + for (int l = 0; l < num_torrent_lists; ++l) { - if ((*i)->state() == torrent_status::checking_files) ++num_checking; - else if ((*i)->state() == torrent_status::queued_for_checking) + std::vector const& list = m_torrent_lists[l]; + for (std::vector::const_iterator i = list.begin() + , end(list.end()); i != end; ++i) { - ++num_queued_for_checking; + TORRENT_ASSERT((*i)->m_links[l].in_list()); } } - // the queue is either empty, or it has exactly one checking torrent in it - TORRENT_ASSERT(m_queued_for_checking.empty() || num_checking == 1 || (m_paused && num_checking == 0)); -// TORRENT_ASSERT(m_queued_for_checking.size() == num_queued_for_checking); +#if TORRENT_HAS_BOOST_UNORDERED + boost::unordered_set unique_torrents; +#else + std::set unique_torrents; +#endif + for (list_iterator i = m_torrent_lru.iterate(); i.get(); i.next()) + { + torrent* t = (torrent*)i.get(); + TORRENT_ASSERT(t->is_loaded()); + TORRENT_ASSERT(unique_torrents.count(t) == 0); + unique_torrents.insert(t); + } + TORRENT_ASSERT(unique_torrents.size() == m_torrent_lru.size()); + int torrent_state_gauges[counters::num_error_torrents - counters::num_checking_torrents + 1]; + memset(torrent_state_gauges, 0, sizeof(torrent_state_gauges)); + +#if defined TORRENT_EXPENSIVE_INVARIANT_CHECKS + +#if TORRENT_HAS_BOOST_UNORDERED + boost::unordered_set unique; +#else std::set unique; +#endif +#endif + int num_active_downloading = 0; int num_active_finished = 0; int total_downloaders = 0; @@ -6462,8 +7799,11 @@ retry: , end(m_torrents.end()); i != end; ++i) { boost::shared_ptr t = i->second; - if (t->is_active_download()) ++num_active_downloading; - else if (t->is_active_finished()) ++num_active_finished; + if (t->want_peers_download()) ++num_active_downloading; + if (t->want_peers_finished()) ++num_active_finished; + TORRENT_ASSERT(!(t->want_peers_download() && t->want_peers_finished())); + + ++torrent_state_gauges[t->current_stats_state() - counters::num_checking_torrents]; int pos = t->queue_position(); if (pos < 0) @@ -6473,16 +7813,31 @@ retry: } ++total_downloaders; +#if defined TORRENT_EXPENSIVE_INVARIANT_CHECKS unique.insert(t->queue_position()); +#endif } - TORRENT_ASSERT(int(unique.size()) == total_downloaders); - TORRENT_ASSERT(num_active_downloading == m_num_active_downloading); - TORRENT_ASSERT(num_active_finished == m_num_active_finished); + for (int i = 0, j = counters::num_checking_torrents; + j < counters::num_error_torrents + 1; ++i, ++j) + { + TORRENT_ASSERT(torrent_state_gauges[i] == m_stats_counters[j]); + } + +#if defined TORRENT_EXPENSIVE_INVARIANT_CHECKS + TORRENT_ASSERT(int(unique.size()) == total_downloaders); +#endif + TORRENT_ASSERT(num_active_downloading == m_torrent_lists[torrent_want_peers_download].size()); + TORRENT_ASSERT(num_active_finished == m_torrent_lists[torrent_want_peers_finished].size()); + +#if TORRENT_HAS_BOOST_UNORDERED + boost::unordered_set unique_peers; +#else std::set unique_peers; - TORRENT_ASSERT(m_settings.connections_limit > 0); - if (m_settings.choking_algorithm == session_settings::auto_expand_choker) - TORRENT_ASSERT(m_allowed_upload_slots >= m_settings.unchoke_slots_limit); +#endif + TORRENT_ASSERT(m_settings.get_int(settings_pack::connections_limit) > 0); + if (m_settings.get_int(settings_pack::choking_algorithm) == settings_pack::auto_expand_choker) + TORRENT_ASSERT(m_allowed_upload_slots >= m_settings.get_int(settings_pack::unchoke_slots_limit)); int unchokes = 0; int num_optimistic = 0; int disk_queue[2] = {0, 0}; @@ -6507,18 +7862,14 @@ retry: ++num_optimistic; TORRENT_ASSERT(!p->is_choked()); } - if (t && p->peer_info_struct() && !p->peer_info_struct()->web_seed) - { - TORRENT_ASSERT(t->get_policy().has_connection(p)); - } } - TORRENT_ASSERT(disk_queue[0] == m_disk_queues[0]); - TORRENT_ASSERT(disk_queue[1] == m_disk_queues[1]); + TORRENT_ASSERT(disk_queue[peer_connection::download_channel] == m_stats_counters[counters::num_peers_down_disk]); + TORRENT_ASSERT(disk_queue[peer_connection::upload_channel] == m_stats_counters[counters::num_peers_up_disk]); - if (m_settings.num_optimistic_unchoke_slots) + if (m_settings.get_int(settings_pack::num_optimistic_unchoke_slots)) { - TORRENT_ASSERT(num_optimistic <= m_settings.num_optimistic_unchoke_slots); + TORRENT_ASSERT(num_optimistic <= m_settings.get_int(settings_pack::num_optimistic_unchoke_slots)); } if (m_num_unchoked != unchokes) @@ -6534,7 +7885,7 @@ retry: #endif #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING - tracker_logger::tracker_logger(session_impl& ses): m_ses(ses) {} + tracker_logger::tracker_logger(session_interface& ses): m_ses(ses) {} void tracker_logger::tracker_warning(tracker_request const& req , std::string const& str) { @@ -6589,17 +7940,13 @@ retry: void tracker_logger::debug_log(const char* fmt, ...) const { - if (!m_ses.m_logger) return; - va_list v; va_start(v, fmt); - + char usr[1024]; vsnprintf(usr, sizeof(usr), fmt, v); va_end(v); - char buf[1280]; - snprintf(buf, sizeof(buf), "%s: %s\n", time_now_string(), usr); - (*m_ses.m_logger) << buf; + m_ses.session_log("%s", usr); } #endif }} diff --git a/src/session_stats.cpp b/src/session_stats.cpp new file mode 100644 index 000000000..f6c000f52 --- /dev/null +++ b/src/session_stats.cpp @@ -0,0 +1,446 @@ +/* + +Copyright (c) 2012-2013, 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. + +*/ + +#include "libtorrent/session.hpp" // for stats_metric +#include "libtorrent/aux_/session_interface.hpp" // for stats counter names +#include "libtorrent/performance_counters.hpp" // for counters +#include + +namespace libtorrent +{ + + int find_metric_idx(std::vector const& metrics + , char const* name) + { + std::vector::const_iterator i = std::find_if(metrics.begin() + , metrics.end(), boost::bind(&strcmp + , boost::bind(&stats_metric::name, _1), name) == 0); + if (i == metrics.end()) return -1; + return i->value_index; + } + +#define METRIC(category, name, type) { #category "." #name, counters:: name, stats_metric:: type}, + const static stats_metric metrics[] = + { + // ``error_peers`` is the total number of peer disconnects + // caused by an error (not initiated by this client) and + // disconnected initiated by this client (``disconnected_peers``). + METRIC(peer, error_peers, type_counter) + METRIC(peer, disconnected_peers, type_counter) + + // these counters break down the peer errors into more specific + // categories. These errors are what the underlying transport + // reported (i.e. TCP or uTP) + METRIC(peer, eof_peers, type_counter) + METRIC(peer, connreset_peers, type_counter) + METRIC(peer, connrefused_peers, type_counter) + METRIC(peer, connaborted_peers, type_counter) + METRIC(peer, perm_peers, type_counter) + METRIC(peer, buffer_peers, type_counter) + METRIC(peer, unreachable_peers, type_counter) + METRIC(peer, broken_pipe_peers, type_counter) + METRIC(peer, addrinuse_peers, type_counter) + METRIC(peer, no_access_peers, type_counter) + METRIC(peer, invalid_arg_peers, type_counter) + METRIC(peer, aborted_peers, type_counter) + + // these counters break down the peer errors into + // whether they happen on incoming or outgoing peers. + METRIC(peer, error_incoming_peers, type_counter) + METRIC(peer, error_outgoing_peers, type_counter) + + // these counters break down the peer errors into + // whether they happen on encrypted peers (just + // encrypted handshake) and rc4 peers (full stream + // encryption). These can indicate whether encrypted + // peers are more or less likely to fail + METRIC(peer, error_rc4_peers, type_counter) + METRIC(peer, error_encrypted_peers, type_counter) + + // these counters break down the peer errors into + // whether they happen on uTP peers or TCP peers. + // these may indicate whether one protocol is + // more error prone + METRIC(peer, error_tcp_peers, type_counter) + METRIC(peer, error_utp_peers, type_counter) + + // these counters break down the reasons to + // disconnect peers. + METRIC(peer, connect_timeouts, type_counter) + METRIC(peer, uninteresting_peers, type_counter) + METRIC(peer, timeout_peers, type_counter) + METRIC(peer, no_memory_peers, type_counter) + METRIC(peer, too_many_peers, type_counter) + METRIC(peer, transport_timeout_peers, type_counter) + METRIC(peer, num_banned_peers, type_counter) + METRIC(peer, banned_for_hash_failure, type_counter) + + METRIC(peer, connection_attempts, type_counter) + METRIC(peer, connection_attempt_loops, type_counter) + METRIC(peer, incoming_connections, type_counter) + + // the number of peer connections for each kind of socket. + // these counts include half-open (connecting) peers. + METRIC(peer, num_tcp_peers, type_gauge) + METRIC(peer, num_socks5_peers, type_gauge) + METRIC(peer, num_http_proxy_peers, type_gauge) + METRIC(peer, num_utp_peers, type_gauge) + METRIC(peer, num_i2p_peers, type_gauge) + METRIC(peer, num_ssl_peers, type_gauge) + METRIC(peer, num_ssl_socks5_peers, type_gauge) + METRIC(peer, num_ssl_http_proxy_peers, type_gauge) + METRIC(peer, num_ssl_utp_peers, type_gauge) + + METRIC(peer, num_peers_half_open, type_gauge) + METRIC(peer, num_peers_connected, type_gauge) + METRIC(peer, num_peers_up_interested, type_gauge) + METRIC(peer, num_peers_down_interested, type_gauge) + METRIC(peer, num_peers_up_unchoked, type_gauge) + METRIC(peer, num_peers_down_unchoked, type_gauge) + METRIC(peer, num_peers_up_requests, type_gauge) + METRIC(peer, num_peers_down_requests, type_gauge) + METRIC(peer, num_peers_end_game, type_gauge) + METRIC(peer, num_peers_up_disk, type_gauge) + METRIC(peer, num_peers_down_disk, type_gauge) + + // These counters count the number of times the + // network thread wakes up for each respective + // reason. If these counters are very large, it + // may indicate a performance issue, causing the + // network thread to wake up too ofte, wasting CPU. + // mitigate it by increasing buffers and limits + // for the specific trigger that wakes up the + // thread. + METRIC(net, on_read_counter, type_counter) + METRIC(net, on_write_counter, type_counter) + METRIC(net, on_tick_counter, type_counter) + METRIC(net, on_lsd_counter, type_counter) + METRIC(net, on_lsd_peer_counter, type_counter) + METRIC(net, on_udp_counter, type_counter) + METRIC(net, on_accept_counter, type_counter) + METRIC(net, on_disk_counter, type_counter) + + // total number of bytes sent and received by the session + METRIC(net, sent_payload_bytes, type_counter) + METRIC(net, sent_bytes, type_counter) + METRIC(net, sent_ip_overhead_bytes, type_counter) + METRIC(net, sent_tracker_bytes, type_counter) + METRIC(net, recv_payload_bytes, type_counter) + METRIC(net, recv_bytes, type_counter) + METRIC(net, recv_ip_overhead_bytes, type_counter) + METRIC(net, recv_tracker_bytes, type_counter) + + // the number of sockets currently waiting for upload and download + // bandwidht from the rate limiter. + METRIC(net, limiter_up_queue, type_gauge) + METRIC(net, limiter_down_queue, type_gauge) + + // the number of upload and download bytes waiting to be handed out from + // the rate limiter. + METRIC(net, limiter_up_bytes, type_gauge) + METRIC(net, limiter_down_bytes, type_gauge) + + // the number of bytes downloaded that had to be discarded because they + // failed the hash check + METRIC(net, recv_failed_bytes, type_counter) + + // the number of downloaded bytes that were discarded because they + // were downloaded multiple times (from different peers) + METRIC(net, recv_redundant_bytes, type_counter) + + // is false by default and set to true when + // the first incoming connection is established + // this is used to know if the client is behind + // NAT or not. + METRIC(net, has_incoming_connections, type_gauge) + + // these gauges count the number of torrents in + // different states. Each torrent only belongs to + // one of these states. For torrents that could + // belong to multiple of these, the most prominent + // in picked. For instance, a torrent with an error + // counts as an error-torrent, regardless of its other + // state. + METRIC(ses, num_checking_torrents, type_gauge) + METRIC(ses, num_stopped_torrents, type_gauge) + METRIC(ses, num_upload_only_torrents, type_gauge) + METRIC(ses, num_downloading_torrents, type_gauge) + METRIC(ses, num_seeding_torrents, type_gauge) + METRIC(ses, num_queued_seeding_torrents, type_gauge) + METRIC(ses, num_queued_download_torrents, type_gauge) + METRIC(ses, num_error_torrents, type_gauge) + + // the number of torrents that are currently loaded + METRIC(ses, num_loaded_torrents, type_gauge) + METRIC(ses, num_pinned_torrents, type_gauge) + + // these count the number of times a piece has passed the + // hash check, the number of times a piece was successfully + // written to disk and the number of total possible pieces + // added by adding torrents. e.g. when adding a torrent with + // 1000 piece, num_total_pieces_added is incremented by 1000. + METRIC(ses, num_piece_passed, type_counter) + METRIC(ses, num_piece_failed, type_counter) + + METRIC(ses, num_have_pieces, type_counter) + METRIC(ses, num_total_pieces_added, type_counter) + + // this counts the number of times a torrent has been + // evicted (only applies when `dynamic loading of torrent files`_ + // is enabled). + METRIC(ses, torrent_evicted_counter, type_counter) + + // the number of allowed unchoked peers + METRIC(peer, num_unchoke_slots, type_gauge) + + // bittorrent message counters. These counters are incremented + // every time a message of the corresponding type is received from + // or sent to a bittorrent peer. + METRIC(ses, num_incoming_choke, type_counter) + METRIC(ses, num_incoming_unchoke, type_counter) + METRIC(ses, num_incoming_interested, type_counter) + METRIC(ses, num_incoming_not_interested, type_counter) + METRIC(ses, num_incoming_have, type_counter) + METRIC(ses, num_incoming_bitfield, type_counter) + METRIC(ses, num_incoming_request, type_counter) + METRIC(ses, num_incoming_piece, type_counter) + METRIC(ses, num_incoming_cancel, type_counter) + METRIC(ses, num_incoming_dht_port, type_counter) + METRIC(ses, num_incoming_suggest, type_counter) + METRIC(ses, num_incoming_have_all, type_counter) + METRIC(ses, num_incoming_have_none, type_counter) + METRIC(ses, num_incoming_reject, type_counter) + METRIC(ses, num_incoming_allowed_fast, type_counter) + METRIC(ses, num_incoming_ext_handshake, type_counter) + METRIC(ses, num_incoming_pex, type_counter) + METRIC(ses, num_incoming_metadata, type_counter) + METRIC(ses, num_incoming_extended, type_counter) + + METRIC(ses, num_outgoing_choke, type_counter) + METRIC(ses, num_outgoing_unchoke, type_counter) + METRIC(ses, num_outgoing_interested, type_counter) + METRIC(ses, num_outgoing_not_interested, type_counter) + METRIC(ses, num_outgoing_have, type_counter) + METRIC(ses, num_outgoing_bitfield, type_counter) + METRIC(ses, num_outgoing_request, type_counter) + METRIC(ses, num_outgoing_piece, type_counter) + METRIC(ses, num_outgoing_cancel, type_counter) + METRIC(ses, num_outgoing_dht_port, type_counter) + METRIC(ses, num_outgoing_suggest, type_counter) + METRIC(ses, num_outgoing_have_all, type_counter) + METRIC(ses, num_outgoing_have_none, type_counter) + METRIC(ses, num_outgoing_reject, type_counter) + METRIC(ses, num_outgoing_allowed_fast, type_counter) + METRIC(ses, num_outgoing_ext_handshake, type_counter) + METRIC(ses, num_outgoing_pex, type_counter) + METRIC(ses, num_outgoing_metadata, type_counter) + METRIC(ses, num_outgoing_extended, type_counter) + + // the number of pieces considered while picking pieces + METRIC(picker, piece_picker_partial_loops, type_counter) + METRIC(picker, piece_picker_suggest_loops, type_counter) + METRIC(picker, piece_picker_sequential_loops, type_counter) + METRIC(picker, piece_picker_reverse_rare_loops, type_counter) + METRIC(picker, piece_picker_rare_loops, type_counter) + METRIC(picker, piece_picker_rand_start_loops, type_counter) + METRIC(picker, piece_picker_rand_loops, type_counter) + METRIC(picker, piece_picker_busy_loops, type_counter) + + // This breaks down the piece picks into the event that + // triggered it + METRIC(picker, reject_piece_picks, type_counter) + METRIC(picker, unchoke_piece_picks, type_counter) + METRIC(picker, incoming_redundant_piece_picks, type_counter) + METRIC(picker, incoming_piece_picks, type_counter) + METRIC(picker, end_game_piece_picks, type_counter) + METRIC(picker, snubbed_piece_picks, type_counter) + METRIC(picker, interesting_piece_picks, type_counter) + METRIC(picker, hash_fail_piece_picks, type_counter) + + METRIC(disk, write_cache_blocks, type_gauge) + METRIC(disk, read_cache_blocks, type_gauge) + METRIC(disk, pinned_blocks, type_gauge) + METRIC(disk, disk_blocks_in_use, type_gauge) + METRIC(disk, queued_disk_jobs, type_gauge) + METRIC(disk, num_read_jobs, type_gauge) + METRIC(disk, num_write_jobs, type_gauge) + METRIC(disk, num_jobs, type_gauge) + METRIC(disk, num_writing_threads, type_gauge) + METRIC(disk, num_running_threads, type_gauge) + METRIC(disk, blocked_disk_jobs, type_gauge) + + // the number of bytes we have sent to the disk I/O + // thread for writing. Every time we hear back from + // the disk I/O thread with a completed write job, this + // is updated to the number of bytes the disk I/O thread + // is actually waiting for to be written (as opposed to + // bytes just hanging out in the cache) + METRIC(disk, queued_write_bytes, type_gauge) + METRIC(disk, arc_mru_size, type_gauge) + METRIC(disk, arc_mru_ghost_size, type_gauge) + METRIC(disk, arc_mfu_size, type_gauge) + METRIC(disk, arc_mfu_ghost_size, type_gauge) + METRIC(disk, arc_write_size, type_gauge) + METRIC(disk, arc_volatile_size, type_gauge) + + METRIC(disk, num_blocks_written, type_counter) + METRIC(disk, num_blocks_read, type_counter) + METRIC(disk, num_blocks_cache_hits, type_counter) + METRIC(disk, num_write_ops, type_counter) + METRIC(disk, num_read_ops, type_counter) + + // cumulative time spent in various disk jobs, as well + // as total for all disk jobs. Measured in microseconds + METRIC(disk, disk_read_time, type_counter) + METRIC(disk, disk_write_time, type_counter) + METRIC(disk, disk_hash_time, type_counter) + METRIC(disk, disk_job_time, type_counter) + + // The number of nodes in the DHT routing table + METRIC(dht, dht_nodes, type_gauge) + + // The number of replacement nodes in the DHT routing table + METRIC(dht, dht_node_cache, type_gauge) + + // the number of torrents currently tracked by our DHT node + METRIC(dht, dht_torrents, type_gauge) + + // the number of peers currently tracked by our DHT node + METRIC(dht, dht_peers, type_gauge) + + // the number of immutable data items tracked by our DHT node + METRIC(dht, dht_immutable_data, type_gauge) + + // the number of mutable data items tracked by our DHT node + METRIC(dht, dht_mutable_data, type_gauge) + + // the number of RPC observers currently allocated + METRIC(dht, dht_allocated_observers, type_gauge) + + // the total number of DHT messages sent and received + METRIC(dht, dht_messages_in, type_counter) + METRIC(dht, dht_messages_out, type_counter) + + // the number of outgoing messages that failed to be + // sent + METRIC(dht, dht_messages_out_dropped, type_counter) + + // the total number of bytes sent and received by the DHT + METRIC(dht, dht_bytes_in, type_counter) + METRIC(dht, dht_bytes_out, type_counter) + + // the number of DHT messages we've sent and received + // by kind. + METRIC(dht, dht_ping_in, type_counter) + METRIC(dht, dht_ping_out, type_counter) + METRIC(dht, dht_find_node_in, type_counter) + METRIC(dht, dht_find_node_out, type_counter) + METRIC(dht, dht_get_peers_in, type_counter) + METRIC(dht, dht_get_peers_out, type_counter) + METRIC(dht, dht_announce_peer_in, type_counter) + METRIC(dht, dht_announce_peer_out, type_counter) + METRIC(dht, dht_get_in, type_counter) + METRIC(dht, dht_get_out, type_counter) + METRIC(dht, dht_put_in, type_counter) + METRIC(dht, dht_put_out, type_counter) + + // uTP counters + METRIC(utp, utp_packet_loss, type_counter) + METRIC(utp, utp_timeout, type_counter) + METRIC(utp, utp_packets_in, type_counter) + METRIC(utp, utp_packets_out, type_counter) + METRIC(utp, utp_fast_retransmit, type_counter) + METRIC(utp, utp_packet_resend, type_counter) + METRIC(utp, utp_samples_above_target, type_counter) + METRIC(utp, utp_samples_below_target, type_counter) + METRIC(utp, utp_payload_pkts_in, type_counter) + METRIC(utp, utp_payload_pkts_out, type_counter) + METRIC(utp, utp_invalid_pkts_in, type_counter) + METRIC(utp, utp_redundant_pkts_in, type_counter) + + // the buffer sizes accepted by + // socket send and receive calls respectively. + // The larger the buffers are, the more efficient, + // because it reqire fewer system calls per byte. + // The size is 1 << n, where n is the number + // at the end of the counter name. i.e. + // 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, + // 16384, 32768, 65536, 131072, 262144, 524288, 1048576 + // bytes + METRIC(sock_bufs, socket_send_size3, type_counter) + METRIC(sock_bufs, socket_send_size4, type_counter) + METRIC(sock_bufs, socket_send_size5, type_counter) + METRIC(sock_bufs, socket_send_size6, type_counter) + METRIC(sock_bufs, socket_send_size7, type_counter) + METRIC(sock_bufs, socket_send_size8, type_counter) + METRIC(sock_bufs, socket_send_size9, type_counter) + METRIC(sock_bufs, socket_send_size10, type_counter) + METRIC(sock_bufs, socket_send_size11, type_counter) + METRIC(sock_bufs, socket_send_size12, type_counter) + METRIC(sock_bufs, socket_send_size13, type_counter) + METRIC(sock_bufs, socket_send_size14, type_counter) + METRIC(sock_bufs, socket_send_size15, type_counter) + METRIC(sock_bufs, socket_send_size16, type_counter) + METRIC(sock_bufs, socket_send_size17, type_counter) + METRIC(sock_bufs, socket_send_size18, type_counter) + METRIC(sock_bufs, socket_send_size19, type_counter) + METRIC(sock_bufs, socket_send_size20, type_counter) + METRIC(sock_bufs, socket_recv_size3, type_counter) + METRIC(sock_bufs, socket_recv_size4, type_counter) + METRIC(sock_bufs, socket_recv_size5, type_counter) + METRIC(sock_bufs, socket_recv_size6, type_counter) + METRIC(sock_bufs, socket_recv_size7, type_counter) + METRIC(sock_bufs, socket_recv_size8, type_counter) + METRIC(sock_bufs, socket_recv_size9, type_counter) + METRIC(sock_bufs, socket_recv_size10, type_counter) + METRIC(sock_bufs, socket_recv_size11, type_counter) + METRIC(sock_bufs, socket_recv_size12, type_counter) + METRIC(sock_bufs, socket_recv_size13, type_counter) + METRIC(sock_bufs, socket_recv_size14, type_counter) + METRIC(sock_bufs, socket_recv_size15, type_counter) + METRIC(sock_bufs, socket_recv_size16, type_counter) + METRIC(sock_bufs, socket_recv_size17, type_counter) + METRIC(sock_bufs, socket_recv_size18, type_counter) + METRIC(sock_bufs, socket_recv_size19, type_counter) + METRIC(sock_bufs, socket_recv_size20, type_counter) + + // ... more + }; +#undef METRIC + + void get_stats_metric_map(std::vector& stats) + { + stats.resize(sizeof(metrics)/sizeof(metrics[0])); + memcpy(&stats[0], metrics, sizeof(metrics)); + } +} + diff --git a/src/settings.cpp b/src/settings.cpp deleted file mode 100644 index 3f055de82..000000000 --- a/src/settings.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/* - -Copyright (c) 2010-2014, 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. - -*/ - -#include "libtorrent/settings.hpp" -#include "libtorrent/lazy_entry.hpp" -#include "libtorrent/entry.hpp" -#include "libtorrent/assert.hpp" - -#include - -namespace libtorrent -{ - - // TODO: 4 eliminate all use of this mechanism - void load_struct(lazy_entry const& e, void* s, bencode_map_entry const* m, int num) - { - for (int i = 0; i < num; ++i) - { - lazy_entry const* key = e.dict_find(m[i].name); - if (key == 0) continue; - void* dest = ((char*)s) + m[i].offset; - switch (m[i].type) - { - case std_string: - { - if (key->type() != lazy_entry::string_t) continue; - *((std::string*)dest) = key->string_value(); - break; - } - case character: - case integer16: - case boolean: - case integer: - case size_integer: - case time_integer: - case floating_point: - { - if (key->type() != lazy_entry::int_t) continue; - size_type val = key->int_value(); - switch (m[i].type) - { - case character: *((char*)dest) = char(val); break; - case integer16: *((boost::uint16_t*)dest) = boost::uint16_t(val); break; - case integer: *((int*)dest) = int(val); break; - case size_integer: *((size_type*)dest) = size_type(val); break; - case time_integer: *((time_t*)dest) = time_t(val); break; - case floating_point: *((float*)dest) = float(val) / 1000.f; break; - case boolean: *((bool*)dest) = (val != 0); break; - } - } - } - } - } - - void save_struct(entry& e, void const* s, bencode_map_entry const* m, int num, void const* def) - { - if (e.type() != entry::dictionary_t) e = entry(entry::dictionary_t); - for (int i = 0; i < num; ++i) - { - char const* key = m[i].name; - void const* src = ((char*)s) + m[i].offset; - if (def) - { - // if we have a default value for this field - // and it is the default, don't save it - void const* default_value = ((char*)def) + m[i].offset; - switch (m[i].type) - { - case std_string: - if (*((std::string*)src) == *((std::string*)default_value)) continue; - break; - case character: - if (*((char*)src) == *((char*)default_value)) continue; - break; - case integer: - if (*((int*)src) == *((int*)default_value)) continue; - break; - case integer16: - if (*((boost::uint16_t*)src) == *((boost::uint16_t*)default_value)) continue; - break; - case size_integer: - if (*((size_type*)src) == *((size_type*)default_value)) continue; - break; - case time_integer: - if (*((time_t*)src) == *((time_t*)default_value)) continue; - break; - case floating_point: - if (*((float*)src) == *((float*)default_value)) continue; - break; - case boolean: - if (*((bool*)src) == *((bool*)default_value)) continue; - break; - default: TORRENT_ASSERT(false); - } - } - entry& val = e[key]; - TORRENT_ASSERT_VAL(val.type() == entry::undefined_t, val.type()); - switch (m[i].type) - { - case std_string: val = *((std::string*)src); break; - case character: val = *((char*)src); break; - case integer: val = *((int*)src); break; - case integer16: val = *((boost::uint16_t*)src); break; - case size_integer: val = *((size_type*)src); break; - case time_integer: val = *((time_t*)src); break; - case floating_point: val = size_type(*((float*)src) * 1000.f); break; - case boolean: val = *((bool*)src); break; - default: TORRENT_ASSERT(false); - } - } - } - -} - diff --git a/src/settings_pack.cpp b/src/settings_pack.cpp new file mode 100644 index 000000000..9570bd3e2 --- /dev/null +++ b/src/settings_pack.cpp @@ -0,0 +1,726 @@ +/* + +Copyright (c) 2012-2013, 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. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/platform_util.hpp" // for total_physical_ram + +#include + +namespace { + + template + void insort_replace(std::vector >& c, std::pair const& v) + { + typedef std::vector > container_t; + typename container_t::iterator i = std::lower_bound(c.begin(), c.end(), v); + if (i != c.end() && i->first == v.first) i->second = v.second; + else c.insert(i, v); + } +} + +namespace libtorrent +{ + struct str_setting_entry_t + { + // the name of this setting. used for serialization and deserialization + char const* name; + // if present, this function is called when the setting is changed + void (aux::session_impl::*fun)(); + char const *default_value; +#ifndef TORRENT_NO_DEPRECATE + // offset into session_settings, used to map + // settings to the deprecated settings struct + int offset; +#endif + }; + + struct int_setting_entry_t + { + // the name of this setting. used for serialization and deserialization + char const* name; + // if present, this function is called when the setting is changed + void (aux::session_impl::*fun)(); + int default_value; +#ifndef TORRENT_NO_DEPRECATE + // offset into session_settings, used to map + // settings to the deprecated settings struct + int offset; +#endif + }; + + struct bool_setting_entry_t + { + // the name of this setting. used for serialization and deserialization + char const* name; + // if present, this function is called when the setting is changed + void (aux::session_impl::*fun)(); + bool default_value; +#ifndef TORRENT_NO_DEPRECATE + // offset into session_settings, used to map + // settings to the deprecated settings struct + int offset; +#endif + }; + + +// SET_NOPREV - this is used for new settings that don't exist in the +// deprecated session_settings. + +#ifdef TORRENT_NO_DEPRECATE +#define SET(name, default_value, fun) { #name, fun, default_value } +#define SET_NOPREV(name, default_value, fun) { #name, fun, default_value } +#define DEPRECATED_SET(name, default_value, fun) { "", NULL, 0 } +#else +#define SET(name, default_value, fun) { #name, fun, default_value, offsetof(libtorrent::session_settings, name) } +#define SET_NOPREV(name, default_value, fun) { #name, fun, default_value, 0 } +#define DEPRECATED_SET(name, default_value, fun) { #name, fun, default_value, offsetof(libtorrent::session_settings, name) } +#endif + + using aux::session_impl; + + str_setting_entry_t str_settings[settings_pack::num_string_settings] = + { + SET(user_agent, "libtorrent/" LIBTORRENT_VERSION, &session_impl::update_user_agent), + SET(announce_ip, 0, 0), + SET(mmap_cache, 0, 0), + SET(handshake_client_version, 0, 0), + SET_NOPREV(outgoing_interfaces, "", &session_impl::update_outgoing_interfaces), + SET_NOPREV(listen_interfaces, "0.0.0.0:6881", &session_impl::update_listen_interfaces), + SET_NOPREV(proxy_hostname, "", &session_impl::update_proxy), + SET_NOPREV(proxy_username, "", &session_impl::update_proxy), + SET_NOPREV(proxy_password, "", &session_impl::update_proxy), + SET_NOPREV(i2p_hostname, "", &session_impl::update_i2p_bridge) + }; + + bool_setting_entry_t bool_settings[settings_pack::num_bool_settings] = + { + SET(allow_multiple_connections_per_ip, false, 0), + DEPRECATED_SET(ignore_limits_on_local_network, true, &session_impl::update_ignore_rate_limits_on_local_network), + SET(send_redundant_have, true, 0), + SET(lazy_bitfields, true, 0), + SET(use_dht_as_fallback, false, 0), + SET(upnp_ignore_nonrouters, false, 0), + SET(use_parole_mode, true, 0), + SET(use_read_cache, true, 0), + SET(use_write_cache, true, 0), + SET(dont_flush_write_cache, false, 0), + SET(explicit_read_cache, false, 0), + SET(coalesce_reads, false, 0), + SET(coalesce_writes, false, 0), + SET(auto_manage_prefer_seeds, false, 0), + SET(dont_count_slow_torrents, true, 0), + SET(close_redundant_connections, true, 0), + SET(prioritize_partial_pieces, false, 0), + SET(rate_limit_ip_overhead, true, 0), + SET(announce_to_all_trackers, false, 0), + SET(announce_to_all_tiers, false, 0), + SET(prefer_udp_trackers, true, 0), + SET(strict_super_seeding, false, 0), + SET(lock_disk_cache, false, 0), + SET(disable_hash_checks, false, 0), + SET(allow_i2p_mixed, false, 0), + SET(low_prio_disk, true, 0), + SET(volatile_read_cache, false, 0), + SET(guided_read_cache, false, 0), + SET(no_atime_storage, true, 0), + SET(incoming_starts_queued_torrents, false, 0), + SET(report_true_downloaded, false, 0), + SET(strict_end_game_mode, true, 0), + SET(broadcast_lsd, true, 0), + SET(enable_outgoing_utp, true, 0), + SET(enable_incoming_utp, true, 0), + SET(enable_outgoing_tcp, true, 0), + SET(enable_incoming_tcp, true, 0), + SET(ignore_resume_timestamps, false, 0), + SET(no_recheck_incomplete_resume, false, 0), + SET(anonymous_mode, true, &session_impl::update_anonymous_mode), + SET(report_web_seed_downloads, true, &session_impl::update_report_web_seed_downloads), + SET(utp_dynamic_sock_buf, true, 0), + DEPRECATED_SET(rate_limit_utp, false, &session_impl::update_rate_limit_utp), + SET(announce_double_nat, false, 0), + SET(seeding_outgoing_connections, true, 0), + SET(no_connect_privileged_ports, false, &session_impl::update_privileged_ports), + SET(smooth_connects, true, 0), + SET(always_send_user_agent, false, 0), + SET(apply_ip_filter_to_trackers, true, 0), + SET(use_disk_read_ahead, true, 0), + SET(lock_files, false, 0), + SET(contiguous_recv_buffer, true, 0), + SET(ban_web_seeds, true, 0), + SET_NOPREV(allow_partial_disk_writes, true, 0), + SET(force_proxy, false, &session_impl::update_force_proxy), + SET(support_share_mode, true, 0), + SET(support_merkle_torrents, true, 0), + SET(report_redundant_bytes, true, 0), + SET_NOPREV(listen_system_port_fallback, true, 0), + SET(use_disk_cache_pool, false, 0), + SET_NOPREV(announce_crypto_support, true, 0), + SET_NOPREV(enable_upnp, true, &session_impl::update_upnp), + SET_NOPREV(enable_natpmp, true, &session_impl::update_natpmp), + SET_NOPREV(enable_lsd, true, &session_impl::update_lsd), + SET_NOPREV(enable_dht, true, &session_impl::update_dht), + SET_NOPREV(prefer_rc4, false, 0), + SET_NOPREV(proxy_hostnames, true, 0), + SET_NOPREV(proxy_peer_connections, true, 0), + }; + + int_setting_entry_t int_settings[settings_pack::num_int_settings] = + { + SET(tracker_completion_timeout, 60, 0), + SET(tracker_receive_timeout, 40, 0), + SET(stop_tracker_timeout, 5, 0), + SET(tracker_maximum_response_length, 1024*1024, 0), + SET(piece_timeout, 20, 0), + SET(request_timeout, 50, 0), + SET(request_queue_time, 3, 0), + SET(max_allowed_in_request_queue, 500, 0), + SET(max_out_request_queue, 500, 0), + SET(whole_pieces_threshold, 20, 0), + SET(peer_timeout, 120, 0), + SET(urlseed_timeout, 20, 0), + SET(urlseed_pipeline_size, 5, 0), + SET(urlseed_wait_retry, 30, 0), + SET(file_pool_size, 40, 0), + SET(max_failcount, 3, 0), + SET(min_reconnect_time, 60, 0), + SET(peer_connect_timeout, 15, 0), + SET(connection_speed, 6, &session_impl::update_connection_speed), + SET(inactivity_timeout, 600, 0), + SET(unchoke_interval, 15, 0), + SET(optimistic_unchoke_interval, 30, 0), + SET(num_want, 200, 0), + SET(initial_picker_threshold, 4, 0), + SET(allowed_fast_set_size, 10, 0), + SET(suggest_mode, settings_pack::no_piece_suggestions, 0), + SET(max_queued_disk_bytes, 1024 * 1024, &session_impl::update_queued_disk_bytes), + SET(handshake_timeout, 10, 0), + SET(send_buffer_low_watermark, 512, 0), + SET(send_buffer_watermark, 500 * 1024, 0), + SET(send_buffer_watermark_factor, 50, 0), + SET(choking_algorithm, settings_pack::fixed_slots_choker, &session_impl::update_choking_algorithm), + SET(seed_choking_algorithm, settings_pack::round_robin, 0), + SET(cache_size, 1024, 0), + SET(cache_buffer_chunk_size, 0, &session_impl::update_cache_buffer_chunk_size), + SET(cache_expiry, 300, 0), + SET(explicit_cache_interval, 30, 0), + SET(disk_io_write_mode, settings_pack::enable_os_cache, 0), + SET(disk_io_read_mode, settings_pack::enable_os_cache, 0), + SET(outgoing_port, 0, 0), + SET(num_outgoing_ports, 0, 0), + SET(peer_tos, 0, &session_impl::update_peer_tos), + SET(active_downloads, 3, &session_impl::trigger_auto_manage), + SET(active_seeds, 5, &session_impl::trigger_auto_manage), + SET(active_dht_limit, 88, 0), + SET(active_tracker_limit, 1600, 0), + SET(active_lsd_limit, 60, 0), + SET(active_limit, 15, &session_impl::trigger_auto_manage), + SET_NOPREV(active_loaded_limit, 100, &session_impl::trigger_auto_manage), + SET(auto_manage_interval, 30, 0), + SET(seed_time_limit, 24 * 60 * 60, 0), + SET(auto_scrape_interval, 1800, 0), + SET(auto_scrape_min_interval, 300, 0), + SET(max_peerlist_size, 3000, 0), + SET(max_paused_peerlist_size, 1000, 0), + SET(min_announce_interval, 5 * 60, 0), + SET(auto_manage_startup, 60, 0), + SET(seeding_piece_quota, 20, 0), +#ifdef TORRENT_WINDOWS + SET(max_sparse_regions, 30000, 0), +#else + SET(max_sparse_regions, 0, 0), +#endif + SET(max_rejects, 50, 0), + SET(recv_socket_buffer_size, 0, &session_impl::update_socket_buffer_size), + SET(send_socket_buffer_size, 0, &session_impl::update_socket_buffer_size), + SET(file_checks_delay_per_block, 0, 0), + SET(read_cache_line_size, 32, 0), + SET(write_cache_line_size, 16, 0), + SET(optimistic_disk_retry, 10 * 60, 0), + SET(max_suggest_pieces, 10, 0), + SET(local_service_announce_interval, 5 * 60, 0), + SET(dht_announce_interval, 15 * 60, &session_impl::update_dht_announce_interval), + SET(udp_tracker_token_expiry, 60, 0), + SET(default_cache_min_age, 1, 0), + SET(num_optimistic_unchoke_slots, 0, 0), + SET(default_est_reciprocation_rate, 16000, 0), + SET(increase_est_reciprocation_rate, 20, 0), + SET(decrease_est_reciprocation_rate, 3, 0), + SET(max_pex_peers, 50, 0), + SET(tick_interval, 500, 0), + SET(share_mode_target, 3, 0), + SET(upload_rate_limit, 0, &session_impl::update_upload_rate), + SET(download_rate_limit, 0, &session_impl::update_download_rate), + DEPRECATED_SET(local_upload_rate_limit, 0, &session_impl::update_local_upload_rate), + DEPRECATED_SET(local_download_rate_limit, 0, &session_impl::update_local_download_rate), + SET(dht_upload_rate_limit, 4000, &session_impl::update_dht_upload_rate_limit), + SET(unchoke_slots_limit, 8, &session_impl::update_choking_algorithm), + SET(half_open_limit, 0, &session_impl::update_half_open), + SET(connections_limit, 200, &session_impl::update_connections_limit), + SET(connections_slack, 10, 0), + SET(utp_target_delay, 100, 0), + SET(utp_gain_factor, 1500, 0), + SET(utp_min_timeout, 500, 0), + SET(utp_syn_resends, 2, 0), + SET(utp_fin_resends, 2, 0), + SET(utp_num_resends, 6, 0), + SET(utp_connect_timeout, 3000, 0), + SET(utp_delayed_ack, 0, 0), + SET(utp_loss_multiplier, 50, 0), + SET(mixed_mode_algorithm, settings_pack::peer_proportional, 0), + SET(listen_queue_size, 5, 0), + SET(torrent_connect_boost, 10, 0), + SET(alert_queue_size, 1000, &session_impl::update_alert_queue_size), + SET(max_metadata_size, 3 * 1024 * 10240, 0), + SET(hashing_threads, 1, 0), + SET(checking_mem_usage, 256, 0), + SET(predictive_piece_announce, 0, 0), + SET(aio_threads, 4, &session_impl::update_disk_threads), + SET(aio_max, 300, 0), + SET(network_threads, 0, &session_impl::update_network_threads), + SET(ssl_listen, 4433, 0), + SET(tracker_backoff, 250, 0), + SET_NOPREV(share_ratio_limit, 200, 0), + SET_NOPREV(seed_time_ratio_limit, 700, 0), + SET_NOPREV(peer_turnover, 4, 0), + SET_NOPREV(peer_turnover_cutoff, 90, 0), + SET(peer_turnover_interval, 300, 0), + SET_NOPREV(connect_seed_every_n_download, 10, 0), + SET(max_http_recv_buffer_size, 4*1024*204, 0), + SET_NOPREV(max_retry_port_bind, 10, 0), + SET_NOPREV(alert_mask, alert::error_notification, &session_impl::update_alert_mask), + SET_NOPREV(out_enc_policy, settings_pack::pe_enabled, 0), + SET_NOPREV(in_enc_policy, settings_pack::pe_enabled, 0), + SET_NOPREV(allowed_enc_level, settings_pack::pe_both, 0), + SET(inactive_down_rate, 2048, 0), + SET(inactive_up_rate, 2048, 0), + SET_NOPREV(proxy_type, settings_pack::none, &session_impl::update_proxy), + SET_NOPREV(proxy_port, 0, &session_impl::update_proxy), + SET_NOPREV(i2p_port, 0, &session_impl::update_i2p_bridge) + }; + +#undef SET + + int setting_by_name(std::string const& key) + { + for (int k = 0; k < sizeof(str_settings)/sizeof(str_settings[0]); ++k) + { + if (key != str_settings[k].name) continue; + return settings_pack::string_type_base + k; + } + for (int k = 0; k < sizeof(int_settings)/sizeof(int_settings[0]); ++k) + { + if (key != int_settings[k].name) continue; + return settings_pack::int_type_base + k; + } + for (int k = 0; k < sizeof(bool_settings)/sizeof(bool_settings[0]); ++k) + { + if (key != bool_settings[k].name) continue; + return settings_pack::bool_type_base + k; + } + return -1; + } + + char const* name_for_setting(int s) + { + switch (s & settings_pack::type_mask) + { + case settings_pack::string_type_base: + return str_settings[s - settings_pack::string_type_base].name; + case settings_pack::int_type_base: + return int_settings[s - settings_pack::int_type_base].name; + case settings_pack::bool_type_base: + return bool_settings[s - settings_pack::bool_type_base].name; + }; + return ""; + } + + settings_pack* load_pack_from_dict(lazy_entry const* settings) + { + settings_pack* pack = new settings_pack; + + for (int i = 0; i < settings->dict_size(); ++i) + { + std::string key; + lazy_entry const* val; + boost::tie(key, val) = settings->dict_at(i); + switch (val->type()) + { + case lazy_entry::dict_t: + case lazy_entry::list_t: + continue; + case lazy_entry::int_t: + { + bool found = false; + for (int k = 0; k < sizeof(int_settings)/sizeof(int_settings[0]); ++k) + { + if (key != int_settings[k].name) continue; + pack->set_int(settings_pack::int_type_base + k, val->int_value()); + found = true; + break; + } + if (found) continue; + for (int k = 0; k < sizeof(bool_settings)/sizeof(bool_settings[0]); ++k) + { + if (key != bool_settings[k].name) continue; + pack->set_bool(settings_pack::bool_type_base + k, val->int_value()); + break; + } + } + break; + case lazy_entry::string_t: + for (int k = 0; k < sizeof(str_settings)/sizeof(str_settings[0]); ++k) + { + if (key != str_settings[k].name) continue; + pack->set_str(settings_pack::string_type_base + k, val->string_value()); + break; + } + break; + case lazy_entry::none_t: + break; + } + } + return pack; + } + + void save_settings_to_dict(aux::session_settings const& s, entry::dictionary_type& sett) + { + // loop over all settings that differ from default + for (int i = 0; i < settings_pack::num_string_settings; ++i) + { + char const* cmp = str_settings[i].default_value == 0 ? "" : str_settings[i].default_value; + if (cmp == s.m_strings[i]) continue; + sett[str_settings[i].name] = s.m_strings[i]; + } + + for (int i = 0; i < settings_pack::num_int_settings; ++i) + { + if (int_settings[i].default_value == s.m_ints[i]) continue; + sett[int_settings[i].name] = s.m_ints[i]; + } + + for (int i = 0; i < settings_pack::num_bool_settings; ++i) + { + if (bool_settings[i].default_value == s.m_bools[i]) continue; + sett[bool_settings[i].name] = s.m_bools[i]; + } + } + +#ifndef TORRENT_NO_DEPRECATE + settings_pack* load_pack_from_struct(aux::session_settings const& current, session_settings const& s) + { + settings_pack* p = new settings_pack; + + for (int i = 0; i < settings_pack::num_string_settings; ++i) + { + if (str_settings[i].offset == 0) continue; + std::string& val = *(std::string*)(((char*)&s) + str_settings[i].offset); + int setting_name = settings_pack::string_type_base + i; + if (val == current.get_str(setting_name)) continue; + p->set_str(setting_name, val); + } + + for (int i = 0; i < settings_pack::num_int_settings; ++i) + { + if (int_settings[i].offset == 0) continue; + int& val = *(int*)(((char*)&s) + int_settings[i].offset); + int setting_name = settings_pack::int_type_base + i; + if (val == current.get_int(setting_name)) continue; + p->set_int(setting_name, val); + } + + for (int i = 0; i < settings_pack::num_bool_settings; ++i) + { + if (bool_settings[i].offset == 0) continue; + bool& val = *(bool*)(((char*)&s) + bool_settings[i].offset); + int setting_name = settings_pack::bool_type_base + i; + if (val == current.get_bool(setting_name)) continue; + p->set_bool(setting_name, val); + } + + // special case for deprecated float values + int val = current.get_int(settings_pack::share_ratio_limit); + if (s.share_ratio_limit != float(val) / 100.f) + p->set_int(settings_pack::share_ratio_limit, s.share_ratio_limit * 100); + + val = current.get_int(settings_pack::seed_time_ratio_limit); + if (s.seed_time_ratio_limit != float(val) / 100.f) + p->set_int(settings_pack::seed_time_ratio_limit, s.seed_time_ratio_limit * 100); + + val = current.get_int(settings_pack::peer_turnover); + if (s.peer_turnover != float(val) / 100.f) + p->set_int(settings_pack::peer_turnover, s.peer_turnover * 100); + + val = current.get_int(settings_pack::peer_turnover_cutoff); + if (s.peer_turnover_cutoff != float(val) / 100.f) + p->set_int(settings_pack::peer_turnover_cutoff, s.peer_turnover_cutoff * 100); + + return p; + } + + void load_struct_from_settings(aux::session_settings const& current, session_settings& ret) + { + for (int i = 0; i < settings_pack::num_string_settings; ++i) + { + if (str_settings[i].offset == 0) continue; + std::string& val = *(std::string*)(((char*)&ret) + str_settings[i].offset); + val = current.get_str(settings_pack::string_type_base + i); + } + + for (int i = 0; i < settings_pack::num_int_settings; ++i) + { + if (int_settings[i].offset == 0) continue; + int& val = *(int*)(((char*)&ret) + int_settings[i].offset); + val = current.get_int(settings_pack::int_type_base + i); + } + + for (int i = 0; i < settings_pack::num_bool_settings; ++i) + { + if (bool_settings[i].offset == 0) continue; + bool& val = *(bool*)(((char*)&ret) + bool_settings[i].offset); + val = current.get_bool(settings_pack::bool_type_base + i); + } + + // special case for deprecated float values + ret.share_ratio_limit = float(current.get_int(settings_pack::share_ratio_limit)) / 100.f; + ret.seed_time_ratio_limit = float(current.get_int(settings_pack::seed_time_ratio_limit)) / 100.f; + ret.peer_turnover = float(current.get_int(settings_pack::peer_turnover)) / 100.f; + ret.peer_turnover_cutoff = float(current.get_int(settings_pack::peer_turnover_cutoff)) / 100.f; + } +#endif + + void initialize_default_settings(aux::session_settings& s) + { + for (int i = 0; i < settings_pack::num_string_settings; ++i) + { + if (str_settings[i].default_value == 0) continue; + s.set_str(settings_pack::string_type_base + i, str_settings[i].default_value); + TORRENT_ASSERT(s.get_str(settings_pack::string_type_base + i) == str_settings[i].default_value); + } + + for (int i = 0; i < settings_pack::num_int_settings; ++i) + { + s.set_int(settings_pack::int_type_base + i, int_settings[i].default_value); + TORRENT_ASSERT(s.get_int(settings_pack::int_type_base + i) == int_settings[i].default_value); + } + + for (int i = 0; i < settings_pack::num_bool_settings; ++i) + { + s.set_bool(settings_pack::bool_type_base + i, bool_settings[i].default_value); + TORRENT_ASSERT(s.get_bool(settings_pack::bool_type_base + i) == bool_settings[i].default_value); + } + + // this seems questionable... +/* + // Some settings have dynamic defaults depending on the machine + // for instance, the disk cache size + + // by default, set the cahe size to an 8:th of the total amount of physical RAM + boost::uint64_t phys_ram = total_physical_ram(); + if (phys_ram > 0) s.set_int(settings_pack::cache_size, phys_ram / 16 / 1024 / 8); +*/ + } + + void apply_pack(settings_pack const* pack, aux::session_settings& sett + , aux::session_impl* ses) + { + typedef void (aux::session_impl::*fun_t)(); + std::vector callbacks; + + for (std::vector >::const_iterator i = pack->m_strings.begin() + , end(pack->m_strings.end()); i != end; ++i) + { + // disregard setting indices that are not string types + if ((i->first & settings_pack::type_mask) != settings_pack::string_type_base) + continue; + + // ignore settings that are out of bounds + int index = i->first & settings_pack::index_mask; + if (index < 0 || index >= settings_pack::num_string_settings) + continue; + + sett.set_str(i->first, i->second); + str_setting_entry_t const& sa = str_settings[i->first & settings_pack::index_mask]; + if (sa.fun && ses + && std::find(callbacks.begin(), callbacks.end(), sa.fun) == callbacks.end()) + callbacks.push_back(sa.fun); + } + + for (std::vector >::const_iterator i = pack->m_ints.begin() + , end(pack->m_ints.end()); i != end; ++i) + { + // disregard setting indices that are not string types + if ((i->first & settings_pack::type_mask) != settings_pack::int_type_base) + continue; + + // ignore settings that are out of bounds + int index = i->first & settings_pack::index_mask; + if (index < 0 || index >= settings_pack::num_int_settings) + continue; + + sett.set_int(i->first, i->second); + int_setting_entry_t const& sa = int_settings[i->first & settings_pack::index_mask]; + if (sa.fun && ses + && std::find(callbacks.begin(), callbacks.end(), sa.fun) == callbacks.end()) + callbacks.push_back(sa.fun); + } + + for (std::vector >::const_iterator i = pack->m_bools.begin() + , end(pack->m_bools.end()); i != end; ++i) + { + // disregard setting indices that are not string types + if ((i->first & settings_pack::type_mask) != settings_pack::bool_type_base) + continue; + + // ignore settings that are out of bounds + int index = i->first & settings_pack::index_mask; + if (index < 0 || index >= settings_pack::num_bool_settings) + continue; + + sett.set_bool(i->first, i->second); + bool_setting_entry_t const& sa = bool_settings[i->first & settings_pack::index_mask]; + if (sa.fun && ses + && std::find(callbacks.begin(), callbacks.end(), sa.fun) == callbacks.end()) + callbacks.push_back(sa.fun); + } + + // call the callbacks once all the settings have been applied, and + // only once per callback + for (std::vector::iterator i = callbacks.begin(), end(callbacks.end()); + i != end; ++i) + { + fun_t const& f = *i; + (ses->*f)(); + } + } + + void settings_pack::set_str(int name, std::string val) + { + TORRENT_ASSERT((name & type_mask) == string_type_base); + if ((name & type_mask) != string_type_base) return; + std::pair v(name, val); + insort_replace(m_strings, v); + } + + void settings_pack::set_int(int name, int val) + { + TORRENT_ASSERT((name & type_mask) == int_type_base); + if ((name & type_mask) != int_type_base) return; + std::pair v(name, val); + insort_replace(m_ints, v); + } + + void settings_pack::set_bool(int name, bool val) + { + TORRENT_ASSERT((name & type_mask) == bool_type_base); + if ((name & type_mask) != bool_type_base) return; + std::pair v(name, val); + insort_replace(m_bools, v); + } + + bool settings_pack::has_val(int name) const + { + switch (name & type_mask) + { + case string_type_base: + { + std::pair v(name, std::string()); + std::vector >::const_iterator i = + std::lower_bound(m_strings.begin(), m_strings.end(), v); + return i != m_strings.end() && i->first == name; + } + case int_type_base: + { + std::pair v(name, 0); + std::vector >::const_iterator i = + std::lower_bound(m_ints.begin(), m_ints.end(), v); + return i != m_ints.end() && i->first == name; + } + case bool_type_base: + { + std::pair v(name, false); + std::vector >::const_iterator i = + std::lower_bound(m_bools.begin(), m_bools.end(), v); + return i != m_bools.end() && i->first == name; + } + } + TORRENT_ASSERT(false); + return false; + } + + std::string settings_pack::get_str(int name) const + { + TORRENT_ASSERT((name & type_mask) == string_type_base); + if ((name & type_mask) != string_type_base) return std::string(); + + std::pair v(name, std::string()); + std::vector >::const_iterator i + = std::lower_bound(m_strings.begin(), m_strings.end(), v); + if (i != m_strings.end() && i->first == name) return i->second; + return std::string(); + } + + int settings_pack::get_int(int name) const + { + TORRENT_ASSERT((name & type_mask) == int_type_base); + if ((name & type_mask) != int_type_base) return 0; + + std::pair v(name, 0); + std::vector >::const_iterator i + = std::lower_bound(m_ints.begin(), m_ints.end(), v); + if (i != m_ints.end() && i->first == name) return i->second; + return 0; + } + + bool settings_pack::get_bool(int name) const + { + TORRENT_ASSERT((name & type_mask) == bool_type_base); + if ((name & type_mask) != bool_type_base) return false; + + std::pair v(name, false); + std::vector >::const_iterator i + = std::lower_bound(m_bools.begin(), m_bools.end(), v); + if (i != m_bools.end() && i->first == name) return i->second; + return false; + } + + void settings_pack::clear() + { + m_strings.clear(); + m_ints.clear(); + m_bools.clear(); + } +} + diff --git a/src/smart_ban.cpp b/src/smart_ban.cpp index 6b47569cf..a02e58fc2 100644 --- a/src/smart_ban.cpp +++ b/src/smart_ban.cpp @@ -61,10 +61,15 @@ POSSIBILITY OF SUCH DAMAGE. //#define TORRENT_LOG_HASH_FAILURES +#ifdef TORRENT_LOGGING +#include "libtorrent/socket_io.hpp" +#endif + #ifdef TORRENT_LOG_HASH_FAILURES #include "libtorrent/peer_id.hpp" // sha1_hash #include "libtorrent/escape_string.hpp" // to_hex +#include "libtorrent/socket_io.hpp" void log_hash_block(FILE** f, libtorrent::torrent const& t, int piece, int block , libtorrent::address a, char const* bytes, int len, bool corrupt) @@ -134,8 +139,8 @@ namespace void on_piece_pass(int p) { #ifdef TORRENT_LOGGING - (*m_torrent.session().m_logger) << time_now_string() << " PIECE PASS [ p: " << p - << " | block_hash_size: " << m_block_hashes.size() << " ]\n"; + m_torrent.debug_log(" PIECE PASS [ p: %d | block_hash_size: %d ]" + , p, int(m_block_hashes.size())); #endif // has this piece failed earlier? If it has, go through the // CRCs from the time it failed and ban the peers that @@ -150,8 +155,9 @@ namespace { if (i->first.block_index == pb.block_index) { - m_torrent.filesystem().async_read(r, boost::bind(&smart_ban_plugin::on_read_ok_block - , shared_from_this(), *i, _1, _2)); + m_torrent.session().disk_thread().async_read(&m_torrent.storage() + , r, boost::bind(&smart_ban_plugin::on_read_ok_block + , shared_from_this(), *i, i->second.peer->address(), _1), (void*)1); m_block_hashes.erase(i++); } else @@ -201,8 +207,14 @@ namespace { if (*i != 0) { - m_torrent.filesystem().async_read(r, boost::bind(&smart_ban_plugin::on_read_failed_block - , shared_from_this(), pb, ((policy::peer*)*i)->address(), _1, _2)); + // for very sad and involved reasons, this read need to force a copy out of the cache + // since the piece has failed, this block is very likely to be replaced with a newly + // downloaded one very soon, and to get a block by reference would fail, since the + // block read will have been deleted by the time it gets back to the network thread + m_torrent.session().disk_thread().async_read(&m_torrent.storage(), r + , boost::bind(&smart_ban_plugin::on_read_failed_block + , shared_from_this(), pb, ((torrent_peer*)*i)->address(), _1), (void*)1 + , disk_io_job::force_copy); } r.start += 16*1024; @@ -219,35 +231,35 @@ namespace // a peer. struct block_entry { - policy::peer* peer; + torrent_peer* peer; sha1_hash digest; }; - void on_read_failed_block(piece_block b, address a, int ret, disk_io_job const& j) + void on_read_failed_block(piece_block b, address a, disk_io_job const* j) { - TORRENT_ASSERT(m_torrent.session().is_network_thread()); + TORRENT_ASSERT(m_torrent.session().is_single_thread()); - disk_buffer_holder buffer(m_torrent.session(), j.buffer); + disk_buffer_holder buffer(m_torrent.session(), *j); // ignore read errors - if (ret != j.buffer_size) return; + if (j->ret != j->d.io.buffer_size) return; hasher h; - h.update(j.buffer, j.buffer_size); + h.update(j->buffer, j->d.io.buffer_size); h.update((char const*)&m_salt, sizeof(m_salt)); std::pair range - = m_torrent.get_policy().find_peers(a); + = m_torrent.find_peers(a); // there is no peer with this address anymore if (range.first == range.second) return; - policy::peer* p = (*range.first); + torrent_peer* p = (*range.first); block_entry e = {p, h.final()}; #ifdef TORRENT_LOG_HASH_FAILURES log_hash_block(&m_log_file, m_torrent, b.piece_index - , b.block_index, p->address(), j.buffer, j.buffer_size, true); + , b.block_index, p->address(), j->buffer, j->buffer_size, true); #endif std::map::iterator i = m_block_hashes.lower_bound(b); @@ -255,18 +267,13 @@ namespace if (i != m_block_hashes.end() && i->first == b && i->second.peer == p) { // this peer has sent us this block before - if (i->second.digest != e.digest) + // if the peer is already banned, it doesn't matter if it sent + // good or bad data. Nothings going to change it + if (!p->banned && i->second.digest != e.digest) { // this time the digest of the block is different // from the first time it sent it // at least one of them must be bad - - // verify that this is not a dangling pointer - // if the pointer is in the policy's list, it - // still live, if it's not, it has been removed - // and we can't use this pointer - if (!m_torrent.get_policy().has_peer(p)) return; - #if defined TORRENT_LOGGING || defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING char const* client = "-"; peer_info info; @@ -275,16 +282,16 @@ namespace p->connection->get_peer_info(info); client = info.client.c_str(); } - (*m_torrent.session().m_logger) << time_now_string() << " BANNING PEER [ p: " << b.piece_index - << " | b: " << b.block_index - << " | c: " << client - << " | hash1: " << i->second.digest - << " | hash2: " << e.digest - << " | ip: " << p->ip() << " ]\n"; + m_torrent.debug_log(" BANNING PEER [ p: %d | b: %d | c: %s" + " | hash1: %s | hash2: %s | ip: %s ]" + , b.piece_index, b.block_index, client + , to_hex(i->second.digest.to_string()).c_str() + , to_hex(e.digest.to_string()).c_str() + , print_endpoint(p->ip()).c_str()); #endif - m_torrent.get_policy().ban_peer(p); + m_torrent.ban_peer(p); if (p->connection) p->connection->disconnect( - errors::peer_banned); + errors::peer_banned, peer_connection_interface::op_bittorrent); } // we already have this exact entry in the map // we don't have to insert it @@ -301,39 +308,46 @@ namespace p->connection->get_peer_info(info); client = info.client.c_str(); } - (*m_torrent.session().m_logger) << time_now_string() << " STORE BLOCK CRC [ p: " << b.piece_index - << " | b: " << b.block_index - << " | c: " << client - << " | digest: " << e.digest - << " | ip: " << p->ip() << " ]\n"; + m_torrent.debug_log(" STORE BLOCK CRC [ p: %d | b: %d | c: %s" + " | digest: %s | ip: %s ]" + , b.piece_index, b.block_index, client + , to_hex(e.digest.to_string()).c_str() + , print_address(p->ip().address()).c_str()); #endif } - void on_read_ok_block(std::pair b, int ret, disk_io_job const& j) + void on_read_ok_block(std::pair b, address a, disk_io_job const* j) { - TORRENT_ASSERT(m_torrent.session().is_network_thread()); + TORRENT_ASSERT(m_torrent.session().is_single_thread()); - disk_buffer_holder buffer(m_torrent.session(), j.buffer); + disk_buffer_holder buffer(m_torrent.session(), *j); // ignore read errors - if (ret != j.buffer_size) return; + if (j->ret != j->d.io.buffer_size) return; hasher h; - h.update(j.buffer, j.buffer_size); + h.update(j->buffer, j->d.io.buffer_size); h.update((char const*)&m_salt, sizeof(m_salt)); sha1_hash ok_digest = h.final(); - policy::peer* p = b.second.peer; - if (b.second.digest == ok_digest) return; - if (p == 0) return; #ifdef TORRENT_LOG_HASH_FAILURES log_hash_block(&m_log_file, m_torrent, b.first.piece_index - , b.first.block_index, p->address(), j.buffer, j.buffer_size, false); + , b.first.block_index, a, j->buffer, j->buffer_size, false); #endif - if (!m_torrent.get_policy().has_peer(p)) return; + // find the peer + std::pair range + = m_torrent.find_peers(a); + if (range.first == range.second) return; + torrent_peer* p = NULL; + for (; range.first != range.second; ++range.first) + { + if (b.second.peer != *range.first) continue; + p = *range.first; + } + if (p == NULL) return; #ifdef TORRENT_LOGGING char const* client = "-"; @@ -343,16 +357,16 @@ namespace p->connection->get_peer_info(info); client = info.client.c_str(); } - (*m_torrent.session().m_logger) << time_now_string() << " BANNING PEER [ p: " << b.first.piece_index - << " | b: " << b.first.block_index - << " | c: " << client - << " | ok_digest: " << ok_digest - << " | bad_digest: " << b.second.digest - << " | ip: " << p->ip() << " ]\n"; + m_torrent.debug_log(" BANNING PEER [ p: %d | b: %d | c: %s" + " | ok_digest: %s | bad_digest: %s | ip: %s ]" + , b.first.piece_index, b.first.block_index, client + , to_hex(ok_digest.to_string()).c_str() + , to_hex(b.second.digest.to_string()).c_str() + , print_address(p->ip().address()).c_str()); #endif - m_torrent.get_policy().ban_peer(p); + m_torrent.ban_peer(p); if (p->connection) p->connection->disconnect( - errors::peer_banned); + errors::peer_banned, peer_connection_interface::op_bittorrent); } torrent& m_torrent; diff --git a/src/socket_io.cpp b/src/socket_io.cpp index d17cded5b..62af4003a 100644 --- a/src/socket_io.cpp +++ b/src/socket_io.cpp @@ -84,6 +84,58 @@ namespace libtorrent return print_endpoint(tcp::endpoint(ep.address(), ep.port())); } + tcp::endpoint parse_endpoint(std::string str, error_code& ec) + { + tcp::endpoint ret; + + std::string::iterator start = str.begin(); + std::string::iterator port_pos; + // remove white spaces in front of the string + while (start != str.end() && is_space(*start)) + ++start; + + // this is for IPv6 addresses + if (start != str.end() && *start == '[') + { + ++start; + port_pos = std::find(start, str.end(), ']'); + if (port_pos == str.end()) + { + ec = errors::expected_close_bracket_in_address; + return ret; + } + *port_pos = '\0'; + ++port_pos; +#if TORRENT_USE_IPV6 + ret.address(address_v6::from_string(&*start, ec)); +#else + ec = boost::asio::error::address_family_not_supported; +#endif + if (ec) return ret; + } + else + { + port_pos = std::find(start, str.end(), ':'); + if (port_pos == str.end()) + { + ec = errors::invalid_port; + return ret; + } + *port_pos = '\0'; + ret.address(address_v4::from_string(&*start, ec)); + if (ec) return ret; + } + + if (port_pos == str.end()) + { + ec = errors::invalid_port; + return ret; + } + ++port_pos; + ret.port(std::atoi(&*port_pos)); + return ret; + } + void hash_address(address const& ip, sha1_hash& h) { #if TORRENT_USE_IPV6 diff --git a/src/socket_type.cpp b/src/socket_type.cpp index 859de3b20..7267af6cc 100644 --- a/src/socket_type.cpp +++ b/src/socket_type.cpp @@ -30,6 +30,7 @@ POSSIBILITY OF SUCH DAMAGE. */ +#include "libtorrent/config.hpp" #include "libtorrent/socket_type.hpp" #ifdef TORRENT_USE_OPENSSL @@ -41,6 +42,10 @@ POSSIBILITY OF SUCH DAMAGE. #endif +#if defined TORRENT_ASIO_DEBUGGING +#include "libtorrent/debug.hpp" +#endif + namespace libtorrent { diff --git a/src/socks5_stream.cpp b/src/socks5_stream.cpp index b1127d8d6..ea3aef15f 100644 --- a/src/socks5_stream.cpp +++ b/src/socks5_stream.cpp @@ -77,24 +77,13 @@ namespace libtorrent #if defined TORRENT_ASIO_DEBUGGING complete_async("socks5_stream::name_lookup"); #endif - if (e || i == tcp::resolver::iterator()) - { - (*h)(e); - error_code ec; - close(ec); - return; - } + if (handle_error(e, h)) return; error_code ec; if (!m_sock.is_open()) { m_sock.open(i->endpoint().protocol(), ec); - if (ec) - { - (*h)(ec); - close(ec); - return; - } + if (handle_error(ec, h)) return; } // TOOD: we could bind the socket here, since we know what the @@ -111,13 +100,7 @@ namespace libtorrent #if defined TORRENT_ASIO_DEBUGGING complete_async("socks5_stream::connected"); #endif - if (e) - { - (*h)(e); - error_code ec; - close(ec); - return; - } + if (handle_error(e, h)) return; using namespace libtorrent::detail; if (m_version == 5) @@ -160,13 +143,7 @@ namespace libtorrent #if defined TORRENT_ASIO_DEBUGGING complete_async("socks5_stream::handshake1"); #endif - if (e) - { - (*h)(e); - error_code ec; - close(ec); - return; - } + if (handle_error(e, h)) return; #if defined TORRENT_ASIO_DEBUGGING add_outstanding_async("socks5_stream::handshake2"); @@ -181,13 +158,7 @@ namespace libtorrent #if defined TORRENT_ASIO_DEBUGGING complete_async("socks5_stream::handshake2"); #endif - if (e) - { - (*h)(e); - error_code ec; - close(ec); - return; - } + if (handle_error(e, h)) return; using namespace libtorrent::detail; @@ -247,13 +218,7 @@ namespace libtorrent #if defined TORRENT_ASIO_DEBUGGING complete_async("socks5_stream::handshake3"); #endif - if (e) - { - (*h)(e); - error_code ec; - close(ec); - return; - } + if (handle_error(e, h)) return; #if defined TORRENT_ASIO_DEBUGGING add_outstanding_async("socks5_stream::handshake4"); @@ -269,13 +234,7 @@ namespace libtorrent #if defined TORRENT_ASIO_DEBUGGING complete_async("socks5_stream::handshake4"); #endif - if (e) - { - (*h)(e); - error_code ec; - close(ec); - return; - } + if (handle_error(e, h)) return; using namespace libtorrent::detail; @@ -364,13 +323,7 @@ namespace libtorrent #if defined TORRENT_ASIO_DEBUGGING complete_async("socks5_stream::connect1"); #endif - if (e) - { - (*h)(e); - error_code ec; - close(ec); - return; - } + if (handle_error(e, h)) return; if (m_version == 5) m_buffer.resize(6 + 4); // assume an IPv4 address @@ -389,13 +342,7 @@ namespace libtorrent #if defined TORRENT_ASIO_DEBUGGING complete_async("socks5_stream::connect2"); #endif - if (e) - { - (*h)(e); - error_code ec; - close(ec); - return; - } + if (handle_error(e, h)) return; using namespace libtorrent::detail; @@ -540,13 +487,7 @@ namespace libtorrent #endif using namespace libtorrent::detail; - if (e) - { - (*h)(e); - error_code ec; - close(ec); - return; - } + if (handle_error(e, h)) return; if (m_command == 2) { diff --git a/src/stat.cpp b/src/stat.cpp index 353908928..3e0bdcf5b 100644 --- a/src/stat.cpp +++ b/src/stat.cpp @@ -42,7 +42,6 @@ void stat_channel::second_tick(int tick_interval_ms) int sample = int(size_type(m_counter) * 1000 / tick_interval_ms); TORRENT_ASSERT(sample >= 0); m_5_sec_average = size_type(m_5_sec_average) * 4 / 5 + sample / 5; - m_30_sec_average = size_type(m_30_sec_average) * 29 / 30 + sample / 30; m_counter = 0; } diff --git a/src/stat_cache.cpp b/src/stat_cache.cpp new file mode 100644 index 000000000..f16dbf66a --- /dev/null +++ b/src/stat_cache.cpp @@ -0,0 +1,97 @@ +/* + +Copyright (c) 2012-2013, Arvid Norberg, Daniel Wallin +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. + +*/ + +#include "libtorrent/stat_cache.hpp" +#include "libtorrent/assert.hpp" + +namespace libtorrent +{ + stat_cache::stat_cache() {} + stat_cache::~stat_cache() {} + + void stat_cache::set_cache(int i, size_type size, time_t time) + { + TORRENT_ASSERT(i >= 0); + if (i >= int(m_stat_cache.size())) + m_stat_cache.resize(i + 1, not_in_cache); + m_stat_cache[i].file_size = size; + m_stat_cache[i].file_time = time; + } + + void stat_cache::set_dirty(int i) + { + TORRENT_ASSERT(i >= 0); + if (i >= int(m_stat_cache.size())) return; + m_stat_cache[i].file_size = not_in_cache; + } + + void stat_cache::set_noexist(int i) + { + TORRENT_ASSERT(i >= 0); + if (i >= int(m_stat_cache.size())) + m_stat_cache.resize(i + 1, not_in_cache); + m_stat_cache[i].file_size = no_exist; + } + + void stat_cache::set_error(int i) + { + TORRENT_ASSERT(i >= 0); + if (i >= int(m_stat_cache.size())) + m_stat_cache.resize(i + 1, not_in_cache); + m_stat_cache[i].file_size = cache_error; + } + + size_type stat_cache::get_filesize(int i) const + { + if (i >= int(m_stat_cache.size())) return not_in_cache; + return m_stat_cache[i].file_size; + } + + time_t stat_cache::get_filetime(int i) const + { + if (i >= int(m_stat_cache.size())) return not_in_cache; + if (m_stat_cache[i].file_size < 0) return m_stat_cache[i].file_size; + return m_stat_cache[i].file_time; + } + + void stat_cache::init(int num_files) + { + m_stat_cache.resize(num_files, not_in_cache); + } + + void stat_cache::clear() + { + std::vector().swap(m_stat_cache); + } + +} + diff --git a/src/storage.cpp b/src/storage.cpp index 574a6527b..afbe3de85 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -63,7 +63,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/aux_/session_impl.hpp" #include "libtorrent/disk_buffer_holder.hpp" #include "libtorrent/alloca.hpp" -#include "libtorrent/allocator.hpp" // page_size +#include "libtorrent/stat_cache.hpp" #include @@ -91,140 +91,57 @@ POSSIBILITY OF SUCH DAMAGE. // for convert_to_wstring and convert_to_native #include "libtorrent/escape_string.hpp" +#define DEBUG_STORAGE 0 +#define DEBUG_DELETE_FILES 0 + +#define DLOG if (DEBUG_STORAGE) fprintf +#define DFLOG if (DEBUG_DELETE_FILES) fprintf + namespace libtorrent { - std::vector > get_filesizes( - file_storage const& storage, std::string const& p) + +#ifdef TORRENT_DISK_STATS + static atomic_count event_id; + static mutex disk_access_mutex; + + // this is opened and closed by the disk_io_thread class + FILE* g_access_log = NULL; + + enum access_log_flags_t { - std::string save_path = complete(p); - std::vector > sizes; - for (int i = 0; i < storage.num_files(); ++i) - { - size_type size = 0; - std::time_t time = 0; - - if (!storage.pad_file_at(i)) - { - file_status s; - error_code ec; - stat_file(storage.file_path(i, save_path), &s, ec); - - if (!ec) - { - size = s.file_size; - time = s.mtime; - } - } - sizes.push_back(std::make_pair(size, time)); - } - return sizes; - } - - // matches the sizes and timestamps of the files passed in - // in non-compact mode, actual file sizes and timestamps - // are allowed to be bigger and more recent than the fast - // resume data. This is because full allocation will not move - // pieces, so any older version of the resume data will - // still be a correct subset of the actual data on disk. - enum flags_t - { - compact_mode = 1, - ignore_timestamps = 2 + op_read = 0, + op_write = 1, + op_start = 0, + op_end = 2 }; - bool match_filesizes( - file_storage const& fs - , std::string p - , std::vector > const& sizes - , int flags - , error_code& error) + void write_access_log(boost::uint64_t offset, boost::uint32_t fileid, int flags, ptime timestamp) { - if ((int)sizes.size() != fs.num_files()) + if (g_access_log == NULL) return; + + // the event format in the log is: + // uint64_t timestamp (microseconds) + // uint64_t file offset + // uint32_t file-id + // uint8_t event (0: start read, 1: start write, 2: complete read, 4: complete write) + char event[29]; + char* ptr = event; + detail::write_uint64(total_microseconds((timestamp - min_time())), ptr); + detail::write_uint64(offset, ptr); + detail::write_uint64((boost::uint64_t)event_id++, ptr); + detail::write_uint32(fileid, ptr); + detail::write_uint8(flags, ptr); + + mutex::scoped_lock l(disk_access_mutex); + int ret = fwrite(event, 1, sizeof(event), g_access_log); + l.unlock(); + if (ret != sizeof(event)) { - error = errors::mismatching_number_of_files; - return false; + fprintf(stderr, "ERROR writing to disk access log: (%d) %s\n" + , errno, strerror(errno)); } - p = complete(p); - - std::vector >::const_iterator size_iter - = sizes.begin(); - for (int i = 0; i < fs.num_files(); ++i, ++size_iter) - { - size_type size = 0; - std::time_t time = 0; - if (fs.pad_file_at(i)) continue; - - file_status s; - error_code ec; - stat_file(fs.file_path(i, p), &s, ec); - - if (!ec) - { - size = s.file_size; - time = s.mtime; - } - - if (((flags & compact_mode) && size != size_iter->first) - || (!(flags & compact_mode) && size < size_iter->first)) - { - error = errors::mismatching_file_size; - return false; - } - - if (flags & ignore_timestamps) continue; - - // if there is no timestamp in the resume data, ignore it - if (size_iter->second == 0) continue; - - // allow one second 'slack', because of FAT volumes - // in sparse mode, allow the files to be more recent - // than the resume data, but only by 5 minutes - if (((flags & compact_mode) && (time > size_iter->second + 1 || time < size_iter->second - 1)) || - (!(flags & compact_mode) && (time > size_iter->second + 5 * 60 || time < size_iter->second - 1))) - { - error = errors::mismatching_file_timestamp; - return false; - } - } - return true; - } - - void storage_interface::set_error(std::string const& file, error_code const& ec) const - { - m_error_file = file; - m_error = ec; - } - - // for backwards compatibility, let the default readv and - // writev implementations be implemented in terms of the - // old read and write - int storage_interface::readv(file::iovec_t const* bufs - , int slot, int offset, int num_bufs, int flags) - { - int ret = 0; - for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i < end; ++i) - { - int r = read((char*)i->iov_base, slot, offset, i->iov_len); - offset += i->iov_len; - if (r == -1) return -1; - ret += r; - } - return ret; - } - - int storage_interface::writev(file::iovec_t const* bufs, int slot - , int offset, int num_bufs, int flags) - { - int ret = 0; - for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i < end; ++i) - { - int r = write((char const*)i->iov_base, slot, offset, i->iov_len); - offset += i->iov_len; - if (r == -1) return -1; - ret += r; - } - return ret; } +#endif int copy_bufs(file::iovec_t const* bufs, int bytes, file::iovec_t* target) { @@ -261,7 +178,7 @@ namespace libtorrent } } - int bufs_size(file::iovec_t const* bufs, int num_bufs) + TORRENT_EXTRA_EXPORT int bufs_size(file::iovec_t const* bufs, int num_bufs) { int size = 0; for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i < end; ++i) @@ -290,119 +207,126 @@ namespace libtorrent } #endif - int piece_manager::hash_for_slot(int slot, partial_hash& ph, int piece_size - , int small_piece_size, sha1_hash* small_hash) + default_storage::default_storage(storage_params const& params) + : m_files(*params.files) + , m_pool(*params.pool) + , m_allocate_files(params.mode == storage_mode_allocate) { - TORRENT_ASSERT_VAL(!error(), error()); - int num_read = 0; - int slot_size = piece_size - ph.offset; - if (slot_size > 0) - { - int block_size = 16 * 1024; - if (m_storage->disk_pool()) block_size = m_storage->disk_pool()->block_size(); - int size = slot_size; - int num_blocks = (size + block_size - 1) / block_size; - - // when we optimize for speed we allocate all the buffers we - // need for the rest of the piece, and read it all in one call - // and then hash it. When optimizing for memory usage, we read - // one block at a time and hash it. This ends up only using a - // single buffer - if (m_storage->settings().optimize_hashing_for_speed) - { - file::iovec_t* bufs = TORRENT_ALLOCA(file::iovec_t, num_blocks); - for (int i = 0; i < num_blocks; ++i) - { - bufs[i].iov_base = m_storage->disk_pool()->allocate_buffer("hash temp"); - bufs[i].iov_len = (std::min)(block_size, size); - size -= bufs[i].iov_len; - } - // deliberately pass in 0 as flags, to disable random_access - num_read = m_storage->readv(bufs, slot, ph.offset, num_blocks, 0); - // TODO: if the read fails, set error and exit immediately - - for (int i = 0; i < num_blocks; ++i) - { - if (small_hash && small_piece_size <= block_size) - { - ph.h.update((char const*)bufs[i].iov_base, small_piece_size); - *small_hash = hasher(ph.h).final(); - small_hash = 0; // avoid this case again - if (int(bufs[i].iov_len) > small_piece_size) - ph.h.update((char const*)bufs[i].iov_base + small_piece_size - , bufs[i].iov_len - small_piece_size); - } - else - { - ph.h.update((char const*)bufs[i].iov_base, bufs[i].iov_len); - small_piece_size -= bufs[i].iov_len; - } - ph.offset += bufs[i].iov_len; - m_storage->disk_pool()->free_buffer((char*)bufs[i].iov_base); - } - } - else - { - file::iovec_t buf; - disk_buffer_holder holder(*m_storage->disk_pool() - , m_storage->disk_pool()->allocate_buffer("hash temp")); - buf.iov_base = holder.get(); - for (int i = 0; i < num_blocks; ++i) - { - buf.iov_len = (std::min)(block_size, size); - // deliberately pass in 0 as flags, to disable random_access - int ret = m_storage->readv(&buf, slot, ph.offset, 1, 0); - if (ret > 0) num_read += ret; - // TODO: if the read fails, set error and exit immediately - - if (small_hash && small_piece_size <= block_size) - { - if (small_piece_size > 0) ph.h.update((char const*)buf.iov_base, small_piece_size); - *small_hash = hasher(ph.h).final(); - small_hash = 0; // avoid this case again - if (int(buf.iov_len) > small_piece_size) - ph.h.update((char const*)buf.iov_base + small_piece_size - , buf.iov_len - small_piece_size); - } - else - { - ph.h.update((char const*)buf.iov_base, buf.iov_len); - small_piece_size -= buf.iov_len; - } - - ph.offset += buf.iov_len; - size -= buf.iov_len; - } - } - if (error()) return 0; - } - return num_read; - } - - default_storage::default_storage(file_storage const& fs, file_storage const* mapped, std::string const& path - , file_pool& fp, std::vector const& file_prio) - : m_files(fs) - , m_file_priority(file_prio) - , m_pool(fp) - , m_page_size(page_size()) - , m_allocate_files(false) - { - if (mapped) m_mapped_files.reset(new file_storage(*mapped)); + if (params.mapped_files) m_mapped_files.reset(new file_storage(*params.mapped_files)); + if (params.priorities) m_file_priority = *params.priorities; TORRENT_ASSERT(m_files.num_files() > 0); - m_save_path = complete(path); + m_save_path = complete(params.path); + m_part_file_name = "." + (params.info + ? to_hex(params.info->info_hash().to_string()) + : params.files->name()) + ".parts"; } - default_storage::~default_storage() { m_pool.release(this); } - - void default_storage::set_file_priority(std::vector const& prio) + default_storage::~default_storage() { - m_file_priority = prio; + error_code ec; + if (m_part_file) m_part_file->flush_metadata(ec); + + // this may be called from a different + // thread than the disk thread + m_pool.release(this); } - bool default_storage::initialize(bool allocate_files) + void default_storage::need_partfile() { - m_allocate_files = allocate_files; + if (m_part_file) return; + + m_part_file.reset(new part_file( + m_save_path, m_part_file_name + , m_files.num_pieces(), m_files.piece_length())); + } + + void default_storage::set_file_priority(std::vector const& prio, storage_error& ec) + { + // extend our file priorities in case it's truncated + // the default assumed priority is 1 + if (prio.size() > m_file_priority.size()) + m_file_priority.resize(prio.size(), 1); + + file_storage const& fs = files(); + for (int i = 0; i < int(prio.size()); ++i) + { + int old_prio = m_file_priority[i]; + int new_prio = prio[i]; + if (old_prio == 0 && new_prio != 0) + { + // move stuff out of the part file + file_handle f = open_file(i, file::read_write | file::random_access, ec.ec); + if (ec || !f) + { + ec.file = i; + ec.operation = storage_error::open; + return; + } + + need_partfile(); + + m_part_file->export_file(*f, fs.file_offset(i), fs.file_size(i), ec.ec); + if (ec) + { + ec.file = i; + ec.operation = storage_error::partfile_write; + return; + } + } + else if (old_prio != 0 && new_prio == 0) + { + // move stuff into the part file + // this is not implemented yet. + // pretend that we didn't set the priority to 0. + + std::string fp = fs.file_path(i, m_save_path); + if (exists(fp)) + new_prio = 1; +/* + file_handle f = open_file(i, file::read_only | file::random_access, ec.ec); + if (ec.ec != boost::system::errc::no_such_file_or_directory) + { + if (ec || !f) + { + ec.file = i; + ec.operation = storage_error::open; + return; + } + need_partfile(); + + m_part_file->import_file(*f, fs.file_offset(i), fs.file_size(i), ec.ec); + if (ec) + { + ec.file = i; + ec.operation = storage_error::partfile_read; + return; + } + // remove the file + std::string p = fs.file_path(i, m_save_path); + delete_one_file(p, ec.ec); + if (ec) + { + ec.file = i; + ec.operation = storage_error::remove; + } + } +*/ + } + ec.ec.clear(); + m_file_priority[i] = new_prio; + } + if (m_part_file) m_part_file->flush_metadata(ec.ec); + if (ec) + { + ec.file = -1; + ec.operation = storage_error::partfile_write; + } + } + + void default_storage::initialize(storage_error& ec) + { + m_stat_cache.init(files().num_files()); #ifdef TORRENT_WINDOWS // don't do full file allocations on network drives @@ -417,7 +341,6 @@ namespace libtorrent m_allocate_files = false; #endif - error_code ec; m_file_created.resize(files().num_files(), false); // first, create all missing directories @@ -426,26 +349,35 @@ namespace libtorrent { // ignore files that have priority 0 if (int(m_file_priority.size()) > file_index - && m_file_priority[file_index] == 0) continue; + && m_file_priority[file_index] == 0) + { + continue; + } // ignore pad files if (files().pad_file_at(file_index)) continue; std::string file_path = files().file_path(file_index, m_save_path); - file_status s; - stat_file(file_path, &s, ec); - if (ec && ec != boost::system::errc::no_such_file_or_directory - && ec != boost::system::errc::not_a_directory) + if (m_stat_cache.get_filesize(file_index) == stat_cache::not_in_cache) { - set_error(file_path, ec); - break; + file_status s; + stat_file(file_path, &s, ec.ec); + if (ec && ec.ec != boost::system::errc::no_such_file_or_directory) + { + m_stat_cache.set_error(file_index); + ec.file = file_index; + ec.operation = storage_error::stat; + break; + } + m_stat_cache.set_cache(file_index, s.file_size, s.mtime); } // if the file already exists, but is larger than what // it's supposed to be, truncate it // if the file is empty, just create it either way. - if ((!ec && s.file_size > files().file_size(file_index)) || files().file_size(file_index) == 0) + if ((!ec && m_stat_cache.get_filesize(file_index) > files().file_size(file_index)) + || files().file_size(file_index) == 0) { std::string dir = parent_path(file_path); @@ -453,58 +385,110 @@ namespace libtorrent { last_path = dir; - create_directories(last_path, ec); - if (ec) + create_directories(last_path, ec.ec); + if (ec.ec) { - set_error(dir, ec); + ec.file = file_index; + ec.operation = storage_error::mkdir; break; } } - ec.clear(); - - boost::intrusive_ptr f = open_file(file_index, file::read_write | file::random_access, ec); - if (ec) set_error(file_path, ec); - else if (f) + ec.ec.clear(); + file_handle f = open_file(file_index, file::read_write | file::random_access, ec.ec); + if (ec || !f) { - f->set_size(files().file_size(file_index), ec); - if (ec) set_error(file_path, ec); + ec.file = file_index; + ec.operation = storage_error::open; + return; + } + f->set_size(files().file_size(file_index), ec.ec); + if (ec) + { + ec.file = file_index; + ec.operation = storage_error::fallocate; + break; } - if (ec) break; } - ec.clear(); + ec.ec.clear(); } // close files that were opened in write mode + m_stat_cache.clear(); m_pool.release(this); - return error() ? true : false; +#if TORRENT_DEBUG_FILE_LEAKS + print_open_files("release files", m_files.name().c_str()); +#endif } #ifndef TORRENT_NO_DEPRECATE - void default_storage::finalize_file(int index) {} + void default_storage::finalize_file(int, storage_error&) {} #endif - bool default_storage::has_any_file() + bool default_storage::has_any_file(storage_error& ec) { + m_stat_cache.init(files().num_files()); + + std::string file_path; for (int i = 0; i < files().num_files(); ++i) { - error_code ec; file_status s; - stat_file(files().file_path(i, m_save_path), &s, ec); - if (ec) continue; - if (s.mode & file_status::regular_file && files().file_size(i) > 0) + size_type cache_status = m_stat_cache.get_filesize(i); + if (cache_status < 0 && cache_status != stat_cache::no_exist) + { + file_path = files().file_path(i, m_save_path); + stat_file(file_path, &s, ec.ec); + size_type r = s.file_size; + if (ec.ec || !(s.mode & file_status::regular_file)) r = -1; + + if (ec && ec.ec == boost::system::errc::no_such_file_or_directory) + { + ec.ec.clear(); + r = -3; + } + m_stat_cache.set_cache(i, r, s.mtime); + + if (ec) + { + ec.file = i; + ec.operation = storage_error::stat; + m_stat_cache.clear(); + return false; + } + } + + // if we didn't find the file, check the next one + if (m_stat_cache.get_filesize(i) == stat_cache::no_exist) continue; + + if (m_stat_cache.get_filesize(i) > 0) return true; } + file_status s; + stat_file(combine_path(m_save_path, m_part_file_name), &s, ec.ec); + if (!ec) return true; + + if (ec && ec.ec == boost::system::errc::no_such_file_or_directory) + ec.ec.clear(); + if (ec) + { + ec.file = -1; + ec.operation = storage_error::stat; + return false; + } + m_stat_cache.clear(); return false; } - bool default_storage::rename_file(int index, std::string const& new_filename) + void default_storage::rename_file(int index, std::string const& new_filename, storage_error& ec) { - if (index < 0 || index >= files().num_files()) return true; + if (index < 0 || index >= files().num_files()) return; std::string old_name = files().file_path(index, m_save_path); m_pool.release(this, index); - error_code ec; +#if TORRENT_DEBUG_FILE_LEAKS + print_open_files("release files", m_files.name().c_str()); +#endif + std::string new_path; if (is_complete(new_filename)) new_path = new_filename; else new_path = combine_path(m_save_path, new_filename); @@ -512,22 +496,27 @@ namespace libtorrent // create any missing directories that the new filename // lands in - create_directories(new_dir, ec); - if (ec) + create_directories(new_dir, ec.ec); + if (ec.ec) { - set_error(new_dir, ec); - return true; + ec.file = index; + ec.operation = storage_error::rename; + return; } - rename(old_name, new_path, ec); + rename(old_name, new_path, ec.ec); // if old_name doesn't exist, that's not an error // here. Once we start writing to the file, it will // be written to the new filename - if (ec && ec != boost::system::errc::no_such_file_or_directory) + if (ec.ec == boost::system::errc::no_such_file_or_directory) + ec.ec.clear(); + + if (ec) { - set_error(old_name, ec); - return true; + ec.file = index; + ec.operation = storage_error::rename; + return; } // if old path doesn't exist, just rename the file @@ -536,29 +525,56 @@ namespace libtorrent if (!m_mapped_files) { m_mapped_files.reset(new file_storage(m_files)); } m_mapped_files->rename_file(index, new_filename); - return false; } - bool default_storage::release_files() - { - m_pool.release(this); - return false; - } - - void default_storage::delete_one_file(std::string const& p) - { - error_code ec; - remove(p, ec); - - if (ec && ec != boost::system::errc::no_such_file_or_directory) - set_error(p, ec); - } - - bool default_storage::delete_files() + void default_storage::release_files(storage_error& ec) { // make sure we don't have the files open m_pool.release(this); +#if TORRENT_DEBUG_FILE_LEAKS + print_open_files("release files", m_files.name().c_str()); +#endif + } + + void default_storage::delete_one_file(std::string const& p, error_code& ec) + { + remove(p, ec); + + DFLOG(stderr, "[%p] delete_one_file: %s [%s]\n", this, p.c_str(), ec.message().c_str()); + + if (ec == boost::system::errc::no_such_file_or_directory) + ec.clear(); + } + + void default_storage::delete_files(storage_error& ec) + { + DFLOG(stderr, "[%p] delete_files\n", this); + +#if TORRENT_USE_ASSERTS + // this is a fence job, we expect no other + // threads to hold any references to any files + // in this file storage. Assert that that's the + // case + if (!m_pool.assert_idle_files(this)) + { +#if TORRENT_DEBUG_FILE_LEAKS + print_open_files("delete-files idle assert failed", m_files.name().c_str()); +#endif + TORRENT_ASSERT(false); + } +#endif + + // make sure we don't have the files open + m_pool.release(this); + +#if TORRENT_DEBUG_FILE_LEAKS + print_open_files("release files", m_files.name().c_str()); +#endif + +#if TORRENT_USE_ASSERTS + m_pool.mark_deleted(m_files); +#endif // delete the files from disk std::set directories; typedef std::set::iterator iter_t; @@ -578,7 +594,8 @@ namespace libtorrent bp = parent_path(bp); } } - delete_one_file(p); + delete_one_file(p, ec.ec); + if (ec) { ec.file = i; ec.operation = storage_error::remove; } } // remove the directories. Reverse order to delete @@ -587,31 +604,79 @@ namespace libtorrent for (std::set::reverse_iterator i = directories.rbegin() , end(directories.rend()); i != end; ++i) { - delete_one_file(*i); + error_code error; + delete_one_file(*i, error); + if (error && !ec) { ec.file = -1; ec.ec = error; ec.operation = storage_error::remove; } } - if (error()) return true; - return false; + error_code error; + remove(combine_path(m_save_path, m_part_file_name), error); + DFLOG(stderr, "[%p] delete partfile %s/%s [%s]\n", this + , m_save_path.c_str(), m_part_file_name.c_str(), error.message().c_str()); + if (error != boost::system::errc::no_such_file_or_directory && !error) + { ec.file = -1; ec.ec = error; ec.operation = storage_error::remove; } + + DFLOG(stderr, "[%p] delete_files result: %s\n", this, ec.ec.message().c_str()); + +#if TORRENT_DEBUG_FILE_LEAKS + print_open_files("delete-files done", m_files.name().c_str()); +#endif } - bool default_storage::write_resume_data(entry& rd) const + void default_storage::write_resume_data(entry& rd, storage_error& ec) const { TORRENT_ASSERT(rd.type() == entry::dictionary_t); - std::vector > file_sizes - = get_filesizes(files(), m_save_path); - entry::list_type& fl = rd["file sizes"].list(); - for (std::vector >::iterator i - = file_sizes.begin(), end(file_sizes.end()); i != end; ++i) + + if (m_part_file) { - entry::list_type p; - p.push_back(entry(i->first)); - p.push_back(entry(i->second)); - fl.push_back(entry(p)); + error_code ignore; + const_cast(*m_part_file).flush_metadata(ignore); + } + + file_storage const& fs = files(); + for (int i = 0; i < fs.num_files(); ++i) + { + size_type file_size = 0; + time_t file_time = 0; + size_type cache_state = m_stat_cache.get_filesize(i); + if (cache_state != stat_cache::not_in_cache) + { + if (cache_state >= 0) + { + file_size = cache_state; + file_time = m_stat_cache.get_filetime(i); + } + } + else + { + file_status s; + error_code ec; + stat_file(fs.file_path(i, m_save_path), &s, ec); + if (!ec) + { + file_size = s.file_size; + file_time = s.mtime; + } + else + { + if (ec == boost::system::errc::no_such_file_or_directory) + { + m_stat_cache.set_noexist(i); + } + else + { + m_stat_cache.set_error(i); + } + } + } + + fl.push_back(entry(entry::list_t)); + entry::list_type& p = fl.back().list(); + p.push_back(entry(file_size)); + p.push_back(entry(file_time)); } - - return false; } int default_storage::sparse_end(int slot) const @@ -633,14 +698,14 @@ namespace libtorrent } error_code ec; - boost::intrusive_ptr file_handle = open_file(file_index, file::read_only, ec); - if (!file_handle || ec) return slot; + file_handle handle = open_file(file_index, file::read_only, ec); + if (!handle || ec) return slot; - size_type data_start = file_handle->sparse_end(file_offset); + size_type data_start = handle->sparse_end(file_offset); return int((data_start + files().piece_length() - 1) / files().piece_length()); } - bool default_storage::verify_resume_data(lazy_entry const& rd, error_code& error) + bool default_storage::verify_resume_data(lazy_entry const& rd, storage_error& ec) { // TODO: make this more generic to not just work if files have been // renamed, but also if they have been merged into a single file for instance @@ -666,34 +731,29 @@ namespace libtorrent m_file_priority[i] = boost::uint8_t(file_priority->list_int_value_at(i, 1)); } - std::vector > file_sizes; lazy_entry const* file_sizes_ent = rd.dict_find_list("file sizes"); if (file_sizes_ent == 0) { - error = errors::missing_file_sizes; + ec.ec = errors::missing_file_sizes; return false; } - for (int i = 0; i < file_sizes_ent->list_size(); ++i) + if (file_sizes_ent->list_size() == 0) { - lazy_entry const* e = file_sizes_ent->list_at(i); - if (e->type() != lazy_entry::list_t - || e->list_size() != 2 - || e->list_at(0)->type() != lazy_entry::int_t - || e->list_at(1)->type() != lazy_entry::int_t) - continue; - file_sizes.push_back(std::pair( - e->list_int_value_at(0), std::time_t(e->list_int_value_at(1)))); + ec.ec = errors::no_files_in_resume_data; + return false; + } + + file_storage const& fs = files(); + if (file_sizes_ent->list_size() != fs.num_files()) + { + ec.ec = errors::mismatching_number_of_files; + ec.file = -1; + ec.operation = storage_error::none; + return false; } - if (file_sizes.empty()) - { - error = errors::no_files_in_resume_data; - return false; - } - bool seed = false; - lazy_entry const* slots = rd.dict_find_list("slots"); if (slots) { @@ -724,57 +784,115 @@ namespace libtorrent } else { - error = errors::missing_pieces; + ec.ec = errors::missing_pieces; return false; } + for (int i = 0; i < file_sizes_ent->list_size(); ++i) + { + if (fs.pad_file_at(i)) continue; + lazy_entry const* e = file_sizes_ent->list_at(i); + if (e->type() != lazy_entry::list_t + || e->list_size() < 2 + || e->list_at(0)->type() != lazy_entry::int_t + || e->list_at(1)->type() != lazy_entry::int_t) + { + ec.ec = errors::missing_file_sizes; + ec.file = i; + ec.operation = storage_error::none; + return false; + } + + size_type expected_size = e->list_int_value_at(0); + time_t expected_time = e->list_int_value_at(1); + + // if we're a seed, the expected size should match + // the actual full size according to the torrent + if (seed && expected_size < fs.file_size(i)) + { + ec.ec = errors::mismatching_file_size; + ec.file = i; + ec.operation = storage_error::none; + return false; + } + + size_type file_size = m_stat_cache.get_filesize(i); + time_t file_time; + if (file_size >= 0) + { + file_time = m_stat_cache.get_filetime(i); + } + else + { + file_status s; + error_code error; + std::string file_path = fs.file_path(i, m_save_path); + stat_file(file_path, &s, error); + file_size = s.file_size; + file_time = s.mtime; + if (error) + { + if (error != boost::system::errc::no_such_file_or_directory) + { + m_stat_cache.set_error(i); + ec.ec = error; + ec.file = i; + ec.operation = storage_error::stat; + return false; + } + m_stat_cache.set_noexist(i); + if (expected_size != 0) + { + ec.ec = errors::mismatching_file_size; + ec.file = i; + ec.operation = storage_error::none; + return false; + } + } + } + + if (expected_size > file_size) + { + ec.ec = errors::mismatching_file_size; + ec.file = i; + ec.operation = storage_error::none; + return false; + } + + if (settings().get_bool(settings_pack::ignore_resume_timestamps)) continue; + + // allow some slack, because of FAT volumes + if (expected_time != 0 && + (file_time > expected_time + 5 * 60 || file_time < expected_time - 5)) + { + ec.ec = errors::mismatching_file_timestamp; + ec.file = i; + ec.operation = storage_error::stat; + return false; + } + } + bool full_allocation_mode = false; if (rd.dict_find_string_value("allocation") != "compact") full_allocation_mode = true; - if (seed) - { - if (files().num_files() != (int)file_sizes.size()) - { - error = errors::mismatching_number_of_files; - return false; - } - - std::vector >::iterator - fs = file_sizes.begin(); - // the resume data says we have the entire torrent - // make sure the file sizes are the right ones - for (int i = 0; i < files().num_files(); ++i, ++fs) - { - if (!files().pad_file_at(i) && files().file_size(i) != fs->first) - { - error = errors::mismatching_file_size; - return false; - } - } - } - int flags = (full_allocation_mode ? 0 : compact_mode) - | (settings().ignore_resume_timestamps ? ignore_timestamps : 0); - - return match_filesizes(files(), m_save_path, file_sizes, flags, error); - + return true; } - // returns true on success - int default_storage::move_storage(std::string const& sp, int flags) + int default_storage::move_storage(std::string const& sp, int flags, storage_error& ec) { int ret = piece_manager::no_error; std::string save_path = complete(sp); // check to see if any of the files exist - error_code ec; + error_code e; file_storage const& f = files(); file_status s; if (flags == fail_if_exist) { - stat_file(combine_path(save_path, f.name()), &s, ec); - if (ec != boost::system::errc::no_such_file_or_directory) + stat_file(save_path, &s, e); + if (e != boost::system::errc::no_such_file_or_directory) { // the directory exists, check all the files for (int i = 0; i < f.num_files(); ++i) @@ -783,9 +901,14 @@ namespace libtorrent if (is_complete(f.file_path(i))) continue; std::string new_path = f.file_path(i, save_path); - stat_file(new_path, &s, ec); - if (ec != boost::system::errc::no_such_file_or_directory) + stat_file(new_path, &s, e); + if (e != boost::system::errc::no_such_file_or_directory) + { + ec.ec = e; + ec.file = i; + ec.operation = storage_error::stat; return piece_manager::file_exist; + } } } } @@ -793,336 +916,128 @@ namespace libtorrent // collect all directories in to_move. This is because we // try to move entire directories by default (instead of // files independently). - std::set to_move; + std::map to_move; for (int i = 0; i < f.num_files(); ++i) { // files moved out to absolute paths are not moved if (is_complete(f.file_path(i))) continue; std::string split = split_path(f.file_path(i)); - to_move.insert(to_move.begin(), split); + to_move.insert(to_move.begin(), std::make_pair(split, i)); } - ec.clear(); - stat_file(save_path, &s, ec); - if (ec == boost::system::errc::no_such_file_or_directory) + e.clear(); + stat_file(save_path, &s, e); + if (e == boost::system::errc::no_such_file_or_directory) { - ec.clear(); - create_directories(save_path, ec); + create_directories(save_path, ec.ec); + if (ec) + { + ec.file = -1; + ec.operation = storage_error::mkdir; + return piece_manager::fatal_disk_error; + } } - - if (ec) + else if (ec) { - set_error(save_path, ec); - return piece_manager::fatal_disk_error; + ec.file = -1; + ec.operation = storage_error::mkdir; + return piece_manager::fatal_disk_error; } m_pool.release(this); - for (std::set::const_iterator i = to_move.begin() +#if TORRENT_DEBUG_FILE_LEAKS + print_open_files("release files", m_files.name().c_str()); +#endif + + for (std::map::const_iterator i = to_move.begin() , end(to_move.end()); i != end; ++i) { - std::string old_path = combine_path(m_save_path, *i); - std::string new_path = combine_path(save_path, *i); + std::string old_path = combine_path(m_save_path, i->first); + std::string new_path = combine_path(save_path, i->first); - rename(old_path, new_path, ec); - if (ec) + e.clear(); + rename(old_path, new_path, e); + // if the source file doesn't exist. That's not a problem + if (e == boost::system::errc::no_such_file_or_directory) + e.clear(); + + if (e) { - if (flags == dont_replace && ec == boost::system::errc::file_exists) + if (flags == dont_replace && e == boost::system::errc::file_exists) { if (ret == piece_manager::no_error) ret = piece_manager::need_full_check; continue; } - if (ec != boost::system::errc::no_such_file_or_directory) + if (e != boost::system::errc::no_such_file_or_directory) { - error_code ec; - recursive_copy(old_path, new_path, ec); - if (ec == boost::system::errc::no_such_file_or_directory) + e.clear(); + recursive_copy(old_path, new_path, ec.ec); + if (ec.ec == boost::system::errc::no_such_file_or_directory) { // it's a bit weird that rename() would not return // ENOENT, but the file still wouldn't exist. But, // in case it does, we're done. - ec.clear(); + ec.ec.clear(); break; } if (ec) { - set_error(old_path, ec); - ret = piece_manager::fatal_disk_error; + ec.file = i->second; + ec.operation = storage_error::copy; } else { - remove_all(old_path, ec); + // ignore errors when removing + error_code e; + remove_all(old_path, e); } break; } } } - if (ret == piece_manager::no_error || ret == piece_manager::need_full_check) + if (!ec) + { + if (m_part_file) + { + // TODO: if everything moves OK, except for the partfile + // we currently won't update the save path, which breaks things. + // it would probably make more sense to give up on the partfile + m_part_file->move_partfile(save_path, ec.ec); + if (ec) + { + ec.file = -1; + ec.operation = storage_error::partfile_move; + return piece_manager::fatal_disk_error; + } + } + m_save_path = save_path; - - return ret; - } - -#ifdef TORRENT_DEBUG -/* - void default_storage::shuffle() - { - int num_pieces = files().num_pieces(); - - std::vector pieces(num_pieces); - for (std::vector::iterator i = pieces.begin(); - i != pieces.end(); ++i) - { - *i = static_cast(i - pieces.begin()); - } - std::srand((unsigned int)std::time(0)); - std::vector targets(pieces); - std::random_shuffle(pieces.begin(), pieces.end()); - std::random_shuffle(targets.begin(), targets.end()); - - for (int i = 0; i < (std::max)(num_pieces / 50, 1); ++i) - { - const int slot_index = targets[i]; - const int piece_index = pieces[i]; - const int slot_size =static_cast(m_files.piece_size(slot_index)); - std::vector buf(slot_size); - read(&buf[0], piece_index, 0, slot_size); - write(&buf[0], slot_index, 0, slot_size); - } - } -*/ -#endif - -#define TORRENT_ALLOCATE_BLOCKS(bufs, num_blocks, piece_size) \ - int num_blocks = (piece_size + disk_pool()->block_size() - 1) / disk_pool()->block_size(); \ - file::iovec_t* bufs = TORRENT_ALLOCA(file::iovec_t, num_blocks); \ - for (int i = 0, size = piece_size; i < num_blocks; ++i) \ - { \ - bufs[i].iov_base = disk_pool()->allocate_buffer("move temp"); \ - bufs[i].iov_len = (std::min)(disk_pool()->block_size(), size); \ - size -= bufs[i].iov_len; \ - } - -#define TORRENT_FREE_BLOCKS(bufs, num_blocks) \ - for (int i = 0; i < num_blocks; ++i) \ - disk_pool()->free_buffer((char*)bufs[i].iov_base); - -#define TORRENT_SET_SIZE(bufs, size, num_bufs) \ - for (num_bufs = 0; size > 0; size -= disk_pool()->block_size(), ++num_bufs) \ - bufs[num_bufs].iov_len = (std::min)(disk_pool()->block_size(), size) - - - bool default_storage::move_slot(int src_slot, int dst_slot) - { - bool r = true; - int piece_size = m_files.piece_size(dst_slot); - - TORRENT_ALLOCATE_BLOCKS(bufs, num_blocks, piece_size); - - readv(bufs, src_slot, 0, num_blocks); if (error()) goto ret; - writev(bufs, dst_slot, 0, num_blocks); if (error()) goto ret; - - r = false; -ret: - TORRENT_FREE_BLOCKS(bufs, num_blocks) - return r; - } - - bool default_storage::swap_slots(int slot1, int slot2) - { - bool r = true; - - // the size of the target slot is the size of the piece - int piece1_size = m_files.piece_size(slot2); - int piece2_size = m_files.piece_size(slot1); - - TORRENT_ALLOCATE_BLOCKS(bufs1, num_blocks1, piece1_size); - TORRENT_ALLOCATE_BLOCKS(bufs2, num_blocks2, piece2_size); - - readv(bufs1, slot1, 0, num_blocks1); if (error()) goto ret; - readv(bufs2, slot2, 0, num_blocks2); if (error()) goto ret; - writev(bufs1, slot2, 0, num_blocks1); if (error()) goto ret; - writev(bufs2, slot1, 0, num_blocks2); if (error()) goto ret; - - r = false; -ret: - TORRENT_FREE_BLOCKS(bufs1, num_blocks1) - TORRENT_FREE_BLOCKS(bufs2, num_blocks2) - return r; - } - - bool default_storage::swap_slots3(int slot1, int slot2, int slot3) - { - bool r = true; - - // the size of the target slot is the size of the piece - int piece_size = m_files.piece_length(); - int piece1_size = m_files.piece_size(slot2); - int piece2_size = m_files.piece_size(slot3); - int piece3_size = m_files.piece_size(slot1); - - TORRENT_ALLOCATE_BLOCKS(bufs1, num_blocks1, piece_size); - TORRENT_ALLOCATE_BLOCKS(bufs2, num_blocks2, piece_size); - - int tmp1 = 0; - int tmp2 = 0; - TORRENT_SET_SIZE(bufs1, piece1_size, tmp1); - readv(bufs1, slot1, 0, tmp1); if (error()) goto ret; - TORRENT_SET_SIZE(bufs2, piece2_size, tmp2); - readv(bufs2, slot2, 0, tmp2); if (error()) goto ret; - writev(bufs1, slot2, 0, tmp1); if (error()) goto ret; - TORRENT_SET_SIZE(bufs1, piece3_size, tmp1); - readv(bufs1, slot3, 0, tmp1); if (error()) goto ret; - writev(bufs2, slot3, 0, tmp2); if (error()) goto ret; - writev(bufs1, slot1, 0, tmp1); if (error()) goto ret; -ret: - TORRENT_FREE_BLOCKS(bufs1, num_blocks1) - TORRENT_FREE_BLOCKS(bufs2, num_blocks2) - return r; - } - - int default_storage::writev(file::iovec_t const* bufs, int slot, int offset - , int num_bufs, int flags) - { -#ifdef TORRENT_DISK_STATS - disk_buffer_pool* pool = disk_pool(); - if (pool) - { - pool->m_disk_access_log << log_time() << " write " - << physical_offset(slot, offset) << std::endl; - } -#endif - fileop op = { &file::writev, &default_storage::write_unaligned - , m_settings ? settings().disk_io_write_mode : 0, file::read_write | flags }; -#ifdef TORRENT_DISK_STATS - int ret = readwritev(bufs, slot, offset, num_bufs, op); - if (pool) - { - pool->m_disk_access_log << log_time() << " write_end " - << (physical_offset(slot, offset) + ret) << std::endl; - } - return ret; -#else - return readwritev(bufs, slot, offset, num_bufs, op); -#endif - } - - size_type default_storage::physical_offset(int slot, int offset) - { - TORRENT_ASSERT(slot >= 0); - TORRENT_ASSERT(slot < m_files.num_pieces()); - TORRENT_ASSERT(offset >= 0); - - // find the file and file - size_type tor_off = size_type(slot) - * files().piece_length() + offset; - int file_index = files().file_index_at_offset(tor_off); - while (files().pad_file_at(file_index)) - { - ++file_index; - if (file_index == files().num_files()) - return size_type(slot) * files().piece_length() + offset; - // update offset as well, since we're moving it up ahead - tor_off = files().file_offset(file_index); - - } - TORRENT_ASSERT(!files().pad_file_at(file_index)); - - size_type file_offset = tor_off - files().file_offset(file_index); - TORRENT_ASSERT(file_offset >= 0); - - // open the file read only to avoid re-opening - // it in case it's already opened in read-only mode - error_code ec; - boost::intrusive_ptr f = open_file(file_index, file::read_only | file::random_access, ec); - - size_type ret = 0; - if (f && !ec) ret = f->phys_offset(file_offset); - - if (ret == 0) - { - // this means we don't support true physical offset - // just make something up - return size_type(slot) * files().piece_length() + offset; } return ret; } - void default_storage::hint_read(int slot, int offset, int size) + int default_storage::readv(file::iovec_t const* bufs, int num_bufs + , int slot, int offset, int flags, storage_error& ec) { - size_type start = slot * (size_type)m_files.piece_length() + offset; - TORRENT_ASSERT(start + size <= m_files.total_size()); - - int file_index = files().file_index_at_offset(start); - TORRENT_ASSERT(start >= files().file_offset(file_index)); - TORRENT_ASSERT(start < files().file_offset(file_index) + files().file_size(file_index)); - size_type file_offset = start - files().file_offset(file_index); - - boost::intrusive_ptr file_handle; - int bytes_left = size; - int slot_size = static_cast(m_files.piece_size(slot)); - - if (offset + bytes_left > slot_size) - bytes_left = slot_size - offset; - - TORRENT_ASSERT(bytes_left >= 0); - - int file_bytes_left; - for (;bytes_left > 0; ++file_index, bytes_left -= file_bytes_left) - { - TORRENT_ASSERT(file_index < files().num_files()); - - file_bytes_left = bytes_left; - if (file_offset + file_bytes_left > files().file_size(file_index)) - file_bytes_left = (std::max)(static_cast(files().file_size(file_index) - file_offset), 0); - - if (file_bytes_left == 0) continue; - - if (files().pad_file_at(file_index)) continue; - - error_code ec; - file_handle = open_file(file_index, file::read_only | file::random_access, ec); - - // failing to hint that we want to read is not a big deal - // just swollow the error and keep going - if (!file_handle || ec) continue; - - file_handle->hint_read(file_offset, file_bytes_left); - file_offset = 0; - } - } - - int default_storage::readv(file::iovec_t const* bufs, int slot, int offset - , int num_bufs, int flags) - { -#ifdef TORRENT_DISK_STATS - disk_buffer_pool* pool = disk_pool(); - if (pool) - { - pool->m_disk_access_log << log_time() << " read " - << physical_offset(slot, offset) << std::endl; - } -#endif - fileop op = { &file::readv, &default_storage::read_unaligned - , m_settings ? settings().disk_io_read_mode : 0, file::read_only | flags }; + fileop op = { &file::readv + , file::read_only | flags }; #ifdef TORRENT_SIMULATE_SLOW_READ boost::thread::sleep(boost::get_system_time() + boost::posix_time::milliseconds(1000)); #endif -#ifdef TORRENT_DISK_STATS - int ret = readwritev(bufs, slot, offset, num_bufs, op); - if (pool) - { - pool->m_disk_access_log << log_time() << " read_end " - << (physical_offset(slot, offset) + ret) << std::endl; - } - return ret; -#else - return readwritev(bufs, slot, offset, num_bufs, op); -#endif + return readwritev(bufs, slot, offset, num_bufs, op, ec); + } + + int default_storage::writev(file::iovec_t const* bufs, int num_bufs + , int slot, int offset, int flags, storage_error& ec) + { + fileop op = { &file::writev + , file::read_write | flags }; + return readwritev(bufs, slot, offset, num_bufs, op, ec); } // much of what needs to be done when reading and writing @@ -1131,17 +1046,17 @@ ret: // is a template, and the fileop decides what to do with the // file and the buffers. int default_storage::readwritev(file::iovec_t const* bufs, int slot, int offset - , int num_bufs, fileop const& op) + , int num_bufs, fileop const& op, storage_error& ec) { TORRENT_ASSERT(bufs != 0); TORRENT_ASSERT(slot >= 0); TORRENT_ASSERT(slot < m_files.num_pieces()); TORRENT_ASSERT(offset >= 0); - TORRENT_ASSERT(offset < m_files.piece_size(slot)); TORRENT_ASSERT(num_bufs > 0); int size = bufs_size(bufs, num_bufs); TORRENT_ASSERT(size > 0); + TORRENT_ASSERT(files().is_loaded()); #if TORRENT_USE_ASSERTS std::vector slices @@ -1149,24 +1064,17 @@ ret: TORRENT_ASSERT(!slices.empty()); #endif - size_type start = slot * (size_type)m_files.piece_length() + offset; - TORRENT_ASSERT(start + size <= m_files.total_size()); - // find the file iterator and file offset - int file_index = files().file_index_at_offset(start); - TORRENT_ASSERT(start >= files().file_offset(file_index)); - TORRENT_ASSERT(start < files().file_offset(file_index) + files().file_size(file_index)); - size_type file_offset = start - files().file_offset(file_index); + boost::uint64_t torrent_offset = slot * boost::uint64_t(m_files.piece_length()) + offset; + int file_index = files().file_index_at_offset(torrent_offset); + TORRENT_ASSERT(torrent_offset >= files().file_offset(file_index)); + TORRENT_ASSERT(torrent_offset < files().file_offset(file_index) + files().file_size(file_index)); + size_type file_offset = torrent_offset - files().file_offset(file_index); int buf_pos = 0; - error_code ec; - boost::intrusive_ptr file_handle; + file_handle handle; int bytes_left = size; - int slot_size = static_cast(m_files.piece_size(slot)); - - if (offset + bytes_left > slot_size) - bytes_left = slot_size - offset; TORRENT_ASSERT(bytes_left >= 0); @@ -1193,8 +1101,6 @@ ret: #if TORRENT_USE_ASSERTS TORRENT_ASSERT(int(slices.size()) > counter); - size_type slice_size = slices[counter].size; - TORRENT_ASSERT(slice_size == file_bytes_left); TORRENT_ASSERT(slices[counter].file_index == file_index); ++counter; #endif @@ -1214,86 +1120,119 @@ ret: continue; } - error_code ec; - file_handle = open_file(file_index, op.mode, ec); - if (((op.mode & file::rw_mask) != file::read_only) - && ec == boost::system::errc::no_such_file_or_directory) - { - // this means the directory the file is in doesn't exist. - // so create it - ec.clear(); - std::string path = files().file_path(file_index, m_save_path); - create_directories(parent_path(path), ec); - // if the directory creation failed, don't try to open the file again - // but actually just fail - if (!ec) file_handle = open_file(file_index, op.mode, ec); - } - - if (!file_handle || ec) - { - std::string path = files().file_path(file_index, m_save_path); - TORRENT_ASSERT(ec); - set_error(path, ec); - return -1; - } - - // if the file has priority 0, don't allocate it - if (m_allocate_files && (op.mode & file::rw_mask) != file::read_only - && (int(m_file_priority.size()) <= file_index || m_file_priority[file_index] > 0)) - { - TORRENT_ASSERT(int(m_file_created.size()) == files().num_files()); - if (m_file_created[file_index] == false) - { - file_handle->set_size(files().file_size(file_index), ec); - m_file_created.set_bit(file_index); - if (ec) - { - set_error(files().file_path(file_index, m_save_path), ec); - return -1; - } - } - } - int num_tmp_bufs = copy_bufs(current_buf, file_bytes_left, tmp_bufs); TORRENT_ASSERT(count_bufs(tmp_bufs, file_bytes_left) == num_tmp_bufs); TORRENT_ASSERT(num_tmp_bufs <= num_bufs); int bytes_transferred = 0; - // if the file is opened in no_buffer mode, and the - // read is unaligned, we need to fall back on a slow - // special read that reads aligned buffers and copies - // it into the one supplied - size_type adjusted_offset = files().file_base(file_index) + file_offset; - if ((file_handle->open_mode() & file::no_buffer) - && ((adjusted_offset & (file_handle->pos_alignment()-1)) != 0 - || (uintptr_t(tmp_bufs->iov_base) & (file_handle->buf_alignment()-1)) != 0)) - { - bytes_transferred = (int)(this->*op.unaligned_op)(file_handle, adjusted_offset - , tmp_bufs, num_tmp_bufs, ec); - if ((op.mode & file::rw_mask) != file::read_only - && adjusted_offset + bytes_transferred >= files().file_size(file_index) - && (file_handle->pos_alignment() > 0 || file_handle->size_alignment() > 0)) - { - // we were writing, and we just wrote the last block of the file - // we likely wrote a bit too much, since we're restricted to - // a specific alignment for writes. Make sure to truncate the size + error_code e; - // TODO: 0 what if file_base is used to merge several virtual files - // into a single physical file? We should probably disable this - // if file_base is used. This is not a widely used feature though - file_handle->set_size(files().file_size(file_index), ec); + if ((op.mode & file::rw_mask) == file::read_write) + { + // invalidate our stat cache for this file, since + // we're writing to it + m_stat_cache.set_dirty(file_index); + } + + if ((file_index < int(m_file_priority.size()) + && m_file_priority[file_index] == 0) + || files().pad_file_at(file_index)) + { + need_partfile(); + + if ((op.mode & file::rw_mask) == file::read_write) + { + // write + bytes_transferred = m_part_file->writev(tmp_bufs, num_tmp_bufs + , slot, offset, e); + } + else + { + // read + bytes_transferred = m_part_file->readv(tmp_bufs, num_tmp_bufs + , slot, offset, e); + } + if (e) + { + ec.ec = e; + ec.file = file_index; + ec.operation = (op.mode & file::rw_mask) == file::read_only + ? storage_error::partfile_read : storage_error::partfile_write; + return -1; } } else { - bytes_transferred = (int)((*file_handle).*op.regular_op)(adjusted_offset - , tmp_bufs, num_tmp_bufs, ec); + handle = open_file(file_index, op.mode, e); + if (((op.mode & file::rw_mask) != file::read_only) + && e == boost::system::errc::no_such_file_or_directory) + { + // this means the directory the file is in doesn't exist. + // so create it + e.clear(); + std::string path = files().file_path(file_index, m_save_path); + create_directories(parent_path(path), e); + + if (e) + { + ec.ec = e; + ec.file = file_index; + ec.operation = storage_error::mkdir; + return -1; + } + + // if the directory creation failed, don't try to open the file again + // but actually just fail + handle = open_file(file_index, op.mode, e); + } + + if (!handle || e) + { + ec.ec = e; + ec.file = file_index; + ec.operation = storage_error::open; + return -1; + } + + if (m_allocate_files && (op.mode & file::rw_mask) != file::read_only) + { + TORRENT_ASSERT(int(m_file_created.size()) == files().num_files()); + TORRENT_ASSERT(file_index < m_file_created.size()); + if (m_file_created[file_index] == false) + { + handle->set_size(files().file_size(file_index), e); + m_file_created.set_bit(file_index); + if (e) + { + ec.ec = e; + ec.file = file_index; + ec.operation = storage_error::fallocate; + return -1; + } + } + } + + size_type adjusted_offset = files().file_base(file_index) + file_offset; + +#ifdef TORRENT_DISK_STATS + int flags = ((op.mode & file::rw_mask) == file::read_only) ? op_read : op_write; + write_access_log(adjusted_offset, handle->file_id(), op_start | flags, time_now_hires()); +#endif + + bytes_transferred = (int)((*handle).*op.op)(adjusted_offset + , tmp_bufs, num_tmp_bufs, e, op.mode); + +#ifdef TORRENT_DISK_STATS + write_access_log(adjusted_offset + bytes_transferred, handle->file_id(), op_end | flags, time_now_hires()); +#endif TORRENT_ASSERT(bytes_transferred <= bufs_size(tmp_bufs, num_tmp_bufs)); } file_offset = 0; - if (ec) + if (e) { - set_error(files().file_path(file_index, m_save_path), ec); + ec.ec = e; + ec.file = file_index; + ec.operation = (op.mode & file::rw_mask) == file::read_only ? storage_error::read : storage_error::write; return -1; } @@ -1306,820 +1245,182 @@ ret: return size; } - // these functions are inefficient, but should be fairly uncommon. The read - // case happens if unaligned files are opened in no_buffer mode or if clients - // makes unaligned requests (and the disk cache is disabled or fully utilized - // for write cache). - // they read an unaligned buffer from a file that requires aligned access - - size_type default_storage::read_unaligned(boost::intrusive_ptr const& file_handle - , size_type file_offset, file::iovec_t const* bufs, int num_bufs, error_code& ec) - { - const int pos_align = file_handle->pos_alignment()-1; - const int size_align = file_handle->size_alignment()-1; - - const int size = bufs_size(bufs, num_bufs); - const int start_adjust = file_offset & pos_align; - TORRENT_ASSERT(start_adjust == (file_offset % file_handle->pos_alignment())); - const size_type aligned_start = file_offset - start_adjust; - const int aligned_size = ((size+start_adjust) & size_align) - ? ((size+start_adjust) & ~size_align) + size_align + 1 : size + start_adjust; - TORRENT_ASSERT((aligned_size & size_align) == 0); - - // allocate a temporary, aligned, buffer - aligned_holder aligned_buf(aligned_size); - file::iovec_t b = {aligned_buf.get(), size_t(aligned_size) }; - size_type ret = file_handle->readv(aligned_start, &b, 1, ec); - if (ret < 0) - { - TORRENT_ASSERT(ec); - return ret; - } - if (ret - start_adjust < size) return (std::max)(ret - start_adjust, size_type(0)); - - char* read_buf = aligned_buf.get() + start_adjust; - for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i != end; ++i) - { - memcpy(i->iov_base, read_buf, i->iov_len); - read_buf += i->iov_len; - } - - return size; - } - - // this is the really expensive one. To write unaligned, we need to read - // an aligned block, overlay the unaligned buffer, and then write it back - size_type default_storage::write_unaligned(boost::intrusive_ptr const& file_handle - , size_type file_offset, file::iovec_t const* bufs, int num_bufs, error_code& ec) - { - const int pos_align = file_handle->pos_alignment()-1; - const int size_align = file_handle->size_alignment()-1; - - const int size = bufs_size(bufs, num_bufs); - const int start_adjust = file_offset & pos_align; - TORRENT_ASSERT(start_adjust == (file_offset % file_handle->pos_alignment())); - const size_type aligned_start = file_offset - start_adjust; - const int aligned_size = ((size+start_adjust) & size_align) - ? ((size+start_adjust) & ~size_align) + size_align + 1 : size + start_adjust; - TORRENT_ASSERT((aligned_size & size_align) == 0); - - size_type actual_file_size = file_handle->get_size(ec); - if (ec && ec != make_error_code(boost::system::errc::no_such_file_or_directory)) return -1; - ec.clear(); - - // allocate a temporary, aligned, buffer - aligned_holder aligned_buf(aligned_size); - file::iovec_t b = {aligned_buf.get(), size_t(aligned_size) }; - // we have something to read - if (aligned_start < actual_file_size && !ec) - { - size_type ret = file_handle->readv(aligned_start, &b, 1, ec); - if (ec -#ifdef TORRENT_WINDOWS - && ec != error_code(ERROR_HANDLE_EOF, get_system_category()) -#endif - ) - return ret; - } - - ec.clear(); - - // OK, we read the portion of the file. Now, overlay the buffer we're writing - - char* write_buf = aligned_buf.get() + start_adjust; - for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i != end; ++i) - { - memcpy(write_buf, i->iov_base, i->iov_len); - write_buf += i->iov_len; - } - - // write the buffer back to disk - size_type ret = file_handle->writev(aligned_start, &b, 1, ec); - - if (ret < 0) - { - TORRENT_ASSERT(ec); - return ret; - } - if (ret - start_adjust < size) return (std::max)(ret - start_adjust, size_type(0)); - return size; - } - - int default_storage::write( - const char* buf - , int slot - , int offset - , int size) - { - file::iovec_t b = { (file::iovec_base_t)buf, size_t(size) }; - return writev(&b, slot, offset, 1, 0); - } - - int default_storage::read( - char* buf - , int slot - , int offset - , int size) - { - file::iovec_t b = { (file::iovec_base_t)buf, size_t(size) }; - return readv(&b, slot, offset, 1); - } - - boost::intrusive_ptr default_storage::open_file(int file_index, int mode + file_handle default_storage::open_file(int file, int mode , error_code& ec) const { - int cache_setting = m_settings ? settings().disk_io_write_mode : 0; - if (cache_setting == session_settings::disable_os_cache - || (cache_setting == session_settings::disable_os_cache_for_aligned_files - && ((files().file_offset(file_index) + files().file_base(file_index)) & (m_page_size-1)) == 0)) - mode |= file::no_buffer; - bool lock_files = m_settings ? settings().lock_files : false; + bool lock_files = m_settings ? settings().get_bool(settings_pack::lock_files) : false; if (lock_files) mode |= file::lock_file; if (!m_allocate_files) mode |= file::sparse; // files with priority 0 should always be sparse - if (int(m_file_priority.size()) > file_index && m_file_priority[file_index] == 0) + if (int(m_file_priority.size()) > file && m_file_priority[file] == 0) mode |= file::sparse; - if (m_settings && settings().no_atime_storage) mode |= file::no_atime; + if (m_settings && settings().get_bool(settings_pack::no_atime_storage)) mode |= file::no_atime; - return m_pool.open_file(const_cast(this), m_save_path, file_index, files(), mode, ec); - } - - storage_interface* default_storage_constructor(file_storage const& fs - , file_storage const* mapped, std::string const& path, file_pool& fp - , std::vector const& file_prio) - { - return new default_storage(fs, mapped, path, fp, file_prio); - } - - int disabled_storage::readv(file::iovec_t const* bufs, int slot, int offset, int num_bufs, int flags) - { -#ifdef TORRENT_DISK_STATS - disk_buffer_pool* pool = disk_pool(); - if (pool) + // if we have a cache already, don't store the data twice by leaving it in the OS cache as well + if (m_settings + && settings().get_int(settings_pack::disk_io_write_mode) + == settings_pack::disable_os_cache) { - pool->m_disk_access_log << log_time() << " read " - << physical_offset(slot, offset) << std::endl; + mode |= file::no_cache; } -#endif + + return m_pool.open_file(const_cast(this), m_save_path, file, files(), mode, ec); + } + + bool default_storage::tick() + { + error_code ec; + if (m_part_file) m_part_file->flush_metadata(ec); + + return false; + } + + storage_interface* default_storage_constructor(storage_params const& params) + { + return new default_storage(params); + } + + int disabled_storage::readv(file::iovec_t const* bufs + , int num_bufs, int slot, int offset, int flags, storage_error& ec) + { + return 0; + } + + int disabled_storage::writev(file::iovec_t const* bufs + , int num_bufs, int slot, int offset, int flags, storage_error& ec) + { + return 0; + } + + storage_interface* disabled_storage_constructor(storage_params const& params) + { + return new disabled_storage(params.files->piece_length()); + } + + // -- zero_storage ------------------------------------------------------ + + int zero_storage::readv(file::iovec_t const* bufs, int num_bufs + , int piece, int offset, int flags, storage_error& ec) + { + int ret = 0; + for (int i = 0; i < num_bufs; ++i) + { + memset(bufs[i].iov_base, 0, bufs[i].iov_len); + ret += bufs[i].iov_len; + } + return 0; + } + + int zero_storage::writev(file::iovec_t const* bufs, int num_bufs + , int piece, int offset, int flags, storage_error& ec) + { int ret = 0; for (int i = 0; i < num_bufs; ++i) ret += bufs[i].iov_len; -#ifdef TORRENT_DISK_STATS - if (pool) - { - pool->m_disk_access_log << log_time() << " read_end " - << (physical_offset(slot, offset) + ret) << std::endl; - } -#endif - return ret; + return 0; } - int disabled_storage::writev(file::iovec_t const* bufs, int slot, int offset, int num_bufs, int flags) + storage_interface* zero_storage_constructor(storage_params const& params) { -#ifdef TORRENT_DISK_STATS - disk_buffer_pool* pool = disk_pool(); - if (pool) - { - pool->m_disk_access_log << log_time() << " write " - << physical_offset(slot, offset) << std::endl; - } -#endif - int ret = 0; - for (int i = 0; i < num_bufs; ++i) - ret += bufs[i].iov_len; -#ifdef TORRENT_DISK_STATS - if (pool) - { - pool->m_disk_access_log << log_time() << " write_end " - << (physical_offset(slot, offset) + ret) << std::endl; - } -#endif - return ret; + return new zero_storage; } - storage_interface* disabled_storage_constructor(file_storage const& fs - , file_storage const* mapped, std::string const& path, file_pool& fp - , std::vector const&) + void storage_piece_set::add_piece(cached_piece_entry* p) { - return new disabled_storage(fs.piece_length()); + TORRENT_ASSERT(p->in_storage == false); + TORRENT_ASSERT(p->storage.get() == this); + TORRENT_ASSERT(m_cached_pieces.count(p) == 0); + m_cached_pieces.insert(p); +#if TORRENT_USE_ASSERTS + p->in_storage = true; +#endif + } + + bool storage_piece_set::has_piece(cached_piece_entry* p) const + { + return m_cached_pieces.count(p) > 0; + } + + void storage_piece_set::remove_piece(cached_piece_entry* p) + { + TORRENT_ASSERT(p->in_storage == true); + TORRENT_ASSERT(m_cached_pieces.count(p) == 1); + m_cached_pieces.erase(p); +#if TORRENT_USE_ASSERTS + p->in_storage = false; +#endif } // -- piece_manager ----------------------------------------------------- piece_manager::piece_manager( - boost::shared_ptr const& torrent - , boost::intrusive_ptr info - , std::string const& save_path - , file_pool& fp - , disk_io_thread& io - , storage_constructor_type sc - , storage_mode_t sm - , std::vector const& file_prio) - : m_info(info) - , m_files(m_info->files()) - , m_storage(sc(m_info->orig_files(), &m_info->files() != &m_info->orig_files() - ? &m_info->files() : 0, save_path, fp, file_prio)) - , m_storage_mode(sm) - , m_save_path(complete(save_path)) - , m_state(state_none) - , m_current_slot(0) - , m_out_of_place(false) - , m_scratch_piece(-1) - , m_last_piece(-1) - , m_storage_constructor(sc) - , m_io_thread(io) + storage_interface* storage_impl + , boost::shared_ptr const& torrent + , file_storage* files) + : m_files(*files) + , m_storage(storage_impl) , m_torrent(torrent) { - m_storage->m_disk_pool = &m_io_thread; } piece_manager::~piece_manager() + {} + +#ifdef TORRENT_DEBUG + void piece_manager::assert_torrent_refcount() const { + if (!m_torrent) return; + // sorry about this layer violation, but it's + // quite convenient to make sure the torrent won't + // get unloaded under our feet later + TORRENT_ASSERT(static_cast(m_torrent.get())->refcount() > 0); } - - void piece_manager::async_set_file_priority( - std::vector const& prios - , boost::function const& handler) - { - std::vector* p = new std::vector(prios); - - disk_io_job j; - j.storage = this; - j.buffer = (char*)p; - j.action = disk_io_job::file_priority; - m_io_thread.add_job(j, handler); - } - - void piece_manager::async_save_resume_data( - boost::function const& handler) - { - disk_io_job j; - j.storage = this; - j.action = disk_io_job::save_resume_data; - m_io_thread.add_job(j, handler); - } - - void piece_manager::async_clear_read_cache( - boost::function const& handler) - { - disk_io_job j; - j.storage = this; - j.action = disk_io_job::clear_read_cache; - m_io_thread.add_job(j, handler); - } - - void piece_manager::async_release_files( - boost::function const& handler) - { - disk_io_job j; - j.storage = this; - j.action = disk_io_job::release_files; - m_io_thread.add_job(j, handler); - } - - void piece_manager::abort_disk_io() - { - m_io_thread.stop(this); - } - - void piece_manager::async_delete_files( - boost::function const& handler) - { - disk_io_job j; - j.storage = this; - j.action = disk_io_job::delete_files; - m_io_thread.add_job(j, handler); - } - - void piece_manager::async_move_storage(std::string const& p, int flags - , boost::function const& handler) - { - disk_io_job j; - j.storage = this; - j.action = disk_io_job::move_storage; - j.str = p; - j.piece = flags; - m_io_thread.add_job(j, handler); - } - - void piece_manager::async_check_fastresume(lazy_entry const* resume_data - , boost::function const& handler) - { - TORRENT_ASSERT(resume_data != 0); - disk_io_job j; - j.storage = this; - j.action = disk_io_job::check_fastresume; - j.buffer = (char*)resume_data; - m_io_thread.add_job(j, handler); - } - - void piece_manager::async_rename_file(int index, std::string const& name - , boost::function const& handler) - { - disk_io_job j; - j.storage = this; - j.piece = index; - j.str = name; - j.action = disk_io_job::rename_file; - m_io_thread.add_job(j, handler); - } - - void piece_manager::async_check_files( - boost::function const& handler) - { - disk_io_job j; - j.storage = this; - j.action = disk_io_job::check_files; - m_io_thread.add_job(j, handler); - } - - void piece_manager::async_read_and_hash( - peer_request const& r - , boost::function const& handler - , int cache_expiry) - { - disk_io_job j; - j.storage = this; - j.action = disk_io_job::read_and_hash; - j.piece = r.piece; - j.offset = r.start; - j.buffer_size = r.length; - j.buffer = 0; - j.cache_min_time = cache_expiry; - TORRENT_ASSERT(r.length <= 16 * 1024); - m_io_thread.add_job(j, handler); -#ifdef TORRENT_USE_ASSERTS - mutex::scoped_lock l(m_mutex); - // if this assert is hit, it suggests - // that check_files was not successful - TORRENT_ASSERT(slot_for(r.piece) >= 0); #endif - } - void piece_manager::async_cache(int piece - , boost::function const& handler - , int cache_expiry) + // used in torrent_handle.cpp + void piece_manager::write_resume_data(entry& rd, storage_error& ec) const { - disk_io_job j; - j.storage = this; - j.action = disk_io_job::cache_piece; - j.piece = piece; - j.offset = 0; - j.buffer_size = 0; - j.buffer = 0; - j.cache_min_time = cache_expiry; - m_io_thread.add_job(j, handler); + m_storage->write_resume_data(rd, ec); } - void piece_manager::async_read( - peer_request const& r - , boost::function const& handler - , int cache_line_size - , int cache_expiry) - { - disk_io_job j; - j.storage = this; - j.action = disk_io_job::read; - j.piece = r.piece; - j.offset = r.start; - j.buffer_size = r.length; - j.buffer = 0; - j.max_cache_line = cache_line_size; - j.cache_min_time = cache_expiry; - - // if a buffer is not specified, only one block can be read - // since that is the size of the pool allocator's buffers - TORRENT_ASSERT(r.length <= 16 * 1024); - m_io_thread.add_job(j, handler); -#ifdef TORRENT_USE_ASSERTS - mutex::scoped_lock l(m_mutex); - // if this assert is hit, it suggests - // that check_files was not successful - TORRENT_ASSERT(slot_for(r.piece) >= 0); -#endif - } - - int piece_manager::async_write( - peer_request const& r - , disk_buffer_holder& buffer - , boost::function const& handler) - { - TORRENT_ASSERT(r.length <= 16 * 1024); - // the buffer needs to be allocated through the io_thread - TORRENT_ASSERT(m_io_thread.is_disk_buffer(buffer.get())); - - disk_io_job j; - j.storage = this; - j.action = disk_io_job::write; - j.piece = r.piece; - j.offset = r.start; - j.buffer_size = r.length; - j.buffer = buffer.get(); - int queue_size = m_io_thread.add_job(j, handler); - buffer.release(); - - return queue_size; - } - - void piece_manager::async_hash(int piece - , boost::function const& handler) - { - disk_io_job j; - j.storage = this; - j.action = disk_io_job::hash; - j.piece = piece; - - m_io_thread.add_job(j, handler); - } - - std::string piece_manager::save_path() const - { - mutex::scoped_lock l(m_mutex); - return m_save_path; - } - - sha1_hash piece_manager::hash_for_piece_impl(int piece, int* readback) - { - TORRENT_ASSERT(!m_storage->error()); - - partial_hash ph; - - std::map::iterator i = m_piece_hasher.find(piece); - if (i != m_piece_hasher.end()) - { - ph = i->second; - m_piece_hasher.erase(i); - } - - int slot = slot_for(piece); - TORRENT_ASSERT(slot != has_no_slot); - if (slot < 0) return sha1_hash(0); - int read = hash_for_slot(slot, ph, m_files.piece_size(piece)); - if (readback) *readback = read; - if (m_storage->error()) return sha1_hash(0); - return ph.h.final(); - } - - int piece_manager::move_storage_impl(std::string const& save_path, int flags) - { - int ret = m_storage->move_storage(save_path, flags); - - if (ret == no_error || ret == need_full_check) - { - m_save_path = complete(save_path); - } - return ret; - } - - void piece_manager::write_resume_data(entry& rd) const - { - mutex::scoped_lock lock(m_mutex); - - INVARIANT_CHECK; - - m_storage->write_resume_data(rd); - - if (m_storage_mode == internal_storage_mode_compact_deprecated) - { - entry::list_type& slots = rd["slots"].list(); - slots.clear(); - std::vector::const_reverse_iterator last; - for (last = m_slot_to_piece.rbegin(); - last != m_slot_to_piece.rend(); ++last) - { - if (*last != unallocated) break; - } - - for (std::vector::const_iterator i = - m_slot_to_piece.begin(); - i != last.base(); ++i) - { - slots.push_back((*i >= 0) ? *i : unassigned); - } - } - - rd["allocation"] = m_storage_mode == storage_mode_sparse?"sparse" - :m_storage_mode == storage_mode_allocate?"full":"compact"; - } - - void piece_manager::mark_failed(int piece_index) - { - mutex::scoped_lock lock(m_mutex); - - INVARIANT_CHECK; - - if (m_storage_mode != internal_storage_mode_compact_deprecated) return; - - TORRENT_ASSERT(piece_index >= 0 && piece_index < (int)m_piece_to_slot.size()); - int slot_index = m_piece_to_slot[piece_index]; - TORRENT_ASSERT(slot_index >= 0); - - m_slot_to_piece[slot_index] = unassigned; - m_piece_to_slot[piece_index] = has_no_slot; - m_free_slots.push_back(slot_index); - } - - void piece_manager::hint_read_impl(int piece_index, int offset, int size) - { - m_last_piece = piece_index; - int slot = slot_for(piece_index); - if (slot <= 0) return; - m_storage->hint_read(slot, offset, size); - } - - int piece_manager::read_impl( - file::iovec_t* bufs - , int piece_index - , int offset - , int num_bufs) - { - TORRENT_ASSERT(bufs); - TORRENT_ASSERT(offset >= 0); - TORRENT_ASSERT(num_bufs > 0); - m_last_piece = piece_index; - int slot = slot_for(piece_index); - TORRENT_ASSERT(slot >= 0); - if (slot < 0) return 0; - return m_storage->readv(bufs, slot, offset, num_bufs); - } - - int piece_manager::write_impl( - file::iovec_t* bufs - , int piece_index - , int offset - , int num_bufs) - { - TORRENT_ASSERT(bufs); - TORRENT_ASSERT(offset >= 0); - TORRENT_ASSERT(num_bufs > 0); - TORRENT_ASSERT(piece_index >= 0 && piece_index < m_files.num_pieces()); - - int size = bufs_size(bufs, num_bufs); - - file::iovec_t* iov = TORRENT_ALLOCA(file::iovec_t, num_bufs); - std::copy(bufs, bufs + num_bufs, iov); - m_last_piece = piece_index; - int slot = allocate_slot_for_piece(piece_index); - int ret = m_storage->writev(bufs, slot, offset, num_bufs); - // only save the partial hash if the write succeeds - if (ret != size) return ret; - - if (m_storage->settings().disable_hash_checks) return ret; - - if (offset == 0) - { - partial_hash& ph = m_piece_hasher[piece_index]; - TORRENT_ASSERT(ph.offset == 0); - ph.offset = size; - - for (file::iovec_t* i = iov, *end(iov + num_bufs); i < end; ++i) - ph.h.update((char const*)i->iov_base, i->iov_len); - - } - else - { - std::map::iterator i = m_piece_hasher.find(piece_index); - if (i != m_piece_hasher.end()) - { -#ifdef TORRENT_USE_ASSERTS - TORRENT_ASSERT(i->second.offset > 0); - int hash_offset = i->second.offset; - TORRENT_ASSERT(offset >= hash_offset); -#endif - if (offset == i->second.offset) - { -#ifdef TORRENT_PARTIAL_HASH_LOG - out << time_now_string() << " UPDATING [" - " s: " << this - << " p: " << piece_index - << " off: " << offset - << " size: " << size - << " entries: " << m_piece_hasher.size() - << " ]" << std::endl; -#endif - for (file::iovec_t* b = iov, *end(iov + num_bufs); b < end; ++b) - { - i->second.h.update((char const*)b->iov_base, b->iov_len); - i->second.offset += b->iov_len; - } - } -#ifdef TORRENT_PARTIAL_HASH_LOG - else - { - out << time_now_string() << " SKIPPING (out of order) [" - " s: " << this - << " p: " << piece_index - << " off: " << offset - << " size: " << size - << " entries: " << m_piece_hasher.size() - << " ]" << std::endl; - } -#endif - } -#ifdef TORRENT_PARTIAL_HASH_LOG - else - { - out << time_now_string() << " SKIPPING (no entry) [" - " s: " << this - << " p: " << piece_index - << " off: " << offset - << " size: " << size - << " entries: " << m_piece_hasher.size() - << " ]" << std::endl; - } -#endif - } - - return ret; - } - - size_type piece_manager::physical_offset( - int piece_index - , int offset) - { - TORRENT_ASSERT(offset >= 0); - TORRENT_ASSERT(piece_index >= 0 && piece_index < m_files.num_pieces()); - - int slot = slot_for(piece_index); - // we may not have a slot for this piece yet. - // assume there is no re-mapping of slots - if (slot < 0) slot = piece_index; - return m_storage->physical_offset(slot, offset); - } - - int piece_manager::identify_data( - sha1_hash const& large_hash - , sha1_hash const& small_hash - , int current_slot) - { -// INVARIANT_CHECK; - typedef std::multimap::const_iterator map_iter; - map_iter begin1; - map_iter end1; - map_iter begin2; - map_iter end2; - - // makes the lookups for the small digest and the large digest - boost::tie(begin1, end1) = m_hash_to_piece.equal_range(small_hash); - boost::tie(begin2, end2) = m_hash_to_piece.equal_range(large_hash); - - // copy all potential piece indices into this vector - std::vector matching_pieces; - for (map_iter i = begin1; i != end1; ++i) - matching_pieces.push_back(i->second); - for (map_iter i = begin2; i != end2; ++i) - matching_pieces.push_back(i->second); - - // no piece matched the data in the slot - if (matching_pieces.empty()) - return unassigned; - - // ------------------------------------------ - // CHECK IF THE PIECE IS IN ITS CORRECT PLACE - // ------------------------------------------ - - if (std::find( - matching_pieces.begin() - , matching_pieces.end() - , current_slot) != matching_pieces.end()) - { - // the current slot is among the matching pieces, so - // we will assume that the piece is in the right place - const int piece_index = current_slot; - - int other_slot = m_piece_to_slot[piece_index]; - if (other_slot >= 0) - { - // we have already found a piece with - // this index. - - // take one of the other matching pieces - // that hasn't already been assigned - int other_piece = -1; - for (std::vector::iterator i = matching_pieces.begin(); - i != matching_pieces.end(); ++i) - { - if (m_piece_to_slot[*i] >= 0 || *i == piece_index) continue; - other_piece = *i; - break; - } - if (other_piece >= 0) - { - // replace the old slot with 'other_piece' - m_slot_to_piece[other_slot] = other_piece; - m_piece_to_slot[other_piece] = other_slot; - } - else - { - // this index is the only piece with this - // hash. The previous slot we found with - // this hash must be the same piece. Mark - // that piece as unassigned, since this slot - // is the correct place for the piece. - m_slot_to_piece[other_slot] = unassigned; - if (m_storage_mode == internal_storage_mode_compact_deprecated) - m_free_slots.push_back(other_slot); - } - TORRENT_ASSERT(m_piece_to_slot[piece_index] != current_slot); - TORRENT_ASSERT(m_piece_to_slot[piece_index] >= 0); - m_piece_to_slot[piece_index] = has_no_slot; - } - - TORRENT_ASSERT(m_piece_to_slot[piece_index] == has_no_slot); - - return piece_index; - } - - // find a matching piece that hasn't - // already been assigned - int free_piece = unassigned; - for (std::vector::iterator i = matching_pieces.begin(); - i != matching_pieces.end(); ++i) - { - if (m_piece_to_slot[*i] >= 0) continue; - free_piece = *i; - break; - } - - if (free_piece >= 0) - { - TORRENT_ASSERT(m_piece_to_slot[free_piece] == has_no_slot); - return free_piece; - } - else - { - TORRENT_ASSERT(free_piece == unassigned); - return unassigned; - } - } - - int piece_manager::check_no_fastresume(error_code& error) + int piece_manager::check_no_fastresume(storage_error& ec) { bool has_files = false; - if (!m_storage->settings().no_recheck_incomplete_resume) + if (!m_storage->settings().get_bool(settings_pack::no_recheck_incomplete_resume)) { - has_files = m_storage->has_any_file(); - if (m_storage->error()) - return fatal_disk_error; + storage_error se; + has_files = m_storage->has_any_file(se); + + if (se) + { + ec = se; + return fatal_disk_error; + } if (has_files) { - m_state = state_full_check; - m_piece_to_slot.clear(); - m_piece_to_slot.resize(m_files.num_pieces(), has_no_slot); - m_slot_to_piece.clear(); - m_slot_to_piece.resize(m_files.num_pieces(), unallocated); - if (m_storage_mode == internal_storage_mode_compact_deprecated) - { - m_unallocated_slots.clear(); - m_free_slots.clear(); - } - TORRENT_ASSERT(int(m_piece_to_slot.size()) == m_files.num_pieces()); - return need_full_check; + // always initialize the storage + int ret = check_init_storage(ec); + return ret != no_error ? ret : need_full_check; } } - if (m_storage_mode == internal_storage_mode_compact_deprecated) - { - // in compact mode without checking, we need to - // populate the unallocated list - TORRENT_ASSERT(m_unallocated_slots.empty()); - for (int i = 0, end(m_files.num_pieces()); i < end; ++i) - m_unallocated_slots.push_back(i); - m_piece_to_slot.clear(); - m_piece_to_slot.resize(m_files.num_pieces(), has_no_slot); - m_slot_to_piece.clear(); - m_slot_to_piece.resize(m_files.num_pieces(), unallocated); - } - - return check_init_storage(error); + return check_init_storage(ec); } - int piece_manager::check_init_storage(error_code& error) + int piece_manager::check_init_storage(storage_error& ec) { - if (m_storage->initialize(m_storage_mode == storage_mode_allocate)) + storage_error se; + m_storage->initialize(se); + if (se) { - error = m_storage->error(); - TORRENT_ASSERT(error); - m_current_slot = 0; - return fatal_disk_error; - } - m_state = state_finished; - m_scratch_buffer.reset(); - m_scratch_buffer2.reset(); - if (m_storage_mode != internal_storage_mode_compact_deprecated) - { - // if no piece is out of place - // since we're in full allocation mode, we can - // forget the piece allocation tables - std::vector().swap(m_piece_to_slot); - std::vector().swap(m_slot_to_piece); - std::vector().swap(m_free_slots); - std::vector().swap(m_unallocated_slots); + ec = se; + return fatal_disk_error; } + return no_error; } @@ -2128,23 +1429,17 @@ ret: // isn't return false and the full check // will be run int piece_manager::check_fastresume( - lazy_entry const& rd, error_code& error) + lazy_entry const& rd, storage_error& ec) { - mutex::scoped_lock lock(m_mutex); - - INVARIANT_CHECK; - TORRENT_ASSERT(m_files.piece_length() > 0); - m_current_slot = 0; - // if we don't have any resume data, return - if (rd.type() == lazy_entry::none_t) return check_no_fastresume(error); + if (rd.type() == lazy_entry::none_t) return check_no_fastresume(ec); if (rd.type() != lazy_entry::dict_t) { - error = errors::not_a_dictionary; - return check_no_fastresume(error); + ec.ec = errors::not_a_dictionary; + return check_no_fastresume(ec); } int block_size = (std::min)(16 * 1024, m_files.piece_length()); @@ -2152,988 +1447,205 @@ ret: if (blocks_per_piece != -1 && blocks_per_piece != m_files.piece_length() / block_size) { - error = errors::invalid_blocks_per_piece; - return check_no_fastresume(error); + ec.ec = errors::invalid_blocks_per_piece; + return check_no_fastresume(ec); } - storage_mode_t storage_mode = internal_storage_mode_compact_deprecated; - if (rd.dict_find_string_value("allocation") != "compact") - storage_mode = storage_mode_sparse; + if (!m_storage->verify_resume_data(rd, ec)) + return check_no_fastresume(ec); - if (!m_storage->verify_resume_data(rd, error)) - return check_no_fastresume(error); + return check_init_storage(ec); + } - // assume no piece is out of place (i.e. in a slot - // other than the one it should be in) - bool out_of_place = false; + // ====== disk_job_fence implementation ======== - // if we don't have a piece map, we need the slots - // if we're in compact mode, we also need the slots map - if (storage_mode == internal_storage_mode_compact_deprecated || rd.dict_find("pieces") == 0) + disk_job_fence::disk_job_fence() + : m_has_fence(0) + , m_outstanding_jobs(0) + {} + + int disk_job_fence::job_complete(disk_io_job* j, tailqueue& jobs) + { + mutex::scoped_lock l(m_mutex); + + TORRENT_ASSERT(j->flags & disk_io_job::in_progress); + j->flags &= ~disk_io_job::in_progress; + + TORRENT_ASSERT(m_outstanding_jobs > 0); + --m_outstanding_jobs; + if (j->flags & disk_io_job::fence) { - // read slots map - lazy_entry const* slots = rd.dict_find_list("slots"); - if (slots == 0) - { - error = errors::missing_slots; - return check_no_fastresume(error); - } + // a fence job just completed. Make sure the fence logic + // works by asserting m_outstanding_jobs is in fact 0 now + TORRENT_ASSERT(m_outstanding_jobs == 0); - if ((int)slots->list_size() > m_files.num_pieces()) - { - error = errors::too_many_slots; - return check_no_fastresume(error); - } + // the fence can now be lowered + --m_has_fence; - if (m_storage_mode == internal_storage_mode_compact_deprecated) + // now we need to post all jobs that have been queued up + // while this fence was up. However, if there's another fence + // in the queue, stop there and raise the fence again + int ret = 0; + while (m_blocked_jobs.size()) { - int num_pieces = int(m_files.num_pieces()); - m_slot_to_piece.resize(num_pieces, unallocated); - m_piece_to_slot.resize(num_pieces, has_no_slot); - for (int i = 0; i < slots->list_size(); ++i) + disk_io_job *bj = (disk_io_job*)m_blocked_jobs.pop_front(); + if (bj->flags & disk_io_job::fence) { - lazy_entry const* e = slots->list_at(i); - if (e->type() != lazy_entry::int_t) + // we encountered another fence. We cannot post anymore + // jobs from the blocked jobs queue. We have to go back + // into a raised fence mode and wait for all current jobs + // to complete. The exception is that if there are no jobs + // executing currently, we should add the fence job. + if (m_outstanding_jobs == 0 && jobs.empty()) { - error = errors::invalid_slot_list; - return check_no_fastresume(error); - } - - int index = int(e->int_value()); - if (index >= num_pieces || index < -2) - { - error = errors::invalid_piece_index; - return check_no_fastresume(error); - } - if (index >= 0) - { - m_slot_to_piece[i] = index; - m_piece_to_slot[index] = i; - if (i != index) out_of_place = true; - } - else if (index == unassigned) - { - if (m_storage_mode == internal_storage_mode_compact_deprecated) - m_free_slots.push_back(i); + TORRENT_ASSERT((bj->flags & disk_io_job::in_progress) == 0); + bj->flags |= disk_io_job::in_progress; + ++m_outstanding_jobs; + ++ret; +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(bj->blocked); + bj->blocked = false; +#endif + jobs.push_back(bj); } else { - TORRENT_ASSERT(index == unallocated); - if (m_storage_mode == internal_storage_mode_compact_deprecated) - m_unallocated_slots.push_back(i); + // put the fence job back in the blocked queue + m_blocked_jobs.push_front(bj); } + return ret; } - } - else - { - for (int i = 0; i < slots->list_size(); ++i) - { - lazy_entry const* e = slots->list_at(i); - if (e->type() != lazy_entry::int_t) - { - error = errors::invalid_slot_list; - return check_no_fastresume(error); - } + TORRENT_ASSERT((bj->flags & disk_io_job::in_progress) == 0); + bj->flags |= disk_io_job::in_progress; - int index = int(e->int_value()); - if (index != i && index >= 0) - { - error = errors::invalid_piece_index; - return check_no_fastresume(error); - } - } + ++m_outstanding_jobs; + ++ret; +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(bj->blocked); + bj->blocked = false; +#endif + jobs.push_back(bj); } - - // This will corrupt the storage - // use while debugging to find - // states that cannot be scanned - // by check_pieces. - // m_storage->shuffle(); - - if (m_storage_mode == internal_storage_mode_compact_deprecated) - { - if (m_unallocated_slots.empty()) switch_to_full_mode(); - } - else - { - TORRENT_ASSERT(m_free_slots.empty()); - TORRENT_ASSERT(m_unallocated_slots.empty()); - - if (out_of_place) - { - // in this case we're in full allocation mode, but - // we're resuming a compact allocated storage - m_state = state_expand_pieces; - m_current_slot = 0; - error = errors::pieces_need_reorder; - TORRENT_ASSERT(int(m_piece_to_slot.size()) == m_files.num_pieces()); - return need_full_check; - } - } - - } - else if (m_storage_mode == internal_storage_mode_compact_deprecated) - { - // read piece map - lazy_entry const* pieces = rd.dict_find("pieces"); - if (pieces == 0 || pieces->type() != lazy_entry::string_t) - { - error = errors::missing_pieces; - return check_no_fastresume(error); - } - - if ((int)pieces->string_length() != m_files.num_pieces()) - { - error = errors::too_many_slots; - return check_no_fastresume(error); - } - - int num_pieces = int(m_files.num_pieces()); - m_slot_to_piece.resize(num_pieces, unallocated); - m_piece_to_slot.resize(num_pieces, has_no_slot); - char const* have_pieces = pieces->string_ptr(); - for (int i = 0; i < num_pieces; ++i) - { - if (have_pieces[i] & 1) - { - m_slot_to_piece[i] = i; - m_piece_to_slot[i] = i; - } - else - { - m_free_slots.push_back(i); - } - } - if (m_unallocated_slots.empty()) switch_to_full_mode(); + return ret; } - return check_init_storage(error); + // there are still outstanding jobs, even if we have a + // fence, it's not time to lower it yet + // also, if we don't have a fence, we're done + if (m_outstanding_jobs > 0 || m_has_fence == 0) return 0; + + // there's a fence raised, and no outstanding operations. + // it means we can execute the fence job right now. + TORRENT_ASSERT(m_blocked_jobs.size() > 0); + + // this is the fence job + disk_io_job *bj = (disk_io_job*)m_blocked_jobs.pop_front(); + TORRENT_ASSERT(bj->flags & disk_io_job::fence); + + TORRENT_ASSERT((bj->flags & disk_io_job::in_progress) == 0); + bj->flags |= disk_io_job::in_progress; + + ++m_outstanding_jobs; +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(bj->blocked); + bj->blocked = false; +#endif + // prioritize fence jobs since they're blocking other jobs + jobs.push_front(bj); + return 1; } -/* - state chart: - - check_fastresume() ----------+ - | - | | | - | v v - | +------------+ +---------------+ - | | full_check |-->| expand_pieses | - | +------------+ +---------------+ - | | | - | v | - | +--------------+ | - +->| finished | <------+ - +--------------+ -*/ - - - // performs the full check and full allocation - // (if necessary). returns true if finished and - // false if it should be called again - // the second return value is the progress the - // file check is at. 0 is nothing done, and 1 - // is finished - int piece_manager::check_files(int& current_slot, int& have_piece, error_code& error) + bool disk_job_fence::is_blocked(disk_io_job* j) { - if (m_state == state_none) return check_no_fastresume(error); + mutex::scoped_lock l(m_mutex); + DLOG(stderr, "[%p] is_blocked: fence: %d num_outstanding: %d\n" + , this, m_has_fence, int(m_outstanding_jobs)); - if (m_piece_to_slot.empty()) + // if this is the job that raised the fence, don't block it + // ignore fence can only ignore one fence. If there are several, + // this job still needs to get queued up + if (m_has_fence == 0) { - m_piece_to_slot.clear(); - m_piece_to_slot.resize(m_files.num_pieces(), has_no_slot); + TORRENT_ASSERT((j->flags & disk_io_job::in_progress) == 0); + j->flags |= disk_io_job::in_progress; + ++m_outstanding_jobs; + return false; } - if (m_slot_to_piece.empty()) - { - m_slot_to_piece.clear(); - m_slot_to_piece.resize(m_files.num_pieces(), unallocated); - } - - current_slot = m_current_slot; - have_piece = -1; - if (m_state == state_expand_pieces) - { - INVARIANT_CHECK; - - if (m_scratch_piece >= 0) - { - int piece = m_scratch_piece; - int other_piece = m_slot_to_piece[piece]; - m_scratch_piece = -1; - - if (other_piece >= 0) - { - if (!m_scratch_buffer2.get()) - m_scratch_buffer2.reset(page_aligned_allocator::malloc(m_files.piece_length())); - - int piece_size = m_files.piece_size(other_piece); - file::iovec_t b = {m_scratch_buffer2.get(), size_t(piece_size) }; - if (m_storage->readv(&b, piece, 0, 1) != piece_size) - { - error = m_storage->error(); - TORRENT_ASSERT(error); - return fatal_disk_error; - } - m_scratch_piece = other_piece; - m_piece_to_slot[other_piece] = unassigned; - } - - // the slot where this piece belongs is - // free. Just move the piece there. - int piece_size = m_files.piece_size(piece); - file::iovec_t b = {m_scratch_buffer.get(), size_t(piece_size) }; - if (m_storage->writev(&b, piece, 0, 1) != piece_size) - { - error = m_storage->error(); - TORRENT_ASSERT(error); - return fatal_disk_error; - } - m_piece_to_slot[piece] = piece; - m_slot_to_piece[piece] = piece; - - if (other_piece >= 0) m_scratch_buffer.swap(m_scratch_buffer2); - TORRENT_ASSERT(int(m_piece_to_slot.size()) == m_files.num_pieces()); - return need_full_check; - } + m_blocked_jobs.push_back(j); - while (m_current_slot < m_files.num_pieces() - && (m_slot_to_piece[m_current_slot] == m_current_slot - || m_slot_to_piece[m_current_slot] < 0)) - { - ++m_current_slot; - } +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(j->blocked == false); + j->blocked = true; +#endif - if (m_current_slot == m_files.num_pieces()) - { - return check_init_storage(error); - } - - TORRENT_ASSERT(m_current_slot < m_files.num_pieces()); - - int piece = m_slot_to_piece[m_current_slot]; - TORRENT_ASSERT(piece >= 0); - int other_piece = m_slot_to_piece[piece]; - if (other_piece >= 0) - { - // there is another piece in the slot - // where this one goes. Store it in the scratch - // buffer until next iteration. - if (!m_scratch_buffer.get()) - m_scratch_buffer.reset(page_aligned_allocator::malloc(m_files.piece_length())); - - int piece_size = m_files.piece_size(other_piece); - file::iovec_t b = {m_scratch_buffer.get(), size_t(piece_size) }; - if (m_storage->readv(&b, piece, 0, 1) != piece_size) - { - error = m_storage->error(); - TORRENT_ASSERT(error); - return fatal_disk_error; - } - m_scratch_piece = other_piece; - m_piece_to_slot[other_piece] = unassigned; - } - - // the slot where this piece belongs is - // free. Just move the piece there. - m_last_piece = piece; - m_storage->move_slot(m_current_slot, piece); - if (m_storage->error()) return -1; - - m_piece_to_slot[piece] = piece; - m_slot_to_piece[m_current_slot] = unassigned; - m_slot_to_piece[piece] = piece; - - TORRENT_ASSERT(int(m_piece_to_slot.size()) == m_files.num_pieces()); - return need_full_check; - } - - TORRENT_ASSERT(m_state == state_full_check); - if (m_state == state_finished) return 0; - - int skip = check_one_piece(have_piece); - TORRENT_ASSERT(m_current_slot <= m_files.num_pieces()); - - if (skip == -1) - { - error = m_storage->error(); - TORRENT_ASSERT(error); - return fatal_disk_error; - } - - if (skip > 0) - { - clear_error(); - // skip means that the piece we checked failed to be read from disk - // completely. This may be caused by the file not being there, or the - // piece overlapping with a sparse region. We should skip 'skip' number - // of pieces - - if (m_storage_mode == internal_storage_mode_compact_deprecated) - { - for (int i = m_current_slot; i < m_current_slot + skip - 1; ++i) - { - TORRENT_ASSERT(m_slot_to_piece[i] == unallocated); - m_unallocated_slots.push_back(i); - } - } - - // current slot will increase by one below - m_current_slot += skip - 1; - TORRENT_ASSERT(m_current_slot <= m_files.num_pieces()); - } - - ++m_current_slot; - current_slot = m_current_slot; - - if (m_current_slot >= m_files.num_pieces()) - { - TORRENT_ASSERT(m_current_slot == m_files.num_pieces()); - - // clear the memory we've been using - std::multimap().swap(m_hash_to_piece); - - if (m_storage_mode != internal_storage_mode_compact_deprecated) - { - if (!m_out_of_place) - { - // if no piece is out of place - // since we're in full allocation mode, we can - // forget the piece allocation tables - - std::vector().swap(m_piece_to_slot); - std::vector().swap(m_slot_to_piece); - return check_init_storage(error); - } - else - { - // in this case we're in full allocation mode, but - // we're resuming a compact allocated storage - m_state = state_expand_pieces; - m_current_slot = 0; - current_slot = m_current_slot; - TORRENT_ASSERT(int(m_piece_to_slot.size()) == m_files.num_pieces()); - return need_full_check; - } - } - else if (m_unallocated_slots.empty()) - { - switch_to_full_mode(); - } - return check_init_storage(error); - } - - TORRENT_ASSERT(int(m_piece_to_slot.size()) == m_files.num_pieces()); - return need_full_check; + return true; } - int piece_manager::skip_file() const + bool disk_job_fence::has_fence() const { - size_type file_offset = 0; - size_type current_offset = size_type(m_current_slot) * m_files.piece_length(); - for (int i = 0; i < m_files.num_files(); ++i) - { - file_offset += m_files.file_size(i); - if (file_offset > current_offset) break; - } - - TORRENT_ASSERT(file_offset > current_offset); - int ret = static_cast( - (file_offset - current_offset + m_files.piece_length() - 1) - / m_files.piece_length()); - TORRENT_ASSERT(ret >= 1); - return ret; + mutex::scoped_lock l(m_mutex); + return m_has_fence; } - // -1 = error, 0 = ok, >0 = skip this many pieces - int piece_manager::check_one_piece(int& have_piece) + int disk_job_fence::num_blocked() const { - // ------------------------ - // DO THE FULL CHECK - // ------------------------ + mutex::scoped_lock l(m_mutex); + return m_blocked_jobs.size(); + } - TORRENT_ASSERT(int(m_piece_to_slot.size()) == m_files.num_pieces()); - TORRENT_ASSERT(int(m_slot_to_piece.size()) == m_files.num_pieces()); - TORRENT_ASSERT(have_piece == -1); + // j is the fence job. It must have exclusive access to the storage + // fj is the flush job. If the job j is queued, we need to issue + // this job + int disk_job_fence::raise_fence(disk_io_job* j, disk_io_job* fj, atomic_count* blocked_counter) + { + TORRENT_ASSERT((j->flags & disk_io_job::fence) == 0); + j->flags |= disk_io_job::fence; - // initialization for the full check - if (m_hash_to_piece.empty()) + mutex::scoped_lock l(m_mutex); + + DLOG(stderr, "[%p] raise_fence: fence: %d num_outstanding: %d\n" + , this, m_has_fence, int(m_outstanding_jobs)); + + if (m_has_fence == 0 && m_outstanding_jobs == 0) { - for (int i = 0; i < m_files.num_pieces(); ++i) - m_hash_to_piece.insert(std::pair(m_info->hash_for_piece(i), i)); + ++m_has_fence; + DLOG(stderr, "[%p] raise_fence: need posting\n", this); + + // the job j is expected to be put on the job queue + // after this, without being passed through is_blocked() + // that's why we're accounting for it here + + // fj is expected to be discarded by the caller + j->flags |= disk_io_job::in_progress; + ++m_outstanding_jobs; + return fence_post_fence; } - partial_hash ph; - int num_read = 0; - int piece_size = m_files.piece_size(m_current_slot); - int small_piece_size = m_files.piece_size(m_files.num_pieces() - 1); - bool read_short = true; - sha1_hash small_hash; - if (piece_size == small_piece_size) + ++m_has_fence; + if (m_has_fence > 1) { - num_read = hash_for_slot(m_current_slot, ph, piece_size, 0, 0); +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(fj->blocked == false); + fj->blocked = true; +#endif + m_blocked_jobs.push_back(fj); + ++*blocked_counter; } else { - num_read = hash_for_slot(m_current_slot, ph, piece_size - , small_piece_size, &small_hash); + // in this case, fj is expected to be put on the job queue + fj->flags |= disk_io_job::in_progress; + ++m_outstanding_jobs; } - read_short = num_read != piece_size; - - if (read_short) - { - if (m_storage->error() -#ifdef TORRENT_WINDOWS - && m_storage->error() != error_code(ERROR_PATH_NOT_FOUND, get_system_category()) - && m_storage->error() != error_code(ERROR_FILE_NOT_FOUND, get_system_category()) - && m_storage->error() != error_code(ERROR_HANDLE_EOF, get_system_category()) - && m_storage->error() != error_code(ERROR_INVALID_HANDLE, get_system_category())) -#else - && m_storage->error() != error_code(ENOENT, get_posix_category())) +#if TORRENT_USE_ASSERTS + TORRENT_ASSERT(j->blocked == false); + j->blocked = true; #endif - { - return -1; - } - // if the file is incomplete, skip the rest of it - return skip_file(); - } + m_blocked_jobs.push_back(j); + ++*blocked_counter; - sha1_hash large_hash = ph.h.final(); - int piece_index = identify_data(large_hash, small_hash, m_current_slot); - - if (piece_index >= 0) have_piece = piece_index; - - if (piece_index != m_current_slot - && piece_index >= 0) - m_out_of_place = true; - - TORRENT_ASSERT(piece_index == unassigned || piece_index >= 0); - - const bool this_should_move = piece_index >= 0 && m_slot_to_piece[piece_index] != unallocated; - const bool other_should_move = m_piece_to_slot[m_current_slot] != has_no_slot; - - // check if this piece should be swapped with any other slot - // this section will ensure that the storage is correctly sorted - // libtorrent will never leave the storage in a state that - // requires this sorting, but other clients may. - - // example of worst case: - // | m_current_slot = 5 - // V - // +---+- - - +---+- - - +---+- - - // | x | | 5 | | 3 | <- piece data in slots - // +---+- - - +---+- - - +---+- - - // 3 y 5 <- slot index - - // in this example, the data in the m_current_slot (5) - // is piece 3. It has to be moved into slot 3. The data - // in slot y (piece 5) should be moved into the m_current_slot. - // and the data in slot 3 (piece x) should be moved to slot y. - - // there are three possible cases. - // 1. There's another piece that should be placed into this slot - // 2. This piece should be placed into another slot. - // 3. There's another piece that should be placed into this slot - // and this piece should be placed into another slot - - // swap piece_index with this slot - - // case 1 - if (this_should_move && !other_should_move) - { - TORRENT_ASSERT(piece_index != m_current_slot); - - const int other_slot = piece_index; - TORRENT_ASSERT(other_slot >= 0); - int other_piece = m_slot_to_piece[other_slot]; - - m_slot_to_piece[other_slot] = piece_index; - m_slot_to_piece[m_current_slot] = other_piece; - m_piece_to_slot[piece_index] = piece_index; - if (other_piece >= 0) m_piece_to_slot[other_piece] = m_current_slot; - - if (other_piece == unassigned) - { - std::vector::iterator i = - std::find(m_free_slots.begin(), m_free_slots.end(), other_slot); - TORRENT_ASSERT(i != m_free_slots.end()); - if (m_storage_mode == internal_storage_mode_compact_deprecated) - { - m_free_slots.erase(i); - m_free_slots.push_back(m_current_slot); - } - } - - bool ret = false; - m_last_piece = piece_index; - if (other_piece >= 0) - ret |= m_storage->swap_slots(other_slot, m_current_slot); - else - ret |= m_storage->move_slot(m_current_slot, other_slot); - - if (ret) return skip_file(); - - TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unassigned - || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); - } - // case 2 - else if (!this_should_move && other_should_move) - { - TORRENT_ASSERT(piece_index != m_current_slot); - - const int other_piece = m_current_slot; - const int other_slot = m_piece_to_slot[other_piece]; - TORRENT_ASSERT(other_slot >= 0); - - m_slot_to_piece[m_current_slot] = other_piece; - m_slot_to_piece[other_slot] = piece_index; - m_piece_to_slot[other_piece] = m_current_slot; - - if (piece_index == unassigned - && m_storage_mode == internal_storage_mode_compact_deprecated) - m_free_slots.push_back(other_slot); - - bool ret = false; - if (piece_index >= 0) - { - m_piece_to_slot[piece_index] = other_slot; - ret |= m_storage->swap_slots(other_slot, m_current_slot); - } - else - { - ret |= m_storage->move_slot(other_slot, m_current_slot); - - } - m_last_piece = other_piece; - if (ret) return skip_file(); - - TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unassigned - || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); - } - else if (this_should_move && other_should_move) - { - TORRENT_ASSERT(piece_index != m_current_slot); - TORRENT_ASSERT(piece_index >= 0); - - const int piece1 = m_slot_to_piece[piece_index]; - const int piece2 = m_current_slot; - const int slot1 = piece_index; - const int slot2 = m_piece_to_slot[piece2]; - - TORRENT_ASSERT(slot1 >= 0); - TORRENT_ASSERT(slot2 >= 0); - TORRENT_ASSERT(piece2 >= 0); - - if (slot1 == slot2) - { - // this means there are only two pieces involved in the swap - TORRENT_ASSERT(piece1 >= 0); - - // movement diagram: - // +-------------------------------+ - // | | - // +--> slot1 --> m_current_slot --+ - - m_slot_to_piece[slot1] = piece_index; - m_slot_to_piece[m_current_slot] = piece1; - - m_piece_to_slot[piece_index] = slot1; - m_piece_to_slot[piece1] = m_current_slot; - - TORRENT_ASSERT(piece1 == m_current_slot); - TORRENT_ASSERT(piece_index == slot1); - - m_last_piece = piece_index; - m_storage->swap_slots(m_current_slot, slot1); - - TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unassigned - || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); - } - else - { - TORRENT_ASSERT(slot1 != slot2); - TORRENT_ASSERT(piece1 != piece2); - - // movement diagram: - // +-----------------------------------------+ - // | | - // +--> slot1 --> slot2 --> m_current_slot --+ - - m_slot_to_piece[slot1] = piece_index; - m_slot_to_piece[slot2] = piece1; - m_slot_to_piece[m_current_slot] = piece2; - - m_piece_to_slot[piece_index] = slot1; - m_piece_to_slot[m_current_slot] = piece2; - - if (piece1 == unassigned) - { - std::vector::iterator i = - std::find(m_free_slots.begin(), m_free_slots.end(), slot1); - TORRENT_ASSERT(i != m_free_slots.end()); - if (m_storage_mode == internal_storage_mode_compact_deprecated) - { - m_free_slots.erase(i); - m_free_slots.push_back(slot2); - } - } - - bool ret = false; - if (piece1 >= 0) - { - m_piece_to_slot[piece1] = slot2; - ret |= m_storage->swap_slots3(m_current_slot, slot1, slot2); - } - else - { - ret |= m_storage->move_slot(m_current_slot, slot1); - ret |= m_storage->move_slot(slot2, m_current_slot); - } - - m_last_piece = piece_index; - if (ret) return skip_file(); - - TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unassigned - || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); - } - } - else - { - TORRENT_ASSERT(m_piece_to_slot[m_current_slot] == has_no_slot || piece_index != m_current_slot); - TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unallocated); - TORRENT_ASSERT(piece_index == unassigned || m_piece_to_slot[piece_index] == has_no_slot); - - // the slot was identified as piece 'piece_index' - if (piece_index != unassigned) - m_piece_to_slot[piece_index] = m_current_slot; - else if (m_storage_mode == internal_storage_mode_compact_deprecated) - m_free_slots.push_back(m_current_slot); - - m_slot_to_piece[m_current_slot] = piece_index; - - TORRENT_ASSERT(m_slot_to_piece[m_current_slot] == unassigned - || m_piece_to_slot[m_slot_to_piece[m_current_slot]] == m_current_slot); - } - - if (piece_index == unassigned) - { - // the data did not match any piece. Maybe we're reading - // from a sparse region, see if we are and skip - if (m_current_slot == m_files.num_pieces() -1) return 0; - - int next_slot = m_storage->sparse_end(m_current_slot + 1); - if (next_slot > m_current_slot + 1) return next_slot - m_current_slot; - } - - return 0; + return m_has_fence > 1 ? fence_post_none : fence_post_flush; } - - void piece_manager::switch_to_full_mode() - { - TORRENT_ASSERT(m_storage_mode == internal_storage_mode_compact_deprecated); - TORRENT_ASSERT(m_unallocated_slots.empty()); - // we have allocated all slots, switch to - // full allocation mode in order to free - // some unnecessary memory. - m_storage_mode = storage_mode_sparse; - std::vector().swap(m_unallocated_slots); - std::vector().swap(m_free_slots); - std::vector().swap(m_piece_to_slot); - std::vector().swap(m_slot_to_piece); - } - - int piece_manager::allocate_slot_for_piece(int piece_index) - { - mutex::scoped_lock lock(m_mutex); - - if (m_storage_mode != internal_storage_mode_compact_deprecated) return piece_index; - -#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS - INVARIANT_CHECK; -#endif - - TORRENT_ASSERT(piece_index >= 0); - TORRENT_ASSERT(piece_index < (int)m_piece_to_slot.size()); - TORRENT_ASSERT(m_piece_to_slot.size() == m_slot_to_piece.size()); - - int slot_index = m_piece_to_slot[piece_index]; - - if (slot_index != has_no_slot) - { - TORRENT_ASSERT(slot_index >= 0); - TORRENT_ASSERT(slot_index < (int)m_slot_to_piece.size()); - return slot_index; - } - - if (m_free_slots.empty()) - { - allocate_slots_impl(1, lock); - TORRENT_ASSERT(!m_free_slots.empty()); - } - - std::vector::iterator iter( - std::find( - m_free_slots.begin() - , m_free_slots.end() - , piece_index)); - - if (iter == m_free_slots.end()) - { - TORRENT_ASSERT(m_slot_to_piece[piece_index] != unassigned); - TORRENT_ASSERT(!m_free_slots.empty()); - iter = m_free_slots.end() - 1; - - // special case to make sure we don't use the last slot - // when we shouldn't, since it's smaller than ordinary slots - if (*iter == m_files.num_pieces() - 1 && piece_index != *iter) - { - if (m_free_slots.size() == 1) - allocate_slots_impl(1, lock); - TORRENT_ASSERT(m_free_slots.size() > 1); - // assumes that all allocated slots - // are put at the end of the free_slots vector - iter = m_free_slots.end() - 1; - } - } - - slot_index = *iter; - m_free_slots.erase(iter); - - TORRENT_ASSERT(m_slot_to_piece[slot_index] == unassigned); - - m_slot_to_piece[slot_index] = piece_index; - m_piece_to_slot[piece_index] = slot_index; - - // there is another piece already assigned to - // the slot we are interested in, swap positions - if (slot_index != piece_index - && m_slot_to_piece[piece_index] >= 0) - { - int piece_at_our_slot = m_slot_to_piece[piece_index]; - TORRENT_ASSERT(m_piece_to_slot[piece_at_our_slot] == piece_index); - - std::swap( - m_slot_to_piece[piece_index] - , m_slot_to_piece[slot_index]); - - std::swap( - m_piece_to_slot[piece_index] - , m_piece_to_slot[piece_at_our_slot]); - - m_last_piece = piece_index; - m_storage->move_slot(piece_index, slot_index); - - TORRENT_ASSERT(m_slot_to_piece[piece_index] == piece_index); - TORRENT_ASSERT(m_piece_to_slot[piece_index] == piece_index); - - slot_index = piece_index; - -#if defined TORRENT_DEBUG && defined TORRENT_STORAGE_DEBUG - debug_log(); -#endif - } - TORRENT_ASSERT(slot_index >= 0); - TORRENT_ASSERT(slot_index < (int)m_slot_to_piece.size()); - - if (m_free_slots.empty() && m_unallocated_slots.empty()) - switch_to_full_mode(); - - return slot_index; - } - - bool piece_manager::allocate_slots_impl(int num_slots, mutex::scoped_lock& l - , bool abort_on_disk) - { - TORRENT_ASSERT(num_slots > 0); - -#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS - INVARIANT_CHECK; -#endif - - TORRENT_ASSERT(!m_unallocated_slots.empty()); - TORRENT_ASSERT(m_storage_mode == internal_storage_mode_compact_deprecated); - - bool written = false; - - for (int i = 0; i < num_slots && !m_unallocated_slots.empty(); ++i) - { - int pos = m_unallocated_slots.front(); - TORRENT_ASSERT(m_slot_to_piece[pos] == unallocated); - TORRENT_ASSERT(m_piece_to_slot[pos] != pos); - - int new_free_slot = pos; - if (m_piece_to_slot[pos] != has_no_slot) - { - m_last_piece = pos; - new_free_slot = m_piece_to_slot[pos]; - m_storage->move_slot(new_free_slot, pos); - m_slot_to_piece[pos] = pos; - m_piece_to_slot[pos] = pos; - written = true; - } - m_unallocated_slots.erase(m_unallocated_slots.begin()); - m_slot_to_piece[new_free_slot] = unassigned; - m_free_slots.push_back(new_free_slot); - if (abort_on_disk && written) break; - } - - TORRENT_ASSERT(m_free_slots.size() > 0); - return written; - } - - int piece_manager::slot_for(int piece) const - { - if (m_storage_mode != internal_storage_mode_compact_deprecated) return piece; - // this happens in seed mode, where we skip checking fastresume - if (m_piece_to_slot.empty()) return piece; - TORRENT_ASSERT(piece < int(m_piece_to_slot.size())); - TORRENT_ASSERT(piece >= 0); - return m_piece_to_slot[piece]; - } - - int piece_manager::piece_for(int slot) const - { - if (m_storage_mode != internal_storage_mode_compact_deprecated) return slot; - TORRENT_ASSERT(slot < int(m_slot_to_piece.size())); - TORRENT_ASSERT(slot >= 0); - return m_slot_to_piece[slot]; - } - -#if TORRENT_USE_INVARIANT_CHECKS - void piece_manager::check_invariant() const - { - TORRENT_ASSERT(m_current_slot <= m_files.num_pieces()); - - if (m_unallocated_slots.empty() - && m_free_slots.empty() - && m_state == state_finished) - { - TORRENT_ASSERT(m_storage_mode != internal_storage_mode_compact_deprecated - || m_files.num_pieces() == 0); - } - - if (m_storage_mode != internal_storage_mode_compact_deprecated) - { - TORRENT_ASSERT(m_unallocated_slots.empty()); - TORRENT_ASSERT(m_free_slots.empty()); - } - - if (m_storage_mode != internal_storage_mode_compact_deprecated - && m_state != state_expand_pieces - && m_state != state_full_check) - { - TORRENT_ASSERT(m_piece_to_slot.empty()); - TORRENT_ASSERT(m_slot_to_piece.empty()); - } - else - { - if (m_piece_to_slot.empty()) return; - - TORRENT_ASSERT((int)m_piece_to_slot.size() == m_files.num_pieces()); - TORRENT_ASSERT((int)m_slot_to_piece.size() == m_files.num_pieces()); - - for (std::vector::const_iterator i = m_free_slots.begin(); - i != m_free_slots.end(); ++i) - { - TORRENT_ASSERT(*i < (int)m_slot_to_piece.size()); - TORRENT_ASSERT(*i >= 0); - TORRENT_ASSERT(m_slot_to_piece[*i] == unassigned); - TORRENT_ASSERT(std::find(i+1, m_free_slots.end(), *i) - == m_free_slots.end()); - } - - for (std::vector::const_iterator i = m_unallocated_slots.begin(); - i != m_unallocated_slots.end(); ++i) - { - TORRENT_ASSERT(*i < (int)m_slot_to_piece.size()); - TORRENT_ASSERT(*i >= 0); - TORRENT_ASSERT(m_slot_to_piece[*i] == unallocated); - TORRENT_ASSERT(std::find(i+1, m_unallocated_slots.end(), *i) - == m_unallocated_slots.end()); - } - - for (int i = 0; i < m_files.num_pieces(); ++i) - { - // Check domain of piece_to_slot's elements - if (m_piece_to_slot[i] != has_no_slot) - { - TORRENT_ASSERT(m_piece_to_slot[i] >= 0); - TORRENT_ASSERT(m_piece_to_slot[i] < (int)m_slot_to_piece.size()); - } - - // Check domain of slot_to_piece's elements - if (m_slot_to_piece[i] != unallocated - && m_slot_to_piece[i] != unassigned) - { - TORRENT_ASSERT(m_slot_to_piece[i] >= 0); - TORRENT_ASSERT(m_slot_to_piece[i] < (int)m_piece_to_slot.size()); - } - - // do more detailed checks on piece_to_slot - if (m_piece_to_slot[i] >= 0) - { - TORRENT_ASSERT(m_slot_to_piece[m_piece_to_slot[i]] == i); - if (m_piece_to_slot[i] != i) - { - TORRENT_ASSERT(m_slot_to_piece[i] == unallocated); - } - } - else - { - TORRENT_ASSERT(m_piece_to_slot[i] == has_no_slot); - } - - // do more detailed checks on slot_to_piece - - if (m_slot_to_piece[i] >= 0) - { - TORRENT_ASSERT(m_slot_to_piece[i] < (int)m_piece_to_slot.size()); - TORRENT_ASSERT(m_piece_to_slot[m_slot_to_piece[i]] == i); -#ifdef TORRENT_STORAGE_DEBUG - TORRENT_ASSERT( - std::find( - m_unallocated_slots.begin() - , m_unallocated_slots.end() - , i) == m_unallocated_slots.end() - ); - TORRENT_ASSERT( - std::find( - m_free_slots.begin() - , m_free_slots.end() - , i) == m_free_slots.end() - ); -#endif - } - else if (m_slot_to_piece[i] == unallocated) - { -#ifdef TORRENT_STORAGE_DEBUG - TORRENT_ASSERT(m_unallocated_slots.empty() - || (std::find( - m_unallocated_slots.begin() - , m_unallocated_slots.end() - , i) != m_unallocated_slots.end()) - ); -#endif - } - else if (m_slot_to_piece[i] == unassigned) - { -#ifdef TORRENT_STORAGE_DEBUG - TORRENT_ASSERT( - std::find( - m_free_slots.begin() - , m_free_slots.end() - , i) != m_free_slots.end() - ); -#endif - } - else - { - TORRENT_ASSERT(false && "m_slot_to_piece[i] is invalid"); - } - } - } - } - -#endif } // namespace libtorrent diff --git a/src/string_util.cpp b/src/string_util.cpp index 576a1ca0a..aebcb94b8 100644 --- a/src/string_util.cpp +++ b/src/string_util.cpp @@ -144,6 +144,79 @@ namespace libtorrent return static_cast(p) + (8 - offset); } + // this parses the string that's used as the liste_interfaces setting. + // it is a comma-separated list of IP or device names with ports. For + // example: "eth0:6881,eth1:6881" or "127.0.0.1:6881" + void parse_comma_separated_string_port(std::string const& in + , std::vector >& out) + { + out.clear(); + + std::string::size_type start = 0; + std::string::size_type end = 0; + + while (start < in.size()) + { + // skip leading spaces + while (start < in.size() + && is_space(in[start])) + ++start; + + end = in.find_first_of(',', start); + if (end == std::string::npos) end = in.size(); + + std::string::size_type colon = in.find_last_of(':', end); + + if (colon != std::string::npos && colon > start) + { + int port = atoi(in.substr(colon + 1, end - colon - 1).c_str()); + + // skip trailing spaces + std::string::size_type soft_end = colon; + while (soft_end > start + && is_space(in[soft_end-1])) + --soft_end; + + // in case this is an IPv6 address, strip off the square brackets + // to make it more easily parseable into an ip::address + if (in[start] == '[') ++start; + if (soft_end > start && in[soft_end-1] == ']') --soft_end; + + out.push_back(std::make_pair(in.substr(start, soft_end - start), port)); + } + + start = end + 1; + } + } + + void parse_comma_separated_string(std::string const& in, std::vector& out) + { + out.clear(); + + std::string::size_type start = 0; + std::string::size_type end = 0; + + while (start < in.size()) + { + // skip leading spaces + while (start < in.size() + && is_space(in[start])) + ++start; + + end = in.find_first_of(',', start); + if (end == std::string::npos) end = in.size(); + + // skip trailing spaces + std::string::size_type soft_end = end; + while (soft_end > start + && is_space(in[soft_end-1])) + --soft_end; + + out.push_back(in.substr(start, soft_end - start)); + start = end + 1; + } + } + char* string_tokenize(char* last, char sep, char** next) { if (last == 0) return 0; diff --git a/src/tailqueue.cpp b/src/tailqueue.cpp new file mode 100644 index 000000000..aee0fa25f --- /dev/null +++ b/src/tailqueue.cpp @@ -0,0 +1,129 @@ +/* + +Copyright (c) 2012-2013, 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. + +*/ + +#include "libtorrent/tailqueue.hpp" + +namespace libtorrent +{ + tailqueue::tailqueue(): m_first(0), m_last(0), m_size(0) {} + + void tailqueue::append(tailqueue& rhs) + { + TORRENT_ASSERT(m_last == 0 || m_last->next == 0); + TORRENT_ASSERT(rhs.m_last == 0 || rhs.m_last->next == 0); + + if (rhs.m_first == 0) return; + + if (m_first == 0) + { + swap(rhs); + return; + } + + m_last->next = rhs.m_first; + m_last = rhs.m_last; + m_size += rhs.m_size; + rhs.m_first = 0; + rhs.m_last = 0; + rhs.m_size = 0; + + TORRENT_ASSERT(m_last == 0 || m_last->next == 0); + } + + void tailqueue::prepend(tailqueue& rhs) + { + TORRENT_ASSERT(m_last == 0 || m_last->next == 0); + TORRENT_ASSERT(rhs.m_last == 0 || rhs.m_last->next == 0); + + if (rhs.m_first == 0) return; + + if (m_first == 0) + { + swap(rhs); + return; + } + + swap(rhs); + append(rhs); + } + + tailqueue_node* tailqueue::pop_front() + { + TORRENT_ASSERT(m_last == 0 || m_last->next == 0); + tailqueue_node* e = m_first; + m_first = m_first->next; + if (e == m_last) m_last = 0; + e->next = 0; + --m_size; + return e; + } + void tailqueue::push_front(tailqueue_node* e) + { + TORRENT_ASSERT(e->next == 0); + TORRENT_ASSERT(m_last == 0 || m_last->next == 0); + e->next = m_first; + m_first = e; + if (!m_last) m_last = e; + ++m_size; + } + void tailqueue::push_back(tailqueue_node* e) + { + TORRENT_ASSERT(e->next == 0); + TORRENT_ASSERT(m_last == 0 || m_last->next == 0); + if (m_last) m_last->next = e; + else m_first = e; + m_last = e; + e->next = 0; + ++m_size; + } + tailqueue_node* tailqueue::get_all() + { + TORRENT_ASSERT(m_last == 0 || m_last->next == 0); + tailqueue_node* e = m_first; + m_first = 0; + m_last = 0; + m_size = 0; + return e; + } + void tailqueue::swap(tailqueue& rhs) + { + tailqueue_node* tmp = m_first; + m_first = rhs.m_first; + rhs.m_first = tmp; + tmp = m_last; + m_last = rhs.m_last; + rhs.m_last = tmp; + int tmp2 = m_size; + m_size = rhs.m_size; + rhs.m_size = tmp2; + } +} diff --git a/src/thread.cpp b/src/thread.cpp index c22aeb0e8..efcdc011b 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -42,6 +42,8 @@ POSSIBILITY OF SUCH DAMAGE. #include #endif +#include + namespace libtorrent { void sleep(int milliseconds) @@ -93,6 +95,11 @@ namespace libtorrent { pthread_cond_broadcast(&m_cond); } + + void condition_variable::notify() + { + pthread_cond_signal(&m_cond); + } #elif defined TORRENT_WINDOWS || defined TORRENT_CYGWIN condition_variable::condition_variable() : m_num_waiters(0) @@ -129,6 +136,11 @@ namespace libtorrent { ReleaseSemaphore(m_sem, m_num_waiters, 0); } + + void condition_variable::notify() + { + ReleaseSemaphore(m_sem, (std::min)(m_num_waiters, 1), 0); + } #elif defined TORRENT_BEOS condition_variable::condition_variable() : m_num_waiters(0) @@ -165,6 +177,11 @@ namespace libtorrent { release_sem_etc(m_sem, m_num_waiters, 0); } + + void condition_variable::notify() + { + release_sem_etc(m_sem, (std::min)(m_num_waiters, 1), 0); + } #else #error not implemented #endif diff --git a/src/time.cpp b/src/time.cpp index bea93856c..1cf561623 100644 --- a/src/time.cpp +++ b/src/time.cpp @@ -56,7 +56,7 @@ namespace libtorrent TORRENT_EXPORT ptime const& time_now() { return aux::g_current_time; } - char const* time_now_string() + TORRENT_EXTRA_EXPORT char const* time_now_string() { static const ptime start = time_now_hires(); static char ret[200]; @@ -81,174 +81,3 @@ namespace libtorrent } } -#if defined TORRENT_USE_BOOST_DATE_TIME - -#include - -namespace libtorrent -{ - ptime time_now_hires() - { return boost::date_time::microsec_clock::universal_time(); } - ptime min_time() - { return boost::posix_time::ptime(boost::posix_time::min_date_time); } - ptime max_time() - { return boost::posix_time::ptime(boost::posix_time::max_date_time); } - time_duration seconds(boost::int64_t s) { return boost::posix_time::seconds(s); } - time_duration milliseconds(boost::int64_t s) { return boost::posix_time::milliseconds(s); } - time_duration microsec(boost::int64_t s) { return boost::posix_time::microsec(s); } - time_duration minutes(boost::int64_t s) { return boost::posix_time::minutes(s); } - time_duration hours(boost::int64_t s) { return boost::posix_time::hours(s); } - - boost::int64_t total_seconds(time_duration td) - { return td.total_seconds(); } - boost::int64_t total_milliseconds(time_duration td) - { return td.total_milliseconds(); } - boost::int64_t total_microseconds(time_duration td) - { return td.total_microseconds(); } -} - -#else // TORRENT_USE_BOOST_DATE_TIME - -namespace libtorrent -{ - ptime min_time() { return ptime(0); } - ptime max_time() { return ptime((std::numeric_limits::max)()); } -} - -#if defined TORRENT_USE_ABSOLUTE_TIME - -#include -#include -#include "libtorrent/assert.hpp" - -// high precision timer for darwin intel and ppc - -namespace libtorrent -{ - ptime time_now_hires() - { - static mach_timebase_info_data_t timebase_info = {0,0}; - if (timebase_info.denom == 0) - mach_timebase_info(&timebase_info); - boost::uint64_t at = mach_absolute_time(); - // make sure we don't overflow - TORRENT_ASSERT((at >= at / 1000 * timebase_info.numer / timebase_info.denom) - || (at < 0 && at < at / 1000 * timebase_info.numer / timebase_info.denom)); - return ptime(at / 1000 * timebase_info.numer / timebase_info.denom); - } -} -#elif defined TORRENT_USE_QUERY_PERFORMANCE_TIMER - -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#include -#include "libtorrent/assert.hpp" - -namespace libtorrent -{ - boost::int64_t performance_counter_to_microseconds(boost::int64_t pc) - { - static LARGE_INTEGER performace_counter_frequency = {0,0}; - if (performace_counter_frequency.QuadPart == 0) - QueryPerformanceFrequency(&performace_counter_frequency); - -#ifdef TORRENT_DEBUG - // make sure we don't overflow - boost::int64_t ret = (pc * 1000 / performace_counter_frequency.QuadPart) * 1000; - TORRENT_ASSERT((pc >= 0 && pc >= ret) || (pc < 0 && pc < ret)); -#endif - return ((pc * 1000 + performace_counter_frequency.QuadPart / 2) / performace_counter_frequency.QuadPart) * 1000; - } - - boost::int64_t microseconds_to_performance_counter(boost::int64_t ms) - { - static LARGE_INTEGER performace_counter_frequency = {0,0}; - if (performace_counter_frequency.QuadPart == 0) - QueryPerformanceFrequency(&performace_counter_frequency); -#ifdef TORRENT_DEBUG - // make sure we don't overflow - boost::int64_t ret = (ms / 1000) * performace_counter_frequency.QuadPart / 1000; - TORRENT_ASSERT((ms >= 0 && ms <= ret) - || (ms < 0 && ms > ret)); -#endif - return (ms / 1000) * performace_counter_frequency.QuadPart / 1000; - } - - ptime time_now_hires() - { - LARGE_INTEGER now; - QueryPerformanceCounter(&now); - return ptime(now.QuadPart); - } - - boost::int64_t total_seconds(time_duration td) - { - return boost::int64_t(performance_counter_to_microseconds(td.diff) - / 1000000); - } - boost::int64_t total_milliseconds(time_duration td) - { - return boost::uint64_t(performance_counter_to_microseconds(td.diff) - / 1000); - } - boost::int64_t total_microseconds(time_duration td) - { - return performance_counter_to_microseconds(td.diff); - } - - time_duration microsec(boost::int64_t s) - { - return time_duration(microseconds_to_performance_counter(s)); - } - time_duration milliseconds(boost::int64_t s) - { - return time_duration(microseconds_to_performance_counter( - s * 1000)); - } - time_duration seconds(boost::int64_t s) - { - return time_duration(microseconds_to_performance_counter( - s * 1000000)); - } - time_duration minutes(boost::int64_t s) - { - return time_duration(microseconds_to_performance_counter( - s * 1000000 * 60)); - } - time_duration hours(boost::int64_t s) - { - return time_duration(microseconds_to_performance_counter( - s * 1000000 * 60 * 60)); - } -} - -#elif defined TORRENT_USE_CLOCK_GETTIME - -#include -#include "libtorrent/assert.hpp" - -namespace libtorrent -{ - ptime time_now_hires() - { - timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return ptime(boost::uint64_t(ts.tv_sec) * 1000000 + ts.tv_nsec / 1000); - } -} - -#elif defined TORRENT_USE_SYSTEM_TIME - -#include - -namespace libtorrent -{ - ptime time_now_hires() - { return ptime(system_time()); } -} - -#endif // TORRENT_USE_SYSTEM_TIME - -#endif // TORRENT_USE_BOOST_DATE_TIME - diff --git a/src/timestamp_history.cpp b/src/timestamp_history.cpp index 8a126e441..ef35f87c3 100644 --- a/src/timestamp_history.cpp +++ b/src/timestamp_history.cpp @@ -45,12 +45,12 @@ bool compare_less_wrap(boost::uint32_t lhs, boost::uint32_t rhs boost::uint32_t timestamp_history::add_sample(boost::uint32_t sample, bool step) { - if (!m_initialized) + if (!initialized()) { for (int i = 0; i < history_size; ++i) m_history[i] = sample; m_base = sample; - m_initialized = true; + m_num_samples = 0; } ++m_num_samples; @@ -93,7 +93,7 @@ boost::uint32_t timestamp_history::add_sample(boost::uint32_t sample, bool step) void timestamp_history::adjust_base(int change) { - TORRENT_ASSERT(m_initialized); + TORRENT_ASSERT(initialized()); m_base += change; // make sure this adjustment sticks by updating all history slots for (int i = 0; i < history_size; ++i) diff --git a/src/torrent.cpp b/src/torrent.cpp index eb6131c41..8e537b638 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -41,6 +41,7 @@ POSSIBILITY OF SUCH DAMAGE. #endif #include +#include #ifdef _MSC_VER #pragma warning(pop) @@ -65,7 +66,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/identify_client.hpp" #include "libtorrent/alert_types.hpp" #include "libtorrent/extensions.hpp" -#include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/aux_/session_interface.hpp" #include "libtorrent/instantiate_connection.hpp" #include "libtorrent/assert.hpp" #include "libtorrent/broadcast_socket.hpp" @@ -75,7 +76,14 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/http_connection.hpp" #include "libtorrent/gzip.hpp" // for inflate_gzip #include "libtorrent/random.hpp" +#include "libtorrent/peer_class.hpp" // for peer_class #include "libtorrent/string_util.hpp" // for allocate_string_copy +#include "libtorrent/socket_io.hpp" // for read_*_endpoint +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/request_blocks.hpp" +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/alert_manager.hpp" // for alert_manageralert_manager +#include "libtorrent/resolver_interface.hpp" #include "libtorrent/alloca.hpp" #ifdef TORRENT_USE_OPENSSL @@ -86,49 +94,14 @@ POSSIBILITY OF SUCH DAMAGE. #endif // BOOST_VERSION #endif // TORRENT_USE_OPENSSL +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING +#include "libtorrent/aux_/session_impl.hpp" // for tracker_logger +#endif + using namespace libtorrent; using boost::tuples::tuple; using boost::tuples::get; using boost::tuples::make_tuple; -using libtorrent::aux::session_impl; - -namespace -{ - struct find_peer_by_ip - { - find_peer_by_ip(tcp::endpoint const& a, const torrent* t) - : ip(a) - , tor(t) - { TORRENT_ASSERT(t != 0); } - - bool operator()(session_impl::connection_map::value_type const& c) const - { - tcp::endpoint const& sender = c->remote(); - if (sender.address() != ip.address()) return false; - if (tor != c->associated_torrent().lock().get()) return false; - return true; - } - - tcp::endpoint const& ip; - torrent const* tor; - }; - - struct peer_by_id - { - peer_by_id(const peer_id& i): pid(i) {} - - bool operator()(session_impl::connection_map::value_type const& p) const - { - if (p->pid() != pid) return false; - // have a special case for all zeros. We can have any number - // of peers with that pid, since it's used to indicate no pid. - if (pid.is_all_zeros()) return false; - return true; - } - - peer_id const& pid; - }; -} namespace libtorrent { @@ -152,22 +125,34 @@ namespace libtorrent bool was_introduced_by(peer_plugin const*, tcp::endpoint const&); #endif + torrent_hot_members::torrent_hot_members(aux::session_interface& ses + , add_torrent_params const& p, int block_size) + : m_ses(ses) + , m_complete(0xffffff) + , m_upload_mode(p.flags & add_torrent_params::flag_upload_mode) + , m_connections_initialized(false) + , m_abort(false) + , m_allow_peers((p.flags & add_torrent_params::flag_paused) == 0) + , m_share_mode(p.flags & add_torrent_params::flag_share_mode) + , m_have_all(false) + , m_graceful_pause_mode(false) + , m_state_subscription(p.flags & add_torrent_params::flag_update_subscribe) + , m_max_connections(0xffffff) + , m_block_size_shift(root2(block_size)) + , m_state(torrent_status::checking_resume_data) + {} + torrent::torrent( - session_impl& ses - , tcp::endpoint const& net_interface + aux::session_interface& ses , int block_size , int seq , add_torrent_params const& p , sha1_hash const& info_hash) - : m_policy(this) + : torrent_hot_members(ses, p, block_size) , m_total_uploaded(0) , m_total_downloaded(0) - , m_started(time_now()) - , m_storage(0) - , m_num_connecting(0) - , m_tracker_timer(ses.m_io_service) - , m_ses(ses) - , m_host_resolver(ses.m_io_service) + , m_tracker_timer(ses.get_io_service()) + , m_host_resolver(ses.get_io_service()) , m_trackerid(p.trackerid) , m_save_path(complete(p.save_path)) , m_url(p.url) @@ -176,27 +161,36 @@ namespace libtorrent , m_storage_constructor(p.storage) , m_added_time(time(0)) , m_completed_time(0) - , m_last_saved_resume(time(0)) , m_last_seen_complete(0) , m_swarm_last_seen_complete(0) , m_num_verified(0) + , m_last_saved_resume(ses.session_time()) + , m_started(ses.session_time()) + , m_checking_piece(0) + , m_num_checked_pieces(0) + , m_refcount(0) + , m_error_file(error_file_none) , m_average_piece_time(0) , m_piece_time_deviation(0) , m_total_failed_bytes(0) , m_total_redundant_bytes(0) , m_sequence_number(seq) + , m_peer_class(0) + , m_num_connecting(0) , m_upload_mode_time(0) - , m_state(torrent_status::checking_resume_data) + , m_announce_to_trackers((p.flags & add_torrent_params::flag_paused) == 0) + , m_announce_to_lsd((p.flags & add_torrent_params::flag_paused) == 0) + , m_has_incoming(false) + , m_files_checked(false) , m_storage_mode(p.storage_mode) , m_announcing(false) , m_waiting_tracker(false) - , m_seed_mode(false) , m_active_time(0) , m_last_working_tracker(-1) , m_finished_time(0) , m_sequential_download(false) , m_got_tracker_response(false) - , m_connections_initialized(false) + , m_seed_mode(false) , m_super_seeding(false) , m_override_resume_data(p.flags & add_torrent_params::flag_override_resume_data) #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES @@ -209,12 +203,7 @@ namespace libtorrent , m_max_uploads((1<<24)-1) , m_save_resume_flags(0) , m_num_uploads(0) - , m_block_size_shift(root2(block_size)) - , m_has_incoming(false) - , m_files_checked(false) - , m_queued_for_checking(false) - , m_max_connections((1<<24)-1) - , m_graceful_pause_mode(false) + , m_need_suggest_pieces_refresh(false) , m_need_connect_boost(true) , m_lsd_seq(0) , m_magnet_link(false) @@ -222,33 +211,33 @@ namespace libtorrent , m_merge_resume_trackers(p.flags & add_torrent_params::flag_merge_resume_trackers) , m_padding(0) , m_priority(0) - , m_complete(0xffffff) - , m_state_subscription(p.flags & add_torrent_params::flag_update_subscribe) + , m_incomplete(0xffffff) + , m_announce_to_dht((p.flags & add_torrent_params::flag_paused) == 0) , m_in_state_updates(false) , m_is_active_download(false) , m_is_active_finished(false) , m_ssl_torrent(false) , m_deleted(false) + , m_pinned(p.flags & add_torrent_params::flag_pinned) + , m_should_be_loaded(true) + , m_last_download(0) + , m_storage_tick(0) + , m_last_upload(0) + , m_auto_managed(p.flags & add_torrent_params::flag_auto_managed) + , m_current_gauge_state(no_gauge_state) , m_moving_storage(false) , m_inactive(false) - , m_incomplete(0xffffff) - , m_abort(false) - , m_announce_to_dht((p.flags & add_torrent_params::flag_paused) == 0) - , m_announce_to_trackers((p.flags & add_torrent_params::flag_paused) == 0) - , m_announce_to_lsd((p.flags & add_torrent_params::flag_paused) == 0) - , m_allow_peers((p.flags & add_torrent_params::flag_paused) == 0) - , m_upload_mode(p.flags & add_torrent_params::flag_upload_mode) - , m_auto_managed(p.flags & add_torrent_params::flag_auto_managed) - , m_share_mode(p.flags & add_torrent_params::flag_share_mode) - , m_last_download(0) - , m_last_scrape(0) - , m_last_upload(0) , m_downloaded(0xffffff) - , m_interface_index(0) + , m_last_scrape(0) , m_progress_ppm(0) , m_inactive_counter(0) , m_use_resume_save_path(p.flags & add_torrent_params::flag_use_resume_save_path) { + if (m_pinned) + m_ses.inc_stats_counter(counters::num_pinned_torrents); + + m_ses.inc_stats_counter(counters::num_loaded_torrents); + // if there is resume data already, we don't need to trigger the initial save // resume data if (!p.resume_data.empty() && (p.flags & add_torrent_params::flag_override_resume_data) == 0) @@ -261,9 +250,20 @@ namespace libtorrent m_save_path = canonicalize_path(m_save_path); #endif - if (!m_apply_ip_filter) ++m_ses.m_non_filtered_torrents; +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + m_logger_time = time_now_hires(); + error_code ec; - update_guage(); + char buf[50]; + snprintf(buf, sizeof(buf), "torrent_%p", this); + + m_logger = m_ses.create_log(buf, m_ses.listen_port()); + debug_log("torrent started"); +#endif + if (!m_apply_ip_filter) + { + m_ses.inc_stats_counter(counters::non_filter_torrents); + } if (!p.ti || !p.ti->is_valid()) { @@ -275,7 +275,7 @@ namespace libtorrent } if (!m_torrent_file) - m_torrent_file = (p.ti ? p.ti : new torrent_info(info_hash)); + m_torrent_file = (p.ti ? p.ti : boost::make_shared(info_hash)); // add web seeds from add_torrent_params for (std::vector::const_iterator i = p.url_seeds.begin() @@ -298,25 +298,27 @@ namespace libtorrent if (!m_url.empty() && m_uuid.empty()) m_uuid = m_url; - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING debug_log("creating torrent: %s", torrent_file().name().c_str()); #endif - m_net_interfaces.push_back(tcp::endpoint(net_interface.address(), 0)); - m_file_priority = p.file_priorities; if (m_seed_mode) + { m_verified.resize(m_torrent_file->num_pieces(), false); + m_verifying.resize(m_torrent_file->num_pieces(), false); + } - m_resume_data = p.resume_data; + if (!p.resume_data.empty()) + { + m_resume_data.reset(new resume_data_t); + m_resume_data->buf = p.resume_data; + } -#ifndef TORRENT_DISABLE_ENCRYPTION - hasher h; - h.update("req2", 4); - h.update((char*)&m_torrent_file->info_hash()[0], 20); - m_obfuscated_hash = h.final(); -#endif + update_want_peers(); + update_want_scrape(); + update_want_tick(); INVARIANT_CHECK; @@ -328,8 +330,8 @@ namespace libtorrent set_max_uploads(p.max_uploads, false); set_max_connections(p.max_connections, false); - set_upload_limit(p.upload_limit, false); - set_download_limit(p.download_limit, false); + set_limit_impl(p.upload_limit, peer_connection::upload_channel, false); + set_limit_impl(p.download_limit, peer_connection::download_channel, false); if (!m_name && !m_url.empty()) m_name.reset(new std::string(m_url)); @@ -352,8 +354,26 @@ namespace libtorrent m_torrent_file->add_tracker(*i); } - if (settings().prefer_udp_trackers) + if (settings().get_bool(settings_pack::prefer_udp_trackers)) prioritize_udp_trackers(); + + // if we don't have metadata, make this torrent pinned. The + // client may unpin it once we have metadata and it has had + // a chance to save it on the metadata_received_alert + if (!valid_metadata()) + { + if (!m_pinned && m_refcount == 0) + m_ses.inc_stats_counter(counters::num_pinned_torrents); + + m_pinned = true; + } + else + { + m_ses.inc_stats_counter(counters::num_total_pieces_added + , m_torrent_file->num_pieces()); + } + + update_gauge(); } #if 0 @@ -371,7 +391,7 @@ namespace libtorrent if (ec && ec != asio::error::eof) { - set_error(ec, m_url); + set_error(ec, error_file_url); pause(); return; } @@ -386,7 +406,7 @@ namespace libtorrent if (parser.header_finished() && parser.status_code() != 200) { - set_error(error_code(parser.status_code(), get_http_category()), parser.message()); + set_error(error_code(parser.status_code(), get_http_category()), error_file_url); pause(); return; } @@ -406,7 +426,7 @@ namespace libtorrent if (inflate_gzip(&m_torrent_file_buf[0], m_torrent_file_buf.size() , buf, 4 * 1024 * 1024, error)) { - set_error(errors::http_failed_decompress, m_url); + set_error(errors::http_failed_decompress, error_file_url); pause(); std::vector().swap(m_torrent_file_buf); return; @@ -416,11 +436,11 @@ namespace libtorrent // we're done! error_code e; - intrusive_ptr tf(new torrent_info( + boost::shared_ptr tf(boost::make_shared( &m_torrent_file_buf[0], m_torrent_file_buf.size(), e)); if (e) { - set_error(e, m_url); + set_error(e, error_file_url); pause(); std::vector().swap(m_torrent_file_buf); return; @@ -442,29 +462,31 @@ namespace libtorrent m_torrent_file = tf; // now, we might already have this torrent in the session. - session_impl::torrent_map::iterator i = m_ses.m_torrents.find(m_torrent_file->info_hash()); - if (i != m_ses.m_torrents.end()) + boost::shared_ptr t = m_ses.find_torrent(m_torrent_file->info_hash()).lock(); + if (t) { - if (!m_uuid.empty() && i->second->uuid().empty()) - i->second->set_uuid(m_uuid); - if (!m_url.empty() && i->second->url().empty()) - i->second->set_url(m_url); - if (!m_source_feed_url.empty() && i->second->source_feed_url().empty()) - i->second->set_source_feed_url(m_source_feed_url); + if (!m_uuid.empty() && t->uuid().empty()) + t->set_uuid(m_uuid); + if (!m_url.empty() && t->url().empty()) + t->set_url(m_url); + if (!m_source_feed_url.empty() && t->source_feed_url().empty()) + t->set_source_feed_url(m_source_feed_url); // insert this torrent in the uuid index if (!m_uuid.empty() || !m_url.empty()) { - m_ses.m_uuids.insert(std::make_pair(m_uuid.empty() - ? m_url : m_uuid, i->second)); + m_ses.insert_uuid_torrent(m_uuid.empty() ? m_url : m_uuid, t); } - set_error(error_code(errors::duplicate_torrent, get_libtorrent_category()), ""); + + // TODO: if the existing torrent doesn't have metadata, insert + // the metadata we just downloaded into it. + + set_error(error_code(errors::duplicate_torrent, get_libtorrent_category()), error_file_url); abort(); return; } - m_ses.m_torrents.insert(std::make_pair(m_torrent_file->info_hash(), me)); - if (!m_uuid.empty()) m_ses.m_uuids.insert(std::make_pair(m_uuid, me)); + m_ses.insert_torrent(m_torrent_file->info_hash(), me, m_uuid); TORRENT_ASSERT(num_torrents == int(m_ses.m_torrents.size())); @@ -489,12 +511,14 @@ namespace libtorrent hasher h; h.update("req2", 4); h.update((char*)&m_torrent_file->info_hash()[0], 20); - m_obfuscated_hash = h.final(); + // this is SHA1("req2" + info-hash), used for + // encrypted hand shakes + m_ses.add_obfuscated_hash(h.final(), shared_from_this()); #endif - if (m_ses.m_alerts.should_post()) + if (m_ses.alerts().should_post()) { - m_ses.m_alerts.post_alert(metadata_received_alert( + m_ses.alerts().post_alert(metadata_received_alert( get_handle())); } @@ -507,6 +531,44 @@ namespace libtorrent } #else // if 0 + int torrent::current_stats_state() const + { + if (m_abort) return counters::num_checking_torrents + no_gauge_state; + + if (has_error()) return counters::num_error_torrents; + if (!m_allow_peers || m_graceful_pause_mode) + { + if (!is_auto_managed()) return counters::num_stopped_torrents; + if (is_seed()) return counters::num_queued_seeding_torrents; + return counters::num_queued_download_torrents; + } + if (state() == torrent_status::checking_files +#ifndef TORRENT_NO_DEPRECATE + || state() == torrent_status::queued_for_checking +#endif + ) + return counters::num_checking_torrents; + else if (is_seed()) return counters::num_seeding_torrents; + else if (is_upload_only()) return counters::num_upload_only_torrents; + return counters::num_downloading_torrents; + } + + void torrent::update_gauge() + { + int new_gauge_state = current_stats_state() - counters::num_checking_torrents; + TORRENT_ASSERT(new_gauge_state >= 0); + TORRENT_ASSERT(new_gauge_state <= no_gauge_state); + + if (new_gauge_state == m_current_gauge_state) return; + + if (m_current_gauge_state != no_gauge_state) + m_ses.inc_stats_counter(m_current_gauge_state + counters::num_checking_torrents, -1); + if (new_gauge_state != no_gauge_state) + m_ses.inc_stats_counter(new_gauge_state + counters::num_checking_torrents, 1); + + m_current_gauge_state = new_gauge_state; + } + void torrent::on_torrent_download(error_code const& ec , http_parser const& parser, char const* data, int size) { @@ -514,24 +576,23 @@ namespace libtorrent if (ec && ec != asio::error::eof) { - set_error(ec, m_url); + set_error(ec, error_file_url); pause(); return; } if (parser.status_code() != 200) { - // #error there should really be an error code category for HTTP - set_error(errors::http_error, parser.message()); + set_error(error_code(parser.status_code(), get_http_category()), error_file_url); pause(); return; } error_code e; - intrusive_ptr tf(new torrent_info(data, size, e)); + boost::shared_ptr tf(boost::make_shared(data, size, boost::ref(e), 0)); if (e) { - set_error(e, m_url); + set_error(e, error_file_url); pause(); return; } @@ -539,10 +600,6 @@ namespace libtorrent // update our torrent_info object and move the // torrent from the old info-hash to the new one // as we replace the torrent_info object -#if TORRENT_USE_ASSERTS - int num_torrents = m_ses.m_torrents.size(); -#endif - // we're about to erase the session's reference to this // torrent, create another reference boost::shared_ptr me(shared_from_this()); @@ -555,34 +612,34 @@ namespace libtorrent m_torrent_file = tf; // now, we might already have this torrent in the session. - session_impl::torrent_map::iterator i = m_ses.m_torrents.find(m_torrent_file->info_hash()); - if (i != m_ses.m_torrents.end()) + boost::shared_ptr t = m_ses.find_torrent(m_torrent_file->info_hash()).lock(); + if (t) { - if (!m_uuid.empty() && i->second->uuid().empty()) - i->second->set_uuid(m_uuid); - if (!m_url.empty() && i->second->url().empty()) - i->second->set_url(m_url); - if (!m_source_feed_url.empty() && i->second->source_feed_url().empty()) - i->second->set_source_feed_url(m_source_feed_url); + if (!m_uuid.empty() && t->uuid().empty()) + t->set_uuid(m_uuid); + if (!m_url.empty() && t->url().empty()) + t->set_url(m_url); + if (!m_source_feed_url.empty() && t->source_feed_url().empty()) + t->set_source_feed_url(m_source_feed_url); // insert this torrent in the uuid index if (!m_uuid.empty() || !m_url.empty()) { - m_ses.m_uuids.insert(std::make_pair(m_uuid.empty() - ? m_url : m_uuid, i->second)); + m_ses.insert_uuid_torrent(m_uuid.empty() ? m_url : m_uuid, t); } - set_error(error_code(errors::duplicate_torrent, get_libtorrent_category()), ""); + + // TODO: if the existing torrent doesn't have metadata, insert + // the metadata we just downloaded into it. + + set_error(error_code(errors::duplicate_torrent, get_libtorrent_category()), error_file_url); abort(); return; } - m_ses.m_torrents.insert(std::make_pair(m_torrent_file->info_hash(), me)); - if (!m_uuid.empty()) m_ses.m_uuids.insert(std::make_pair(m_uuid, me)); - - TORRENT_ASSERT(num_torrents == int(m_ses.m_torrents.size())); + m_ses.insert_torrent(m_torrent_file->info_hash(), me, m_uuid); // if the user added any trackers while downloading the - // .torrent file, serge them into the new tracker list + // .torrent file, merge them into the new tracker list std::vector new_trackers = m_torrent_file->trackers(); for (std::vector::iterator i = m_trackers.begin() , end(m_trackers.end()); i != end; ++i) @@ -602,12 +659,12 @@ namespace libtorrent hasher h; h.update("req2", 4); h.update((char*)&m_torrent_file->info_hash()[0], 20); - m_obfuscated_hash = h.final(); + m_ses.add_obfuscated_hash(h.final(), shared_from_this()); #endif - if (m_ses.m_alerts.should_post()) + if (m_ses.alerts().should_post()) { - m_ses.m_alerts.post_alert(metadata_received_alert( + m_ses.alerts().post_alert(metadata_received_alert( get_handle())); } @@ -646,11 +703,13 @@ namespace libtorrent // to be a seed after all if (!seed) { + m_have_all = false; set_state(torrent_status::downloading); force_recheck(); } m_num_verified = 0; m_verified.clear(); + m_verifying.clear(); } void torrent::verified(int piece) @@ -664,7 +723,7 @@ namespace libtorrent void torrent::start() { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING debug_log("starting torrent"); #endif @@ -672,25 +731,21 @@ namespace libtorrent if (!m_seed_mode) { - m_picker.reset(new piece_picker()); - std::fill(m_file_progress.begin(), m_file_progress.end(), 0); + std::vector().swap(m_file_progress); - if (!m_resume_data.empty()) + if (m_resume_data) { int pos; error_code ec; - if (lazy_bdecode(&m_resume_data[0], &m_resume_data[0] - + m_resume_data.size(), m_resume_entry, ec, &pos) != 0) + if (lazy_bdecode(&m_resume_data->buf[0], &m_resume_data->buf[0] + + m_resume_data->buf.size(), m_resume_data->entry, ec, &pos) != 0) { - std::vector().swap(m_resume_data); - lazy_entry().swap(m_resume_entry); + m_resume_data.reset(); #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING debug_log("resume data rejected: %s pos: %d", ec.message().c_str(), pos); #endif - if (m_ses.m_alerts.should_post()) - { - m_ses.m_alerts.post_alert(fastresume_rejected_alert(get_handle(), ec)); - } + if (m_ses.alerts().should_post()) + m_ses.alerts().post_alert(fastresume_rejected_alert(get_handle(), ec, "", 0)); } } } @@ -713,73 +768,27 @@ namespace libtorrent } } - bool torrent::is_active_download() const - { - return (m_state == torrent_status::downloading - || m_state == torrent_status::downloading_metadata) - && m_allow_peers - && !m_abort; - } - - bool torrent::is_active_finished() const - { - return (m_state == torrent_status::finished - || m_state == torrent_status::seeding) - && m_allow_peers - && !m_abort; - } - - void torrent::update_guage() - { - bool is_active_download = (m_state == torrent_status::downloading - || m_state == torrent_status::downloading_metadata) - && m_allow_peers - && !m_abort; - - bool is_active_finished = (m_state == torrent_status::finished - || m_state == torrent_status::seeding) - && m_allow_peers - && !m_abort; - - // update finished and downloading counters - if (is_active_download != m_is_active_download) - { - if (is_active_download) - m_ses.inc_active_downloading(); - else - m_ses.dec_active_downloading(); - m_is_active_download = is_active_download; - } - - if (is_active_finished != m_is_active_finished) - { - if (is_active_finished) - m_ses.inc_active_finished(); - else - m_ses.dec_active_finished(); - m_is_active_finished = is_active_finished; - } - } - void torrent::start_download_url() { TORRENT_ASSERT(!m_url.empty()); TORRENT_ASSERT(!m_torrent_file->is_valid()); boost::shared_ptr conn( - new http_connection(m_ses.m_io_service, m_ses.m_half_open + new http_connection(m_ses.get_io_service(), m_ses.half_open() + , m_ses.get_resolver() , boost::bind(&torrent::on_torrent_download, shared_from_this() , _1, _2, _3, _4) , true // bottled - , m_ses.settings().max_http_recv_buffer_size // bottled buffer size + //bottled buffer size + , m_ses.settings().get_int(settings_pack::max_http_recv_buffer_size) , http_connect_handler() , http_filter_handler() #ifdef TORRENT_USE_OPENSSL , m_ssl_ctx.get() #endif )); - - conn->get(m_url, seconds(30), 0, &m_ses.proxy() - , 5, m_ses.m_settings.user_agent); + proxy_settings ps = m_ses.proxy(); + conn->get(m_url, seconds(30), 0, &ps + , 5, m_ses.settings().get_str(settings_pack::user_agent)); set_state(torrent_status::downloading_metadata); } @@ -788,25 +797,24 @@ namespace libtorrent if (b == m_apply_ip_filter) return; if (b) { - TORRENT_ASSERT(m_ses.m_non_filtered_torrents > 0); - --m_ses.m_non_filtered_torrents; + m_ses.inc_stats_counter(counters::non_filter_torrents, -1); } else { - ++m_ses.m_non_filtered_torrents; + m_ses.inc_stats_counter(counters::non_filter_torrents); } m_apply_ip_filter = b; - m_policy.ip_filter_updated(); + ip_filter_updated(); state_updated(); } #ifndef TORRENT_DISABLE_DHT bool torrent::should_announce_dht() const { - TORRENT_ASSERT(m_ses.is_network_thread()); - if (m_ses.m_listen_sockets.empty()) return false; + TORRENT_ASSERT(m_ses.is_single_thread()); + if (!m_ses.announce_dht()) return false; - if (!m_ses.m_dht) return false; + if (!m_ses.dht()) return false; if (m_torrent_file->is_valid() && !m_files_checked) return false; if (!m_announce_to_dht) return false; if (!m_allow_peers) return false; @@ -819,7 +827,7 @@ namespace libtorrent // don't announce private torrents if (m_torrent_file->is_valid() && m_torrent_file->priv()) return false; if (m_trackers.empty()) return true; - if (!settings().use_dht_as_fallback) return true; + if (!settings().get_bool(settings_pack::use_dht_as_fallback)) return true; int verified_trackers = 0; for (std::vector::const_iterator i = m_trackers.begin() @@ -833,14 +841,25 @@ namespace libtorrent torrent::~torrent() { - if (!m_apply_ip_filter) - { - TORRENT_ASSERT(m_ses.m_non_filtered_torrents > 0); - --m_ses.m_non_filtered_torrents; - m_apply_ip_filter = true; - } + TORRENT_ASSERT(m_abort); + TORRENT_ASSERT(prev == NULL && next == NULL); + +#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS + for (int i = 0; i < aux::session_interface::num_torrent_lists; ++i) + { + if (!m_links[i].in_list()) continue; + m_links[i].unlink(m_ses.torrent_list(i), i); + } +#endif + + TORRENT_ASSERT(m_refcount == 0); + + if (m_pinned) + m_ses.inc_stats_counter(counters::num_pinned_torrents, -1); + + if (is_loaded()) + m_ses.inc_stats_counter(counters::num_loaded_torrents, -1); - TORRENT_ASSERT(m_ses.is_network_thread()); // The invariant can't be maintained here, since the torrent // is being destructed, all weak references to it have been // reset, which means that all its peers already have an @@ -850,25 +869,24 @@ namespace libtorrent // been closed by the time the torrent is destructed. And they are // supposed to be closed. So we can still do the invariant check. - INVARIANT_CHECK; - -#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING || defined TORRENT_LOGGING - log_to_all_peers("DESTRUCTING TORRENT"); -#endif + // however, the torrent object may be destructed from the main + // thread when shutting down, if the disk cache has references to it. + // this means that the invariant check that this is called from the + // network thread cannot be maintained TORRENT_ASSERT(m_abort); TORRENT_ASSERT(m_connections.empty()); if (!m_connections.empty()) - disconnect_all(errors::torrent_aborted); + disconnect_all(errors::torrent_aborted, peer_connection_interface::op_bittorrent); } void torrent::read_piece(int piece) { - if (m_abort) + if (m_abort || m_deleted) { // failed - m_ses.m_alerts.post_alert(read_piece_alert( - get_handle(), piece, error_code(boost::system::errc::operation_canceled, get_system_category()))); + m_ses.alerts().post_alert(read_piece_alert( + get_handle(), piece, error_code(boost::system::errc::operation_canceled, system_category()))); return; } @@ -888,19 +906,28 @@ namespace libtorrent peer_request r; r.piece = piece; r.start = 0; + rp->blocks_left = blocks_in_piece; + if (!need_loaded()) + { + rp->piece_data.reset(); + m_ses.alerts().post_alert(read_piece_alert( + get_handle(), r.piece, rp->piece_data, 0)); + delete rp; + return; + } for (int i = 0; i < blocks_in_piece; ++i, r.start += block_size()) { r.length = (std::min)(piece_size - r.start, block_size()); - filesystem().async_read(r, boost::bind(&torrent::on_disk_read_complete - , shared_from_this(), _1, _2, r, rp)); - ++rp->blocks_left; + inc_refcount("read_piece"); + m_ses.disk_thread().async_read(&storage(), r, boost::bind(&torrent::on_disk_read_complete + , shared_from_this(), _1, r, rp), (void*)1); } } void torrent::send_share_mode() { #ifndef TORRENT_DISABLE_EXTENSIONS - for (std::set::iterator i = m_connections.begin() + for (peer_iterator i = m_connections.begin() , end(m_connections.end()); i != end; ++i) { if ((*i)->type() != peer_connection::bittorrent_connection) continue; @@ -916,16 +943,34 @@ namespace libtorrent if (share_mode()) return; if (super_seeding()) return; - for (std::set::iterator i = m_connections.begin(); - i != m_connections.end();) + int idx = 0; + for (peer_iterator i = m_connections.begin(); + i != m_connections.end(); ++idx) { // since the call to disconnect_if_redundant() may // delete the entry from this container, make sure // to increment the iterator early bt_peer_connection* p = (bt_peer_connection*)*i; - ++i; if (p->type() == peer_connection::bittorrent_connection) - p->write_upload_only(); + { + boost::shared_ptr me(p->self()); + if (!p->is_disconnecting()) + { + p->send_not_interested(); + p->write_upload_only(); + } + } + + + if (p->is_disconnecting()) + { + i = m_connections.begin() + idx; + --idx; + } + else + { + ++i; + } } #endif } @@ -937,7 +982,11 @@ namespace libtorrent m_share_mode = s; // in share mode, all pieces have their priorities initialized to 0 - std::fill(m_file_priority.begin(), m_file_priority.end(), !m_share_mode); + if (m_share_mode && valid_metadata()) + { + m_file_priority.clear(); + m_file_priority.resize(m_torrent_file->num_files(), 0); + } update_piece_priorities(); @@ -950,13 +999,14 @@ namespace libtorrent m_upload_mode = b; + update_gauge(); state_updated(); send_upload_only(); if (m_upload_mode) { // clear request queues of all peers - for (std::set::iterator i = m_connections.begin() + for (peer_iterator i = m_connections.begin() , end(m_connections.end()); i != end; ++i) { peer_connection* p = (*i); @@ -965,17 +1015,17 @@ namespace libtorrent // this is used to try leaving upload only mode periodically m_upload_mode_time = 0; } - else + else if (m_policy) { // reset last_connected, to force fast reconnect after leaving upload mode - for (policy::iterator i = m_policy.begin_peer() - , end(m_policy.end_peer()); i != end; ++i) + for (policy::iterator i = m_policy->begin_peer() + , end(m_policy->end_peer()); i != end; ++i) { (*i)->last_connected = 0; } // send_block_requests on all peers - for (std::set::iterator i = m_connections.begin() + for (peer_iterator i = m_connections.begin() , end(m_connections.end()); i != end; ++i) { peer_connection* p = (*i); @@ -984,47 +1034,96 @@ namespace libtorrent } } - void torrent::handle_disk_error(disk_io_job const& j, peer_connection* c) + void torrent::need_policy() { - TORRENT_ASSERT(m_ses.is_network_thread()); - if (!j.error) return; + if (m_policy) return; + m_policy.reset(new policy); + } + + void torrent::handle_disk_error(disk_io_job const* j, peer_connection* c) + { + TORRENT_ASSERT(m_ses.is_single_thread()); + if (!j->error) return; #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING - debug_log("disk error: (%d) %s in file: %s", j.error.value(), j.error.message().c_str() - , j.error_file.c_str()); + debug_log("disk error: (%d) %s in file: %s", j->error.ec.value(), j->error.ec.message().c_str() + , resolve_filename(j->error.file).c_str()); #endif - TORRENT_ASSERT(j.piece >= 0); + TORRENT_ASSERT(j->piece >= 0); - piece_block block_finished(j.piece, j.offset / block_size()); - - if (j.action == disk_io_job::write) + if (j->action == disk_io_job::write) { - // we failed to write j.piece to disk tell the piece picker - if (has_picker() && j.piece >= 0) picker().write_failed(block_finished); + piece_block block_finished(j->piece, j->d.io.offset / block_size()); + + // we failed to write j->piece to disk tell the piece picker + if (j->piece >= 0) + { + // this will block any other peer from issuing requests + // to this piece, until we've cleared it. + if (j->error.ec == asio::error::operation_aborted) + { + if (has_picker()) + picker().mark_as_canceled(block_finished, NULL); + } + else + { + // TODO: 3 if any other peer has a busy request to this block, we need to cancel it too + if (has_picker()) + picker().write_failed(block_finished); + + if (m_storage) + { + // when this returns, all outstanding jobs to the + // piece are done, and we can restore it, allowing + // new requests to it + m_ses.disk_thread().async_clear_piece(m_storage.get(), j->piece + , boost::bind(&torrent::on_piece_fail_sync, shared_from_this(), _1, block_finished)); + } + else + { + // is m_abort true? if so, we should probably just + // exit this function early, no need to keep the picker + // state up-to-date, right? + disk_io_job sj; + sj.piece = j->piece; + on_piece_fail_sync(&sj, block_finished); + } + } + update_gauge(); + } } - if (j.error == error_code(boost::system::errc::not_enough_memory, generic_category())) + if (j->error.ec == error_code(boost::system::errc::not_enough_memory, generic_category())) { if (alerts().should_post()) - alerts().post_alert(file_error_alert(j.error_file, get_handle(), j.error)); - if (c) c->disconnect(errors::no_memory); + alerts().post_alert(file_error_alert(j->error.ec + , resolve_filename(j->error.file), j->error.operation_str(), get_handle())); + if (c) c->disconnect(errors::no_memory, peer_connection_interface::op_file); return; } + if (j->error.ec == asio::error::operation_aborted) return; + // notify the user of the error if (alerts().should_post()) - alerts().post_alert(file_error_alert(j.error_file, get_handle(), j.error)); + alerts().post_alert(file_error_alert(j->error.ec + , resolve_filename(j->error.file), j->error.operation_str(), get_handle())); // put the torrent in an error-state - set_error(j.error, j.error_file); + set_error(j->error.ec, j->error.file); - if (j.action == disk_io_job::write - && (j.error == boost::system::errc::read_only_file_system - || j.error == boost::system::errc::permission_denied - || j.error == boost::system::errc::operation_not_permitted - || j.error == boost::system::errc::no_space_on_device - || j.error == boost::system::errc::file_too_large)) + // if a write operation failed, and future writes are likely to + // fail, while reads may succeed, just set the torrent to upload mode + // if we make an incorrect assumption here, it's not the end of the + // world, if we ever issue a read request and it fails as well, we + // won't get in here and we'll actually end up pausing the torrent + if (j->action == disk_io_job::write + && (j->error.ec == boost::system::errc::read_only_file_system + || j->error.ec == boost::system::errc::permission_denied + || j->error.ec == boost::system::errc::operation_not_permitted + || j->error.ec == boost::system::errc::no_space_on_device + || j->error.ec == boost::system::errc::file_too_large)) { // if we failed to write, stop downloading and just // keep seeding. @@ -1041,23 +1140,51 @@ namespace libtorrent pause(); } - void torrent::on_disk_read_complete(int ret, disk_io_job const& j - , peer_request r, read_piece_struct* rp) + void torrent::on_piece_fail_sync(disk_io_job const* j, piece_block b) { - TORRENT_ASSERT(m_ses.is_network_thread()); + update_gauge(); + // some peers that previously was no longer interesting may + // now have become interesting, since we lack this one piece now. + for (peer_iterator i = begin(); i != end();) + { + peer_connection* p = *i; + // update_interest may disconnect the peer and + // invalidate the iterator + ++i; + // no need to do anything with peers that + // already are interested. Gaining a piece may + // only make uninteresting peers interesting again. + if (p->is_interesting()) continue; + p->update_interest(); + if (!m_abort) + { + if (request_a_block(*this, *p)) + m_ses.inc_stats_counter(counters::hash_fail_piece_picks); + p->send_block_requests(); + } + } + } - disk_buffer_holder buffer(m_ses, j.buffer); + void torrent::on_disk_read_complete(disk_io_job const* j, peer_request r, read_piece_struct* rp) + { + // hold a reference until this function returns + torrent_ref_holder h(this, "read_piece"); + + dec_refcount("read_piece"); + TORRENT_ASSERT(m_ses.is_single_thread()); + + disk_buffer_holder buffer(m_ses, *j); --rp->blocks_left; - if (ret != r.length) + if (j->ret != r.length) { rp->fail = true; - rp->error = j.error; + rp->error = j->error.ec; handle_disk_error(j); } else { - std::memcpy(rp->piece_data.get() + r.start, j.buffer, r.length); + std::memcpy(rp->piece_data.get() + r.start, j->buffer, r.length); } if (rp->blocks_left == 0) @@ -1065,21 +1192,45 @@ namespace libtorrent int size = m_torrent_file->piece_size(r.piece); if (rp->fail) { - m_ses.m_alerts.post_alert(read_piece_alert( + m_ses.alerts().post_alert(read_piece_alert( get_handle(), r.piece, rp->error)); } else { - m_ses.m_alerts.post_alert(read_piece_alert( + m_ses.alerts().post_alert(read_piece_alert( get_handle(), r.piece, rp->piece_data, size)); } delete rp; } } + void torrent::need_picker() + { + if (m_picker) return; + + INVARIANT_CHECK; + + // if we have all pieces we should not have a picker + TORRENT_ASSERT(!m_have_all); + + m_picker.reset(new piece_picker()); + int blocks_per_piece = (m_torrent_file->piece_length() + block_size() - 1) / block_size(); + int blocks_in_last_piece = ((m_torrent_file->total_size() % m_torrent_file->piece_length()) + + block_size() - 1) / block_size(); + m_picker->init(blocks_per_piece, blocks_in_last_piece, m_torrent_file->num_pieces()); + + update_gauge(); + + for (peer_iterator i = m_connections.begin() + , end(m_connections.end()); i != end; ++i) + { + peer_has((*i)->get_bitfield(), *i); + } + } + void torrent::add_piece(int piece, char const* data, int flags) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); TORRENT_ASSERT(piece >= 0 && piece < m_torrent_file->num_pieces()); int piece_size = m_torrent_file->piece_size(piece); int blocks_in_piece = (piece_size + block_size() - 1) / block_size(); @@ -1087,7 +1238,9 @@ namespace libtorrent if (m_deleted) return; // avoid crash trying to access the picker when there is none - if (!has_picker()) return; + if (m_have_all && !has_picker()) return; + + need_picker(); if (picker().have_piece(piece) && (flags & torrent::overwrite_existing) == 0) @@ -1113,21 +1266,46 @@ namespace libtorrent } disk_buffer_holder holder(m_ses, buffer); std::memcpy(buffer, data + p.start, p.length); - filesystem().async_write(p, holder, boost::bind(&torrent::on_disk_write_complete - , shared_from_this(), _1, _2, p)); + + if (!need_loaded()) + { + // failed to load .torrent file + picker().dec_refcount(piece, 0); + return; + } + inc_refcount("add_piece"); + m_ses.disk_thread().async_write(&storage(), p, holder + , boost::bind(&torrent::on_disk_write_complete + , shared_from_this(), _1, p)); piece_block block(piece, i); picker().mark_as_downloading(block, 0, piece_picker::fast); picker().mark_as_writing(block, 0); } - async_verify_piece(piece, boost::bind(&torrent::piece_finished - , shared_from_this(), piece, _1)); + verify_piece(piece); picker().dec_refcount(piece, 0); } - void torrent::on_disk_write_complete(int ret, disk_io_job const& j + void torrent::schedule_storage_tick() + { + // schedule a disk tick in 2 minutes or so + if (m_storage_tick != 0) return; + m_storage_tick = 120 + (random() % 60); + update_want_tick(); + } + + void torrent::on_disk_write_complete(disk_io_job const* j , peer_request p) { - TORRENT_ASSERT(m_ses.is_network_thread()); + // hold a reference until this function returns + torrent_ref_holder h(this, "add_piece"); + + dec_refcount("add_piece"); + TORRENT_ASSERT(m_ses.is_single_thread()); + + schedule_storage_tick(); + +// fprintf(stderr, "torrent::on_disk_write_complete ret:%d piece:%d block:%d\n" +// , j->ret, j->piece, j->offset/0x4000); INVARIANT_CHECK; @@ -1139,7 +1317,7 @@ namespace libtorrent piece_block block_finished(p.piece, p.start / block_size()); - if (ret == -1) + if (j->ret == -1) { handle_disk_error(j); return; @@ -1153,14 +1331,30 @@ namespace libtorrent if (picker().is_finished(block_finished)) return; picker().mark_as_finished(block_finished, 0); + maybe_done_flushing(); } - void torrent::on_disk_cache_complete(int ret, disk_io_job const& j) + void torrent::on_disk_cache_complete(disk_io_job const* j) { + TORRENT_ASSERT(have_piece(j->piece)); + + dec_refcount("cache_piece"); + + if (j->ret < 0) return; + // suggest this piece to all peers for (peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i) - (*i)->send_suggest(j.piece); + (*i)->send_suggest(j->piece); + } + + void torrent::on_disk_tick_done(disk_io_job const* j) + { + if (j->ret && m_storage_tick == 0) + { + m_storage_tick = 120 + (random() % 20); + update_want_tick(); + } } bool torrent::add_merkle_nodes(std::map const& nodes, int piece) @@ -1197,6 +1391,13 @@ namespace libtorrent m_extensions.push_back(ext); } + void torrent::remove_extension(boost::shared_ptr ext) + { + extension_list_t::iterator i = std::find(m_extensions.begin(), m_extensions.end(), ext); + if (i == m_extensions.end()) return; + m_extensions.erase(i); + } + void torrent::add_extension(boost::function(torrent*, void*)> const& ext , void* userdata) { @@ -1323,7 +1524,7 @@ namespace libtorrent // this is needed for openssl < 1.0 to decrypt keys created by openssl 1.0+ OpenSSL_add_all_algorithms(); - boost::uint64_t now = total_microseconds(time_now_hires() - min_time()); + boost::uint64_t now = time_now_hires().time_since_epoch().count(); // assume 9 bits of entropy (i.e. about 1 millisecond) RAND_add(&now, 8, 1.125); RAND_add(&info_hash()[0], 20, 3); @@ -1335,14 +1536,13 @@ namespace libtorrent // create the SSL context for this torrent. We need to // inject the root certificate, and no other, to // verify other peers against - boost::shared_ptr ctx( - new (std::nothrow) context(m_ses.m_io_service, context::sslv23)); + boost::shared_ptr ctx = boost::make_shared(boost::ref(m_ses.get_io_service()), context::sslv23); if (!ctx) { error_code ec(::ERR_get_error(), asio::error::get_ssl_category()); - set_error(ec, "SSL context"); + set_error(ec, error_file_ssl_ctx); pause(); return; } @@ -1357,7 +1557,7 @@ namespace libtorrent | context::verify_client_once, ec); if (ec) { - set_error(ec, "SSL verify mode"); + set_error(ec, error_file_ssl_ctx); pause(); return; } @@ -1368,7 +1568,7 @@ namespace libtorrent ctx->set_verify_callback(boost::bind(&torrent::verify_peer_cert, this, _1, _2), ec); if (ec) { - set_error(ec, "SSL verify callback"); + set_error(ec, error_file_ssl_ctx); pause(); return; } @@ -1380,7 +1580,7 @@ namespace libtorrent { error_code ec(::ERR_get_error(), asio::error::get_ssl_category()); - set_error(ec, "x.509 certificate store"); + set_error(ec, error_file_ssl_ctx); pause(); return; } @@ -1399,7 +1599,7 @@ namespace libtorrent error_code ec(::ERR_get_error(), asio::error::get_ssl_category()); X509_STORE_free(cert_store); - set_error(ec, "x.509 certificate"); + set_error(ec, error_file_ssl_ctx); pause(); return; } @@ -1424,13 +1624,42 @@ namespace libtorrent // tell the client we need a cert for this torrent alerts().post_alert(torrent_need_cert_alert(get_handle())); #else - set_error(asio::error::operation_not_supported, "x.509 certificate"); + set_error(asio::error::operation_not_supported, error_file_ssl_ctx); pause(); #endif } #endif // TORRENT_OPENSSL + void torrent::construct_storage() + { + storage_params params; + + if (&m_torrent_file->orig_files() != &m_torrent_file->files()) + { + params.mapped_files = &m_torrent_file->files(); + params.files = &m_torrent_file->orig_files(); + } + else + { + params.files = &m_torrent_file->files(); + params.mapped_files = 0; + } + params.path = m_save_path; + params.pool = &m_ses.disk_thread().files(); + params.mode = (storage_mode_t)m_storage_mode; + params.priorities = &m_file_priority; + params.info = m_torrent_file.get(); + + TORRENT_ASSERT(m_storage_constructor); + storage_interface* storage_impl = m_storage_constructor(params); + + // the shared_from_this() will create an intentional + // cycle of ownership, se the hpp file for description. + m_storage = boost::make_shared( + storage_impl, shared_from_this(), (file_storage*)&m_torrent_file->files()); + } + peer_connection* torrent::find_lowest_ranking_peer() const { const_peer_iterator lowest_rank = end(); @@ -1450,9 +1679,11 @@ namespace libtorrent // shared_from_this() void torrent::init() { - TORRENT_ASSERT(m_ses.is_network_thread()); - TORRENT_ASSERT(m_torrent_file->is_valid()); + TORRENT_ASSERT(m_ses.is_single_thread()); + + if (!need_loaded()) return; TORRENT_ASSERT(m_torrent_file->num_files() > 0); + TORRENT_ASSERT(m_torrent_file->is_valid()); TORRENT_ASSERT(m_torrent_file->total_size() >= 0); if (int(m_file_priority.size()) > m_torrent_file->num_files()) @@ -1467,29 +1698,18 @@ namespace libtorrent #endif } - m_file_priority.resize(m_torrent_file->num_files(), 1); - - // initialize pad files to priority 0 - file_storage const& fs = m_torrent_file->files(); - for (int i = 0; i < fs.num_files(); ++i) - { - if (!fs.pad_file_at(i)) continue; - m_file_priority[i] = 0; - } - m_file_progress.resize(m_torrent_file->num_files(), 0); - m_block_size_shift = root2((std::min)(int(block_size()), m_torrent_file->piece_length())); if (m_torrent_file->num_pieces() > piece_picker::max_pieces) { - set_error(errors::too_many_pieces_in_torrent, ""); + set_error(errors::too_many_pieces_in_torrent, error_file_none); pause(); return; } if (m_torrent_file->num_pieces() == 0) { - set_error(errors::torrent_invalid_length, ""); + set_error(errors::torrent_invalid_length, error_file_none); pause(); return; } @@ -1499,31 +1719,21 @@ namespace libtorrent // loading resume data. So peek ahead in this case. // only do this if the user is willing to have the resume data // settings override the settings set in add_torrent_params - if (!m_use_resume_save_path && m_resume_entry.type() == lazy_entry::dict_t) + if (!m_use_resume_save_path + && m_resume_data + && m_resume_data->entry.type() == lazy_entry::dict_t) { - std::string p = m_resume_entry.dict_find_string_value("save_path"); + std::string p = m_resume_data->entry.dict_find_string_value("save_path"); if (!p.empty()) m_save_path = p; } - // the shared_from_this() will create an intentional - // cycle of ownership, se the hpp file for description. - m_owning_storage = new piece_manager(shared_from_this(), m_torrent_file - , m_save_path, m_ses.m_files, m_ses.m_disk_thread, m_storage_constructor - , (storage_mode_t)m_storage_mode, m_file_priority); - m_storage = m_owning_storage.get(); + construct_storage(); - if (has_picker()) - { - int blocks_per_piece = (m_torrent_file->piece_length() + block_size() - 1) / block_size(); - int blocks_in_last_piece = ((m_torrent_file->total_size() % m_torrent_file->piece_length()) - + block_size() - 1) / block_size(); - m_picker->init(blocks_per_piece, blocks_in_last_piece, m_torrent_file->num_pieces()); - } - - if (m_share_mode) + if (m_share_mode && valid_metadata()) { // in share mode, all pieces have their priorities initialized to 0 - std::fill(m_file_priority.begin(), m_file_priority.end(), 0); + m_file_priority.clear(); + m_file_priority.resize(m_torrent_file->num_files(), 0); } if (!m_connections_initialized) @@ -1552,34 +1762,35 @@ namespace libtorrent if (m_seed_mode) { - m_ses.m_io_service.post(boost::bind(&torrent::files_checked, shared_from_this())); - std::vector().swap(m_resume_data); - lazy_entry().swap(m_resume_entry); + m_have_all = true; + m_ses.get_io_service().post(boost::bind(&torrent::files_checked, shared_from_this())); + m_resume_data.reset(); #if TORRENT_USE_ASSERTS m_resume_data_loaded = true; #endif + update_gauge(); return; } set_state(torrent_status::checking_resume_data); - if (m_resume_entry.type() == lazy_entry::dict_t) + if (m_resume_data && m_resume_data->entry.type() == lazy_entry::dict_t) { int ev = 0; - if (m_resume_entry.dict_find_string_value("file-format") != "libtorrent resume file") + if (m_resume_data->entry.dict_find_string_value("file-format") != "libtorrent resume file") ev = errors::invalid_file_tag; - std::string info_hash = m_resume_entry.dict_find_string_value("info-hash"); + std::string info_hash = m_resume_data->entry.dict_find_string_value("info-hash"); if (!ev && info_hash.empty()) ev = errors::missing_info_hash; if (!ev && sha1_hash(info_hash) != m_torrent_file->info_hash()) ev = errors::mismatching_info_hash; - if (ev && m_ses.m_alerts.should_post()) + if (ev && m_ses.alerts().should_post()) { - m_ses.m_alerts.post_alert(fastresume_rejected_alert(get_handle() - , error_code(ev, get_libtorrent_category()))); + error_code ec = error_code(ev, get_libtorrent_category()); + m_ses.alerts().post_alert(fastresume_rejected_alert(get_handle(), ec, "", 0)); } if (ev) @@ -1588,12 +1799,11 @@ namespace libtorrent debug_log("fastresume data rejected: %s" , error_code(ev, get_libtorrent_category()).message().c_str()); #endif - std::vector().swap(m_resume_data); - lazy_entry().swap(m_resume_entry); + m_resume_data.reset(); } else { - read_resume_data(m_resume_entry); + read_resume_data(m_resume_data->entry); } } @@ -1603,6 +1813,7 @@ namespace libtorrent int num_pad_files = 0; TORRENT_ASSERT(block_size() > 0); + file_storage const& fs = m_torrent_file->files(); for (int i = 0; i < fs.num_files(); ++i) { if (fs.pad_file_at(i)) ++num_pad_files; @@ -1610,6 +1821,10 @@ namespace libtorrent if (!fs.pad_file_at(i) || fs.file_size(i) == 0) continue; m_padding += boost::uint32_t(fs.file_size(i)); + // TODO: instead of creating the picker up front here, + // maybe this whole section should move to need_picker() + need_picker(); + peer_request pr = m_torrent_file->map_file(i, 0, fs.file_size(i)); int off = pr.start & (block_size()-1); if (off != 0) { pr.length -= block_size() - off; pr.start += block_size() - off; } @@ -1638,7 +1853,7 @@ namespace libtorrent // if we marked an entire piece as finished, we actually // need to consider it finished - std::vector const& dq + std::vector dq = m_picker->get_download_queue(); std::vector have_pieces; @@ -1654,15 +1869,174 @@ namespace libtorrent for (std::vector::iterator i = have_pieces.begin(); i != have_pieces.end(); ++i) { + picker().piece_passed(*i); + TORRENT_ASSERT(picker().have_piece(*i)); we_have(*i); + update_gauge(); } } - m_picker->set_num_pad_files(num_pad_files); + if (!need_loaded()) return; - m_storage->async_check_fastresume(&m_resume_entry + if (num_pad_files > 0) + m_picker->set_num_pad_files(num_pad_files); + + inc_refcount("check_fastresume"); + m_ses.disk_thread().async_check_fastresume( + m_storage.get(), m_resume_data ? &m_resume_data->entry : NULL , boost::bind(&torrent::on_resume_data_checked - , shared_from_this(), _1, _2)); + , shared_from_this(), _1)); +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING + debug_log("init, async_check_fastresume"); +#endif + + update_want_peers(); + + maybe_done_flushing(); + } + + bool torrent::need_loaded() + { + m_should_be_loaded = true; + + // if we don't have the metadata yet, pretend the file is loaded + if (!m_torrent_file->is_valid() + || m_torrent_file->is_loaded()) + { + // bump this torrent to the top of the torrent LRU of + // which torrents are most active + m_ses.bump_torrent(this); + + return true; + } + + // load the specified torrent and also evict one torrent, + // except for the one specified. if we're not at our limit + // yet, no torrent is evicted + return m_ses.load_torrent(this); + } + + void torrent::dec_refcount(char const* purpose) + { + TORRENT_ASSERT(m_ses.is_single_thread()); + TORRENT_ASSERT(m_refcount > 0); + --m_refcount; + if (m_refcount == 0) + { + if (!m_pinned) + m_ses.inc_stats_counter(counters::num_pinned_torrents, -1); + + if (m_should_be_loaded == false) + unload(); + } + } + + void torrent::inc_refcount(char const* purpose) + { + TORRENT_ASSERT(m_ses.is_single_thread()); + TORRENT_ASSERT(is_loaded()); + ++m_refcount; + if (!m_pinned && m_refcount == 1) + m_ses.inc_stats_counter(counters::num_pinned_torrents); + } + + void torrent::set_pinned(bool p) + { + TORRENT_ASSERT(m_ses.is_single_thread()); + if (m_pinned == p) return; + m_pinned = p; + + if (m_refcount == 0) + m_ses.inc_stats_counter(counters::num_pinned_torrents, p ? 1 : -1); + + // if the torrent was just un-pinned, we need to insert + // it into the LRU + m_ses.bump_torrent(this, true); + } + + bool torrent::load(std::vector& buffer) + { + error_code ec; + m_torrent_file->load(&buffer[0], buffer.size(), ec); + if (ec) + { + set_error(ec, error_file_metadata); + return false; + } + + state_updated(); +/* +#ifndef TORRENT_DISABLE_EXTENSIONS + // create the extensions again + + // TOOD: should we store add_torrent_params::userdata + // in torrent just to have it available here? + m_ses.add_extensions_to_torrent(shared_from_this(), NULL); + + // and call on_load() on them + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + TORRENT_TRY { + (*i)->on_load(); + } TORRENT_CATCH (std::exception&) {} + } +#endif +*/ + + m_ses.inc_stats_counter(counters::num_loaded_torrents); + + construct_storage(); + + return true; + } + + // this is called when this torrent hasn't been active in long enough + // to warrant swapping it out, in favor of a more active torrent. + void torrent::unload() + { + TORRENT_ASSERT(is_loaded()); + + // pinned torrents are not allowed to be swapped out + TORRENT_ASSERT(!m_pinned); + + m_should_be_loaded = false; + + // make sure it's not unloaded in the middle of some operation that uses it + if (m_refcount > 0) return; + + // call on_unload() on extensions +#ifndef TORRENT_DISABLE_EXTENSIONS + for (extension_list_t::iterator i = m_extensions.begin() + , end(m_extensions.end()); i != end; ++i) + { + TORRENT_TRY { + (*i)->on_unload(); + } TORRENT_CATCH (std::exception&) {} + } + + // also remove extensions and re-instantiate them when the torrent is loaded again + // they end up using a significant amount of memory + // TODO: there may be peer extensions relying on the torrent extension + // still being alive. Only do this if there are no peers. And when the last peer + // is disconnected, if the torrent is unloaded, clear the extensions +// m_extensions.clear(); +#endif + + // someone else holds a reference to the torrent_info + // make the torrent release its reference to it, + // after making a copy and then unloading that version + // as soon as the user is done with its copy of torrent_info + // it will be freed, and we'll have the unloaded version left + if (!m_torrent_file.unique()) + m_torrent_file = boost::make_shared(*m_torrent_file); + + m_torrent_file->unload(); + m_ses.inc_stats_counter(counters::num_loaded_torrents, -1); + + m_storage.reset(); + + state_updated(); } bt_peer_connection* torrent::find_introducer(tcp::endpoint const& ep) const @@ -1678,7 +2052,7 @@ namespace libtorrent if (was_introduced_by(pp, ep)) return (bt_peer_connection*)p; } #endif - return 0; + return NULL; } bt_peer_connection* torrent::find_peer(tcp::endpoint const& ep) const @@ -1689,81 +2063,108 @@ namespace libtorrent if (p->type() != peer_connection::bittorrent_connection) continue; if (p->remote() == ep) return (bt_peer_connection*)p; } + return NULL; + } + + peer_connection* torrent::find_peer(sha1_hash const& pid) + { + for (peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i) + { + peer_connection* p = *i; + if (p->pid() == pid) return p; + } return 0; } - void torrent::on_resume_data_checked(int ret, disk_io_job const& j) + void torrent::on_resume_data_checked(disk_io_job const* j) { - TORRENT_ASSERT(m_ses.is_network_thread()); + // hold a reference until this function returns + torrent_ref_holder h(this, "check_fastresume"); - if (ret == piece_manager::fatal_disk_error) + // when applying some of the resume data to the torrent, we will + // trigger calls that set m_need_save_resume_data, even though we're + // just applying the state of the resume data we loaded with. We don't + // want anything in this function to affect the state of + // m_need_save_resume_data, so we save it in a local variable and reset + // it at the end of the function. + bool need_save_resume_data = m_need_save_resume_data; + + dec_refcount("check_fastresume"); + TORRENT_ASSERT(m_ses.is_single_thread()); + + if (j->ret == piece_manager::fatal_disk_error) { handle_disk_error(j); auto_managed(false); pause(); - set_state(torrent_status::queued_for_checking); - std::vector().swap(m_resume_data); - lazy_entry().swap(m_resume_entry); + set_state(torrent_status::checking_files); + if (should_check_files()) start_checking(); + m_resume_data.reset(); return; } state_updated(); - if (m_resume_entry.type() == lazy_entry::dict_t) + if (m_resume_data && m_resume_data->entry.type() == lazy_entry::dict_t) { using namespace libtorrent::detail; // for read_*_endpoint() - peer_id id(0); - if (lazy_entry const* peers_entry = m_resume_entry.dict_find_string("peers")) + if (lazy_entry const* peers_entry = m_resume_data->entry.dict_find_string("peers")) { int num_peers = peers_entry->string_length() / (sizeof(address_v4::bytes_type) + 2); char const* ptr = peers_entry->string_ptr(); for (int i = 0; i < num_peers; ++i) { - m_policy.add_peer(read_v4_endpoint(ptr) - , id, peer_info::resume_data, 0); + add_peer(read_v4_endpoint(ptr) + , peer_info::resume_data); } + update_want_peers(); } - if (lazy_entry const* banned_peers_entry = m_resume_entry.dict_find_string("banned_peers")) + if (lazy_entry const* banned_peers_entry = m_resume_data->entry.dict_find_string("banned_peers")) { int num_peers = banned_peers_entry->string_length() / (sizeof(address_v4::bytes_type) + 2); char const* ptr = banned_peers_entry->string_ptr(); for (int i = 0; i < num_peers; ++i) { - policy::peer* p = m_policy.add_peer(read_v4_endpoint(ptr) - , id, peer_info::resume_data, 0); - if (p) m_policy.ban_peer(p); + std::vector peers; + torrent_peer* p = add_peer(read_v4_endpoint(ptr) + , peer_info::resume_data); + peers_erased(peers); + if (p) ban_peer(p); } + update_want_peers(); } #if TORRENT_USE_IPV6 - if (lazy_entry const* peers6_entry = m_resume_entry.dict_find_string("peers6")) + if (lazy_entry const* peers6_entry = m_resume_data->entry.dict_find_string("peers6")) { int num_peers = peers6_entry->string_length() / (sizeof(address_v6::bytes_type) + 2); char const* ptr = peers6_entry->string_ptr(); for (int i = 0; i < num_peers; ++i) { - m_policy.add_peer(read_v6_endpoint(ptr) - , id, peer_info::resume_data, 0); + add_peer(read_v6_endpoint(ptr) + , peer_info::resume_data); } + update_want_peers(); } - if (lazy_entry const* banned_peers6_entry = m_resume_entry.dict_find_string("banned_peers6")) + if (lazy_entry const* banned_peers6_entry = m_resume_data->entry.dict_find_string("banned_peers6")) { int num_peers = banned_peers6_entry->string_length() / (sizeof(address_v6::bytes_type) + 2); char const* ptr = banned_peers6_entry->string_ptr(); for (int i = 0; i < num_peers; ++i) { - policy::peer* p = m_policy.add_peer(read_v6_endpoint(ptr) - , id, peer_info::resume_data, 0); - if (p) m_policy.ban_peer(p); + torrent_peer* p = add_peer(read_v6_endpoint(ptr) + , peer_info::resume_data); + if (p) ban_peer(p); } + update_want_peers(); } #endif // parse out "peers" from the resume data and add them to the peer list - if (lazy_entry const* peers_entry = m_resume_entry.dict_find_list("peers")) + if (lazy_entry const* peers_entry = m_resume_data->entry.dict_find_list("peers")) { for (int i = 0; i < peers_entry->list_size(); ++i) { @@ -1775,12 +2176,13 @@ namespace libtorrent error_code ec; tcp::endpoint a(address::from_string(ip, ec), (unsigned short)port); if (ec) continue; - m_policy.add_peer(a, id, peer_info::resume_data, 0); + add_peer(a, peer_info::resume_data); } + update_want_peers(); } // parse out "banned_peers" and add them as banned - if (lazy_entry const* banned_peers_entry = m_resume_entry.dict_find_list("banned_peers")) + if (lazy_entry const* banned_peers_entry = m_resume_data->entry.dict_find_list("banned_peers")) { for (int i = 0; i < banned_peers_entry->list_size(); ++i) { @@ -1792,24 +2194,31 @@ namespace libtorrent error_code ec; tcp::endpoint a(address::from_string(ip, ec), (unsigned short)port); if (ec) continue; - policy::peer* p = m_policy.add_peer(a, id, peer_info::resume_data, 0); - if (p) m_policy.ban_peer(p); + torrent_peer* p = add_peer(a, peer_info::resume_data); + if (p) ban_peer(p); } + update_want_peers(); } } +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + if (m_policy && m_policy->num_peers() > 0) + debug_log("resume added peers (%d)", m_policy->num_peers()); +#endif + // only report this error if the user actually provided resume data - if ((j.error || ret != 0) && !m_resume_data.empty() - && m_ses.m_alerts.should_post()) + if ((j->error || j->ret != 0) && m_resume_data + && m_ses.alerts().should_post()) { - m_ses.m_alerts.post_alert(fastresume_rejected_alert(get_handle(), j.error)); + m_ses.alerts().post_alert(fastresume_rejected_alert(get_handle(), j->error.ec + , resolve_filename(j->error.file), j->error.operation_str())); } #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING - if (ret != 0) + if (j->ret != 0) { debug_log("fastresume data rejected: ret: %d (%d) %s" - , ret, j.error.value(), j.error.message().c_str()); + , j->ret, j->error.ec.value(), j->error.ec.message().c_str()); } #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING else @@ -1821,34 +2230,48 @@ namespace libtorrent // that when the resume data check fails. For instance, if the resume data // is incorrect, but we don't have any files, we skip the check and initialize // the storage to not have anything. - if (ret == 0) + if (j->ret == 0) { // there are either no files for this torrent // or the resume_data was accepted - if (!j.error && m_resume_entry.type() == lazy_entry::dict_t) + if (!j->error && m_resume_data && m_resume_data->entry.type() == lazy_entry::dict_t) { // parse have bitmask - lazy_entry const* pieces = m_resume_entry.dict_find("pieces"); + lazy_entry const* pieces = m_resume_data->entry.dict_find("pieces"); if (pieces && pieces->type() == lazy_entry::string_t && int(pieces->string_length()) == m_torrent_file->num_pieces()) { char const* pieces_str = pieces->string_ptr(); for (int i = 0, end(pieces->string_length()); i < end; ++i) { - if (pieces_str[i] & 1) we_have(i); + if (pieces_str[i] & 1) + { + need_picker(); + m_picker->we_have(i); + m_ses.inc_stats_counter(counters::num_piece_passed); + update_gauge(); + we_have(i); + } if (m_seed_mode && (pieces_str[i] & 2)) m_verified.set_bit(i); } } else { - lazy_entry const* slots = m_resume_entry.dict_find("slots"); + lazy_entry const* slots = m_resume_data->entry.dict_find("slots"); if (slots && slots->type() == lazy_entry::list_t) { for (int i = 0; i < slots->list_size(); ++i) { int piece = slots->list_int_value_at(i, -1); - if (piece >= 0) we_have(piece); + if (piece >= 0) + { + need_picker(); + m_picker->we_have(piece); + update_gauge(); + m_ses.inc_stats_counter(counters::num_piece_passed); + we_have(piece); + } } } } @@ -1857,7 +2280,7 @@ namespace libtorrent int num_blocks_per_piece = static_cast(torrent_file().piece_length()) / block_size(); - if (lazy_entry const* unfinished_ent = m_resume_entry.dict_find_list("unfinished")) + if (lazy_entry const* unfinished_ent = m_resume_data->entry.dict_find_list("unfinished")) { for (int i = 0; i < unfinished_ent->list_size(); ++i) { @@ -1866,12 +2289,17 @@ namespace libtorrent int piece = e->dict_find_int_value("piece", -1); if (piece < 0 || piece > torrent_file().num_pieces()) continue; - if (m_picker->have_piece(piece)) + if (has_picker() && m_picker->have_piece(piece)) + { m_picker->we_dont_have(piece); + update_gauge(); + } std::string bitmask = e->dict_find_string_value("bitmask"); if (bitmask.empty()) continue; + need_picker(); + const int num_bitmask_bytes = (std::max)(num_blocks_per_piece / 8, 1); if ((int)bitmask.size() != num_bitmask_bytes) continue; for (int k = 0; k < num_bitmask_bytes; ++k) @@ -1884,12 +2312,13 @@ namespace libtorrent if (bits & (1 << b)) { m_picker->mark_as_finished(piece_block(piece, block), 0); - if (m_picker->is_piece_finished(piece)) - async_verify_piece(piece, boost::bind(&torrent::piece_finished - , shared_from_this(), piece, _1)); } } } + if (m_picker->is_piece_finished(piece)) + { + verify_piece(piece); + } } } } @@ -1900,33 +2329,24 @@ namespace libtorrent { // either the fastresume data was rejected or there are // some files - set_state(torrent_status::queued_for_checking); - if (should_check_files()) - queue_torrent_check(); + set_state(torrent_status::checking_files); + + // start the checking right away (potentially) + m_ses.trigger_auto_manage(); } - std::vector().swap(m_resume_data); - lazy_entry().swap(m_resume_entry); - } + maybe_done_flushing(); + m_resume_data.reset(); - void torrent::queue_torrent_check() - { - TORRENT_ASSERT(m_ses.is_network_thread()); - if (m_queued_for_checking) return; - m_queued_for_checking = true; - m_ses.queue_check_torrent(shared_from_this()); - } - - void torrent::dequeue_torrent_check() - { - TORRENT_ASSERT(m_ses.is_network_thread()); - if (!m_queued_for_checking) return; - m_queued_for_checking = false; - m_ses.dequeue_check_torrent(shared_from_this()); + // restore m_need_save_resume_data to its state when we entered this + // function. + m_need_save_resume_data = need_save_resume_data; } void torrent::force_recheck() { + INVARIANT_CHECK; + if (!valid_metadata()) return; // if the torrent is already queued to check its files @@ -1937,145 +2357,289 @@ namespace libtorrent clear_error(); - disconnect_all(errors::stopping_torrent); + if (!need_loaded()) return; + + disconnect_all(errors::stopping_torrent, peer_connection_interface::op_bittorrent); stop_announcing(); - m_owning_storage->async_release_files(); - if (!m_picker) m_picker.reset(new piece_picker()); - std::fill(m_file_progress.begin(), m_file_progress.end(), 0); + m_ses.disk_thread().async_release_files(m_storage.get() + , boost::function()); - int blocks_per_piece = (m_torrent_file->piece_length() + block_size() - 1) / block_size(); - int blocks_in_last_piece = ((m_torrent_file->total_size() % m_torrent_file->piece_length()) - + block_size() - 1) / block_size(); - m_picker->init(blocks_per_piece, blocks_in_last_piece, m_torrent_file->num_pieces()); + // forget that we have any pieces + m_have_all = false; + +// removing the piece picker will clear the user priorities +// instead, just clear which pieces we have +// m_picker.reset(); + if (m_picker) + { + int blocks_per_piece = (m_torrent_file->piece_length() + block_size() - 1) / block_size(); + int blocks_in_last_piece = ((m_torrent_file->total_size() % m_torrent_file->piece_length()) + + block_size() - 1) / block_size(); + m_picker->init(blocks_per_piece, blocks_in_last_piece, m_torrent_file->num_pieces()); + } + + // file progress is allocated lazily, the first time the client + // asks for it + std::vector().swap(m_file_progress); // assume that we don't have anything - TORRENT_ASSERT(m_picker->num_have() == 0); m_files_checked = false; - set_state(torrent_status::checking_resume_data); - m_policy.recalculate_connect_candidates(); + update_gauge(); + update_want_tick(); + set_state(torrent_status::checking_resume_data); if (m_auto_managed && !is_finished()) set_queue_position((std::numeric_limits::max)()); - std::vector().swap(m_resume_data); - lazy_entry().swap(m_resume_entry); - m_storage->async_check_fastresume(&m_resume_entry + m_resume_data.reset(); + + inc_refcount("force_recheck"); + m_ses.disk_thread().async_check_fastresume(m_storage.get(), NULL , boost::bind(&torrent::on_force_recheck - , shared_from_this(), _1, _2)); + , shared_from_this(), _1)); } - void torrent::on_force_recheck(int ret, disk_io_job const& j) + void torrent::on_force_recheck(disk_io_job const* j) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); + // hold a reference until this function returns + torrent_ref_holder h(this, "force_recheck"); + + dec_refcount("force_recheck"); state_updated(); - if (ret == piece_manager::fatal_disk_error) + if (j->ret == piece_manager::fatal_disk_error) { handle_disk_error(j); return; } - if (ret == 0) + if (j->ret == 0) { // if there are no files, just start files_checked(); } else { - set_state(torrent_status::queued_for_checking); - if (should_check_files()) - queue_torrent_check(); + set_state(torrent_status::checking_files); + if (m_auto_managed) pause(true); + if (should_check_files()) start_checking(); + else m_ses.trigger_auto_manage(); } } void torrent::start_checking() { TORRENT_ASSERT(should_check_files()); - set_state(torrent_status::checking_files); - m_storage->async_check_files(boost::bind( - &torrent::on_piece_checked - , shared_from_this(), _1, _2)); + int num_outstanding = m_ses.settings().get_int(settings_pack::checking_mem_usage) * block_size() + / m_torrent_file->piece_length(); + if (num_outstanding <= 0) num_outstanding = 1; + + // we might already have some outstanding jobs, if we were paused and + // resumed quickly, before the outstanding jobs completed + if (m_checking_piece >= m_torrent_file->num_pieces()) + { +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING + debug_log("start_checking, checking_piece >= num_pieces. %d >= %d" + , m_checking_piece, m_torrent_file->num_pieces()); +#endif + return; + } + + // subtract the number of pieces we already have outstanding + num_outstanding -= (m_checking_piece - m_num_checked_pieces); + if (num_outstanding < 0) num_outstanding = 0; + + if (!need_loaded()) + { +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + debug_log("start_checking, need_loaded() failed"); +#endif + return; + } + + for (int i = 0; i < num_outstanding; ++i) + { + inc_refcount("start_checking"); + m_ses.disk_thread().async_hash(m_storage.get(), m_checking_piece++ + , disk_io_job::sequential_access | disk_io_job::volatile_read + , boost::bind(&torrent::on_piece_hashed + , shared_from_this(), _1), (void*)1); + if (m_checking_piece >= m_torrent_file->num_pieces()) break; + } +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING + debug_log("start_checking, m_checking_piece: %d", m_checking_piece); +#endif } - void torrent::on_piece_checked(int ret, disk_io_job const& j) + void nop() {} + + // This is only used for checking of torrents. i.e. force-recheck or initial checking + // of existing files + void torrent::on_piece_hashed(disk_io_job const* j) { - TORRENT_ASSERT(m_ses.is_network_thread()); + // hold a reference until this function returns + torrent_ref_holder h(this, "start_checking"); + + TORRENT_ASSERT(m_ses.is_single_thread()); INVARIANT_CHECK; + dec_refcount("start_checking"); + + if (j->ret == piece_manager::disk_check_aborted) + { + m_checking_piece = 0; + m_num_checked_pieces = 0; +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING + debug_log("on_piece_hashed, disk_check_aborted"); +#endif + pause(); + return; + } + state_updated(); - if (ret == piece_manager::disk_check_aborted) + ++m_num_checked_pieces; + + if (j->ret == piece_manager::fatal_disk_error) { - dequeue_torrent_check(); - pause(); - return; - } - if (ret == piece_manager::fatal_disk_error) - { - if (m_ses.m_alerts.should_post()) + if (j->error.ec == boost::system::errc::no_such_file_or_directory) { - m_ses.m_alerts.post_alert(file_error_alert(j.error_file, get_handle(), j.error)); + // skip this file by updating m_checking_piece to the first piece following it + file_storage const& st = m_torrent_file->files(); + boost::uint64_t file_size = st.file_size(j->error.file); + int last = st.map_file(j->error.file, file_size, 0).piece; + if (m_checking_piece < last) + { + int diff = last - m_checking_piece; + m_num_checked_pieces += diff; + m_checking_piece += diff; + } } + else + { + m_checking_piece = 0; + m_num_checked_pieces = 0; + if (m_ses.alerts().should_post()) + m_ses.alerts().post_alert(file_error_alert(j->error.ec, + resolve_filename(j->error.file), j->error.operation_str(), get_handle())); + #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING - debug_log("fatal disk error: (%d) %s", j.error.value(), j.error.message().c_str()); + debug_log("on_piece_hashed, fatal disk error: (%d) %s", j->error.ec.value(), j->error.ec.message().c_str()); +#endif + auto_managed(false); + pause(); + set_error(j->error.ec, j->error.file); + + // recalculate auto-managed torrents sooner + // in order to start checking the next torrent + m_ses.trigger_auto_manage(); + return; + } + } + + m_progress_ppm = size_type(m_num_checked_pieces) * 1000000 / torrent_file().num_pieces(); + + // we're using the piece hashes here, we need the torrent to be loaded + if (!need_loaded()) + { +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + debug_log("on_piece_hashed, need_loaded failed"); #endif - auto_managed(false); - pause(); - set_error(j.error, j.error_file); - // recalculate auto-managed torrents sooner - // in order to start checking the next torrent - m_ses.trigger_auto_manage(); return; } - m_progress_ppm = size_type(j.piece) * 1000000 / torrent_file().num_pieces(); - - TORRENT_ASSERT(m_picker); - if (j.offset >= 0 && !m_picker->have_piece(j.offset)) + if (m_ses.settings().get_bool(settings_pack::disable_hash_checks) + || sha1_hash(j->d.piece_hash) == m_torrent_file->hash_for_piece(j->piece)) { - we_have(j.offset); - remove_time_critical_piece(j.offset); + if (has_picker() || !m_have_all) + { + need_picker(); + m_picker->we_have(j->piece); + update_gauge(); + } + we_have(j->piece); + } + else + { + // if the hash failed, remove it from the cache + if (m_storage) + m_ses.disk_thread().clear_piece(m_storage.get(), j->piece); } - // we're not done checking yet - // this handler will be called repeatedly until - // we're done, or encounter a failure - if (ret == piece_manager::need_full_check) return; + if (m_num_checked_pieces < m_torrent_file->num_pieces()) + { + // we're not done yet, issue another job + if (m_checking_piece >= m_torrent_file->num_pieces()) + { + // actually, we already have outstanding jobs for + // the remaining pieces. We just need to wait for them + // to finish + return; + } - dequeue_torrent_check(); + if (m_graceful_pause_mode && !m_allow_peers && m_checking_piece == m_num_checked_pieces) + { + // we are in graceful pause mode, and we just completed the last outstanding job. + // now we can be considered paused + if (alerts().should_post()) + alerts().post_alert(torrent_paused_alert(get_handle())); + } + + // we paused the checking + if (!should_check_files()) + { +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING + debug_log("on_piece_hashed, checking paused"); +#endif + return; + } + + if (!need_loaded()) + { +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + debug_log("on_piece_hashed, need_loaded failed"); +#endif + return; + } + + inc_refcount("start_checking"); + m_ses.disk_thread().async_hash(m_storage.get(), m_checking_piece++ + , disk_io_job::sequential_access | disk_io_job::volatile_read + , boost::bind(&torrent::on_piece_hashed + , shared_from_this(), _1), (void*)1); +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING + debug_log("on_piece_hashed, m_checking_piece: %d", m_checking_piece); +#endif + return; + } + +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING + debug_log("on_piece_hashed, completed"); +#endif + // we're done checking! files_checked(); + + // recalculate auto-managed torrents sooner + // in order to start checking the next torrent + m_ses.trigger_auto_manage(); + + // reset the checking state + m_checking_piece = 0; + m_num_checked_pieces = 0; } +#ifndef TORRENT_NO_DEPRECATED void torrent::use_interface(std::string net_interfaces) { - INVARIANT_CHECK; - m_net_interfaces.clear(); - - char* str = allocate_string_copy(net_interfaces.c_str()); - char* ptr = str; - - while (ptr) - { - char* space = strchr(ptr, ','); - if (space) *space++ = 0; - error_code ec; - address a(address::from_string(ptr, ec)); - ptr = space; - if (ec) continue; - m_net_interfaces.push_back(tcp::endpoint(a, 0)); - } - free(str); - } - - tcp::endpoint torrent::get_interface() const - { - if (m_net_interfaces.empty()) return tcp::endpoint(address_v4(), 0); - if (m_interface_index >= m_net_interfaces.size()) m_interface_index = 0; - return m_net_interfaces[m_interface_index++]; + settings_pack* p = new settings_pack; + p->set_str(settings_pack::outgoing_interfaces, net_interfaces); + m_ses.apply_settings_pack(p); } +#endif void torrent::on_tracker_announce_disp(boost::weak_ptr p , error_code const& e) @@ -2091,7 +2655,7 @@ namespace libtorrent void torrent::on_tracker_announce() { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); m_waiting_tracker = false; if (m_abort) return; announce_with_tracker(); @@ -2108,14 +2672,22 @@ namespace libtorrent if (!m_announce_to_lsd) return; + // private torrents are never announced on LSD + if (m_torrent_file->is_valid() && m_torrent_file->priv()) return; + + // i2p torrents are also never announced on LSD + // unless we allow mixed swarms if (m_torrent_file->is_valid() - && (m_torrent_file->priv() - || (torrent_file().is_i2p() - && !settings().allow_i2p_mixed))) + && (torrent_file().is_i2p() && !settings().get_bool(settings_pack::allow_i2p_mixed))) return; if (is_paused()) return; + if (!m_ses.has_lsd()) return; + + // TODO: this pattern is repeated in a few places. Factor this into + // a function and generalize the concept of a torrent having a + // dedicated listen port #ifdef TORRENT_USE_OPENSSL int port = is_ssl_torrent() ? m_ses.ssl_listen_port() : m_ses.listen_port(); #else @@ -2124,7 +2696,7 @@ namespace libtorrent // announce with the local discovery service m_ses.announce_lsd(m_torrent_file->info_hash(), port - , m_ses.settings().broadcast_lsd && m_lsd_seq == 0); + , m_ses.settings().get_bool(settings_pack::broadcast_lsd) && m_lsd_seq == 0); ++m_lsd_seq; } @@ -2132,9 +2704,48 @@ namespace libtorrent void torrent::dht_announce() { - TORRENT_ASSERT(m_ses.is_network_thread()); - if (!m_ses.m_dht) return; - if (!should_announce_dht()) return; + TORRENT_ASSERT(m_ses.is_single_thread()); + if (!m_ses.dht()) + { +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + debug_log("DHT: no dht initialized"); +#endif + return; + } + if (!should_announce_dht()) + { +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + if (!m_ses.announce_dht()) + debug_log("DHT: no listen sockets"); + + if (m_torrent_file->is_valid() && !m_files_checked) + debug_log("DHT: files not checked, skipping DHT announce"); + + if (!m_announce_to_dht) + debug_log("DHT: queueing disabled DHT announce"); + + if (!m_allow_peers) + debug_log("DHT: torrent paused, no DHT announce"); + + if (!m_torrent_file->is_valid() && !m_url.empty()) + debug_log("DHT: no info-hash, waiting for \"%s\"", m_url.c_str()); + + if (m_torrent_file->is_valid() && m_torrent_file->priv()) + debug_log("DHT: private torrent, no DHT announce"); + + if (settings().get_bool(settings_pack::use_dht_as_fallback)) + { + int verified_trackers = 0; + for (std::vector::const_iterator i = m_trackers.begin() + , end(m_trackers.end()); i != end; ++i) + if (i->verified) ++verified_trackers; + + if (verified_trackers > 0) + debug_log("DHT: only using DHT as callback, and there are %d working trackers", verified_trackers); + } +#endif + return; + } TORRENT_ASSERT(m_allow_peers); @@ -2144,7 +2755,10 @@ namespace libtorrent int port = m_ses.listen_port(); #endif - boost::weak_ptr self(shared_from_this()); +#if defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + debug_log("START DHT announce"); + m_dht_start_time = time_now_hires(); +#endif // if we're a seed, we tell the DHT for better scrape stats int flags = is_seed() ? dht::dht_tracker::flag_seed : 0; @@ -2152,9 +2766,11 @@ namespace libtorrent // argument in the announce, this will make the DHT node use // our source port in the packet as our listen port, which is // likely more accurate when behind a NAT - if (settings().enable_incoming_utp) flags |= dht::dht_tracker::flag_implied_port; + if (settings().get_bool(settings_pack::enable_incoming_utp)) + flags |= dht::dht_tracker::flag_implied_port; - m_ses.m_dht->announce(m_torrent_file->info_hash() + boost::weak_ptr self(shared_from_this()); + m_ses.dht()->announce(m_torrent_file->info_hash() , port, flags , boost::bind(&torrent::on_dht_announce_response_disp, self, _1)); } @@ -2169,31 +2785,39 @@ namespace libtorrent void torrent::on_dht_announce_response(std::vector const& peers) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); + +#if defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + debug_log("END DHT announce (%d ms) (%d peers)" + , int(total_milliseconds(time_now_hires() - m_dht_start_time)) + , int(peers.size())); +#endif + if (peers.empty()) return; - if (m_ses.m_alerts.should_post()) + if (m_ses.alerts().should_post()) { - m_ses.m_alerts.post_alert(dht_reply_alert( + m_ses.alerts().post_alert(dht_reply_alert( get_handle(), peers.size())); } if (torrent_file().priv() || (torrent_file().is_i2p() - && !settings().allow_i2p_mixed)) return; + && !settings().get_bool(settings_pack::allow_i2p_mixed))) return; std::for_each(peers.begin(), peers.end(), boost::bind( - &policy::add_peer, boost::ref(m_policy), _1, peer_id(0) - , peer_info::dht, 0)); + &torrent::add_peer, this, _1, peer_info::dht, 0)); do_connect_boost(); + + update_want_peers(); } #endif - void torrent::announce_with_tracker(tracker_request::event_t e + void torrent::announce_with_tracker(boost::uint8_t e , address const& bind_interface) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); INVARIANT_CHECK; if (m_trackers.empty()) @@ -2231,7 +2855,8 @@ namespace libtorrent e = tracker_request::paused; tracker_request req; - req.apply_ip_filter = m_apply_ip_filter && m_ses.m_settings.apply_ip_filter_to_trackers; + req.apply_ip_filter = m_apply_ip_filter + && m_ses.settings().get_bool(settings_pack::apply_ip_filter_to_trackers); req.info_hash = m_torrent_file->info_hash(); req.pid = m_ses.get_peer_id(); req.downloaded = m_stat.total_payload_download() - m_total_failed_bytes; @@ -2246,7 +2871,7 @@ namespace libtorrent #endif // exclude redundant bytes if we should - if (!settings().report_true_downloaded) + if (!settings().get_bool(settings_pack::report_true_downloaded)) req.downloaded -= m_total_redundant_bytes; if (req.downloaded < 0) req.downloaded = 0; @@ -2255,18 +2880,7 @@ namespace libtorrent // if we are aborting. we don't want any new peers req.num_want = (req.event == tracker_request::stopped) - ?0:settings().num_want; - - // SSL torrents use their own listen socket -#ifdef TORRENT_USE_OPENSSL - if (is_ssl_torrent()) req.listen_port = m_ses.ssl_listen_port(); - else -#endif - req.listen_port = m_ses.listen_port(); - if (m_ses.m_key) - req.key = m_ses.m_key; - else - req.key = tracker_key(); + ?0:settings().get_int(settings_pack::num_want); ptime now = time_now_hires(); @@ -2287,21 +2901,22 @@ namespace libtorrent " i->tier: %d tier: %d " " is_working: %d fails: %d fail_limit: %d updating: %d" " can_announce: %d sent_announce: %d ]" - , ae.url.c_str(), settings().announce_to_all_tiers - , settings().announce_to_all_trackers + , ae.url.c_str(), settings().get_bool(settings_pack::announce_to_all_tiers) + , settings().get_bool(settings_pack::announce_to_all_trackers) , ae.tier, tier, ae.is_working(), ae.fails, ae.fail_limit , ae.updating, ae.can_announce(now, is_seed()), sent_announce); #endif // if trackerid is not specified for tracker use default one, probably set explicitly req.trackerid = ae.trackerid.empty() ? m_trackerid : ae.trackerid; - if (settings().announce_to_all_tiers - && !settings().announce_to_all_trackers + if (settings().get_bool(settings_pack::announce_to_all_tiers) + && !settings().get_bool(settings_pack::announce_to_all_trackers) && sent_announce && ae.tier <= tier && tier != INT_MAX) continue; - if (ae.tier > tier && sent_announce && !settings().announce_to_all_tiers) break; + if (ae.tier > tier && sent_announce + && !settings().get_bool(settings_pack::announce_to_all_tiers)) break; if (ae.is_working()) { tier = ae.tier; sent_announce = false; } if (!ae.can_announce(now, is_seed())) { @@ -2318,26 +2933,25 @@ namespace libtorrent else if (!ae.complete_sent && is_seed()) req.event = tracker_request::completed; } - if (!is_any(bind_interface)) req.bind_ip = bind_interface; - else req.bind_ip = m_ses.m_listen_interface.address(); + req.bind_ip = bind_interface; - if (settings().force_proxy) + if (settings().get_bool(settings_pack::force_proxy)) { // in force_proxy mode we don't talk directly to trackers // we only allow trackers if there is a proxy and issue // a warning if there isn't one std::string protocol = req.url.substr(0, req.url.find(':')); - int proxy_type = m_ses.m_proxy.type; + int proxy_type = m_ses.settings().get_int(settings_pack::proxy_type); // http can run over any proxy, so as long as one is used // it's OK. If no proxy is configured, skip this tracker if ((protocol == "http" || protocol == "https") - && proxy_type == proxy_settings::none) + && proxy_type == settings_pack::none) { ae.next_announce = now + minutes(10); - if (m_ses.m_alerts.should_post()) + if (m_ses.alerts().should_post()) { - m_ses.m_alerts.post_alert( + m_ses.alerts().post_alert( anonymous_mode_alert(get_handle() , anonymous_mode_alert::tracker_not_anonymous, req.url)); } @@ -2348,14 +2962,14 @@ namespace libtorrent // if we're not using one of those proxues with a UDP // tracker, skip it if (protocol == "udp" - && proxy_type != proxy_settings::socks5 - && proxy_type != proxy_settings::socks5_pw - && proxy_type != proxy_settings::i2p_proxy) + && proxy_type != settings_pack::socks5 + && proxy_type != settings_pack::socks5_pw + && proxy_type != settings_pack::i2p_proxy) { ae.next_announce = now + minutes(10); - if (m_ses.m_alerts.should_post()) + if (m_ses.alerts().should_post()) { - m_ses.m_alerts.post_alert( + m_ses.alerts().post_alert( anonymous_mode_alert(get_handle() , anonymous_mode_alert::tracker_not_anonymous, req.url)); } @@ -2372,30 +2986,28 @@ namespace libtorrent if (m_abort) { boost::shared_ptr tl(new aux::tracker_logger(m_ses)); - m_ses.m_tracker_manager.queue_request(m_ses.m_io_service, m_ses.m_half_open, req - , tracker_login(), tl); + m_ses.queue_tracker_request(req, tracker_login(), tl, tracker_key()); } else #endif { - m_ses.m_tracker_manager.queue_request(m_ses.m_io_service, m_ses.m_half_open, req - , tracker_login() , shared_from_this()); + m_ses.queue_tracker_request(req, tracker_login(), shared_from_this(), tracker_key()); } ae.updating = true; ae.next_announce = now + seconds(20); ae.min_announce = now + seconds(10); - if (m_ses.m_alerts.should_post()) + if (m_ses.alerts().should_post()) { - m_ses.m_alerts.post_alert( + m_ses.alerts().post_alert( tracker_announce_alert(get_handle(), req.url, req.event)); } sent_announce = true; if (ae.is_working() - && !settings().announce_to_all_trackers - && !settings().announce_to_all_tiers) + && !settings().get_bool(settings_pack::announce_to_all_trackers) + && !settings().get_bool(settings_pack::announce_to_all_tiers)) break; } update_tracker_timer(now); @@ -2403,7 +3015,7 @@ namespace libtorrent void torrent::scrape_tracker() { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); m_last_scrape = 0; if (m_trackers.empty()) return; @@ -2412,29 +3024,28 @@ namespace libtorrent if (i == -1) i = 0; tracker_request req; - req.apply_ip_filter = m_apply_ip_filter && m_ses.m_settings.apply_ip_filter_to_trackers; + req.apply_ip_filter = m_apply_ip_filter + && m_ses.settings().get_bool(settings_pack::apply_ip_filter_to_trackers); req.info_hash = m_torrent_file->info_hash(); req.kind = tracker_request::scrape_request; req.url = m_trackers[i].url; - req.bind_ip = m_ses.m_listen_interface.address(); - m_ses.m_tracker_manager.queue_request(m_ses.m_io_service, m_ses.m_half_open, req - , tracker_login(), shared_from_this()); + m_ses.queue_tracker_request(req, tracker_login(), shared_from_this(), tracker_key()); } void torrent::tracker_warning(tracker_request const& req, std::string const& msg) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); INVARIANT_CHECK; - if (m_ses.m_alerts.should_post()) - m_ses.m_alerts.post_alert(tracker_warning_alert(get_handle(), req.url, msg)); + if (m_ses.alerts().should_post()) + m_ses.alerts().post_alert(tracker_warning_alert(get_handle(), req.url, msg)); } void torrent::tracker_scrape_response(tracker_request const& req , int complete, int incomplete, int downloaded, int downloaders) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); INVARIANT_CHECK; TORRENT_ASSERT(req.kind == tracker_request::scrape_request); @@ -2449,9 +3060,9 @@ namespace libtorrent update_scrape_state(); } - if (m_ses.m_alerts.should_post()) + if (m_ses.alerts().should_post()) { - m_ses.m_alerts.post_alert(scrape_reply_alert( + m_ses.alerts().post_alert(scrape_reply_alert( get_handle(), incomplete, complete, req.url)); } } @@ -2494,19 +3105,19 @@ namespace libtorrent , address const& external_ip , const std::string& trackerid) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); INVARIANT_CHECK; TORRENT_ASSERT(r.kind == tracker_request::announce_request); if (external_ip != address() && !tracker_ips.empty()) - m_ses.set_external_address(external_ip, aux::session_impl::source_tracker + m_ses.set_external_address(external_ip, aux::session_interface::source_tracker , *tracker_ips.begin()); ptime now = time_now(); - if (interval < settings().min_announce_interval) - interval = settings().min_announce_interval; + if (interval < settings().get_int(settings_pack::min_announce_interval)) + interval = settings().get_int(settings_pack::min_announce_interval); announce_entry* ae = find_tracker(r); if (ae) @@ -2529,8 +3140,8 @@ namespace libtorrent if ((!trackerid.empty()) && (ae->trackerid != trackerid)) { ae->trackerid = trackerid; - if (m_ses.m_alerts.should_post()) - m_ses.m_alerts.post_alert(trackerid_alert(get_handle(), r.url, trackerid)); + if (m_ses.alerts().should_post()) + m_ses.alerts().post_alert(trackerid_alert(get_handle(), r.url, trackerid)); } update_scrape_state(); @@ -2576,20 +3187,24 @@ namespace libtorrent #if TORRENT_USE_I2P char const* top_domain = strrchr(i->ip.c_str(), '.'); - if (top_domain && strcmp(top_domain, ".i2p") == 0 && m_ses.m_i2p_conn.is_open()) + if (top_domain && strcmp(top_domain, ".i2p") == 0) { // this is an i2p name, we need to use the sam connection // to do the name lookup /* m_ses.m_i2p_conn.async_name_lookup(i->ip.c_str() , boost::bind(&torrent::on_i2p_resolve - , shared_from_this(), _1, _2)); + , shared_from_this(), _1)); */ // it seems like you're not supposed to do a name lookup // on the peers returned from the tracker, but just strip // the .i2p and use it as a destination i->ip.resize(i->ip.size() - 4); - m_policy.add_i2p_peer(i->ip.c_str(), peer_info::tracker, 0); + torrent_state st = get_policy_state(); + need_policy(); + if (m_policy->add_i2p_peer(i->ip.c_str(), peer_info::tracker, 0, &st)) + state_updated(); + peers_erased(st.erased); } else #endif @@ -2598,8 +3213,11 @@ namespace libtorrent add_outstanding_async("torrent::on_peer_name_lookup"); #endif tcp::resolver::query q(i->ip, to_string(i->port).elems); - m_host_resolver.async_resolve(q, - boost::bind(&torrent::on_peer_name_lookup, shared_from_this(), _1, _2, i->pid)); + // TODO: instead, borrow host resolvers from a pool in session_impl. That + // would make the torrent object smaller + m_ses.get_resolver().async_resolve(i->ip, 0 + , boost::bind(&torrent::on_peer_name_lookup + , shared_from_this(), _1, _2, i->port)); } } else @@ -2613,13 +3231,15 @@ namespace libtorrent // trackers records a peer's internal and external IP, and match up // peers on the same local network // if (is_local(a.address()) && !is_local(tracker_ip)) continue; - m_policy.add_peer(a, i->pid, peer_info::tracker, 0); + if (add_peer(a, peer_info::tracker)) + state_updated(); } } + update_want_peers(); - if (m_ses.m_alerts.should_post()) + if (m_ses.alerts().should_post()) { - m_ses.m_alerts.post_alert(tracker_reply_alert( + m_ses.alerts().post_alert(tracker_reply_alert( get_handle(), peer_list.size(), r.url)); } m_got_tracker_response = true; @@ -2634,10 +3254,10 @@ namespace libtorrent // announce was the second one // don't connect twice just to tell it we're stopping - if (((!is_any(m_ses.m_ipv6_interface.address()) && tracker_ip.is_v4()) - || (!is_any(m_ses.m_ipv4_interface.address()) && tracker_ip.is_v6())) - && r.bind_ip != m_ses.m_ipv4_interface.address() - && r.bind_ip != m_ses.m_ipv6_interface.address() + if (((!is_any(m_ses.get_ipv6_interface().address()) && tracker_ip.is_v4()) + || (!is_any(m_ses.get_ipv4_interface().address()) && tracker_ip.is_v6())) + && r.bind_ip != m_ses.get_ipv4_interface().address() + && r.bind_ip != m_ses.get_ipv6_interface().address() && r.event != tracker_request::stopped) { std::list
::const_iterator i = std::find_if(tracker_ips.begin() @@ -2649,8 +3269,8 @@ namespace libtorrent // tell the tracker to bind to the opposite protocol type address bind_interface = tracker_ip.is_v4() - ?m_ses.m_ipv6_interface.address() - :m_ses.m_ipv4_interface.address(); + ?m_ses.get_ipv6_interface().address() + :m_ses.get_ipv4_interface().address(); announce_with_tracker(r.event, bind_interface); #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING debug_log("announce again using %s as the bind interface" @@ -2668,26 +3288,58 @@ namespace libtorrent { if (!m_need_connect_boost) return; - m_need_connect_boost = false; // this is the first tracker response for this torrent // instead of waiting one second for session_impl::on_tick() // to be called, connect to a few peers immediately - int conns = (std::min)((std::min)((std::min)(m_ses.m_settings.torrent_connect_boost - , m_ses.m_settings.connections_limit - m_ses.num_connections()) - , m_ses.m_half_open.free_slots()) - , m_ses.m_boost_connections - m_ses.m_settings.connection_speed); + int conns = (std::min)((std::min)( + m_ses.settings().get_int(settings_pack::torrent_connect_boost) + , m_ses.settings().get_int(settings_pack::connections_limit) - m_ses.num_connections()) + , m_ses.half_open().free_slots()); - while (want_more_peers() && conns > 0) + if (conns > 0) m_need_connect_boost = false; + + // if we don't know of any peers + if (!m_policy) return; + + while (want_peers() && conns > 0) { - if (!m_policy.connect_one_peer(m_ses.session_time())) break; - // increase m_ses.m_boost_connections for each connection - // attempt. This will be deducted from the connect speed - // the next time session_impl::on_tick() is triggered --conns; - ++m_ses.m_boost_connections; + torrent_state st = get_policy_state(); + torrent_peer* p = m_policy->connect_one_peer(m_ses.session_time(), &st); + peers_erased(st.erased); + m_ses.inc_stats_counter(counters::connection_attempt_loops, st.loop_counter); + if (p == NULL) + { + update_want_peers(); + continue; + } + +#if defined TORRENT_LOGGING || defined TORRENT_VERBOSE_LOGGING + external_ip const& external = m_ses.external_address(); + debug_log(" *** FOUND CONNECTION CANDIDATE [" + " ip: %s rank: %u external: %s t: %d ]" + , print_endpoint(p->ip()).c_str() + , p->rank(external, m_ses.listen_port()) + , print_address(external.external_address(p->address())).c_str() + , m_ses.session_time() - p->last_connected); +#endif + + if (!connect_to_peer(p)) + { + m_policy->inc_failcount(p); + update_want_peers(); + } + else + { + // increase m_ses.m_boost_connections for each connection + // attempt. This will be deducted from the connect speed + // the next time session_impl::on_tick() is triggered + m_ses.inc_boost_connections(); + update_want_peers(); + } } - if (want_more_peers()) m_ses.prioritize_connections(shared_from_this()); + if (want_peers()) m_ses.prioritize_connections(shared_from_this()); } ptime torrent::next_announce() const @@ -2726,7 +3378,7 @@ namespace libtorrent #if TORRENT_USE_I2P void torrent::on_i2p_resolve(error_code const& ec, char const* dest) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); INVARIANT_CHECK; @@ -2736,14 +3388,18 @@ namespace libtorrent #endif if (ec || m_ses.is_aborted()) return; - m_policy.add_i2p_peer(dest, peer_info::tracker, 0); + need_policy(); + torrent_state st = get_policy_state(); + if (m_policy->add_i2p_peer(dest, peer_info::tracker, 0, &st)) + state_updated(); + peers_erased(st.erased); } #endif - void torrent::on_peer_name_lookup(error_code const& e, tcp::resolver::iterator host - , peer_id pid) + void torrent::on_peer_name_lookup(error_code const& e + , std::vector
const& host_list, int port) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); INVARIANT_CHECK; @@ -2755,23 +3411,27 @@ namespace libtorrent if (e) debug_log("peer name lookup error: %s", e.message().c_str()); #endif - if (e || host == tcp::resolver::iterator() || - m_ses.is_aborted()) return; + + if (e || host_list.empty() || m_ses.is_aborted()) return; + + tcp::endpoint host(host_list.front(), port); if (m_apply_ip_filter - && m_ses.m_ip_filter.access(host->endpoint().address()) & ip_filter::blocked) + && m_ses.get_ip_filter().access(host.address()) & ip_filter::blocked) { #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING error_code ec; - debug_log("blocked ip from tracker: %s", host->endpoint().address().to_string(ec).c_str()); + debug_log("blocked ip from tracker: %s", host.address().to_string(ec).c_str()); #endif - if (m_ses.m_alerts.should_post()) - m_ses.m_alerts.post_alert(peer_blocked_alert(get_handle() - , host->endpoint().address(), peer_blocked_alert::ip_filter)); + if (m_ses.alerts().should_post()) + m_ses.alerts().post_alert(peer_blocked_alert(get_handle() + , host.address(), peer_blocked_alert::ip_filter)); return; } - - m_policy.add_peer(*host, pid, peer_info::tracker, 0); + + if (add_peer(host, peer_info::tracker)) + state_updated(); + update_want_peers(); } size_type torrent::bytes_left() const @@ -2792,7 +3452,7 @@ namespace libtorrent if (m_torrent_file->num_pieces() == 0) return 0; - if (is_seed()) return m_torrent_file->total_size(); + if (!has_picker()) return m_have_all ? m_torrent_file->total_size() : 0; // if any piece hash fails, we'll be taken out of seed mode // and m_seed_mode will be false @@ -2801,12 +3461,12 @@ namespace libtorrent const int last_piece = m_torrent_file->num_pieces() - 1; size_type total_done - = size_type(num_have()) * m_torrent_file->piece_length(); + = boost::uint64_t(m_picker->num_passed()) * m_torrent_file->piece_length(); // if we have the last piece, we have to correct // the amount we have, since the first calculation // assumed all pieces were of equal size - if (m_picker->have_piece(last_piece)) + if (m_picker->has_piece_passed(last_piece)) { int corr = m_torrent_file->piece_size(last_piece) - m_torrent_file->piece_length(); @@ -2870,14 +3530,23 @@ namespace libtorrent st.total_wanted = st.total_done; return; } + else if (!has_picker()) + { + st.total_done = 0; + st.total_wanted_done = 0; + st.total_wanted = m_torrent_file->total_size() - m_padding; + return; + } TORRENT_ASSERT(num_have() >= m_picker->num_have_filtered()); - st.total_wanted_done = size_type(num_have() - m_picker->num_have_filtered()) + st.total_wanted_done = size_type(num_passed() - m_picker->num_have_filtered()) * piece_size; TORRENT_ASSERT(st.total_wanted_done >= 0); - st.total_done = size_type(num_have()) * piece_size; - TORRENT_ASSERT(num_have() < m_torrent_file->num_pieces()); + st.total_done = size_type(num_passed()) * piece_size; + // if num_passed() == num_pieces(), we should be a seed, and taken the + // branch above + TORRENT_ASSERT(num_passed() <= m_torrent_file->num_pieces()); int num_filtered_pieces = m_picker->num_filtered() + m_picker->num_have_filtered(); @@ -2885,14 +3554,16 @@ namespace libtorrent if (m_picker->piece_priority(last_piece_index) == 0) { st.total_wanted -= m_torrent_file->piece_size(last_piece_index); + TORRENT_ASSERT(st.total_wanted >= 0); --num_filtered_pieces; } st.total_wanted -= size_type(num_filtered_pieces) * piece_size; + TORRENT_ASSERT(st.total_wanted >= 0); // if we have the last piece, we have to correct // the amount we have, since the first calculation // assumed all pieces were of equal size - if (m_picker->have_piece(last_piece)) + if (m_picker->has_piece_passed(last_piece)) { TORRENT_ASSERT(st.total_done >= piece_size); int corr = m_torrent_file->piece_size(last_piece) @@ -2914,6 +3585,10 @@ namespace libtorrent // subtract padding files if (m_padding > 0) { + // this is a bit unfortunate + // (both the const cast and the requirement to load the torrent) + if (!const_cast(this)->need_loaded()) return; + file_storage const& files = m_torrent_file->files(); for (int i = 0; i < files.num_files(); ++i) { @@ -2922,7 +3597,7 @@ namespace libtorrent for (int j = p.piece; p.length > 0; ++j) { int deduction = (std::min)(p.length, piece_size - p.start); - bool done = m_picker->have_piece(j); + bool done = m_picker->has_piece_passed(j); bool wanted = m_picker->piece_priority(j) > 0; if (done) st.total_done -= deduction; if (wanted) st.total_wanted -= deduction; @@ -2937,12 +3612,11 @@ namespace libtorrent } } - TORRENT_ASSERT(st.total_done <= m_torrent_file->total_size() - m_padding); - TORRENT_ASSERT(st.total_wanted_done <= m_torrent_file->total_size() - m_padding); + TORRENT_ASSERT(!accurate || st.total_done <= m_torrent_file->total_size() - m_padding); TORRENT_ASSERT(st.total_wanted_done >= 0); TORRENT_ASSERT(st.total_done >= st.total_wanted_done); - const std::vector& dl_queue + std::vector dl_queue = m_picker->get_download_queue(); const int blocks_per_piece = (piece_size + block_size() - 1) / block_size(); @@ -2955,7 +3629,7 @@ namespace libtorrent int corr = 0; int index = i->index; // completed pieces are already accounted for - if (m_picker->have_piece(index)) continue; + if (m_picker->has_piece_passed(index)) continue; TORRENT_ASSERT(i->finished <= m_picker->blocks_in_piece(index)); #if TORRENT_USE_ASSERTS @@ -3000,7 +3674,7 @@ namespace libtorrent = pc->downloading_piece_progress(); if (!p) continue; - if (m_picker->have_piece(p->piece_index)) + if (m_picker->has_piece_passed(p->piece_index)) continue; piece_block block(p->piece_index, p->block_index); @@ -3073,29 +3747,58 @@ namespace libtorrent TORRENT_ASSERT(st.total_done >= st.total_wanted_done); } - // passed_hash_check - // 0: success, piece passed check - // -1: disk failure - // -2: piece failed check - void torrent::piece_finished(int index, int passed_hash_check) + void torrent::on_piece_verified(disk_io_job const* j) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); + + torrent_ref_holder h(this, "verify_piece"); + + dec_refcount("verify_piece"); + + int ret = j->ret; + if (m_ses.settings().get_bool(settings_pack::disable_hash_checks)) + { + ret = 0; + } + else if (ret == -1) + { + handle_disk_error(j); + } + // we're using the piece hashes here, we need the torrent to be loaded + else if (need_loaded()) + { + if (sha1_hash(j->d.piece_hash) != m_torrent_file->hash_for_piece(j->piece)) + ret = -2; + } + else + { + // failing to load the .torrent file counts as disk failure + ret = -1; + } + + // 0: success, piece passed check + // -1: disk failure + // -2: piece failed check + #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING debug_log("*** PIECE_FINISHED [ p: %d | chk: %s | size: %d ]" - , index, ((passed_hash_check == 0) - ?"passed":passed_hash_check == -1 + , j->piece, ((ret == 0) + ?"passed":ret == -1 ?"disk failed":"failed") - , m_torrent_file->piece_size(index)); + , m_torrent_file->piece_size(j->piece)); #endif - TORRENT_ASSERT(valid_metadata()); - // it's possible to get here if the last piece was downloaded - // from peers and inserted with add_piece at the same time. - // if we're a seed, we won't have a piece picker, and can't continue - if (is_seed()) return; + // if we're a seed we don't have a picker + // and we also don't have to do anything because + // we already have this piece + if (!has_picker() && m_have_all) return; - TORRENT_ASSERT(!m_picker->have_piece(index)); + need_picker(); + + TORRENT_ASSERT(!m_picker->have_piece(j->piece)); + + picker().mark_as_done_checking(j->piece); state_updated(); @@ -3105,28 +3808,28 @@ namespace libtorrent // called, and the piece is no longer finished. // in this case, we have to ignore the fact that // it passed the check - if (!m_picker->is_piece_finished(index)) return; + if (!m_picker->is_piece_finished(j->piece)) return; - if (passed_hash_check == 0) + if (ret == 0) { // the following call may cause picker to become invalid // in case we just became a seed - piece_passed(index); + piece_passed(j->piece); // if we're in seed mode, we just acquired this piece // mark it as verified - if (m_seed_mode) verified(index); + if (m_seed_mode) verified(j->piece); } - else if (passed_hash_check == -2) + else if (ret == -2) { // piece_failed() will restore the piece - piece_failed(index); + piece_failed(j->piece); } else { - TORRENT_ASSERT(passed_hash_check == -1); - m_picker->restore_piece(index); - restore_piece_state(index); + TORRENT_ASSERT(ret == -1); + update_gauge(); } + } void torrent::update_sparse_piece_prio(int i, int start, int end) @@ -3140,137 +3843,60 @@ namespace libtorrent m_picker->set_piece_priority(i, 7); else if (have_after || have_before) m_picker->set_piece_priority(i, 6); + update_gauge(); } + // this is called once we have completely downloaded piece + // 'index', its hash has been verified. It's also called + // during initial file check when we find a piece whose hash + // is correct void torrent::we_have(int index) { - TORRENT_ASSERT(m_ses.is_network_thread()); - // update m_file_progress - TORRENT_ASSERT(m_picker); - TORRENT_ASSERT(!have_piece(index)); - TORRENT_ASSERT(!m_picker->have_piece(index)); + TORRENT_ASSERT(m_ses.is_single_thread()); + TORRENT_ASSERT(!has_picker() || m_picker->has_piece_passed(index)); - const int piece_size = m_torrent_file->piece_length(); - size_type off = size_type(index) * piece_size; - int file_index = m_torrent_file->files().file_index_at_offset(off); - int size = m_torrent_file->piece_size(index); - file_storage const& fs = m_torrent_file->files(); - for (; size > 0; ++file_index) + m_ses.inc_stats_counter(counters::num_have_pieces); + + // at this point, we have the piece for sure. It has been + // successfully written to disk. We may announce it to peers + // (unless it has already been announced through predictive_piece_announce + // feature). + bool announce_piece = true; + std::vector::iterator i = std::lower_bound(m_predictive_pieces.begin() + , m_predictive_pieces.end(), index); + if (i != m_predictive_pieces.end() && *i == index) { - size_type file_offset = off - fs.file_offset(file_index); - TORRENT_ASSERT(file_index != fs.num_files()); - TORRENT_ASSERT(file_offset <= fs.file_size(file_index)); - int add = (std::min)(fs.file_size(file_index) - file_offset, size_type(size)); - m_file_progress[file_index] += add; - - TORRENT_ASSERT(m_file_progress[file_index] - <= m_torrent_file->files().file_size(file_index)); - - if (m_file_progress[file_index] >= m_torrent_file->files().file_size(file_index)) - { - if (!m_torrent_file->files().pad_file_at(file_index)) - { - if (m_ses.m_alerts.should_post()) - { - // this file just completed, post alert - m_ses.m_alerts.post_alert(file_completed_alert(get_handle() - , file_index)); - } - } - } - size -= add; - off += add; - TORRENT_ASSERT(size >= 0); + // this means we've already announced the piece + announce_piece = false; + m_predictive_pieces.erase(i); } - remove_time_critical_piece(index, true); + // make a copy of the peer list since peers + // may disconnect while looping + std::vector peers = m_connections; - m_picker->we_have(index); - } - - void torrent::piece_passed(int index) - { -// INVARIANT_CHECK; - TORRENT_ASSERT(m_ses.is_network_thread()); - - TORRENT_ASSERT(index >= 0); - TORRENT_ASSERT(index < m_torrent_file->num_pieces()); -#ifdef TORRENT_DEBUG - // make sure all blocks were successfully written before we - // declare the piece as "we have". - piece_picker::downloading_piece dp; - m_picker->piece_info(index, dp); - int blocks_in_piece = m_picker->blocks_in_piece(index); - TORRENT_ASSERT(dp.finished == blocks_in_piece); - TORRENT_ASSERT(dp.writing == 0); - TORRENT_ASSERT(dp.requested == 0); - TORRENT_ASSERT(dp.index == index); -#endif - - if (m_ses.m_alerts.should_post()) + for (peer_iterator i = peers.begin(); i != peers.end(); ++i) { - m_ses.m_alerts.post_alert(piece_finished_alert(get_handle() - , index)); + boost::shared_ptr p = (*i)->self(); + + // received_piece will check to see if we're still interested + // in this peer, and if neither of us is interested in the other, + // disconnect it. + p->received_piece(index); + if (p->is_disconnecting()) continue; + + // if we're not announcing the piece, it means we + // already have, and that we might have received + // a request for it, and not sending it because + // we were waiting to receive the piece, now that + // we have received it, try to send stuff (fill_send_buffer) + if (announce_piece) p->announce_piece(index); + else p->fill_send_buffer(); } - m_need_save_resume_data = true; - state_updated(); - - remove_time_critical_piece(index, true); - - bool was_finished = m_picker->num_filtered() + num_have() - == torrent_file().num_pieces(); - - std::vector downloaders; - m_picker->get_downloaders(downloaders, index); - - // increase the trust point of all peers that sent - // parts of this piece. - std::set peers; - - // these policy::peer pointers are owned by m_policy and they may be - // invalidated if a peer disconnects. We cannot keep them across any - // significant operations, but we should use them right away - // ignore NULL pointers - std::remove_copy(downloaders.begin(), downloaders.end() - , std::inserter(peers, peers.begin()), (policy::peer*)0); - - for (std::set::iterator i = peers.begin() - , end(peers.end()); i != end; ++i) - { - policy::peer* p = static_cast(*i); - TORRENT_ASSERT(p != 0); - if (p == 0) continue; - TORRENT_ASSERT(p->in_use); - p->on_parole = false; - int trust_points = p->trust_points; - ++trust_points; - if (trust_points > 8) trust_points = 8; - p->trust_points = trust_points; - if (p->connection) - { - TORRENT_ASSERT(p->connection->m_in_use == 1337); - p->connection->received_valid_data(index); - } - } - - // announcing a piece may invalidate the policy::peer pointers - // so we can't use them anymore - - downloaders.clear(); - peers.clear(); - - we_have(index); - - for (peer_iterator i = m_connections.begin(); i != m_connections.end();) - { - intrusive_ptr p = *i; - ++i; - p->announce_piece(index); - } - - if (settings().max_sparse_regions > 0 - && m_picker->sparse_regions() > settings().max_sparse_regions) + if (settings().get_int(settings_pack::max_sparse_regions) > 0 + && has_picker() + && m_picker->sparse_regions() > settings().get_int(settings_pack::max_sparse_regions)) { // we have too many sparse regions. Prioritize pieces // that won't introduce new sparse regions @@ -3305,12 +3931,68 @@ namespace libtorrent // if we're not interested already, no need to check if (!p->is_interesting()) continue; // if the peer doesn't have the piece we just got, it - // wouldn't affect our interest + // shouldn't affect our interest if (!p->has_piece(index)) continue; p->update_interest(); } - if (!was_finished && is_finished()) + if (settings().get_int(settings_pack::suggest_mode) == settings_pack::suggest_read_cache) + { + // we just got a new piece. Chances are that it's actually the + // rarest piece (since we're likely to download pieces rarest first) + // if it's rarer than any other piece that we currently suggest, insert + // it in the suggest set and pop the last one out + add_suggest_piece(index); + } + + m_need_save_resume_data = true; + state_updated(); + + if (m_ses.alerts().should_post()) + m_ses.alerts().post_alert(piece_finished_alert(get_handle(), index)); + + // update m_file_progress (if we have one) + if (!m_file_progress.empty()) + { + const int piece_size = m_torrent_file->piece_length(); + size_type off = size_type(index) * piece_size; + int file_index = m_torrent_file->files().file_index_at_offset(off); + int size = m_torrent_file->piece_size(index); + file_storage const& fs = m_torrent_file->files(); + for (; size > 0; ++file_index) + { + size_type file_offset = off - fs.file_offset(file_index); + TORRENT_ASSERT(file_index != fs.num_files()); + TORRENT_ASSERT(file_offset <= fs.file_size(file_index)); + int add = (std::min)(fs.file_size(file_index) - file_offset, (size_type)size); + m_file_progress[file_index] += add; + + TORRENT_ASSERT(m_file_progress[file_index] + <= m_torrent_file->files().file_size(file_index)); + + if (m_file_progress[file_index] >= m_torrent_file->files().file_size(file_index)) + { + if (!m_torrent_file->files().pad_file_at(file_index)) + { + if (m_ses.alerts().should_post()) + { + // this file just completed, post alert + m_ses.alerts().post_alert(file_completed_alert(get_handle() + , file_index)); + } + } + } + size -= add; + off += add; + TORRENT_ASSERT(size >= 0); + } + } + + remove_time_critical_piece(index, true); + + if (is_finished() + && m_state != torrent_status::finished + && m_state != torrent_status::seeding) { // torrent finished // i.e. all the pieces we're interested in have @@ -3327,6 +4009,102 @@ namespace libtorrent recalc_share_mode(); } + // this is called when the piece hash is checked as correct. Note + // that the piece picker and the torrent won't necessarily consider + // us to have this piece yet, since it might not have been flushed + // to disk yet. Only if we have predictive_piece_announce on will + // we announce this piece to peers at this point. + void torrent::piece_passed(int index) + { +// INVARIANT_CHECK; + TORRENT_ASSERT(m_ses.is_single_thread()); + TORRENT_ASSERT(!m_picker->has_piece_passed(index)); + +#if defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + debug_log("PIECE_PASSED (%d)", num_passed()); +#endif + +// fprintf(stderr, "torrent::piece_passed piece:%d\n", index); + + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < m_torrent_file->num_pieces()); + + m_need_save_resume_data = true; + + m_ses.inc_stats_counter(counters::num_piece_passed); + + remove_time_critical_piece(index, true); + + std::vector downloaders; + m_picker->get_downloaders(downloaders, index); + + // increase the trust point of all peers that sent + // parts of this piece. + std::set peers; + + // these torrent_peer pointers are owned by m_policy and they may be + // invalidated if a peer disconnects. We cannot keep them across any + // significant operations, but we should use them right away + // ignore NULL pointers + std::remove_copy(downloaders.begin(), downloaders.end() + , std::inserter(peers, peers.begin()), (torrent_peer*)0); + + for (std::set::iterator i = peers.begin() + , end(peers.end()); i != end; ++i) + { + torrent_peer* p = static_cast(*i); + TORRENT_ASSERT(p != 0); + if (p == 0) continue; + TORRENT_ASSERT(p->in_use); + p->on_parole = false; + int trust_points = p->trust_points; + ++trust_points; + if (trust_points > 8) trust_points = 8; + p->trust_points = trust_points; + if (p->connection) + { + peer_connection* peer = static_cast(p->connection); + TORRENT_ASSERT(peer->m_in_use == 1337); + peer->received_valid_data(index); + } + } + // announcing a piece may invalidate the torrent_peer pointers + // so we can't use them anymore + + downloaders.clear(); + peers.clear(); + + // make the disk cache flush the piece to disk + if (m_storage) + m_ses.disk_thread().async_flush_piece(m_storage.get(), index); + m_picker->piece_passed(index); + update_gauge(); + we_have(index); + } + + // we believe we will complete this piece very soon + // announce it to peers ahead of time to eliminate the + // round-trip times involved in announcing it, requesting it + // and sending it + void torrent::predicted_have_piece(int index, int milliseconds) + { + std::vector::iterator i = std::lower_bound(m_predictive_pieces.begin() + , m_predictive_pieces.end(), index); + if (i != m_predictive_pieces.end() && *i == index) return; + + for (peer_iterator p = m_connections.begin() + , end(m_connections.end()); p != end; ++p) + { +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING + (*p)->peer_log(">>> PREDICTIVE_HAVE [ piece: %d expected in %d ms]" + , index, milliseconds); +#endif + (*p)->announce_piece(index); + } + + m_predictive_pieces.insert(i, index); + } + void torrent::piece_failed(int index) { // if the last piece fails the peer connection will still @@ -3335,41 +4113,36 @@ namespace libtorrent // invariant check here since it assumes: // (total_done == m_torrent_file->total_size()) => is_seed() INVARIANT_CHECK; - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); - TORRENT_ASSERT(m_storage); - TORRENT_ASSERT(m_storage->refcount() > 0); TORRENT_ASSERT(m_picker.get()); TORRENT_ASSERT(index >= 0); TORRENT_ASSERT(index < m_torrent_file->num_pieces()); - if (m_ses.m_alerts.should_post()) - m_ses.m_alerts.post_alert(hash_failed_alert(get_handle(), index)); + m_ses.inc_stats_counter(counters::num_piece_failed); + if (m_ses.alerts().should_post()) + m_ses.alerts().post_alert(hash_failed_alert(get_handle(), index)); + + std::vector::iterator i = std::lower_bound(m_predictive_pieces.begin() + , m_predictive_pieces.end(), index); + if (i != m_predictive_pieces.end() && *i == index) + { + for (peer_iterator p = m_connections.begin() + , end(m_connections.end()); p != end; ++p) + { + // send reject messages for + // potential outstanding requests to this piece + (*p)->reject_piece(index); + // let peers that support the dont-have message + // know that we don't actually have this piece + (*p)->write_dont_have(index); + } + m_predictive_pieces.erase(i); + } // increase the total amount of failed bytes add_failed_bytes(m_torrent_file->piece_size(index)); - std::vector downloaders; - m_picker->get_downloaders(downloaders, index); - - // decrease the trust point of all peers that sent - // parts of this piece. - // first, build a set of all peers that participated - std::set peers; - std::copy(downloaders.begin(), downloaders.end(), std::inserter(peers, peers.begin())); - -#ifdef TORRENT_DEBUG - for (std::vector::iterator i = downloaders.begin() - , end(downloaders.end()); i != end; ++i) - { - policy::peer* p = (policy::peer*)*i; - if (p && p->connection) - { - p->connection->piece_failed = true; - } - } -#endif - #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) @@ -3380,27 +4153,51 @@ namespace libtorrent } #endif + std::vector downloaders; + if (m_picker) + m_picker->get_downloaders(downloaders, index); + + // decrease the trust point of all peers that sent + // parts of this piece. + // first, build a set of all peers that participated + std::set peers; + std::copy(downloaders.begin(), downloaders.end(), std::inserter(peers, peers.begin())); + +#ifdef TORRENT_DEBUG + for (std::vector::iterator i = downloaders.begin() + , end(downloaders.end()); i != end; ++i) + { + torrent_peer* p = (torrent_peer*)*i; + if (p && p->connection) + { + peer_connection* peer = static_cast(p->connection); + peer->piece_failed = true; + } + } +#endif + // did we receive this piece from a single peer? bool single_peer = peers.size() == 1; for (std::set::iterator i = peers.begin() , end(peers.end()); i != end; ++i) { - policy::peer* p = static_cast(*i); + torrent_peer* p = static_cast(*i); if (p == 0) continue; TORRENT_ASSERT(p->in_use); bool allow_disconnect = true; if (p->connection) { - TORRENT_ASSERT(p->connection->m_in_use == 1337); + peer_connection* peer = static_cast(p->connection); + TORRENT_ASSERT(peer->m_in_use == 1337); // the peer implementation can ask not to be disconnected. // this is used for web seeds for instance, to instead of // disconnecting, mark the file as not being haved. - allow_disconnect = p->connection->received_invalid_data(index, single_peer); + allow_disconnect = peer->received_invalid_data(index, single_peer); } - if (m_ses.settings().use_parole_mode) + if (m_ses.settings().get_bool(settings_pack::use_parole_mode)) p->on_parole = true; int hashfails = p->hashfails; @@ -3424,67 +4221,117 @@ namespace libtorrent { // we don't trust this peer anymore // ban it. - if (m_ses.m_alerts.should_post()) + if (m_ses.alerts().should_post()) { peer_id pid(0); if (p->connection) pid = p->connection->pid(); - m_ses.m_alerts.post_alert(peer_ban_alert( + m_ses.alerts().post_alert(peer_ban_alert( get_handle(), p->ip(), pid)); } // mark the peer as banned - m_policy.ban_peer(p); -#ifdef TORRENT_STATS - ++m_ses.m_banned_for_hash_failure; -#endif + ban_peer(p); + update_want_peers(); + m_ses.inc_stats_counter(counters::banned_for_hash_failure); if (p->connection) { -#ifdef TORRENT_LOGGING + peer_connection* peer = static_cast(p->connection); +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING debug_log("*** BANNING PEER: \"%s\" Too many corrupt pieces" , print_endpoint(p->ip()).c_str()); #endif #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING - p->connection->peer_log("*** BANNING PEER: Too many corrupt pieces"); + peer->peer_log("*** BANNING PEER: Too many corrupt pieces"); #endif - p->connection->disconnect(errors::too_many_corrupt_pieces); + peer->disconnect(errors::too_many_corrupt_pieces, peer_connection_interface::op_bittorrent); } } } - // we have to let the piece_picker know that - // this piece failed the check as it can restore it - // and mark it as being interesting for download - m_picker->restore_piece(index); + // If m_storage isn't set here, it means we're shutting down + if (m_storage) + { + // it doesn't make much sense to fail to hash a piece + // without having a storage associated with the torrent. + // restoring the piece in the piece picker without calling + // clear piece on the disk thread will make them out of + // sync, and if we try to write more blocks to this piece + // the disk thread will barf, because it hasn't been cleared + TORRENT_ASSERT(m_storage); - // we might still have outstanding requests to this - // piece that hasn't been received yet. If this is the - // case, we need to re-open the piece and mark any - // blocks we're still waiting for as requested - restore_piece_state(index); + // don't allow picking any blocks from this piece + // until we're done synchronizing with the disk threads. + m_picker->lock_piece(index); - TORRENT_ASSERT(m_storage); - - TORRENT_ASSERT(m_picker->have_piece(index) == false); + // don't do this until after the plugins have had a chance + // to read back the blocks that failed, for blame purposes + // this way they have a chance to hit the cache + m_ses.disk_thread().async_clear_piece(m_storage.get(), index + , boost::bind(&torrent::on_piece_sync, shared_from_this(), _1)); + } + else + { + TORRENT_ASSERT(m_abort); + // it doesn't really matter what we do + // here, since we're about to destruct the + // torrent anyway. + disk_io_job j; + j.piece = index; + on_piece_sync(&j); + } #ifdef TORRENT_DEBUG for (std::vector::iterator i = downloaders.begin() , end(downloaders.end()); i != end; ++i) { - policy::peer* p = (policy::peer*)*i; + torrent_peer* p = (torrent_peer*)*i; if (p && p->connection) { - p->connection->piece_failed = false; + peer_connection* peer = static_cast(p->connection); + peer->piece_failed = false; } } #endif } - void torrent::restore_piece_state(int index) + void torrent::peer_is_interesting(peer_connection& c) { - TORRENT_ASSERT(has_picker()); - for (peer_iterator i = m_connections.begin(); - i != m_connections.end(); ++i) + INVARIANT_CHECK; + + // no peer should be interesting if we're finished + TORRENT_ASSERT(!is_finished()); + + if (c.in_handshake()) return; + c.send_interested(); + if (c.has_peer_choked() + && c.allowed_fast().empty()) + return; + + if (request_a_block(*this, c)) + m_ses.inc_stats_counter(counters::interesting_piece_picks); + c.send_block_requests(); + } + + void torrent::on_piece_sync(disk_io_job const* j) + { + // the user may have called force_recheck, which clears + // the piece picker + if (!has_picker()) return; + + // unlock the piece and restore it, as if no block was + // ever downloaded for it. + m_picker->restore_piece(j->piece); + + // we have to let the piece_picker know that + // this piece failed the check as it can restore it + // and mark it as being interesting for download + TORRENT_ASSERT(m_picker->have_piece(j->piece) == false); + + // loop over all peers and re-request potential duplicate + // blocks to this piece + for (std::vector::iterator i = m_connections.begin() + , end(m_connections.end()); i != end; ++i) { peer_connection* p = *i; std::vector const& dq = p->download_queue(); @@ -3493,30 +4340,256 @@ namespace libtorrent , end(dq.end()); k != end; ++k) { if (k->timed_out || k->not_wanted) continue; - if (int(k->block.piece_index) != index) continue; + if (int(k->block.piece_index) != j->piece) continue; m_picker->mark_as_downloading(k->block, p->peer_info_struct() , (piece_picker::piece_state_t)p->peer_speed()); } for (std::vector::const_iterator k = rq.begin() , end(rq.end()); k != end; ++k) { - if (int(k->block.piece_index) != index) continue; + if (int(k->block.piece_index) != j->piece) continue; m_picker->mark_as_downloading(k->block, p->peer_info_struct() , (piece_picker::piece_state_t)p->peer_speed()); } } } + void torrent::peer_has(int index, peer_connection const* peer) + { + if (has_picker()) + { + m_picker->inc_refcount(index, peer); + update_suggest_piece(index, 1); + } +#ifdef TORRENT_DEBUG + else + { + TORRENT_ASSERT(is_seed() || !m_have_all); + } +#endif + } + + // when we get a bitfield message, this is called for that piece + void torrent::peer_has(bitfield const& bits, peer_connection const* peer) + { + if (has_picker()) + { + m_picker->inc_refcount(bits, peer); + refresh_suggest_pieces(); + } +#ifdef TORRENT_DEBUG + else + { + TORRENT_ASSERT(is_seed() || !m_have_all); + } +#endif + } + + void torrent::peer_has_all(peer_connection const* peer) + { + if (has_picker()) + { + m_picker->inc_refcount_all(peer); + } +#ifdef TORRENT_DEBUG + else + { + TORRENT_ASSERT(is_seed() || !m_have_all); + } +#endif + } + + void torrent::peer_lost(bitfield const& bits, peer_connection const* peer) + { + if (has_picker()) + { + m_picker->dec_refcount(bits, peer); + // TODO: update suggest_piece? + } +#ifdef TORRENT_DEBUG + else + { + TORRENT_ASSERT(is_seed() || !m_have_all); + } +#endif + } + + void torrent::peer_lost(int index, peer_connection const* peer) + { + if (m_picker.get()) + { + m_picker->dec_refcount(index, peer); + update_suggest_piece(index, -1); + } +#ifdef TORRENT_DEBUG + else + { + TORRENT_ASSERT(is_seed() || !m_have_all); + } +#endif + } + + void torrent::add_suggest_piece(int index) + { + // it would be nice if we would keep track of piece + // availability even when we're a seed, for + // the suggest piece feature + if (!has_picker()) return; + + int num_peers = m_picker->get_availability(index); + + TORRENT_ASSERT(has_piece_passed(index)); + + // in order to avoid unnecessary churn in the suggested pieces + // the new piece has to beat the existing piece by at least one + // peer in availability. + // m_suggested_pieces is sorted by rarity, the last element + // should have the most peers (num_peers). + if (m_suggested_pieces.empty() + || num_peers < m_suggested_pieces[m_suggested_pieces.size()-1].num_peers - 1) + { + suggest_piece_t p; + p.piece_index = index; + p.num_peers = num_peers; + + typedef std::vector::iterator iter; + + std::pair range = std::equal_range( + m_suggested_pieces.begin(), m_suggested_pieces.end(), p); + + // make sure this piece isn't already in the suggested set. + // if it is, just ignore it + iter i = std::find_if(range.first, range.second + , boost::bind(&suggest_piece_t::piece_index, _1) == index); + if (i != range.second) return; + + m_suggested_pieces.insert(range.second, p); + if (m_suggested_pieces.size() > 0) + m_suggested_pieces.pop_back(); + + // tell all peers about this new suggested piece + for (peer_iterator p = m_connections.begin() + , end(m_connections.end()); p != end; ++p) + { + (*p)->send_suggest(index); + } + + refresh_suggest_pieces(); + } + } + + void torrent::update_suggest_piece(int index, int change) + { + for (std::vector::iterator i = m_suggested_pieces.begin() + , end(m_suggested_pieces.end()); i != end; ++i) + { + if (i->piece_index != index) continue; + + i->num_peers += change; + if (change > 0) + std::stable_sort(i, end); + else if (change < 0) + std::stable_sort(m_suggested_pieces.begin(), i + 1); + } + + if (!m_suggested_pieces.empty() && m_suggested_pieces[0].num_peers > m_connections.size() * 2 / 3) + { + // the rarest piece we have in the suggest set is not very + // rare anymore. at least 2/3 of the peers has it now. Refresh + refresh_suggest_pieces(); + } + } + + void torrent::refresh_suggest_pieces() + { + m_need_suggest_pieces_refresh = true; + } + + void torrent::do_refresh_suggest_pieces() + { + m_need_suggest_pieces_refresh = false; + + if (settings().get_int(settings_pack::suggest_mode) + == settings_pack::no_piece_suggestions) + return; + + if (!valid_metadata()) return; + + boost::shared_ptr t = shared_from_this(); + TORRENT_ASSERT(t); + cache_status cs; + m_ses.disk_thread().get_cache_info(&cs, m_storage.get() == NULL, m_storage.get()); + + // remove write cache entries + cs.pieces.erase(std::remove_if(cs.pieces.begin(), cs.pieces.end() + , boost::bind(&cached_piece_info::kind, _1) == cached_piece_info::write_cache) + , cs.pieces.end()); + + std::vector& pieces = m_suggested_pieces; + pieces.clear(); + pieces.reserve(cs.pieces.size()); + + // sort in ascending order, to get most recently used first + std::sort(cs.pieces.begin(), cs.pieces.end() + , boost::bind(&cached_piece_info::last_use, _1) + > boost::bind(&cached_piece_info::last_use, _2)); + + for (std::vector::iterator i = cs.pieces.begin() + , end(cs.pieces.end()); i != end; ++i) + { + TORRENT_ASSERT(i->storage == m_storage.get()); + if (!has_piece_passed(i->piece)) continue; + suggest_piece_t p; + p.piece_index = i->piece; + if (has_picker()) + { + p.num_peers = m_picker->get_availability(i->piece); + } + else + { + // TODO: really, we should just keep the picker around + // in this case to maintain the availability counters + p.num_peers = 0; + for (const_peer_iterator i = m_connections.begin() + , end(m_connections.end()); i != end; ++i) + { + peer_connection* peer = *i; + if (peer->has_piece(p.piece_index)) ++p.num_peers; + } + } + pieces.push_back(p); + } + + // sort by rarity (stable, to maintain sort + // by last use) + std::stable_sort(pieces.begin(), pieces.end()); + + // only suggest half of the pieces + pieces.resize(pieces.size() / 2); + + // send new suggests to peers + // the peers will filter out pieces we've + // already suggested to them + for (std::vector::iterator i = pieces.begin() + , end(pieces.end()); i != end; ++i) + { + for (peer_iterator p = m_connections.begin(); + p != m_connections.end(); ++p) + (*p)->send_suggest(i->piece_index); + } + } + void torrent::abort() { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); INVARIANT_CHECK; if (m_abort) return; m_abort = true; - - update_guage(); + update_want_peers(); + update_want_tick(); + update_gauge(); // if the torrent is paused, it doesn't need // to announce with even=stopped again. @@ -3531,15 +4604,15 @@ namespace libtorrent // disconnect all peers and close all // files belonging to the torrents - disconnect_all(errors::torrent_aborted); + disconnect_all(errors::torrent_aborted, peer_connection_interface::op_bittorrent); // post a message to the main thread to destruct // the torrent object from there - if (m_owning_storage.get()) + if (m_storage.get()) { - m_storage->abort_disk_io(); - m_storage->async_release_files( - boost::bind(&torrent::on_cache_flushed, shared_from_this(), _1, _2)); + inc_refcount("release_files"); + m_ses.disk_thread().async_stop_torrent(m_storage.get() + , boost::bind(&torrent::on_cache_flushed, shared_from_this(), _1)); } else { @@ -3548,13 +4621,26 @@ namespace libtorrent alerts().post_alert(cache_flushed_alert(get_handle())); } - dequeue_torrent_check(); - - if (m_state == torrent_status::checking_files) - set_state(torrent_status::queued_for_checking); - - m_owning_storage = 0; + m_storage.reset(); m_host_resolver.cancel(); + // TODO: 2 abort lookups this torrent has made via the + // session host resolver interface + + if (!m_apply_ip_filter) + { + m_ses.inc_stats_counter(counters::non_filter_torrents, -1); + m_apply_ip_filter = true; + } + + m_allow_peers = false; + m_auto_managed = false; + for (int i = 0; i < aux::session_interface::num_torrent_lists; ++i) + { + if (!m_links[i].in_list()) continue; + m_links[i].unlink(m_ses.torrent_list(i), i); + } + // don't re-add this torrent to the state-update list + m_state_subscription = false; } void torrent::super_seeding(bool on) @@ -3622,13 +4708,16 @@ namespace libtorrent return avail_vec[random() % avail_vec.size()]; } - void torrent::on_files_deleted(int ret, disk_io_job const& j) + void torrent::on_files_deleted(disk_io_job const* j) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); - if (ret != 0) + dec_refcount("delete_files"); + if (j->ret != 0) { - alerts().post_alert(torrent_delete_failed_alert(get_handle(), j.error, m_torrent_file->info_hash())); + if (alerts().should_post()) + alerts().post_alert(torrent_delete_failed_alert(get_handle() + , j->error.ec, m_torrent_file->info_hash())); } else { @@ -3636,58 +4725,50 @@ namespace libtorrent } } - void torrent::on_files_released(int ret, disk_io_job const& j) + void torrent::on_save_resume_data(disk_io_job const* j) { -/* - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); + torrent_ref_holder h(this, "save_resume"); + dec_refcount("save_resume"); + m_ses.done_async_resume(); - if (alerts().should_post()) + if (!j->buffer) { - alerts().post_alert(torrent_paused_alert(get_handle())); + alerts().post_alert(save_resume_data_failed_alert(get_handle(), j->error.ec)); + return; } -*/ + + m_need_save_resume_data = false; + m_last_saved_resume = m_ses.session_time(); + write_resume_data(*((entry*)j->buffer)); + alerts().post_alert(save_resume_data_alert(boost::shared_ptr((entry*)j->buffer) + , get_handle())); + const_cast(j)->buffer = 0; + state_updated(); } - void torrent::on_save_resume_data(int ret, disk_io_job const& j) + void torrent::on_file_renamed(disk_io_job const* j) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); + dec_refcount("rename_file"); - if (!j.resume_data) - { - alerts().post_alert(save_resume_data_failed_alert(get_handle(), j.error)); - } - else - { - m_need_save_resume_data = false; - m_last_saved_resume = time(0); - write_resume_data(*j.resume_data); - alerts().post_alert(save_resume_data_alert(j.resume_data - , get_handle())); - state_updated(); - } - } - - void torrent::on_file_renamed(int ret, disk_io_job const& j) - { - TORRENT_ASSERT(m_ses.is_network_thread()); - - if (ret == 0) + if (j->ret == 0) { if (alerts().should_post()) - alerts().post_alert(file_renamed_alert(get_handle(), j.str, j.piece)); - m_torrent_file->rename_file(j.piece, j.str); + alerts().post_alert(file_renamed_alert(get_handle(), j->buffer, j->piece)); + m_torrent_file->rename_file(j->piece, j->buffer); } else { if (alerts().should_post()) alerts().post_alert(file_rename_failed_alert(get_handle() - , j.piece, j.error)); + , j->piece, j->error.ec)); } } - void torrent::on_torrent_paused(int ret, disk_io_job const& j) + void torrent::on_torrent_paused(disk_io_job const* j) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); if (alerts().should_post()) alerts().post_alert(torrent_paused_alert(get_handle())); @@ -3714,13 +4795,13 @@ namespace libtorrent void torrent::cancel_non_critical() { std::set time_critical; - for (std::deque::iterator i = m_time_critical_pieces.begin() + for (std::vector::iterator i = m_time_critical_pieces.begin() , end(m_time_critical_pieces.end()); i != end; ++i) { time_critical.insert(i->piece); } - for (std::set::iterator i + for (std::vector::iterator i = m_connections.begin(), end(m_connections.end()); i != end; ++i) { // for each peer, go through its download and request queue @@ -3755,8 +4836,8 @@ namespace libtorrent // failed if (flags & torrent_handle::alert_when_available) { - m_ses.m_alerts.post_alert(read_piece_alert( - get_handle(), piece, error_code(boost::system::errc::operation_canceled, get_system_category()))); + m_ses.alerts().post_alert(read_piece_alert( + get_handle(), piece, error_code(boost::system::errc::operation_canceled, system_category()))); } return; } @@ -3766,7 +4847,7 @@ namespace libtorrent // if we already have the piece, no need to set the deadline. // however, if the user asked to get the piece data back, we still // need to read it and post it back to the user - if (is_seed() || m_picker->have_piece(piece)) + if (is_seed() || (has_picker() && m_picker->has_piece_passed(piece))) { if (flags & torrent_handle::alert_when_available) read_piece(piece); @@ -3780,10 +4861,10 @@ namespace libtorrent // defer this by posting it to the end of the message queue. // this gives the client a chance to specify multiple time critical // pieces before libtorrent cancels requests - m_ses.m_io_service.post(boost::bind(&torrent::cancel_non_critical, this)); + m_ses.get_io_service().post(boost::bind(&torrent::cancel_non_critical, this)); } - for (std::deque::iterator i = m_time_critical_pieces.begin() + for (std::vector::iterator i = m_time_critical_pieces.begin() , end(m_time_critical_pieces.end()); i != end; ++i) { if (i->piece != piece) continue; @@ -3802,10 +4883,14 @@ namespace libtorrent --i; } // just in case this piece had priority 0 + int prev_prio = m_picker->piece_priority(piece); m_picker->set_piece_priority(piece, 7); + if (prev_prio == 0) update_gauge(); return; } + need_picker(); + time_critical_piece p; p.first_requested = min_time(); p.last_requested = min_time(); @@ -3813,12 +4898,14 @@ namespace libtorrent p.deadline = deadline; p.peers = 0; p.piece = piece; - std::deque::iterator i = std::upper_bound(m_time_critical_pieces.begin() + std::vector::iterator i = std::upper_bound(m_time_critical_pieces.begin() , m_time_critical_pieces.end(), p); m_time_critical_pieces.insert(i, p); // just in case this piece had priority 0 + int prev_prio = m_picker->piece_priority(piece); m_picker->set_piece_priority(piece, 7); + if (prev_prio == 0) update_gauge(); piece_picker::downloading_piece pi; m_picker->piece_info(piece, pi); @@ -3833,9 +4920,10 @@ namespace libtorrent for (std::vector::iterator i = downloaders.begin() , end(downloaders.end()); i != end; ++i, ++block) { - policy::peer* p = (policy::peer*)*i; + torrent_peer* p = (torrent_peer*)*i; if (p == 0 || p->connection == 0) continue; - p->connection->make_time_critical(piece_block(piece, block)); + peer_connection* peer = static_cast(p->connection); + peer->make_time_critical(piece_block(piece, block)); } } @@ -3846,8 +4934,9 @@ namespace libtorrent void torrent::remove_time_critical_piece(int piece, bool finished) { - for (std::deque::iterator i = m_time_critical_pieces.begin() - , end(m_time_critical_pieces.end()); i != end; ++i) + for (std::vector::iterator i + = m_time_critical_pieces.begin(), end(m_time_critical_pieces.end()); + i != end; ++i) { if (i->piece != piece) continue; if (finished) @@ -3882,8 +4971,8 @@ namespace libtorrent else if (i->flags & torrent_handle::alert_when_available) { // post an empty read_piece_alert to indicate it failed - m_ses.m_alerts.post_alert(read_piece_alert( - get_handle(), piece, error_code(boost::system::errc::operation_canceled, get_system_category()))); + alerts().post_alert(read_piece_alert( + get_handle(), piece, error_code(boost::system::errc::operation_canceled, system_category()))); } if (has_picker()) m_picker->set_piece_priority(piece, 1); m_time_critical_pieces.erase(i); @@ -3893,14 +4982,14 @@ namespace libtorrent void torrent::clear_time_critical() { - for (std::deque::iterator i = m_time_critical_pieces.begin(); + for (std::vector::iterator i = m_time_critical_pieces.begin(); i != m_time_critical_pieces.end();) { if (i->flags & torrent_handle::alert_when_available) { // post an empty read_piece_alert to indicate it failed - m_ses.m_alerts.post_alert(read_piece_alert( - get_handle(), i->piece, error_code(boost::system::errc::operation_canceled, get_system_category()))); + m_ses.alerts().post_alert(read_piece_alert( + get_handle(), i->piece, error_code(boost::system::errc::operation_canceled, system_category()))); } if (has_picker()) m_picker->set_piece_priority(i->piece, 1); i = m_time_critical_pieces.erase(i); @@ -3910,7 +4999,7 @@ namespace libtorrent // remove time critical pieces where priority is 0 void torrent::remove_time_critical_pieces(std::vector const& priority) { - for (std::deque::iterator i = m_time_critical_pieces.begin(); + for (std::vector::iterator i = m_time_critical_pieces.begin(); i != m_time_critical_pieces.end();) { if (priority[i->piece] == 0) @@ -3918,8 +5007,8 @@ namespace libtorrent if (i->flags & torrent_handle::alert_when_available) { // post an empty read_piece_alert to indicate it failed - m_ses.m_alerts.post_alert(read_piece_alert( - get_handle(), i->piece, error_code(boost::system::errc::operation_canceled, get_system_category()))); + alerts().post_alert(read_piece_alert( + get_handle(), i->piece, error_code(boost::system::errc::operation_canceled, system_category()))); } i = m_time_critical_pieces.erase(i); continue; @@ -3950,14 +5039,18 @@ namespace libtorrent if (is_seed()) return; // this call is only valid on torrents with metadata - TORRENT_ASSERT(m_picker.get()); TORRENT_ASSERT(index >= 0); TORRENT_ASSERT(index < m_torrent_file->num_pieces()); if (index < 0 || index >= m_torrent_file->num_pieces()) return; + need_picker(); + bool was_finished = is_finished(); bool filter_updated = m_picker->set_piece_priority(index, priority); TORRENT_ASSERT(num_have() >= m_picker->num_have_filtered()); + + update_gauge(); + if (filter_updated) { update_peer_interest(was_finished); @@ -3971,10 +5064,9 @@ namespace libtorrent // INVARIANT_CHECK; TORRENT_ASSERT(valid_metadata()); - if (is_seed()) return 1; + if (!has_picker()) return 1; // this call is only valid on torrents with metadata - TORRENT_ASSERT(m_picker.get()); TORRENT_ASSERT(index >= 0); TORRENT_ASSERT(index < m_torrent_file->num_pieces()); if (index < 0 || index >= m_torrent_file->num_pieces()) return 0; @@ -3982,6 +5074,44 @@ namespace libtorrent return m_picker->piece_priority(index); } + void torrent::prioritize_piece_list(std::vector > const& pieces) + { + INVARIANT_CHECK; + + // this call is only valid on torrents with metadata + TORRENT_ASSERT(valid_metadata()); + if (is_seed()) return; + + need_picker(); + + bool filter_updated = false; + bool was_finished = is_finished(); + for (std::vector >::const_iterator i = pieces.begin() + , end(pieces.end()); i != end; ++i) + { + TORRENT_ASSERT(i->second >= 0); + TORRENT_ASSERT(i->second <= 7); + TORRENT_ASSERT(i->first >= 0); + TORRENT_ASSERT(i->first < m_torrent_file->num_pieces()); + + if (i->first < 0 || i->first >= m_torrent_file->num_pieces() || i->second < 0 || i->second > 7) + continue; + + filter_updated |= m_picker->set_piece_priority(i->first, i->second); + TORRENT_ASSERT(num_have() >= m_picker->num_have_filtered()); + } + update_gauge(); + if (filter_updated) + { + // we need to save this new state + m_need_save_resume_data = true; + + update_peer_interest(was_finished); + } + + state_updated(); + } + void torrent::prioritize_pieces(std::vector const& pieces) { INVARIANT_CHECK; @@ -3990,7 +5120,7 @@ namespace libtorrent TORRENT_ASSERT(valid_metadata()); if (is_seed()) return; - TORRENT_ASSERT(m_picker.get()); + need_picker(); int index = 0; bool filter_updated = false; @@ -4003,6 +5133,7 @@ namespace libtorrent filter_updated |= m_picker->set_piece_priority(index, *i); TORRENT_ASSERT(num_have() >= m_picker->num_have_filtered()); } + update_gauge(); if (filter_updated) { // we need to save this new state @@ -4021,7 +5152,7 @@ namespace libtorrent // this call is only valid on torrents with metadata TORRENT_ASSERT(valid_metadata()); - if (is_seed()) + if (!has_picker()) { pieces->clear(); pieces->resize(m_torrent_file->num_pieces(), 1); @@ -4040,7 +5171,10 @@ namespace libtorrent } } - void nop() {} + void torrent::on_file_priority() + { + dec_refcount("file_priority"); + } void torrent::prioritize_files(std::vector const& files) { @@ -4049,29 +5183,36 @@ namespace libtorrent // this call is only valid on torrents with metadata if (!valid_metadata() || is_seed()) return; - // the bitmask need to have exactly one bit for every file + // the vector need to have exactly one element for every file // in the torrent TORRENT_ASSERT(int(files.size()) == m_torrent_file->num_files()); - if (m_torrent_file->num_pieces() == 0) return; - int limit = int(files.size()); if (valid_metadata() && limit > m_torrent_file->num_files()) limit = m_torrent_file->num_files(); if (int(m_file_priority.size()) < limit) - m_file_priority.resize(limit); + m_file_priority.resize(limit, 1); std::copy(files.begin(), files.begin() + limit, m_file_priority.begin()); if (valid_metadata() && m_torrent_file->num_files() > int(m_file_priority.size())) m_file_priority.resize(m_torrent_file->num_files(), 1); + // initialize pad files to priority 0 + file_storage const& fs = m_torrent_file->files(); + for (int i = 0; i < (std::min)(fs.num_files(), limit); ++i) + { + if (!fs.pad_file_at(i)) continue; + m_file_priority[i] = 0; + } + // storage may be NULL during shutdown if (m_torrent_file->num_pieces() > 0 && m_storage) { - filesystem().async_set_file_priority(m_file_priority - , boost::bind(&nop)); + inc_refcount("file_priority"); + m_ses.disk_thread().async_set_file_priority(m_storage.get() + , m_file_priority, boost::bind(&torrent::on_file_priority, this)); } update_piece_priorities(); @@ -4089,16 +5230,29 @@ namespace libtorrent else if (prio > 7) prio = 7; if (int(m_file_priority.size()) <= index) { + // any unallocated slot is assumed to be 1 if (prio == 1) return; - m_file_priority.resize(m_torrent_file->num_files(), 1); + m_file_priority.resize(index+1, 1); + + // initialize pad files to priority 0 + file_storage const& fs = m_torrent_file->files(); + for (int i = 0; i < (std::min)(fs.num_files(), index+1); ++i) + { + if (!fs.pad_file_at(i)) continue; + m_file_priority[i] = 0; + } + } + if (m_file_priority[index] == prio) return; m_file_priority[index] = prio; + // stoage may be NULL during shutdown if (m_storage) { - filesystem().async_set_file_priority(m_file_priority - , boost::bind(&nop)); + inc_refcount("file_priority"); + m_ses.disk_thread().async_set_file_priority(m_storage.get() + , m_file_priority, boost::bind(&torrent::on_file_priority, this)); } update_piece_priorities(); } @@ -4109,13 +5263,19 @@ namespace libtorrent if (!valid_metadata()) return 1; if (index < 0 || index >= m_torrent_file->num_files()) return 0; - if (int(m_file_priority.size()) <= index) return 1; + + // any unallocated slot is assumed to be 1 + // unless it's a pad file + if (int(m_file_priority.size()) <= index) + return m_torrent_file->files().pad_file_at(index) ? 0 : 1; + return m_file_priority[index]; } void torrent::file_priorities(std::vector* files) const { INVARIANT_CHECK; + if (!valid_metadata()) { files->resize(m_file_priority.size()); @@ -4123,6 +5283,7 @@ namespace libtorrent return; } + files->clear(); files->resize(m_torrent_file->num_files(), 1); TORRENT_ASSERT(int(m_file_priority.size()) <= m_torrent_file->num_files()); std::copy(m_file_priority.begin(), m_file_priority.end(), files->begin()); @@ -4134,6 +5295,7 @@ namespace libtorrent if (m_torrent_file->num_pieces() == 0) return; + bool need_update = false; size_type position = 0; int piece_length = m_torrent_file->piece_length(); // initialize the piece priorities to 0, then only allow @@ -4142,12 +5304,23 @@ namespace libtorrent file_storage const& fs = m_torrent_file->files(); for (int i = 0; i < fs.num_files(); ++i) { - if (i >= m_torrent_file->num_files()) break; + if (i >= fs.num_files()) break; + size_type start = position; size_type size = m_torrent_file->files().file_size(i); if (size == 0) continue; position += size; - if (m_file_priority[i] == 0) continue; + int file_prio; + if (m_file_priority.size() <= i) + file_prio = 1; + else + file_prio = m_file_priority[i]; + + if (file_prio == 0) + { + need_update = true; + continue; + } // mark all pieces of the file with this file's priority // but only if the priority is higher than the pieces @@ -4159,9 +5332,12 @@ namespace libtorrent // come here several times with the same start_piece, end_piece std::for_each(pieces.begin() + start_piece , pieces.begin() + last_piece + 1 - , boost::bind(&set_if_greater, _1, m_file_priority[i])); + , boost::bind(&set_if_greater, _1, file_prio)); + + if (has_picker() || file_prio != 1) + need_update = true; } - prioritize_pieces(pieces); + if (need_update) prioritize_pieces(pieces); } // this is called when piece priorities have been updated @@ -4201,9 +5377,9 @@ namespace libtorrent TORRENT_ASSERT(valid_metadata()); if (is_seed()) return; + need_picker(); // this call is only valid on torrents with metadata - TORRENT_ASSERT(m_picker.get()); TORRENT_ASSERT(index >= 0); TORRENT_ASSERT(index < m_torrent_file->num_pieces()); @@ -4212,6 +5388,7 @@ namespace libtorrent bool was_finished = is_finished(); m_picker->set_piece_priority(index, filter ? 1 : 0); update_peer_interest(was_finished); + update_gauge(); } void torrent::filter_pieces(std::vector const& bitmask) @@ -4222,7 +5399,7 @@ namespace libtorrent TORRENT_ASSERT(valid_metadata()); if (is_seed()) return; - TORRENT_ASSERT(m_picker.get()); + need_picker(); bool was_finished = is_finished(); int index = 0; @@ -4236,13 +5413,14 @@ namespace libtorrent m_picker->set_piece_priority(index, 1); } update_peer_interest(was_finished); + update_gauge(); } bool torrent::is_piece_filtered(int index) const { // this call is only valid on torrents with metadata TORRENT_ASSERT(valid_metadata()); - if (is_seed()) return false; + if (!has_picker()) return false; TORRENT_ASSERT(m_picker.get()); TORRENT_ASSERT(index >= 0); @@ -4259,7 +5437,7 @@ namespace libtorrent // this call is only valid on torrents with metadata TORRENT_ASSERT(valid_metadata()); - if (is_seed()) + if (!has_picker()) { bitmask.clear(); bitmask.resize(m_torrent_file->num_pieces(), false); @@ -4324,7 +5502,7 @@ namespace libtorrent , end(m_trackers.end()); i != end; ++i) if (i->source == 0) i->source = announce_entry::source_client; - if (settings().prefer_udp_trackers) + if (settings().get_bool(settings_pack::prefer_udp_trackers)) prioritize_udp_trackers(); if (!m_trackers.empty()) announce_with_tracker(); @@ -4378,7 +5556,7 @@ namespace libtorrent if (k - m_trackers.begin() < m_last_working_tracker) ++m_last_working_tracker; k = m_trackers.insert(k, url); if (k->source == 0) k->source = announce_entry::source_client; - if (!m_trackers.empty()) announce_with_tracker(); + if (m_allow_peers && !m_trackers.empty()) announce_with_tracker(); return true; } @@ -4445,7 +5623,7 @@ namespace libtorrent { if (alerts().should_post()) alerts().post_alert(torrent_error_alert(get_handle() - , error_code(errors::not_an_ssl_torrent))); + , error_code(errors::not_an_ssl_torrent), "")); return; } @@ -4455,25 +5633,25 @@ namespace libtorrent if (ec) { if (alerts().should_post()) - alerts().post_alert(torrent_error_alert(get_handle(), ec)); + alerts().post_alert(torrent_error_alert(get_handle(), ec, "")); } m_ssl_ctx->use_certificate_file(certificate, context::pem, ec); if (ec) { if (alerts().should_post()) - alerts().post_alert(torrent_error_alert(get_handle(), ec)); + alerts().post_alert(torrent_error_alert(get_handle(), ec, certificate)); } m_ssl_ctx->use_private_key_file(private_key, context::pem, ec); if (ec) { if (alerts().should_post()) - alerts().post_alert(torrent_error_alert(get_handle(), ec)); + alerts().post_alert(torrent_error_alert(get_handle(), ec, private_key)); } m_ssl_ctx->use_tmp_dh_file(dh_params, ec); if (ec) { if (alerts().should_post()) - alerts().post_alert(torrent_error_alert(get_handle(), ec)); + alerts().post_alert(torrent_error_alert(get_handle(), ec, dh_params)); } } @@ -4486,7 +5664,7 @@ namespace libtorrent #if BOOST_VERSION < 105400 if (alerts().should_post()) alerts().post_alert(torrent_error_alert(get_handle() - , error_code(boost::system::errc::not_supported, get_system_category()))); + , error_code(boost::system::errc::not_supported, system_category()), "[certificate]")); #else boost::asio::const_buffer certificate_buf(certificate.c_str(), certificate.size()); @@ -4496,7 +5674,7 @@ namespace libtorrent if (ec) { if (alerts().should_post()) - alerts().post_alert(torrent_error_alert(get_handle(), ec)); + alerts().post_alert(torrent_error_alert(get_handle(), ec, "[certificate]")); } boost::asio::const_buffer private_key_buf(private_key.c_str(), private_key.size()); @@ -4504,7 +5682,7 @@ namespace libtorrent if (ec) { if (alerts().should_post()) - alerts().post_alert(torrent_error_alert(get_handle(), ec)); + alerts().post_alert(torrent_error_alert(get_handle(), ec, "[private key]")); } boost::asio::const_buffer dh_params_buf(dh_params.c_str(), dh_params.size()); @@ -4512,7 +5690,7 @@ namespace libtorrent if (ec) { if (alerts().should_post()) - alerts().post_alert(torrent_error_alert(get_handle(), ec)); + alerts().post_alert(torrent_error_alert(get_handle(), ec, "[dh params]")); } #endif // BOOST_VERSION } @@ -4521,12 +5699,12 @@ namespace libtorrent void torrent::remove_peer(peer_connection* p) { -// INVARIANT_CHECK; + INVARIANT_CHECK; TORRENT_ASSERT(p != 0); - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); - peer_iterator i = m_connections.find(p); + peer_iterator i = sorted_find(m_connections, p); if (i == m_connections.end()) { TORRENT_ASSERT(false); @@ -4540,14 +5718,14 @@ namespace libtorrent if (p->is_seed()) { - if (m_picker.get()) + if (has_picker()) { m_picker->dec_refcount_all(p); } } else { - if (m_picker.get()) + if (has_picker()) { bitfield const& pieces = p->get_bitfield(); TORRENT_ASSERT(pieces.count() <= int(pieces.size())); @@ -4559,14 +5737,14 @@ namespace libtorrent if (!p->is_choked() && !p->ignore_unchoke_slots()) { --m_num_uploads; - m_ses.m_unchoke_time_scaler = 0; + m_ses.trigger_unchoke(); } - policy::peer* pp = p->peer_info_struct(); + torrent_peer* pp = p->peer_info_struct(); if (pp) { if (pp->optimistically_unchoked) - m_ses.m_optimistic_unchoke_time_scaler = 0; + m_ses.trigger_optimistic_unchoke(); TORRENT_ASSERT(pp->prev_amount_upload == 0); TORRENT_ASSERT(pp->prev_amount_download == 0); @@ -4574,10 +5752,15 @@ namespace libtorrent pp->prev_amount_upload += p->statistics().total_payload_upload() >> 10; } - m_policy.connection_closed(*p, m_ses.session_time()); + torrent_state st = get_policy_state(); + if (m_policy) m_policy->connection_closed(*p, m_ses.session_time(), &st); + peers_erased(st.erased); + p->set_peer_info(0); TORRENT_ASSERT(i != m_connections.end()); m_connections.erase(i); + update_want_peers(); + update_want_tick(); } void torrent::remove_web_seed(std::list::iterator web) @@ -4587,7 +5770,7 @@ namespace libtorrent web->removed = true; return; } - peer_connection * peer = web->peer_info.connection; + peer_connection* peer = static_cast(web->peer_info.connection); if (peer) { TORRENT_ASSERT(peer->m_in_use == 1337); peer->set_peer_info(0); @@ -4596,18 +5779,19 @@ namespace libtorrent m_web_seeds.erase(web); + update_want_tick(); } void torrent::connect_to_url_seed(std::list::iterator web) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); INVARIANT_CHECK; TORRENT_ASSERT(!web->resolving); if (web->resolving) return; if (int(m_connections.size()) >= m_max_connections - || m_ses.num_connections() >= m_ses.settings().connections_limit) + || m_ses.num_connections() >= m_ses.settings().get_int(settings_pack::connections_limit)) return; std::string protocol; @@ -4628,9 +5812,9 @@ namespace libtorrent #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING debug_log("failed to parse web seed url: %s", ec.message().c_str()); #endif - if (m_ses.m_alerts.should_post()) + if (m_ses.alerts().should_post()) { - m_ses.m_alerts.post_alert( + m_ses.alerts().post_alert( url_seed_alert(get_handle(), web->url, ec)); } // never try it again @@ -4643,9 +5827,9 @@ namespace libtorrent #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING debug_log("banned web seed: %s", web->url.c_str()); #endif - if (m_ses.m_alerts.should_post()) + if (m_ses.alerts().should_post()) { - m_ses.m_alerts.post_alert( + m_ses.alerts().post_alert( url_seed_alert(get_handle(), web->url , error_code(libtorrent::errors::peer_banned, get_libtorrent_category()))); } @@ -4660,9 +5844,9 @@ namespace libtorrent if (protocol != "http") #endif { - if (m_ses.m_alerts.should_post()) + if (m_ses.alerts().should_post()) { - m_ses.m_alerts.post_alert( + m_ses.alerts().post_alert( url_seed_alert(get_handle(), web->url, errors::unsupported_url_protocol)); } // never try it again @@ -4672,9 +5856,9 @@ namespace libtorrent if (hostname.empty()) { - if (m_ses.m_alerts.should_post()) + if (m_ses.alerts().should_post()) { - m_ses.m_alerts.post_alert( + m_ses.alerts().post_alert( url_seed_alert(get_handle(), web->url, errors::invalid_hostname)); } // never try it again @@ -4684,9 +5868,9 @@ namespace libtorrent if (port == 0) { - if (m_ses.m_alerts.should_post()) + if (m_ses.alerts().should_post()) { - m_ses.m_alerts.post_alert( + m_ses.alerts().post_alert( url_seed_alert(get_handle(), web->url, errors::invalid_port)); } // never try it again @@ -4694,11 +5878,11 @@ namespace libtorrent return; } - if (m_ses.m_port_filter.access(port) & port_filter::blocked) + if (m_ses.get_port_filter().access(port) & port_filter::blocked) { - if (m_ses.m_alerts.should_post()) + if (m_ses.alerts().should_post()) { - m_ses.m_alerts.post_alert( + m_ses.alerts().post_alert( url_seed_alert(get_handle(), web->url, errors::port_blocked)); } // never try it again @@ -4717,8 +5901,8 @@ namespace libtorrent #endif proxy_settings const& ps = m_ses.proxy(); - if (ps.type == proxy_settings::http - || ps.type == proxy_settings::http_pw) + if (ps.type == settings_pack::http + || ps.type == settings_pack::http_pw) { #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING debug_log("resolving proxy for web seed: %s", web->url.c_str()); @@ -4731,8 +5915,8 @@ namespace libtorrent boost::bind(&torrent::on_proxy_name_lookup, shared_from_this(), _1, _2, web)); } else if (ps.proxy_hostnames - && (ps.type == proxy_settings::socks5 - || ps.type == proxy_settings::socks5_pw)) + && (ps.type == settings_pack::socks5 + || ps.type == settings_pack::socks5_pw)) { connect_web_seed(web, tcp::endpoint(address(), port)); } @@ -4753,7 +5937,7 @@ namespace libtorrent void torrent::on_proxy_name_lookup(error_code const& e, tcp::resolver::iterator host , std::list::iterator web) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); INVARIANT_CHECK; @@ -4780,9 +5964,9 @@ namespace libtorrent if (e || host == tcp::resolver::iterator()) { - if (m_ses.m_alerts.should_post()) + if (m_ses.alerts().should_post()) { - m_ses.m_alerts.post_alert( + m_ses.alerts().post_alert( url_seed_alert(get_handle(), web->url, e)); } @@ -4803,7 +5987,7 @@ namespace libtorrent #endif if (int(m_connections.size()) >= m_max_connections - || m_ses.num_connections() >= m_ses.settings().connections_limit) + || m_ses.num_connections() >= m_ses.settings().get_int(settings_pack::connections_limit)) return; tcp::endpoint a(host->endpoint()); @@ -4819,9 +6003,9 @@ namespace libtorrent if (ec) { - if (m_ses.m_alerts.should_post()) + if (m_ses.alerts().should_post()) { - m_ses.m_alerts.post_alert( + m_ses.alerts().post_alert( url_seed_alert(get_handle(), web->url, ec)); } remove_web_seed(web); @@ -4829,10 +6013,10 @@ namespace libtorrent } if (m_apply_ip_filter - && m_ses.m_ip_filter.access(a.address()) & ip_filter::blocked) + && m_ses.get_ip_filter().access(a.address()) & ip_filter::blocked) { - if (m_ses.m_alerts.should_post()) - m_ses.m_alerts.post_alert(peer_blocked_alert(get_handle() + if (m_ses.alerts().should_post()) + m_ses.alerts().post_alert(peer_blocked_alert(get_handle() , a.address(), peer_blocked_alert::ip_filter)); return; } @@ -4846,7 +6030,7 @@ namespace libtorrent void torrent::on_name_lookup(error_code const& e, tcp::resolver::iterator host , std::list::iterator web, tcp::endpoint proxy) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); INVARIANT_CHECK; @@ -4868,8 +6052,8 @@ namespace libtorrent if (e || host == tcp::resolver::iterator()) { - if (m_ses.m_alerts.should_post()) - m_ses.m_alerts.post_alert(url_seed_alert(get_handle(), web->url, e)); + if (m_ses.alerts().should_post()) + m_ses.alerts().post_alert(url_seed_alert(get_handle(), web->url, e)); #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING debug_log("*** HOSTNAME LOOKUP FAILED: %s: (%d) %s" , web->url.c_str(), e.value(), e.message().c_str()); @@ -4886,7 +6070,7 @@ namespace libtorrent web->endpoint = a; if (int(m_connections.size()) >= m_max_connections - || m_ses.num_connections() >= m_ses.settings().connections_limit) + || m_ses.num_connections() >= m_ses.settings().get_int(settings_pack::connections_limit)) return; connect_web_seed(web, a); @@ -4894,12 +6078,14 @@ namespace libtorrent void torrent::connect_web_seed(std::list::iterator web, tcp::endpoint a) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); + if (m_abort) return; + if (m_apply_ip_filter - && m_ses.m_ip_filter.access(a.address()) & ip_filter::blocked) + && m_ses.get_ip_filter().access(a.address()) & ip_filter::blocked) { - if (m_ses.m_alerts.should_post()) - m_ses.m_alerts.post_alert(peer_blocked_alert(get_handle() + if (m_ses.alerts().should_post()) + m_ses.alerts().post_alert(peer_blocked_alert(get_handle() , a.address(), peer_blocked_alert::ip_filter)); return; } @@ -4917,7 +6103,8 @@ namespace libtorrent if (is_paused()) return; if (m_ses.is_aborted()) return; - boost::shared_ptr s(new (std::nothrow) socket_type(m_ses.m_io_service)); + boost::shared_ptr s + = boost::make_shared(boost::ref(m_ses.get_io_service())); if (!s) return; void* userdata = 0; @@ -4926,14 +6113,13 @@ namespace libtorrent if (ssl) { userdata = m_ssl_ctx.get(); - if (!userdata) userdata = &m_ses.m_ssl_ctx; + if (!userdata) userdata = m_ses.ssl_ctx(); } #endif - bool ret = instantiate_connection(m_ses.m_io_service, m_ses.proxy(), *s, userdata, 0, true); + bool ret = instantiate_connection(m_ses.get_io_service(), m_ses.proxy(), *s, userdata, 0, true); (void)ret; TORRENT_ASSERT(ret); - proxy_settings const& ps = m_ses.proxy(); if (s->get()) { // the web seed connection will talk immediately to @@ -4948,14 +6134,17 @@ namespace libtorrent = parse_url_components(web->url, ec); if (ec) { - if (m_ses.m_alerts.should_post()) - m_ses.m_alerts.post_alert(url_seed_alert(get_handle(), web->url, ec)); + if (m_ses.alerts().should_post()) + m_ses.alerts().post_alert(url_seed_alert(get_handle(), web->url, ec)); return; } - if (ps.proxy_hostnames - && (ps.type == proxy_settings::socks5 - || ps.type == proxy_settings::socks5_pw)) + bool proxy_hostnames = m_ses.settings().get_bool(settings_pack::proxy_hostnames); + int proxy_type = m_ses.settings().get_int(settings_pack::proxy_type); + + if (proxy_hostnames + && (proxy_type == settings_pack::socks5 + || proxy_type == settings_pack::socks5_pw)) { // we're using a socks proxy and we're resolving // hostnames through it @@ -4972,21 +6161,25 @@ namespace libtorrent setup_ssl_hostname(*s, hostname, ec); if (ec) { - if (m_ses.m_alerts.should_post()) - m_ses.m_alerts.post_alert(url_seed_alert(get_handle(), web->url, ec)); + if (m_ses.alerts().should_post()) + m_ses.alerts().post_alert(url_seed_alert(get_handle(), web->url, ec)); return; } - boost::intrusive_ptr c; + boost::shared_ptr c; if (web->type == web_seed_entry::url_seed) { - c = new (std::nothrow) web_peer_connection( - m_ses, shared_from_this(), s, a, *web); + c = boost::make_shared( + boost::ref(m_ses), m_ses.settings(), boost::ref(m_ses) + , boost::ref(m_ses.disk_thread()) + , shared_from_this(), s, boost::ref(*web)); } else if (web->type == web_seed_entry::http_seed) { - c = new (std::nothrow) http_seed_connection( - m_ses, shared_from_this(), s, a, *web); + c = boost::make_shared( + boost::ref(m_ses), m_ses.settings(), boost::ref(m_ses) + , boost::ref(m_ses.disk_thread()) + , shared_from_this(), s, boost::ref(*web)); } if (!c) return; @@ -5008,8 +6201,10 @@ namespace libtorrent { TORRENT_ASSERT(!c->m_in_constructor); // add the newly connected peer to this torrent's peer list - m_connections.insert(boost::get_pointer(c)); - m_ses.m_connections.insert(c); + sorted_insert(m_connections, boost::get_pointer(c)); + update_want_peers(); + update_want_tick(); + m_ses.insert_peer(c); TORRENT_ASSERT(!web->peer_info.connection); web->peer_info.connection = c.get(); @@ -5029,19 +6224,22 @@ namespace libtorrent if (c->is_disconnecting()) return; - m_ses.m_half_open.enqueue( - boost::bind(&peer_connection::on_connect, c, _1) - , boost::bind(&peer_connection::on_timeout, c) - , seconds(settings().peer_connect_timeout)); + c->m_queued_for_connection = true; + m_ses.half_open().enqueue(c.get() + , seconds(settings().get_int(settings_pack::peer_connect_timeout))); + +#if defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + debug_log("START queue peer [%p] (%d)", c.get(), num_peers()); +#endif } TORRENT_CATCH (std::exception& e) { TORRENT_DECLARE_DUMMY(std::exception, e); (void)e; #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING - debug_log("*** HOST NAME LOOKUP FAILED: %s", e.what()); + debug_log("*** PEER_ERROR: %s", e.what()); #endif - c->disconnect(errors::no_error, 1); + c->disconnect(errors::no_error, peer_connection_interface::op_bittorrent, 1); } } @@ -5053,10 +6251,18 @@ namespace libtorrent return (a >> 24) | ((a & 0xff0000) >> 8) | ((a & 0xff00) << 8) | ((a & 0xff) << 24); } } - - void torrent::resolve_peer_country(boost::intrusive_ptr const& p) const + + void torrent::resolve_countries(bool r) + { m_resolve_countries = r; } + + bool torrent::resolving_countries() const { - TORRENT_ASSERT(m_ses.is_network_thread()); + return m_resolve_countries && !m_ses.settings().get_bool(settings_pack::force_proxy); + } + + void torrent::resolve_peer_country(boost::shared_ptr const& p) const + { + TORRENT_ASSERT(m_ses.is_single_thread()); if (m_resolving_country || is_local(p->remote().address()) || p->has_country() @@ -5067,15 +6273,15 @@ namespace libtorrent asio::ip::address_v4 reversed(swap_bytes(p->remote().address().to_v4().to_ulong())); error_code ec; - tcp::resolver::query q(reversed.to_string(ec) + ".zz.countries.nerd.dk", "0"); + std::string hostname = reversed.to_string(ec) + ".zz.countries.nerd.dk"; if (ec) { p->set_country("!!"); return; } m_resolving_country = true; - m_host_resolver.async_resolve(q, - boost::bind(&torrent::on_country_lookup, shared_from_this(), _1, _2, p)); + m_ses.get_resolver().async_resolve(hostname, 0 + , boost::bind(&torrent::on_country_lookup, shared_from_this(), _1, _2, p)); } namespace @@ -5087,10 +6293,11 @@ namespace libtorrent }; } - void torrent::on_country_lookup(error_code const& error, tcp::resolver::iterator i - , intrusive_ptr p) const + void torrent::on_country_lookup(error_code const& error + , std::vector
const& host_list + , boost::shared_ptr p) const { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); INVARIANT_CHECK; @@ -5151,7 +6358,7 @@ namespace libtorrent , {876, "WF"}, {882, "WS"}, {887, "YE"}, {891, "CS"}, {894, "ZM"} }; - if (error || i == tcp::resolver::iterator()) + if (error || host_list.empty()) { // this is used to indicate that we shouldn't // try to resolve it again @@ -5159,33 +6366,38 @@ namespace libtorrent return; } - while (i != tcp::resolver::iterator() - && !i->endpoint().address().is_v4()) ++i; - if (i != tcp::resolver::iterator()) + int idx = 0; + while (idx < host_list.size() && !host_list[idx].is_v4()) + ++idx; + + if (idx >= host_list.size()) { - // country is an ISO 3166 country code - int country = i->endpoint().address().to_v4().to_ulong() & 0xffff; - - // look up the country code in the map - const int size = sizeof(country_map)/sizeof(country_map[0]); - country_entry tmp = {country, ""}; - country_entry const* j = - std::lower_bound(country_map, country_map + size, tmp - , boost::bind(&country_entry::code, _1) < boost::bind(&country_entry::code, _2)); - if (j == country_map + size - || j->code != country) - { - // unknown country! - p->set_country("!!"); -#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING - debug_log("IP \"%s\" was mapped to unknown country: %d" - , print_address(p->remote().address()).c_str(), country); -#endif - return; - } - - p->set_country(j->name); + p->set_country("--"); + return; } + + // country is an ISO 3166 country code + int country = host_list[idx].to_v4().to_ulong() & 0xffff; + + // look up the country code in the map + const int size = sizeof(country_map)/sizeof(country_map[0]); + country_entry tmp = {country, ""}; + country_entry const* j = + std::lower_bound(country_map, country_map + size, tmp + , boost::bind(&country_entry::code, _1) < boost::bind(&country_entry::code, _2)); + if (j == country_map + size + || j->code != country) + { + // unknown country! + p->set_country("!!"); +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + debug_log("IP \"%s\" was mapped to unknown country: %d" + , print_address(p->remote().address()).c_str(), country); +#endif + return; + } + + p->set_country(j->name); } #endif @@ -5205,7 +6417,11 @@ namespace libtorrent set_max_connections(rd.dict_find_int_value("max_connections", -1)); set_max_uploads(rd.dict_find_int_value("max_uploads", -1)); m_seed_mode = rd.dict_find_int_value("seed_mode", 0) && m_torrent_file->is_valid(); - if (m_seed_mode) m_verified.resize(m_torrent_file->num_pieces(), false); + if (m_seed_mode) + { + m_verified.resize(m_torrent_file->num_pieces(), false); + m_verifying.resize(m_torrent_file->num_pieces(), false); + } super_seeding(rd.dict_find_int_value("super_seeding", 0)); m_last_scrape = rd.dict_find_int_value("last_scrape", 0); @@ -5227,8 +6443,7 @@ namespace libtorrent boost::shared_ptr me(shared_from_this()); // insert this torrent in the uuid index - m_ses.m_uuids.insert(std::make_pair(m_uuid.empty() - ? m_url : m_uuid, me)); + m_ses.insert_uuid_torrent(m_uuid.empty() ? m_url : m_uuid, me); } // TODO: make this more generic to not just work if files have been @@ -5257,8 +6472,24 @@ namespace libtorrent if (file_priority && file_priority->list_size() == m_torrent_file->num_files()) { - for (int i = 0; i < file_priority->list_size(); ++i) + int num_files = m_torrent_file->num_files(); + m_file_priority.resize(num_files); + for (int i = 0; i < num_files; ++i) m_file_priority[i] = file_priority->list_int_value_at(i, 1); + // unallocated slots are assumed to be priority 1, so cut off any + // trailing ones + int end_range = num_files - 1; + for (; end_range >= 0; --end_range) if (m_file_priority[end_range] != 1) break; + m_file_priority.resize(end_range + 1); + + // initialize pad files to priority 0 + file_storage const& fs = m_torrent_file->files(); + for (int i = 0; i < (std::min)(fs.num_files(), end_range + 1); ++i) + { + if (!fs.pad_file_at(i)) continue; + m_file_priority[i] = 0; + } + update_piece_priorities(); } @@ -5268,14 +6499,20 @@ namespace libtorrent { char const* p = piece_priority->string_ptr(); for (int i = 0; i < piece_priority->string_length(); ++i) + { + int prio = p[i]; + if (!has_picker() && prio == 1) continue; + need_picker(); m_picker->set_piece_priority(i, p[i]); - m_policy.recalculate_connect_candidates(); + update_gauge(); + } } if (!m_override_resume_data) { int auto_managed_ = rd.dict_find_int_value("auto_managed", -1); if (auto_managed_ != -1) m_auto_managed = auto_managed_; + update_gauge(); } int sequential_ = rd.dict_find_int_value("sequential_download", -1); @@ -5287,9 +6524,14 @@ namespace libtorrent if (paused_ != -1) { set_allow_peers(!paused_); + m_announce_to_dht = !paused_; m_announce_to_trackers = !paused_; m_announce_to_lsd = !paused_; + + update_gauge(); + update_want_peers(); + update_want_scrape(); } int dht_ = rd.dict_find_int_value("announce_to_dht", -1); if (dht_ != -1) m_announce_to_dht = dht_; @@ -5324,7 +6566,7 @@ namespace libtorrent std::sort(m_trackers.begin(), m_trackers.end(), boost::bind(&announce_entry::tier, _1) < boost::bind(&announce_entry::tier, _2)); - if (settings().prefer_udp_trackers) + if (settings().get_bool(settings_pack::prefer_udp_trackers)) prioritize_udp_trackers(); } @@ -5376,14 +6618,19 @@ namespace libtorrent TORRENT_ASSERT(false); } } + + // updating some of the torrent state may have set need_save_resume_data. + // clear it here since we've just restored the resume data we already + // have. Nothing has changed from that state yet. + m_need_save_resume_data = false; } - boost::intrusive_ptr torrent::get_torrent_copy() + boost::shared_ptr torrent::get_torrent_copy() { - if (!m_torrent_file->is_valid()) return boost::intrusive_ptr(); + if (!m_torrent_file->is_valid()) return boost::shared_ptr(); + if (!need_loaded()) return boost::shared_ptr(); - // copy the torrent_info object - return boost::intrusive_ptr(new torrent_info(*m_torrent_file)); + return m_torrent_file; } void torrent::write_resume_data(entry& ret) const @@ -5449,10 +6696,11 @@ namespace libtorrent } // if this torrent is a seed, we won't have a piece picker - // and there will be no half-finished pieces. + // if we don't have anything, we may also not have a picker + // in either case; there will be no half-finished pieces. if (has_picker()) { - const std::vector& q + std::vector q = m_picker->get_download_queue(); // unfinished pieces @@ -5539,9 +6787,9 @@ namespace libtorrent // bit 1: set if we have verified the piece (in seed mode) entry::string_type& pieces = ret["pieces"].string(); pieces.resize(m_torrent_file->num_pieces()); - if (is_seed()) + if (!has_picker()) { - std::memset(&pieces[0], 1, pieces.size()); + std::memset(&pieces[0], m_have_all, pieces.size()); } else { @@ -5552,6 +6800,7 @@ namespace libtorrent if (m_seed_mode) { TORRENT_ASSERT(m_verified.size() == pieces.size()); + TORRENT_ASSERT(m_verifying.size() == pieces.size()); for (int i = 0, end(pieces.size()); i < end; ++i) pieces[i] |= m_verified[i] ? 2 : 0; } @@ -5581,70 +6830,73 @@ namespace libtorrent #endif // failcount is a 5 bit value - int max_failcount = (std::min)(settings().max_failcount, 31); + int max_failcount = (std::min)(settings().get_int(settings_pack::max_failcount), 31); int num_saved_peers = 0; - for (policy::const_iterator i = m_policy.begin_peer() - , end(m_policy.end_peer()); i != end; ++i) + if (m_policy) { - error_code ec; - policy::peer const* p = *i; - address addr = p->address(); - if (p->banned) + for (policy::const_iterator i = m_policy->begin_peer() + , end(m_policy->end_peer()); i != end; ++i) { + error_code ec; + torrent_peer const* p = *i; + address addr = p->address(); + if (p->banned) + { +#if TORRENT_USE_IPV6 + if (addr.is_v6()) + { + write_address(addr, banned_peers6); + write_uint16(p->port, banned_peers6); + } + else +#endif + { + write_address(addr, banned_peers); + write_uint16(p->port, banned_peers); + } + continue; + } + + // we cannot save remote connection + // since we don't know their listen port + // unless they gave us their listen port + // through the extension handshake + // so, if the peer is not connectable (i.e. we + // don't know its listen port) or if it has + // been banned, don't save it. + if (!p->connectable) continue; + + // don't save peers that don't work + if (int(p->failcount) >= max_failcount) continue; + + // the more peers we've saved, the more picky we get + // about which ones are worth saving + if (num_saved_peers > 10 + && int (p->failcount) > 0 + && int(p->failcount) > (40 - (num_saved_peers - 10)) * max_failcount / 40) + continue; + + // if we have 40 peers, don't save any peers whom + // we've only heard from through the resume data + if (num_saved_peers > 40 && p->source == peer_info::resume_data) + continue; + #if TORRENT_USE_IPV6 if (addr.is_v6()) { - write_address(addr, banned_peers6); - write_uint16(p->port, banned_peers6); + write_address(addr, peers6); + write_uint16(p->port, peers6); } else #endif { - write_address(addr, banned_peers); - write_uint16(p->port, banned_peers); + write_address(addr, peers); + write_uint16(p->port, peers); } - continue; + ++num_saved_peers; } - - // we cannot save remote connection - // since we don't know their listen port - // unless they gave us their listen port - // through the extension handshake - // so, if the peer is not connectable (i.e. we - // don't know its listen port) or if it has - // been banned, don't save it. - if (!p->connectable) continue; - - // don't save peers that don't work - if (int(p->failcount) >= max_failcount) continue; - - // the more peers we've saved, the more picky we get - // about which ones are worth saving - if (num_saved_peers > 10 - && int (p->failcount) > 0 - && int(p->failcount) > (40 - (num_saved_peers - 10)) * max_failcount / 40) - continue; - - // if we have 40 peers, don't save any peers whom - // we've only heard from through the resume data - if (num_saved_peers > 40 && p->source == peer_info::resume_data) - continue; - -#if TORRENT_USE_IPV6 - if (addr.is_v6()) - { - write_address(addr, peers6); - write_uint16(p->port, peers6); - } - else -#endif - { - write_address(addr, peers); - write_uint16(p->port, peers); - } - ++num_saved_peers; } ret["upload_rate_limit"] = upload_limit(); @@ -5658,16 +6910,25 @@ namespace libtorrent ret["auto_managed"] = m_auto_managed; // write piece priorities - entry::string_type& piece_priority = ret["piece_priority"].string(); - piece_priority.resize(m_torrent_file->num_pieces()); - if (is_seed()) + // but only if they are not set to the default + if (has_picker()) { - std::memset(&piece_priority[0], 1, pieces.size()); - } - else - { - for (int i = 0, end(piece_priority.size()); i < end; ++i) - piece_priority[i] = m_picker->piece_priority(i); + bool default_prio = true; + for (int i = 0, end(m_torrent_file->num_pieces()); i < end; ++i) + { + if (m_picker->piece_priority(i) == 1) continue; + default_prio = false; + break; + } + + if (!default_prio) + { + entry::string_type& piece_priority = ret["piece_priority"].string(); + piece_priority.resize(m_torrent_file->num_pieces()); + + for (int i = 0, end(piece_priority.size()); i < end; ++i) + piece_priority[i] = m_picker->piece_priority(i); + } } // write file priorities @@ -5680,9 +6941,11 @@ namespace libtorrent void torrent::get_full_peer_list(std::vector& v) const { v.clear(); - v.reserve(m_policy.num_peers()); - for (policy::const_iterator i = m_policy.begin_peer(); - i != m_policy.end_peer(); ++i) + if (!m_policy) return; + + v.reserve(m_policy->num_peers()); + for (policy::const_iterator i = m_policy->begin_peer(); + i != m_policy->end_peer(); ++i) { peer_list_entry e; e.ip = (*i)->ip(); @@ -5712,22 +6975,23 @@ namespace libtorrent peer->get_peer_info(p); #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES if (resolving_countries()) - resolve_peer_country(intrusive_ptr(peer)); + resolve_peer_country(peer->self()); #endif } } void torrent::get_download_queue(std::vector* queue) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); queue->clear(); - std::vector& blk = m_ses.m_block_info_storage; + std::vector& blk = m_ses.block_info_storage(); blk.clear(); if (!valid_metadata() || !has_picker()) return; piece_picker const& p = picker(); - std::vector const& q + std::vector q = p.get_download_queue(); + if (q.empty()) return; const int blocks_per_piece = m_picker->blocks_in_piece(0); blk.resize(q.size() * blocks_per_piece); @@ -5765,14 +7029,17 @@ namespace libtorrent } else { - policy::peer* p = static_cast(i->info[j].peer); + torrent_peer* p = static_cast(i->info[j].peer); + TORRENT_ASSERT(p->in_use); if (p->connection) { - bi.set_peer(p->connection->remote()); + peer_connection* peer = static_cast(p->connection); + TORRENT_ASSERT(peer->m_in_use); + bi.set_peer(peer->remote()); if (bi.state == block_info::requested) { boost::optional pbp - = p->connection->downloading_piece_progress(); + = peer->downloading_piece_progress(); if (pbp && pbp->piece_index == i->index && pbp->block_index == j) { bi.bytes_progress = pbp->bytes_downloaded; @@ -5803,17 +7070,19 @@ namespace libtorrent } - bool torrent::connect_to_peer(policy::peer* peerinfo, bool ignore_limit) + bool torrent::connect_to_peer(torrent_peer* peerinfo, bool ignore_limit) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); INVARIANT_CHECK; TORRENT_ASSERT(peerinfo); TORRENT_ASSERT(peerinfo->connection == 0); + if (m_abort) return false; + peerinfo->last_connected = m_ses.session_time(); #ifdef TORRENT_DEBUG - if (!settings().allow_multiple_connections_per_ip) + if (!settings().get_bool(settings_pack::allow_multiple_connections_per_ip)) { // this asserts that we don't have duplicates in the policy's peer list peer_iterator i_ = std::find_if(m_connections.begin(), m_connections.end() @@ -5832,25 +7101,26 @@ namespace libtorrent // extend connect timeout by this many seconds int timeout_extend = 0; - TORRENT_ASSERT(want_more_peers() || ignore_limit); - TORRENT_ASSERT(m_ses.num_connections() < m_ses.settings().connections_limit || ignore_limit); + TORRENT_ASSERT(want_peers() || ignore_limit); + TORRENT_ASSERT(m_ses.num_connections() + < m_ses.settings().get_int(settings_pack::connections_limit) || ignore_limit); tcp::endpoint a(peerinfo->ip()); TORRENT_ASSERT(!m_apply_ip_filter - || (m_ses.m_ip_filter.access(peerinfo->address()) & ip_filter::blocked) == 0); + || (m_ses.get_ip_filter().access(peerinfo->address()) & ip_filter::blocked) == 0); - boost::shared_ptr s(new socket_type(m_ses.m_io_service)); + boost::shared_ptr s(new socket_type(m_ses.get_io_service())); #if TORRENT_USE_I2P bool i2p = peerinfo->is_i2p_addr; if (i2p) { - bool ret = instantiate_connection(m_ses.m_io_service, m_ses.i2p_proxy(), *s); + bool ret = instantiate_connection(m_ses.get_io_service(), m_ses.i2p_proxy(), *s); (void)ret; TORRENT_ASSERT(ret); - s->get()->set_destination(static_cast(peerinfo)->destination); + s->get()->set_destination(static_cast(peerinfo)->destination); s->get()->set_command(i2p_stream::cmd_connect); - s->get()->set_session_id(m_ses.m_i2p_conn.session_id()); + s->get()->set_session_id(m_ses.i2p_session()); // i2p setups are slow timeout_extend = 20; } @@ -5858,22 +7128,22 @@ namespace libtorrent #endif { // this is where we determine if we open a regular TCP connection - // or a uTP connection. If the m_utp_socket_manager pointer is not passed in + // or a uTP connection. If the utp_socket_manager pointer is not passed in // we'll instantiate a TCP connection utp_socket_manager* sm = 0; - if (m_ses.m_settings.enable_outgoing_utp - && (!m_ses.m_settings.enable_outgoing_tcp + if (m_ses.settings().get_bool(settings_pack::enable_outgoing_utp) + && (!m_ses.settings().get_bool(settings_pack::enable_outgoing_tcp) || peerinfo->supports_utp || peerinfo->confirmed_supports_utp)) - sm = &m_ses.m_utp_socket_manager; + sm = m_ses.utp_socket_manager(); // don't make a TCP connection if it's disabled - if (sm == 0 && !m_ses.m_settings.enable_outgoing_tcp) return false; + if (sm == 0 && !m_ses.settings().get_bool(settings_pack::enable_outgoing_tcp)) return false; void* userdata = 0; #ifdef TORRENT_USE_OPENSSL - if (is_ssl_torrent() && m_ses.settings().ssl_listen != 0) + if (is_ssl_torrent() && m_ses.settings().get_int(settings_pack::ssl_listen) != 0) { userdata = m_ssl_ctx.get(); // SSL handshakes are slow @@ -5881,7 +7151,7 @@ namespace libtorrent } #endif - bool ret = instantiate_connection(m_ses.m_io_service, m_ses.proxy(), *s, userdata, sm, true); + bool ret = instantiate_connection(m_ses.get_io_service(), m_ses.proxy(), *s, userdata, sm, true); (void)ret; TORRENT_ASSERT(ret); @@ -5909,8 +7179,10 @@ namespace libtorrent m_ses.setup_socket_buffers(*s); - boost::intrusive_ptr c(new bt_peer_connection( - m_ses, s, a, peerinfo, m_ses.get_peer_id(), shared_from_this(), true)); + boost::shared_ptr c = boost::make_shared( + boost::ref(m_ses), m_ses.settings(), boost::ref(m_ses) + , boost::ref(m_ses.disk_thread()) + , s, a, peerinfo, m_ses.get_peer_id(), shared_from_this()); #if TORRENT_USE_ASSERTS c->m_in_constructor = false; @@ -5933,28 +7205,40 @@ namespace libtorrent #endif // add the newly connected peer to this torrent's peer list - m_connections.insert(boost::get_pointer(c)); - m_ses.m_connections.insert(c); - m_policy.set_connection(peerinfo, c.get()); + sorted_insert(m_connections, boost::get_pointer(c)); + m_ses.insert_peer(c); + need_policy(); + m_policy->set_connection(peerinfo, c.get()); + update_want_peers(); + update_want_tick(); c->start(); - int timeout = settings().peer_connect_timeout; + if (c->is_disconnecting()) return false; + + int timeout = settings().get_int(settings_pack::peer_connect_timeout); if (peerinfo) timeout += 3 * peerinfo->failcount; timeout += timeout_extend; TORRENT_TRY { - m_ses.m_half_open.enqueue( - boost::bind(&peer_connection::on_connect, c, _1) - , boost::bind(&peer_connection::on_timeout, c) + c->m_queued_for_connection = true; + m_ses.half_open().enqueue(c.get() , seconds(timeout)); + +#if defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + debug_log("START queue peer [%p] (%d)", c.get(), num_peers()); +#endif } TORRENT_CATCH (std::exception&) { - std::set::iterator i - = m_connections.find(boost::get_pointer(c)); - if (i != m_connections.end()) m_connections.erase(i); - c->disconnect(errors::no_error, 1); + peer_iterator i = sorted_find(m_connections, boost::get_pointer(c)); + if (i != m_connections.end()) + { + m_connections.erase(i); + update_want_peers(); + update_want_tick(); + } + c->disconnect(errors::no_error, peer_connection_interface::op_bittorrent, 1); return false; } @@ -5966,7 +7250,7 @@ namespace libtorrent bool torrent::set_metadata(char const* metadata_buf, int metadata_size) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); INVARIANT_CHECK; if (m_torrent_file->is_valid()) return false; @@ -5990,6 +7274,7 @@ namespace libtorrent int ret = lazy_bdecode(metadata_buf, metadata_buf + metadata_size, metadata, ec); if (ret != 0 || !m_torrent_file->parse_info_section(metadata, ec, 0)) { + update_gauge(); // this means the metadata is correct, since we // verified it against the info-hash, but we // failed to parse it. Pause the torrent @@ -5997,14 +7282,16 @@ namespace libtorrent { alerts().post_alert(metadata_failed_alert(get_handle(), ec)); } - set_error(errors::invalid_swarm_metadata, ""); + set_error(errors::invalid_swarm_metadata, error_file_none); pause(); return false; } - if (m_ses.m_alerts.should_post()) + update_gauge(); + + if (m_ses.alerts().should_post()) { - m_ses.m_alerts.post_alert(metadata_received_alert( + m_ses.alerts().post_alert(metadata_received_alert( get_handle())); } @@ -6020,12 +7307,23 @@ namespace libtorrent // we're a seed, because we have all 0 pieces init(); + m_ses.inc_stats_counter(counters::num_total_pieces_added + , m_torrent_file->num_pieces()); + // disconnect redundant peers - for (std::set::iterator i = m_connections.begin() - , end(m_connections.end()); i != end;) + int idx = 0; + for (peer_iterator i = m_connections.begin(); + i != m_connections.end(); ++idx) { - std::set::iterator p = i++; - (*p)->disconnect_if_redundant(); + if ((*i)->disconnect_if_redundant()) + { + i = m_connections.begin() + idx; + --idx; + } + else + { + ++i; + } } m_need_save_resume_data = true; @@ -6077,14 +7375,14 @@ namespace libtorrent if (ssl_conn == 0) { // don't allow non SSL peers on SSL torrents - p->disconnect(errors::requires_ssl_connection); + p->disconnect(errors::requires_ssl_connection, peer_connection_interface::op_bittorrent); return false; } if (!m_ssl_ctx) { // we don't have a valid cert, don't accept any connection! - p->disconnect(errors::invalid_ssl_cert); + p->disconnect(errors::invalid_ssl_cert, peer_connection_interface::op_ssl_handshake); return false; } @@ -6095,14 +7393,14 @@ namespace libtorrent // connected to one torrent, and the BitTorrent protocol // to a different one. This is probably an attempt to circumvent // access control. Don't allow it. - p->disconnect(errors::invalid_ssl_cert); + p->disconnect(errors::invalid_ssl_cert, peer_connection_interface::op_bittorrent); return false; } } #else // BOOST_VERSION if (is_ssl_torrent()) { - p->disconnect(asio::error::operation_not_supported); + p->disconnect(asio::error::operation_not_supported, peer_connection_interface::op_bittorrent); return false; } #endif @@ -6111,7 +7409,7 @@ namespace libtorrent { // Don't accidentally allow seeding of SSL torrents, just // because libtorrent wasn't built with SSL support - p->disconnect(errors::requires_ssl_connection); + p->disconnect(errors::requires_ssl_connection, peer_connection_interface::op_ssl_handshake); return false; } #endif // TORRENT_USE_OPENSSL @@ -6122,39 +7420,50 @@ namespace libtorrent m_has_incoming = true; if (m_apply_ip_filter - && m_ses.m_ip_filter.access(p->remote().address()) & ip_filter::blocked) + && m_ses.get_ip_filter().access(p->remote().address()) & ip_filter::blocked) { - if (m_ses.m_alerts.should_post()) - m_ses.m_alerts.post_alert(peer_blocked_alert(get_handle() + if (m_ses.alerts().should_post()) + m_ses.alerts().post_alert(peer_blocked_alert(get_handle() , p->remote().address(), peer_blocked_alert::ip_filter)); - p->disconnect(errors::banned_by_ip_filter); + p->disconnect(errors::banned_by_ip_filter, peer_connection_interface::op_bittorrent); return false; } - if ((m_state == torrent_status::queued_for_checking - || m_state == torrent_status::checking_files + if ((m_state == torrent_status::checking_files || m_state == torrent_status::checking_resume_data) && valid_metadata()) { - p->disconnect(errors::torrent_not_ready); + p->disconnect(errors::torrent_not_ready, peer_connection_interface::op_bittorrent); return false; } - if (m_ses.m_connections.find(p) == m_ses.m_connections.end()) + if (!m_ses.has_connection(p)) { - p->disconnect(errors::peer_not_constructed); + p->disconnect(errors::peer_not_constructed, peer_connection_interface::op_bittorrent); return false; } if (m_ses.is_aborted()) { - p->disconnect(errors::session_closing); + p->disconnect(errors::session_closing, peer_connection_interface::op_bittorrent); return false; } + int connection_limit_factor = 0; + for (int i = 0; i < p->num_classes(); ++i) + { + int pc = p->class_at(i); + if (m_ses.peer_classes().at(pc) == NULL) continue; + int f = m_ses.peer_classes().at(pc)->connection_limit_factor; + if (connection_limit_factor < f) connection_limit_factor = f; + } + if (connection_limit_factor == 0) connection_limit_factor = 100; + + boost::uint64_t limit = boost::uint64_t(m_max_connections) * 100 / connection_limit_factor; + bool maybe_replace_peer = false; - if (m_connections.size() >= m_max_connections) + if (m_connections.size() >= limit) { // if more than 10% of the connections are outgoing // connection attempts that haven't completed yet, @@ -6165,19 +7474,18 @@ namespace libtorrent // find one of the connecting peers and disconnect it // find any peer that's connecting (i.e. a half-open TCP connection) // that's also not disconnecting - // disconnect the peer that's been wating to establish a connection // the longest - std::set::iterator i = std::max_element(begin(), end() + std::vector::iterator i = std::max_element(begin(), end() , &connecting_time_compare); if (i == end() || !(*i)->is_connecting() || (*i)->is_disconnecting()) { // this seems odd, but we might as well handle it - p->disconnect(errors::too_many_connections); + p->disconnect(errors::too_many_connections, peer_connection_interface::op_bittorrent); return false; } - (*i)->disconnect(errors::too_many_connections); + (*i)->disconnect(errors::too_many_connections, peer_connection_interface::op_bittorrent); // if this peer was let in via connections slack, // it has done its duty of causing the disconnection @@ -6200,15 +7508,20 @@ namespace libtorrent if (pp) p->add_extension(pp); } #endif - if (!m_policy.new_connection(*p, m_ses.session_time())) + torrent_state st = get_policy_state(); + need_policy(); + if (!m_policy->new_connection(*p, m_ses.session_time(), &st)) { + peers_erased(st.erased); #if defined TORRENT_LOGGING debug_log("CLOSING CONNECTION \"%s\" peer list full" , print_endpoint(p->remote()).c_str()); #endif - p->disconnect(errors::too_many_connections); + p->disconnect(errors::too_many_connections, peer_connection_interface::op_bittorrent); return false; } + peers_erased(st.erased); + update_want_peers(); } TORRENT_CATCH (std::exception& e) { @@ -6218,11 +7531,18 @@ namespace libtorrent debug_log("CLOSING CONNECTION \"%s\" caught exception: %s" , print_endpoint(p->remote()).c_str(), e.what()); #endif - p->disconnect(errors::no_error); + p->disconnect(errors::no_error, peer_connection_interface::op_bittorrent); return false; } - TORRENT_ASSERT(m_connections.find(p) == m_connections.end()); - m_connections.insert(p); + TORRENT_ASSERT(sorted_find(m_connections, p) == m_connections.end()); + sorted_insert(m_connections, p); + update_want_peers(); + update_want_tick(); + +#if defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + debug_log("incoming peer (%d)", int(m_connections.size())); +#endif + #ifdef TORRENT_DEBUG error_code ec; TORRENT_ASSERT(p->remote() == p->get_socket()->remote_endpoint(ec) || ec); @@ -6242,12 +7562,12 @@ namespace libtorrent // TODO: 3 if peer is a really good peer, maybe we shouldn't disconnect it if (peer && peer->peer_rank() < p->peer_rank()) { - peer->disconnect(errors::too_many_connections); + peer->disconnect(errors::too_many_connections, peer_connection_interface::op_bittorrent); p->peer_disconnected_other(); } else { - p->disconnect(errors::too_many_connections); + p->disconnect(errors::too_many_connections, peer_connection_interface::op_bittorrent); // we have to do this here because from the peer's point of // it wasn't really attached to the torrent, but we do need // to let policy know we're removing it @@ -6257,7 +7577,7 @@ namespace libtorrent } #if TORRENT_USE_INVARIANT_CHECKS - m_policy.check_invariant(); + if (m_policy) m_policy->check_invariant(); #endif if (m_share_mode) @@ -6266,22 +7586,89 @@ namespace libtorrent return true; } - bool torrent::want_more_peers() const + bool torrent::want_tick() const + { + if (m_abort) return false; + + if (!m_connections.empty()) return true; + + // there's a deferred storage tick waiting + // to happen + if (m_storage_tick) return true; + + // we might want to connect web seeds + if (!is_finished() && !m_web_seeds.empty() && m_files_checked) + return true; + + if (m_stat.low_pass_upload_rate() > 0 || m_stat.low_pass_download_rate() > 0) + return true; + + return false; + } + + void torrent::update_want_tick() + { + update_list(aux::session_interface::torrent_want_tick, want_tick()); + } + + bool torrent::want_peers() const { return m_connections.size() < m_max_connections && !is_paused() && ((m_state != torrent_status::checking_files - && m_state != torrent_status::checking_resume_data - && m_state != torrent_status::queued_for_checking) + && m_state != torrent_status::checking_resume_data) || !valid_metadata()) - && m_policy.num_connect_candidates() > 0 + && m_policy + && m_policy->num_connect_candidates() > 0 && !m_abort - && (m_ses.settings().seeding_outgoing_connections + && (m_ses.settings().get_bool(settings_pack::seeding_outgoing_connections) || (m_state != torrent_status::seeding && m_state != torrent_status::finished)); } - void torrent::disconnect_all(error_code const& ec) + bool torrent::want_peers_download() const + { + return (m_state == torrent_status::downloading + || m_state == torrent_status::downloading_metadata) + && want_peers(); + } + + bool torrent::want_peers_finished() const + { + return (m_state == torrent_status::finished + || m_state == torrent_status::seeding) + && want_peers(); + } + + void torrent::update_want_peers() + { + update_list(aux::session_interface::torrent_want_peers_download, want_peers_download()); + update_list(aux::session_interface::torrent_want_peers_finished, want_peers_finished()); + } + + void torrent::update_want_scrape() + { + update_list(aux::session_interface::torrent_want_scrape, !m_allow_peers && m_auto_managed && !m_abort); + } + + void torrent::update_list(int list, bool in) + { + link& l = m_links[list]; + std::vector& v = m_ses.torrent_list(list); + if (in) + { + if (l.in_list()) return; + l.insert(v, this); + } + else + { + if (!l.in_list()) return; + l.unlink(v, list); + } + + } + + void torrent::disconnect_all(error_code const& ec, peer_connection_interface::operation_t op) { // doesn't work with the !m_allow_peers -> m_num_peers == 0 condition // INVARIANT_CHECK; @@ -6300,9 +7687,12 @@ namespace libtorrent if (p->is_disconnecting()) m_connections.erase(m_connections.begin()); else - p->disconnect(ec); + p->disconnect(ec, (peer_connection::operation_t)op); TORRENT_ASSERT(m_connections.size() <= size); } + + update_want_peers(); + update_want_tick(); } // this returns true if lhs is a better disconnect candidate than rhs @@ -6349,7 +7739,7 @@ namespace libtorrent INVARIANT_CHECK; #ifdef TORRENT_DEBUG - for (std::set::iterator i = m_connections.begin() + for (peer_iterator i = m_connections.begin() , end(m_connections.end()); i != end; ++i) { // make sure this peer is not a dangling pointer @@ -6359,7 +7749,7 @@ namespace libtorrent int ret = 0; while (ret < num && !m_connections.empty()) { - std::set::iterator i = std::min_element( + peer_iterator i = std::min_element( m_connections.begin(), m_connections.end(), compare_disconnect_peer); peer_connection* p = *i; @@ -6368,18 +7758,13 @@ namespace libtorrent #if TORRENT_USE_ASSERTS int num_conns = m_connections.size(); #endif - p->disconnect(ec); + p->disconnect(ec, peer_connection_interface::op_bittorrent); TORRENT_ASSERT(int(m_connections.size()) == num_conns - 1); } return ret; } - int torrent::bandwidth_throttle(int channel) const - { - return m_bandwidth_channel[channel].throttle(); - } - // called when torrent is finished (all interesting // pieces have been downloaded) void torrent::finished() @@ -6387,7 +7772,6 @@ namespace libtorrent INVARIANT_CHECK; TORRENT_ASSERT(is_finished()); - TORRENT_ASSERT(m_state != torrent_status::finished && m_state != torrent_status::seeding); set_state(torrent_status::finished); set_queue_position(-1); @@ -6405,7 +7789,7 @@ namespace libtorrent m_completed_time = time(0); // disconnect all seeds - if (settings().close_redundant_connections) + if (settings().get_bool(settings_pack::close_redundant_connections)) { // TODO: 1 should disconnect all peers that have the pieces we have // not just seeds. It would be pretty expensive to check all pieces @@ -6425,17 +7809,20 @@ namespace libtorrent } } std::for_each(seeds.begin(), seeds.end() - , boost::bind(&peer_connection::disconnect, _1, errors::torrent_finished, 0)); + , boost::bind(&peer_connection::disconnect, _1, errors::torrent_finished + , peer_connection_interface::op_bittorrent, 0)); } if (m_abort) return; - m_policy.recalculate_connect_candidates(); + update_want_peers(); TORRENT_ASSERT(m_storage); + // we need to keep the object alive during this operation - m_storage->async_release_files( - boost::bind(&torrent::on_files_released, shared_from_this(), _1, _2)); + inc_refcount("release_files"); + m_ses.disk_thread().async_release_files(m_storage.get() + , boost::bind(&torrent::on_cache_flushed, shared_from_this(), _1)); // this torrent just completed downloads, which means it will fall // under a different limit with the auto-manager. Make sure we @@ -6448,7 +7835,9 @@ namespace libtorrent // marked for downloading, and we are no longer finished void torrent::resume_download() { - INVARIANT_CHECK; + // the invariant doesn't hold here, because it expects the torrent + // to be in downloading state (which it will be set to shortly) +// INVARIANT_CHECK; if (m_state == torrent_status::checking_resume_data || m_state == torrent_status::checking_files @@ -6464,7 +7853,6 @@ namespace libtorrent TORRENT_ASSERT(!is_finished()); set_state(torrent_status::downloading); set_queue_position((std::numeric_limits::max)()); - m_policy.recalculate_connect_candidates(); m_completed_time = 0; @@ -6472,14 +7860,35 @@ namespace libtorrent debug_log("*** RESUME_DOWNLOAD"); #endif send_upload_only(); + update_want_tick(); } - // called when torrent is complete (all pieces downloaded) + void torrent::maybe_done_flushing() + { + if (!has_picker()) return; + + // when we're suggesting read cache pieces, we + // still need the piece picker, to keep track + // of availability counts for pieces + if (m_picker->is_seeding() + && settings().get_int(settings_pack::suggest_mode) != settings_pack::suggest_read_cache) + { + // no need for the piece picker anymore + m_picker.reset(); + m_have_all = true; + update_gauge(); + } + } + + // called when torrent is complete. i.e. all pieces downloaded + // not necessarily flushed to disk void torrent::completed() { - m_picker.reset(); + maybe_done_flushing(); set_state(torrent_status::seeding); + // no need for this anymore + std::vector().swap(m_file_progress); if (!m_announcing) return; ptime now = time_now(); @@ -6536,7 +7945,7 @@ namespace libtorrent void torrent::files_checked() { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); TORRENT_ASSERT(m_torrent_file->is_valid()); if (m_abort) @@ -6556,9 +7965,9 @@ namespace libtorrent INVARIANT_CHECK; - if (m_ses.m_alerts.should_post()) + if (m_ses.alerts().should_post()) { - m_ses.m_alerts.post_alert(torrent_checked_alert( + m_ses.alerts().post_alert(torrent_checked_alert( get_handle())); } @@ -6610,6 +8019,8 @@ namespace libtorrent m_connections_initialized = true; m_files_checked = true; + update_want_tick(); + for (torrent::peer_iterator i = m_connections.begin(); i != m_connections.end();) { @@ -6631,8 +8042,11 @@ namespace libtorrent #endif if (pc->is_interesting() && !pc->has_peer_choked()) { - request_a_block(*this, *pc); - pc->send_block_requests(); + if (request_a_block(*this, *pc)) + { + m_ses.inc_stats_counter(counters::unchoke_piece_picks); + pc->send_block_requests(); + } } } @@ -6644,8 +8058,8 @@ namespace libtorrent alert_manager& torrent::alerts() const { - TORRENT_ASSERT(m_ses.is_network_thread()); - return m_ses.m_alerts; + TORRENT_ASSERT(m_ses.is_single_thread()); + return m_ses.alerts(); } std::string torrent::save_path() const @@ -6660,35 +8074,39 @@ namespace libtorrent TORRENT_ASSERT(index >= 0); TORRENT_ASSERT(index < m_torrent_file->num_files()); - if (!m_owning_storage.get()) return false; + // stoage may be NULL during shutdown + if (!m_storage.get()) return false; - m_owning_storage->async_rename_file(index, name - , boost::bind(&torrent::on_file_renamed, shared_from_this(), _1, _2)); + inc_refcount("rename_file"); + m_ses.disk_thread().async_rename_file(m_storage.get(), index, name + , boost::bind(&torrent::on_file_renamed, shared_from_this(), _1)); return true; } void torrent::move_storage(std::string const& save_path, int flags) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); INVARIANT_CHECK; if (m_abort) { if (alerts().should_post()) - alerts().post_alert(storage_moved_failed_alert(get_handle(), boost::asio::error::operation_aborted)); + alerts().post_alert(storage_moved_failed_alert(get_handle(), boost::asio::error::operation_aborted + , "", "")); return; } // storage may be NULL during shutdown - if (m_owning_storage.get()) + if (m_storage.get()) { #if TORRENT_USE_UNC_PATHS std::string path = canonicalize_path(save_path); #else std::string const& path = save_path; #endif - m_owning_storage->async_move_storage(path, flags - , boost::bind(&torrent::on_storage_moved, shared_from_this(), _1, _2)); + inc_refcount("move_storage"); + m_ses.disk_thread().async_move_storage(m_storage.get(), path, flags + , boost::bind(&torrent::on_storage_moved, shared_from_this(), _1)); m_moving_storage = true; } else @@ -6708,101 +8126,105 @@ namespace libtorrent } } - void torrent::on_storage_moved(int ret, disk_io_job const& j) + void torrent::on_storage_moved(disk_io_job const* j) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); m_moving_storage = false; - - if (ret == piece_manager::no_error || ret == piece_manager::need_full_check) + dec_refcount("move_storage"); + if (j->ret == piece_manager::no_error || j->ret == piece_manager::need_full_check) { if (alerts().should_post()) - alerts().post_alert(storage_moved_alert(get_handle(), j.str)); - m_save_path = j.str; + alerts().post_alert(storage_moved_alert(get_handle(), j->buffer)); + m_save_path = j->buffer; m_need_save_resume_data = true; - if (ret == piece_manager::need_full_check) + if (j->ret == piece_manager::need_full_check) force_recheck(); } else { if (alerts().should_post()) - alerts().post_alert(storage_moved_failed_alert(get_handle(), j.error)); + alerts().post_alert(storage_moved_failed_alert(get_handle(), j->error.ec + , resolve_filename(j->error.file), j->error.operation_str())); } } - piece_manager& torrent::filesystem() + piece_manager& torrent::storage() { - TORRENT_ASSERT(m_owning_storage.get()); - TORRENT_ASSERT(m_storage); + TORRENT_ASSERT(m_storage.get()); return *m_storage; } torrent_handle torrent::get_handle() { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); return torrent_handle(shared_from_this()); } - session_settings const& torrent::settings() const + aux::session_settings const& torrent::settings() const { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); return m_ses.settings(); } #if TORRENT_USE_INVARIANT_CHECKS void torrent::check_invariant() const { - for (std::deque::const_iterator i = m_time_critical_pieces.begin() + TORRENT_ASSERT(current_stats_state() == m_current_gauge_state + counters::num_checking_torrents + || m_current_gauge_state == no_gauge_state); + + for (std::vector::const_iterator i = m_time_critical_pieces.begin() , end(m_time_critical_pieces.end()); i != end; ++i) { TORRENT_ASSERT(!is_seed()); TORRENT_ASSERT(!has_picker() || !m_picker->have_piece(i->piece)); } - TORRENT_ASSERT(m_ses.is_network_thread()); - if (is_paused()) TORRENT_ASSERT(num_peers() == 0 || m_graceful_pause_mode); - - if (!should_check_files()) - TORRENT_ASSERT(m_state != torrent_status::checking_files); - else - TORRENT_ASSERT(m_queued_for_checking); - -#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS - if (!m_ses.m_queued_for_checking.empty()) + switch (current_stats_state()) { - // if there are torrents waiting to be checked - // assert that there's a torrent that is being - // processed right now - int found = 0; - int found_active = 0; - for (aux::session_impl::torrent_map::iterator i = m_ses.m_torrents.begin() - , end(m_ses.m_torrents.end()); i != end; ++i) - if (i->second->m_state == torrent_status::checking_files) - { - ++found; - if (i->second->should_check_files()) ++found_active; - } + case counters::num_error_torrents: TORRENT_ASSERT(has_error()); break; + case counters::num_checking_torrents: +#ifdef TORRENT_NO_DEPRECATE + TORRENT_ASSERT(state() == torrent_status::checking_files); +#else + TORRENT_ASSERT(state() == torrent_status::checking_files + || state() == torrent_status::queued_for_checking); +#endif + break; + case counters::num_seeding_torrents: TORRENT_ASSERT(is_seed()); break; + case counters::num_upload_only_torrents: TORRENT_ASSERT(is_upload_only()); break; + case counters::num_stopped_torrents: TORRENT_ASSERT(!is_auto_managed() + && (!m_allow_peers || m_graceful_pause_mode)); + break; + case counters::num_queued_seeding_torrents: + TORRENT_ASSERT((!m_allow_peers || m_graceful_pause_mode) && is_seed()); break; + } - // if the session is paused, there might still be some torrents - // in the checking_files state that haven't been dequeued yet - if (m_ses.is_paused()) - { - TORRENT_ASSERT(found_active == 0); - } - else - { - // the case of 2 is in the special case where one switches over from - // checking to complete. - TORRENT_ASSERT(found_active >= 1); - TORRENT_ASSERT(found_active <= 2); - TORRENT_ASSERT(found >= 1); - } +#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS + for (int i = 0; i < aux::session_interface::num_torrent_lists; ++i) + { + if (!m_links[i].in_list()) continue; + int index = m_links[i].index; + + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_ses.torrent_list(i).size())); } #endif - TORRENT_ASSERT(m_resume_entry.type() == lazy_entry::dict_t - || m_resume_entry.type() == lazy_entry::none_t); + if (!is_loaded()) return; + + TORRENT_ASSERT(want_peers_download() == m_links[aux::session_interface::torrent_want_peers_download].in_list()); + TORRENT_ASSERT(want_peers_finished() == m_links[aux::session_interface::torrent_want_peers_finished].in_list()); + TORRENT_ASSERT(want_tick() == m_links[aux::session_interface::torrent_want_tick].in_list()); + TORRENT_ASSERT((!m_allow_peers && m_auto_managed) == m_links[aux::session_interface::torrent_want_scrape].in_list()); + + TORRENT_ASSERT(m_ses.is_single_thread()); + // this fires during disconnecting peers +// if (is_paused()) TORRENT_ASSERT(num_peers() == 0 || m_graceful_pause_mode); + + TORRENT_ASSERT(!m_resume_data || m_resume_data->entry.type() == lazy_entry::dict_t + || m_resume_data->entry.type() == lazy_entry::none_t); int num_uploads = 0; std::map num_requests; @@ -6815,7 +8237,7 @@ namespace libtorrent peer_connection const& p = *(*i); for (std::vector::const_iterator i = p.request_queue().begin() , end(p.request_queue().end()); i != end; ++i) - ++num_requests[i->block]; + if (!i->not_wanted && !i->timed_out) ++num_requests[i->block]; for (std::vector::const_iterator i = p.download_queue().begin() , end(p.download_queue().end()); i != end; ++i) if (!i->not_wanted && !i->timed_out) ++num_requests[i->block]; @@ -6840,7 +8262,34 @@ namespace libtorrent // returns 0 requests, regardless of how many peers may still // have the block in their queue if (!m_picker->is_downloaded(b) && m_picker->is_downloading(b.piece_index)) - TORRENT_ASSERT(picker_count == count); + { + if (picker_count != count) + { + fprintf(stderr, "picker count discrepancy: " + "picker: %d != peerlist: %d\n", picker_count, count); + + for (const_peer_iterator i = this->begin(); i != this->end(); ++i) + { + peer_connection const& p = *(*i); + fprintf(stderr, "peer: %s\n", print_endpoint(p.remote()).c_str()); + for (std::vector::const_iterator i = p.request_queue().begin() + , end(p.request_queue().end()); i != end; ++i) + { + fprintf(stderr, " rq: (%d, %d) %s %s %s\n", i->block.piece_index + , i->block.block_index, i->not_wanted ? "not-wanted" : "" + , i->timed_out ? "timed-out" : "", i->busy ? "busy": ""); + } + for (std::vector::const_iterator i = p.download_queue().begin() + , end(p.download_queue().end()); i != end; ++i) + { + fprintf(stderr, " dq: (%d, %d) %s %s %s\n", i->block.piece_index + , i->block.block_index, i->not_wanted ? "not-wanted" : "" + , i->timed_out ? "timed-out" : "", i->busy ? "busy": ""); + } + } + TORRENT_ASSERT(false); + } + } } TORRENT_ASSERT(num_have() >= m_picker->num_have_filtered()); } @@ -6857,12 +8306,12 @@ namespace libtorrent #ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS // make sure we haven't modified the peer object // in a way that breaks the sort order - if (m_policy.begin_peer() != m_policy.end_peer()) + if (m_policy && m_policy->begin_peer() != m_policy->end_peer()) { - policy::const_iterator i = m_policy.begin_peer(); + policy::const_iterator i = m_policy->begin_peer(); policy::const_iterator prev = i++; - policy::const_iterator end(m_policy.end_peer()); - policy::peer_address_compare cmp; + policy::const_iterator end(m_policy->end_peer()); + peer_address_compare cmp; for (; i != end; ++i, ++prev) { TORRENT_ASSERT(!cmp(*i, *prev)); @@ -6890,7 +8339,7 @@ namespace libtorrent // make sure that pieces that have completed the download // of all their blocks are in the disk io thread's queue // to be checked. - const std::vector& dl_queue + std::vector dl_queue = m_picker->get_download_queue(); for (std::vector::const_iterator i = dl_queue.begin(); i != dl_queue.end(); ++i) @@ -6914,19 +8363,21 @@ namespace libtorrent TORRENT_ASSERT(block_size() > 0); } - - for (std::vector::const_iterator i = m_file_progress.begin() - , end(m_file_progress.end()); i != end; ++i) + if (!m_file_progress.empty()) { - int index = i - m_file_progress.begin(); - TORRENT_ASSERT(*i <= m_torrent_file->files().file_size(index)); + for (std::vector::const_iterator i = m_file_progress.begin() + , end(m_file_progress.end()); i != end; ++i) + { + int index = i - m_file_progress.begin(); + TORRENT_ASSERT(*i <= m_torrent_file->files().file_size(index)); + } } } #endif void torrent::set_sequential_download(bool sd) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); if (m_sequential_download == sd) return; m_sequential_download = sd; @@ -6948,109 +8399,40 @@ namespace libtorrent void torrent::set_queue_position(int p) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); TORRENT_ASSERT((p == -1) == is_finished() || (!m_auto_managed && p == -1) || (m_abort && p == -1)); if (is_finished() && p != -1) return; if (p == m_sequence_number) return; + TORRENT_ASSERT(p >= -1); + state_updated(); - session_impl::torrent_map& torrents = m_ses.m_torrents; - if (p >= 0 && m_sequence_number == -1) - { - int max_seq = -1; - for (session_impl::torrent_map::iterator i = torrents.begin() - , end(torrents.end()); i != end; ++i) - { - torrent* t = i->second.get(); - if (t->m_sequence_number > max_seq) max_seq = t->m_sequence_number; - if (t->m_sequence_number >= p) - { - ++t->m_sequence_number; - t->state_updated(); - } - } - m_sequence_number = (std::min)(max_seq + 1, p); - } - else if (p < 0) - { - for (session_impl::torrent_map::iterator i = torrents.begin() - , end(torrents.end()); i != end; ++i) - { - torrent* t = i->second.get(); - if (t == this) continue; - if (t->m_sequence_number >= m_sequence_number - && t->m_sequence_number != -1) - { - --t->m_sequence_number; - t->state_updated(); - } - } - m_sequence_number = p; - } - else if (p < m_sequence_number) - { - for (session_impl::torrent_map::iterator i = torrents.begin() - , end(torrents.end()); i != end; ++i) - { - torrent* t = i->second.get(); - if (t == this) continue; - if (t->m_sequence_number >= p - && t->m_sequence_number < m_sequence_number - && t->m_sequence_number != -1) - { - ++t->m_sequence_number; - t->state_updated(); - } - } - m_sequence_number = p; - } - else if (p > m_sequence_number) - { - int max_seq = 0; - for (session_impl::torrent_map::iterator i = torrents.begin() - , end(torrents.end()); i != end; ++i) - { - torrent* t = i->second.get(); - int pos = t->m_sequence_number; - if (pos > max_seq) max_seq = pos; - if (t == this) continue; - - if (pos <= p - && pos > m_sequence_number - && pos != -1) - { - --t->m_sequence_number; - t->state_updated(); - } - - } - m_sequence_number = (std::min)(max_seq, p); - } - - m_ses.m_auto_manage_time_scaler = 2; + m_ses.set_queue_position(this, p); } void torrent::set_max_uploads(int limit, bool state_update) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); TORRENT_ASSERT(limit >= -1); if (limit <= 0) limit = (1<<24)-1; if (m_max_uploads != limit && state_update) state_updated(); m_max_uploads = limit; - m_need_save_resume_data = true; + if (state_update) + m_need_save_resume_data = true; } void torrent::set_max_connections(int limit, bool state_update) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); TORRENT_ASSERT(limit >= -1); if (limit <= 0) limit = (1<<24)-1; if (m_max_connections != limit && state_update) state_updated(); m_max_connections = limit; + update_want_peers(); if (num_peers() > int(m_max_connections)) { @@ -7058,106 +8440,85 @@ namespace libtorrent , error_code(errors::too_many_connections, get_libtorrent_category())); } + if (state_update) + m_need_save_resume_data = true; + } + + void torrent::set_upload_limit(int limit) + { + set_limit_impl(limit, peer_connection::upload_channel); m_need_save_resume_data = true; } - int torrent::get_peer_upload_limit(tcp::endpoint ip) const + void torrent::set_download_limit(int limit) { - TORRENT_ASSERT(m_ses.is_network_thread()); - const_peer_iterator i = std::find_if(m_connections.begin(), m_connections.end() - , boost::bind(&peer_connection::remote, _1) == ip); - if (i == m_connections.end()) return -1; - return (*i)->get_upload_limit(); + set_limit_impl(limit, peer_connection::download_channel); + m_need_save_resume_data = true; } - int torrent::get_peer_download_limit(tcp::endpoint ip) const + void torrent::set_limit_impl(int limit, int channel, bool state_update) { - TORRENT_ASSERT(m_ses.is_network_thread()); - const_peer_iterator i = std::find_if(m_connections.begin(), m_connections.end() - , boost::bind(&peer_connection::remote, _1) == ip); - if (i == m_connections.end()) return -1; - return (*i)->get_download_limit(); - } - - void torrent::set_peer_upload_limit(tcp::endpoint ip, int limit) - { - TORRENT_ASSERT(m_ses.is_network_thread()); - TORRENT_ASSERT(limit >= -1); - peer_iterator i = std::find_if(m_connections.begin(), m_connections.end() - , boost::bind(&peer_connection::remote, _1) == ip); - if (i == m_connections.end()) return; - (*i)->set_upload_limit(limit); - } - - void torrent::set_peer_download_limit(tcp::endpoint ip, int limit) - { - TORRENT_ASSERT(m_ses.is_network_thread()); - TORRENT_ASSERT(limit >= -1); - peer_iterator i = std::find_if(m_connections.begin(), m_connections.end() - , boost::bind(&peer_connection::remote, _1) == ip); - if (i == m_connections.end()) return; - (*i)->set_download_limit(limit); - } - - void torrent::set_upload_limit(int limit, bool state_update) - { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); TORRENT_ASSERT(limit >= -1); if (limit <= 0) limit = 0; - if (m_bandwidth_channel[peer_connection::upload_channel].throttle() != limit - && state_update) - state_updated(); - m_bandwidth_channel[peer_connection::upload_channel].throttle(limit); - m_need_save_resume_data = true; + if (m_peer_class == 0 && limit == 0) return; + + if (m_peer_class == 0) + setup_peer_class(); + + struct peer_class* tpc = m_ses.peer_classes().at(m_peer_class); + TORRENT_ASSERT(tpc); + if (tpc->channel[channel].throttle() != limit && state_update) + state_updated(); + tpc->channel[channel].throttle(limit); + } + + void torrent::setup_peer_class() + { + TORRENT_ASSERT(m_peer_class == 0); + m_peer_class = m_ses.peer_classes().new_peer_class(name()); + add_class(m_ses.peer_classes(), m_peer_class); + } + + int torrent::limit_impl(int channel) const + { + TORRENT_ASSERT(m_ses.is_single_thread()); + + if (m_peer_class == 0) return -1; + int limit = m_ses.peer_classes().at(m_peer_class)->channel[channel].throttle(); + if (limit == (std::numeric_limits::max)()) limit = -1; + return limit; } int torrent::upload_limit() const { - TORRENT_ASSERT(m_ses.is_network_thread()); - int limit = m_bandwidth_channel[peer_connection::upload_channel].throttle(); - if (limit == (std::numeric_limits::max)()) limit = -1; - return limit; - } - - void torrent::set_download_limit(int limit, bool state_update) - { - TORRENT_ASSERT(m_ses.is_network_thread()); - TORRENT_ASSERT(limit >= -1); - if (limit <= 0) limit = 0; - if (m_bandwidth_channel[peer_connection::download_channel].throttle() != limit - && state_update) - state_updated(); - m_bandwidth_channel[peer_connection::download_channel].throttle(limit); - - m_need_save_resume_data = true; + return limit_impl(peer_connection::upload_channel); } int torrent::download_limit() const { - TORRENT_ASSERT(m_ses.is_network_thread()); - int limit = m_bandwidth_channel[peer_connection::download_channel].throttle(); - if (limit == (std::numeric_limits::max)()) limit = -1; - return limit; + return limit_impl(peer_connection::download_channel); } bool torrent::delete_files() { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING || defined TORRENT_LOGGING log_to_all_peers("DELETING FILES IN TORRENT"); #endif - disconnect_all(errors::torrent_removed); + disconnect_all(errors::torrent_removed, peer_connection_interface::op_bittorrent); stop_announcing(); // storage may be NULL during shutdown - if (m_owning_storage.get()) + if (m_storage.get()) { TORRENT_ASSERT(m_storage); - m_storage->async_delete_files( - boost::bind(&torrent::on_files_deleted, shared_from_this(), _1, _2)); + inc_refcount("delete_files"); + m_ses.disk_thread().async_delete_files(m_storage.get() + , boost::bind(&torrent::on_files_deleted, shared_from_this(), _1)); m_deleted = true; return true; } @@ -7166,13 +8527,14 @@ namespace libtorrent void torrent::clear_error() { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); if (!m_error) return; bool checking_files = should_check_files(); m_ses.trigger_auto_manage(); m_error = error_code(); - m_error_file.clear(); + m_error_file = error_file_none; + update_gauge(); state_updated(); // if we haven't downloaded the metadata from m_url, try again @@ -7182,49 +8544,62 @@ namespace libtorrent return; } // if the error happened during initialization, try again now - if (!m_storage) init(); + if (!m_connections_initialized && valid_metadata()) init(); if (!checking_files && should_check_files()) - queue_torrent_check(); + start_checking(); + } + std::string torrent::resolve_filename(int file) const + { + if (file == error_file_none) return ""; + if (file == error_file_url) return m_url; + if (file == error_file_ssl_ctx) return "SSL Context"; + if (file == error_file_metadata) return "metadata (from user load function)"; + + if (m_storage && file >= 0) + { + file_storage const& st = m_torrent_file->files(); + return combine_path(m_save_path, st.file_path(file)); + } + else + { + return m_save_path; + } } - void torrent::set_error(error_code const& ec, std::string const& error_file) + void torrent::set_error(error_code const& ec, int error_file) { - TORRENT_ASSERT(m_ses.is_network_thread()); - bool checking_files = should_check_files(); + TORRENT_ASSERT(m_ses.is_single_thread()); m_error = ec; m_error_file = error_file; + update_gauge(); + if (alerts().should_post()) - alerts().post_alert(torrent_error_alert(get_handle(), ec)); + alerts().post_alert(torrent_error_alert(get_handle(), ec, resolve_filename(error_file))); #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING || defined TORRENT_LOGGING if (ec) { char buf[1024]; - snprintf(buf, sizeof(buf), "TORRENT ERROR: %s: %s", ec.message().c_str(), error_file.c_str()); + snprintf(buf, sizeof(buf), "TORRENT ERROR: %s: %s", ec.message().c_str() + , resolve_filename(error_file).c_str()); log_to_all_peers(buf); } #endif - if (checking_files && !should_check_files()) - { - // stop checking - m_storage->abort_disk_io(); - dequeue_torrent_check(); - set_state(torrent_status::queued_for_checking); - } - state_updated(); } void torrent::auto_managed(bool a) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); INVARIANT_CHECK; if (m_auto_managed == a) return; bool checking_files = should_check_files(); m_auto_managed = a; + update_gauge(); + update_want_scrape(); state_updated(); @@ -7237,26 +8612,41 @@ namespace libtorrent if (!checking_files && should_check_files()) { - queue_torrent_check(); + start_checking(); } - else if (checking_files && !should_check_files()) + } + + void torrent::step_session_time(int seconds) + { + if (m_policy) { - // stop checking - m_storage->abort_disk_io(); - dequeue_torrent_check(); - set_state(torrent_status::queued_for_checking); + for (policy::iterator j = m_policy->begin_peer() + , end(m_policy->end_peer()); j != end; ++j) + { + torrent_peer* pe = *j; + + if (pe->last_optimistically_unchoked < seconds) + pe->last_optimistically_unchoked = 0; + else + pe->last_optimistically_unchoked -= seconds; + + if (pe->last_connected < seconds) + pe->last_connected = 0; + else + pe->last_connected -= seconds; + } } - // if this torrent is running and just became auto-managed - // we might want to pause it in favor of some other torrent - if (m_auto_managed && !is_paused()) - m_ses.m_auto_manage_time_scaler = 2; + if (m_started < seconds) m_started = 0; + else m_started -= seconds; + if (m_last_saved_resume < seconds) m_last_saved_resume = 0; + else m_last_saved_resume -= seconds; } // the higher seed rank, the more important to seed - int torrent::seed_rank(session_settings const& s) const + int torrent::seed_rank(aux::session_settings const& s) const { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); enum flags { seed_ratio_not_met = 0x40000000, @@ -7272,24 +8662,23 @@ namespace libtorrent int ret = 0; - ptime now = time_now(); - - int finished_time = m_finished_time; - int download_time = int(m_active_time) - finished_time; + size_type finished_time = m_finished_time; + size_type download_time = int(m_active_time) - finished_time; // if we haven't yet met the seed limits, set the seed_ratio_not_met // flag. That will make this seed prioritized // downloaded may be 0 if the torrent is 0-sized size_type downloaded = (std::max)(m_total_downloaded, m_torrent_file->total_size()); - if (finished_time < s.seed_time_limit - && (download_time > 1 && finished_time / download_time < s.seed_time_ratio_limit) + if (finished_time < s.get_int(settings_pack::seed_time_limit) + && (download_time > 1 + && finished_time * 100 / download_time < s.get_int(settings_pack::seed_time_ratio_limit)) && downloaded > 0 - && m_total_uploaded / downloaded < s.share_ratio_limit) + && m_total_uploaded * 100 / downloaded < s.get_int(settings_pack::share_ratio_limit)) ret |= seed_ratio_not_met; // if this torrent is running, and it was started less // than 30 minutes ago, give it priority, to avoid oscillation - if (!is_paused() && now - m_started < minutes(30)) + if (!is_paused() && (m_ses.session_time() - m_started) < 30 * 60) ret |= recently_started; // if we have any scrape data, use it to calculate @@ -7298,10 +8687,10 @@ namespace libtorrent int downloaders = 0; if (m_complete != 0xffffff) seeds = m_complete; - else seeds = m_policy.num_seeds(); + else seeds = m_policy ? m_policy->num_seeds() : 0; if (m_incomplete != 0xffffff) downloaders = m_incomplete; - else downloaders = m_policy.num_peers() - m_policy.num_seeds(); + else downloaders = m_policy ? m_policy->num_peers() - m_policy->num_seeds() : 0; if (seeds == 0) { @@ -7317,9 +8706,12 @@ namespace libtorrent } // this is an async operation triggered by the client + // TODO: add a flag to ignore stats, and only care about resume data for + // content. For unchanged files, don't trigger a load of the metadata + // just to save an empty resume data file void torrent::save_resume_data(int flags) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); INVARIANT_CHECK; if (!valid_metadata()) @@ -7329,45 +8721,75 @@ namespace libtorrent return; } - if (!m_owning_storage.get()) + if (!m_storage.get()) { alerts().post_alert(save_resume_data_failed_alert(get_handle() , errors::destructing_torrent)); return; } + if ((flags & torrent_handle::only_if_modified) && !m_need_save_resume_data) + { + alerts().post_alert(save_resume_data_failed_alert(get_handle() + , errors::resume_data_not_modified)); + return; + } + m_need_save_resume_data = false; - m_last_saved_resume = time(0); + m_last_saved_resume = m_ses.session_time(); m_save_resume_flags = boost::uint8_t(flags); state_updated(); TORRENT_ASSERT(m_storage); - if (m_state == torrent_status::queued_for_checking - || m_state == torrent_status::checking_files + if (m_state == torrent_status::checking_files || m_state == torrent_status::checking_resume_data) { + if (!need_loaded()) + { + alerts().post_alert(save_resume_data_failed_alert(get_handle() + , m_error)); + return; + } + boost::shared_ptr rd(new entry); write_resume_data(*rd); - alerts().post_alert(save_resume_data_alert(rd - , get_handle())); + alerts().post_alert(save_resume_data_alert(rd, get_handle())); return; } // storage may be NULL during shutdown if ((flags & torrent_handle::flush_disk_cache) && m_storage) - m_storage->async_release_files(); + m_ses.disk_thread().async_release_files(m_storage.get()); - m_storage->async_save_resume_data( - boost::bind(&torrent::on_save_resume_data, shared_from_this(), _1, _2)); + m_ses.queue_async_resume_data(shared_from_this()); + } + + bool torrent::do_async_save_resume_data() + { + if (!need_loaded()) + { + alerts().post_alert(save_resume_data_failed_alert(get_handle(), m_error)); + return false; + } + // storage may be NULL during shutdown + if (!m_storage) + { + TORRENT_ASSERT(m_abort); + alerts().post_alert(save_resume_data_failed_alert(get_handle(), boost::asio::error::operation_aborted)); + return false; + } + inc_refcount("save_resume"); + m_ses.disk_thread().async_save_resume_data(m_storage.get() + , boost::bind(&torrent::on_save_resume_data, shared_from_this(), _1)); + return true; } bool torrent::should_check_files() const { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); // #error should m_allow_peers really affect checking? - return (m_state == torrent_status::checking_files - || m_state == torrent_status::queued_for_checking) - && (m_allow_peers || m_auto_managed) + return m_state == torrent_status::checking_files + && m_allow_peers && !has_error() && !m_abort && !m_graceful_pause_mode @@ -7376,21 +8798,23 @@ namespace libtorrent void torrent::flush_cache() { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); // storage may be NULL during shutdown - if (!m_owning_storage) + if (!m_storage) { TORRENT_ASSERT(m_abort); return; } - m_storage->async_release_files( - boost::bind(&torrent::on_cache_flushed, shared_from_this(), _1, _2)); + inc_refcount("release_files"); + m_ses.disk_thread().async_release_files(m_storage.get() + , boost::bind(&torrent::on_cache_flushed, shared_from_this(), _1)); } - void torrent::on_cache_flushed(int /* ret */, disk_io_job const& j) + void torrent::on_cache_flushed(disk_io_job const* j) { - TORRENT_ASSERT(m_ses.is_network_thread()); + dec_refcount("release_files"); + TORRENT_ASSERT(m_ses.is_single_thread()); if (m_ses.is_aborted()) return; @@ -7400,20 +8824,24 @@ namespace libtorrent bool torrent::is_paused() const { - TORRENT_ASSERT(m_ses.is_network_thread()); return !m_allow_peers || m_ses.is_paused() || m_graceful_pause_mode; } void torrent::pause(bool graceful) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); INVARIANT_CHECK; if (!m_allow_peers) return; if (!graceful) set_allow_peers(false); + m_announce_to_dht = false; m_announce_to_trackers = false; m_announce_to_lsd = false; + update_gauge(); + + update_want_peers(); + update_want_scrape(); // we need to save this new state m_need_save_resume_data = true; @@ -7421,6 +8849,7 @@ namespace libtorrent bool prev_graceful = m_graceful_pause_mode; m_graceful_pause_mode = graceful; + update_gauge(); if (!m_ses.is_paused() || (prev_graceful && !m_graceful_pause_mode)) { @@ -7433,7 +8862,7 @@ namespace libtorrent void torrent::do_pause() { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); if (!is_paused()) return; #ifndef TORRENT_DISABLE_EXTENSIONS @@ -7449,45 +8878,61 @@ namespace libtorrent m_inactive = false; state_updated(); + update_want_peers(); + update_want_scrape(); #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING || defined TORRENT_LOGGING log_to_all_peers("PAUSING TORRENT"); #endif - // this will make the storage close all - // files and flush all cached data - if (m_owning_storage.get()) + // when checking and being paused in graceful pause mode, we + // post the paused alert when the last outstanding disk job completes + if (m_state == torrent_status::checking_files) { - TORRENT_ASSERT(m_storage); - m_storage->async_release_files( - boost::bind(&torrent::on_torrent_paused, shared_from_this(), _1, _2)); - m_storage->async_clear_read_cache(); - } - else - { - if (alerts().should_post()) - alerts().post_alert(torrent_paused_alert(get_handle())); + if (m_checking_piece == m_num_checked_pieces) + { + if (alerts().should_post()) + alerts().post_alert(torrent_paused_alert(get_handle())); + } + disconnect_all(errors::torrent_paused, peer_connection_interface::op_bittorrent); + return; } if (!m_graceful_pause_mode) { - disconnect_all(errors::torrent_paused); + // this will make the storage close all + // files and flush all cached data + if (m_storage.get()) + { + TORRENT_ASSERT(m_storage); + m_ses.disk_thread().async_stop_torrent(m_storage.get() + , boost::bind(&torrent::on_torrent_paused, shared_from_this(), _1)); + } + else + { + if (alerts().should_post()) + alerts().post_alert(torrent_paused_alert(get_handle())); + } + + disconnect_all(errors::torrent_paused, peer_connection_interface::op_bittorrent); } else { // disconnect all peers with no outstanding data to receive // and choke all remaining peers to prevent responding to new // requests - for (std::set::iterator i = m_connections.begin() - , end(m_connections.end()); i != end;) + bool update_ticks = false; + for (peer_iterator i = m_connections.begin(); + i != m_connections.end();) { - std::set::iterator j = i++; - peer_connection* p = *j; + peer_iterator j = i++; + boost::shared_ptr p = (*j)->self(); TORRENT_ASSERT(p->associated_torrent().lock().get() == this); if (p->is_disconnecting()) { - m_connections.erase(j); + i = m_connections.erase(j); + update_ticks = true; continue; } @@ -7499,33 +8944,37 @@ namespace libtorrent // remove any un-sent requests from the queue p->clear_request_queue(); // don't accept new requests from the peer - if (!p->is_choked()) m_ses.choke_peer(*p); + if (!p->is_choked() && !p->ignore_unchoke_slots()) + m_ses.choke_peer(*p); continue; } #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING p->peer_log("*** CLOSING CONNECTION: torrent_paused"); #endif - p->disconnect(errors::torrent_paused); + p->disconnect(errors::torrent_paused, peer_connection_interface::op_bittorrent); + i = j; + } + if (update_ticks) + { + update_want_peers(); + update_want_tick(); } } stop_announcing(); - if (m_queued_for_checking && !should_check_files()) + // if the torrent is pinned, we should not unload it + if (!is_pinned()) { - // stop checking - m_storage->abort_disk_io(); - dequeue_torrent_check(); - set_state(torrent_status::queued_for_checking); - TORRENT_ASSERT(!m_queued_for_checking); + m_ses.evict_torrent(this); } } #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING || defined TORRENT_LOGGING void torrent::log_to_all_peers(char const* message) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING for (peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i) @@ -7559,7 +9008,7 @@ namespace libtorrent void torrent::set_allow_peers(bool b, bool graceful) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); if (m_allow_peers == b && m_graceful_pause_mode == graceful) return; @@ -7568,6 +9017,9 @@ namespace libtorrent if (!m_ses.is_paused()) m_graceful_pause_mode = graceful; + update_gauge(); + update_want_scrape(); + if (!b) { m_announce_to_dht = false; @@ -7579,37 +9031,37 @@ namespace libtorrent { do_resume(); } - - update_guage(); } void torrent::resume() { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); INVARIANT_CHECK; if (m_allow_peers && m_announce_to_dht && m_announce_to_trackers && m_announce_to_lsd) return; + m_announce_to_dht = true; m_announce_to_trackers = true; m_announce_to_lsd = true; - // this call will trigger a tracker announce, that's - // why it's important to set announce_to_trackers to - // true first - set_allow_peers(true); + m_allow_peers = true; if (!m_ses.is_paused()) m_graceful_pause_mode = false; + update_gauge(); + // we need to save this new state m_need_save_resume_data = true; + update_want_scrape(); + do_resume(); } void torrent::do_resume() { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); if (is_paused()) return; #ifndef TORRENT_DISABLE_EXTENSIONS @@ -7625,18 +9077,20 @@ namespace libtorrent if (alerts().should_post()) alerts().post_alert(torrent_resumed_alert(get_handle())); - state_updated(); - - m_started = time_now(); + m_started = m_ses.session_time(); clear_error(); + + state_updated(); + update_want_peers(); + update_want_tick(); + update_want_scrape(); + start_announcing(); - if (!m_queued_for_checking && should_check_files()) - queue_torrent_check(); } void torrent::update_tracker_timer(ptime now) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); if (!m_announcing) { #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING @@ -7659,19 +9113,19 @@ namespace libtorrent "[ announce_to_all_tiers: %d announce_to_all_trackers: %d" " found_working: %d i->tier: %d tier: %d " " is_working: %d fails: %d fail_limit: %d updating: %d ]" - , i->url.c_str(), settings().announce_to_all_tiers - , settings().announce_to_all_trackers, found_working + , i->url.c_str(), settings().get_bool(settings_pack::announce_to_all_tiers) + , settings().get_bool(settings_pack::announce_to_all_trackers), found_working , i->tier, tier, i->is_working(), i->fails, i->fail_limit , i->updating); debug_log(msg); #endif - if (settings().announce_to_all_tiers + if (settings().get_bool(settings_pack::announce_to_all_tiers) && found_working && i->tier <= tier && tier != INT_MAX) continue; - if (i->tier > tier && !settings().announce_to_all_tiers) break; + if (i->tier > tier && !settings().get_bool(settings_pack::announce_to_all_tiers)) break; if (i->is_working()) { tier = i->tier; found_working = false; } if (i->fails >= i->fail_limit && i->fail_limit != 0) continue; if (i->updating) @@ -7687,8 +9141,8 @@ namespace libtorrent } if (i->is_working()) found_working = true; if (found_working - && !settings().announce_to_all_trackers - && !settings().announce_to_all_tiers) break; + && !settings().get_bool(settings_pack::announce_to_all_trackers) + && !settings().get_bool(settings_pack::announce_to_all_tiers)) break; } #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING @@ -7717,7 +9171,7 @@ namespace libtorrent void torrent::start_announcing() { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); if (is_paused()) { #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING @@ -7747,7 +9201,7 @@ namespace libtorrent m_announcing = true; #ifndef TORRENT_DISABLE_DHT - if (m_policy.num_peers() < 50 && m_ses.m_dht) + if ((!m_policy || m_policy->num_peers() < 50) && m_ses.dht()) { // we don't have any peers, prioritize // announcing this torrent with the DHT @@ -7768,22 +9222,16 @@ namespace libtorrent m_total_redundant_bytes = 0; m_stat.clear(); + update_want_tick(); + announce_with_tracker(); - // private torrents are never announced on LSD - // or on DHT, we don't need this timer. - if (!m_torrent_file->is_valid() - || (!m_torrent_file->priv() - && (!m_torrent_file->is_i2p() - || settings().allow_i2p_mixed))) - { - if (m_ses.m_lsd) lsd_announce(); - } + lsd_announce(); } void torrent::stop_announcing() { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); if (!m_announcing) return; error_code ec; @@ -7801,9 +9249,10 @@ namespace libtorrent announce_with_tracker(tracker_request::stopped); } - void torrent::second_tick(stat& accumulator, int tick_interval_ms) + void torrent::second_tick(int tick_interval_ms, int residual) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(want_tick()); + TORRENT_ASSERT(m_ses.is_single_thread()); INVARIANT_CHECK; boost::weak_ptr self(shared_from_this()); @@ -7820,14 +9269,50 @@ namespace libtorrent if (m_abort) return; #endif + // if we're in upload only mode and we're auto-managed + // leave upload mode every 10 minutes hoping that the error + // condition has been fixed + if (m_upload_mode && m_auto_managed && int(m_upload_mode_time) + >= settings().get_int(settings_pack::optimistic_disk_retry)) + { + set_upload_mode(false); + } + + if (m_storage_tick > 0 && is_loaded()) + { + --m_storage_tick; + if (m_storage_tick == 0 && m_storage) + { + m_ses.disk_thread().async_tick_torrent(&storage() + , boost::bind(&torrent::on_disk_tick_done + , shared_from_this(), _1)); + update_want_tick(); + } + } + + if (is_paused() && !m_graceful_pause_mode) + { + // let the stats fade out to 0 + m_stat.second_tick(tick_interval_ms); + // if the rate is 0, there's no update because of network transfers + if (m_stat.low_pass_upload_rate() > 0 || m_stat.low_pass_download_rate() > 0) + state_updated(); + else + update_want_tick(); + + return; + } + if (m_need_suggest_pieces_refresh) + do_refresh_suggest_pieces(); + m_time_scaler--; if (m_time_scaler <= 0) { m_time_scaler = 10; - if (settings().max_sparse_regions > 0 - && m_picker - && m_picker->sparse_regions() > settings().max_sparse_regions) + if (settings().get_int(settings_pack::max_sparse_regions) > 0 + && has_picker() + && m_picker->sparse_regions() > settings().get_int(settings_pack::max_sparse_regions)) { // we have too many sparse regions. Prioritize pieces // that won't introduce new sparse regions @@ -7840,30 +9325,10 @@ namespace libtorrent } } - // if we're in upload only mode and we're auto-managed - // leave upload mode every 10 minutes hoping that the error - // condition has been fixed - if (m_upload_mode && m_auto_managed && int(m_upload_mode_time) - >= settings().optimistic_disk_retry) + if (settings().get_bool(settings_pack::rate_limit_ip_overhead)) { - set_upload_mode(false); - } - - if (is_paused() && !m_graceful_pause_mode) - { - // let the stats fade out to 0 - accumulator += m_stat; - m_stat.second_tick(tick_interval_ms); - // if the rate is 0, there's no update because of network transfers - if (m_stat.low_pass_upload_rate() > 0 || m_stat.low_pass_download_rate() > 0) - state_updated(); - return; - } - - if (settings().rate_limit_ip_overhead) - { - int up_limit = m_bandwidth_channel[peer_connection::upload_channel].throttle(); - int down_limit = m_bandwidth_channel[peer_connection::download_channel].throttle(); + int up_limit = upload_limit(); + int down_limit = download_limit(); if (down_limit > 0 && m_stat.download_ip_overhead() >= down_limit @@ -7882,8 +9347,7 @@ namespace libtorrent } } - int seconds_since_last_tick = 1; - if (m_ses.m_tick_residual >= 1000) ++seconds_since_last_tick; + int seconds_since_last_tick = 1 + residual; if (is_seed()) m_seeding_time += seconds_since_last_tick; if (is_finished()) m_finished_time += seconds_since_last_tick; @@ -7929,18 +9393,18 @@ namespace libtorrent maybe_connect_web_seeds(); m_swarm_last_seen_complete = m_last_seen_complete; + int idx = 0; for (peer_iterator i = m_connections.begin(); - i != m_connections.end();) + i != m_connections.end(); ++idx) { - peer_connection* p = *i; + // keep the peer object alive while we're + // inspecting it + boost::shared_ptr p = (*i)->self(); ++i; // look for the peer that saw a seed most recently m_swarm_last_seen_complete = (std::max)(p->last_seen_complete(), m_swarm_last_seen_complete); - if (!p->ignore_stats()) - m_stat += p->statistics(); - // updates the peer connection's ul/dl bandwidth // resource requests TORRENT_TRY { @@ -7953,13 +9417,18 @@ namespace libtorrent #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING p->peer_log("*** ERROR %s", e.what()); #endif - p->disconnect(errors::no_error, 1); + p->disconnect(errors::no_error, peer_connection_interface::op_bittorrent, 1); + } + + if (p->is_disconnecting()) + { + i = m_connections.begin() + idx; + --idx; } } - if (m_ses.m_alerts.should_post()) - m_ses.m_alerts.post_alert(stats_alert(get_handle(), tick_interval_ms, m_stat)); + if (m_ses.alerts().should_post()) + m_ses.alerts().post_alert(stats_alert(get_handle(), tick_interval_ms, m_stat)); - accumulator += m_stat; m_total_uploaded += m_stat.last_payload_uploaded(); m_total_downloaded += m_stat.last_payload_downloaded(); m_stat.second_tick(tick_interval_ms); @@ -7974,9 +9443,9 @@ namespace libtorrent // filter in order to avoid flapping (auto_manage_startup). bool is_inactive = false; if (is_finished()) - is_inactive = m_stat.upload_payload_rate() < m_ses.m_settings.inactive_up_rate; + is_inactive = m_stat.upload_payload_rate() < m_ses.settings().get_int(settings_pack::inactive_up_rate); else - is_inactive = m_stat.download_payload_rate() < m_ses.m_settings.inactive_down_rate; + is_inactive = m_stat.download_payload_rate() < m_ses.settings().get_int(settings_pack::inactive_down_rate); if (is_inactive) { @@ -7988,10 +9457,10 @@ namespace libtorrent // if this torrent was just considered inactive, we may want // to dequeue some other torrent if (m_inactive == false - && m_inactive_counter >= m_ses.m_settings.auto_manage_startup) + && m_inactive_counter >= m_ses.settings().get_int(settings_pack::auto_manage_startup)) { m_inactive = true; - if (m_ses.m_settings.dont_count_slow_torrents) + if (m_ses.settings().get_bool(settings_pack::dont_count_slow_torrents)) m_ses.trigger_auto_manage(); } } @@ -8006,14 +9475,16 @@ namespace libtorrent // if this torrent was just considered active, we may want // to queue some other torrent if (m_inactive == true - && m_inactive_counter <= -m_ses.m_settings.auto_manage_startup) + && m_inactive_counter <= -m_ses.settings().get_int(settings_pack::auto_manage_startup)) { m_inactive = false; - if (m_ses.m_settings.dont_count_slow_torrents) + if (m_ses.settings().get_bool(settings_pack::dont_count_slow_torrents)) m_ses.trigger_auto_manage(); } } } + + update_want_tick(); } void torrent::maybe_connect_web_seeds() @@ -8023,7 +9494,7 @@ namespace libtorrent // if we have everything we want we don't need to connect to any web-seed if (!is_finished() && !m_web_seeds.empty() && m_files_checked && int(m_connections.size()) < m_max_connections - && m_ses.num_connections() < m_ses.settings().connections_limit) + && m_ses.num_connections() < m_ses.settings().get_int(settings_pack::connections_limit)) { // keep trying web-seeds if there are any // first find out which web seeds we are connected to @@ -8051,7 +9522,7 @@ namespace libtorrent int num_downloaders = 0; int missing_pieces = 0; int num_interested = 0; - for (std::set::iterator i = m_connections.begin() + for (peer_iterator i = m_connections.begin() , end(m_connections.end()); i != end; ++i) { peer_connection* p = *i; @@ -8083,7 +9554,7 @@ namespace libtorrent int to_disconnect = num_seeds - num_peers / 2; std::vector seeds; seeds.reserve(num_seeds); - for (std::set::iterator i = m_connections.begin() + for (peer_iterator i = m_connections.begin() , end(m_connections.end()); i != end; ++i) { peer_connection* p = *i; @@ -8093,7 +9564,7 @@ namespace libtorrent std::random_shuffle(seeds.begin(), seeds.end()); TORRENT_ASSERT(to_disconnect <= int(seeds.size())); for (int i = 0; i < to_disconnect; ++i) - seeds[i]->disconnect(errors::upload_upload_connection); + seeds[i]->disconnect(errors::upload_upload_connection, peer_connection_interface::op_bittorrent); } if (num_downloaders == 0) return; @@ -8115,13 +9586,13 @@ namespace libtorrent , pieces_in_torrent - m_picker->num_filtered()); if (num_downloaded_pieces * m_torrent_file->piece_length() - * settings().share_mode_target > m_total_uploaded + * settings().get_int(settings_pack::share_mode_target) > m_total_uploaded && num_downloaded_pieces > 0) return; // don't have more pieces downloading in parallel than 5% of the total // number of pieces we have downloaded - if (int(m_picker->get_download_queue().size()) > num_downloaded_pieces / 20) + if (m_picker->get_download_queue_size() > num_downloaded_pieces / 20) return; // one more important property is that there are enough pieces @@ -8139,7 +9610,7 @@ namespace libtorrent { piece_picker::piece_pos const& pp = m_picker->piece_stats(i); if (pp.peer_count == 0) continue; - if (pp.filtered() && (pp.have() || pp.downloading)) + if (pp.filtered() && (pp.have() || pp.downloading())) { m_picker->set_piece_priority(i, 1); prio_updated = true; @@ -8159,8 +9630,8 @@ namespace libtorrent rarest_pieces.push_back(i); } - if (prio_updated) - m_policy.recalculate_connect_candidates(); + update_gauge(); + update_want_peers(); // now, rarest_pieces is a list of all pieces that are the rarest ones. // and rarest_rarity is the number of peers that have the rarest pieces @@ -8168,7 +9639,7 @@ namespace libtorrent // if there's only a single peer that doesn't have the rarest piece // it's impossible for us to download one piece and upload it // twice. i.e. we cannot get a positive share ratio - if (num_peers - rarest_rarity < settings().share_mode_target) return; + if (num_peers - rarest_rarity < settings().get_int(settings_pack::share_mode_target)) return; // we might be able to do better than a share ratio of 2 if there are // enough downloaders of the pieces we already have. @@ -8181,20 +9652,25 @@ namespace libtorrent int pick = random() % rarest_pieces.size(); bool was_finished = is_finished(); m_picker->set_piece_priority(rarest_pieces[pick], 1); + update_gauge(); update_peer_interest(was_finished); - m_policy.recalculate_connect_candidates(); + update_want_peers(); } void torrent::refresh_explicit_cache(int cache_size) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); if (!ready_for_connections()) return; if (m_abort) return; TORRENT_ASSERT(m_storage); + if (!is_loaded()) return; + // rotate the cached pieces + cache_status status; + m_ses.disk_thread().get_cache_info(&status, false, m_storage.get()); // add blocks_per_piece / 2 in order to round to closest whole piece int blocks_per_piece = m_torrent_file->piece_length() / block_size(); @@ -8235,18 +9711,17 @@ namespace libtorrent else pieces[i].first = avail_vec[i]; } + // remove write cache entries + status.pieces.erase(std::remove_if(status.pieces.begin(), status.pieces.end() + , boost::bind(&cached_piece_info::kind, _1) == cached_piece_info::write_cache) + , status.pieces.end()); + // decrease the availability of the pieces that are // already in the read cache, to move them closer to // the beginning of the pieces list, and more likely // to be included in this round of cache pieces - std::vector ret; - m_ses.m_disk_thread.get_cache_info(info_hash(), ret); - // remove write cache entries - ret.erase(std::remove_if(ret.begin(), ret.end() - , boost::bind(&cached_piece_info::kind, _1) == cached_piece_info::write_cache) - , ret.end()); - for (std::vector::iterator i = ret.begin() - , end(ret.end()); i != end; ++i) + for (std::vector::iterator i = status.pieces.begin() + , end(status.pieces.end()); i != end; ++i) { --pieces[i->piece].first; } @@ -8272,53 +9747,43 @@ namespace libtorrent for (std::vector::iterator i = avail_vec.begin() , end(avail_vec.end()); i != end; ++i) - filesystem().async_cache(*i, boost::bind(&torrent::on_disk_cache_complete - , shared_from_this(), _1, _2)); + { + inc_refcount("cache_piece"); + m_ses.disk_thread().async_cache_piece(m_storage.get(), *i + , boost::bind(&torrent::on_disk_cache_complete + , shared_from_this(), _1)); + } } } - void torrent::get_suggested_pieces(std::vector& s) const + void torrent::sent_bytes(int bytes_payload, int bytes_protocol) { - TORRENT_ASSERT(m_ses.is_network_thread()); - if (settings().suggest_mode == session_settings::no_piece_suggestions) - { - s.clear(); - return; - } - - std::vector ret; - m_ses.m_disk_thread.get_cache_info(info_hash(), ret); - - // remove write cache entries - ret.erase(std::remove_if(ret.begin(), ret.end() - , boost::bind(&cached_piece_info::kind, _1) == cached_piece_info::write_cache) - , ret.end()); - - // sort by how new the cached entry is, new pieces first - std::sort(ret.begin(), ret.end() - , boost::bind(&cached_piece_info::last_use, _1) - < boost::bind(&cached_piece_info::last_use, _2)); - - // cut off the oldest pieces that we don't want to suggest - // if we have an explicit cache, it's much more likely to - // stick around, so we should suggest all pieces - int num_pieces_to_suggest = int(ret.size()); - if (num_pieces_to_suggest == 0) return; - - if (!settings().explicit_read_cache) - num_pieces_to_suggest = (std::max)(1, int(ret.size() / 2)); - ret.resize(num_pieces_to_suggest); - - std::transform(ret.begin(), ret.end(), std::back_inserter(s) - , boost::bind(&cached_piece_info::piece, _1)); + m_stat.sent_bytes(bytes_payload, bytes_protocol); + m_ses.sent_bytes(bytes_payload, bytes_protocol); } - void torrent::add_stats(stat const& s) + void torrent::received_bytes(int bytes_payload, int bytes_protocol) { - TORRENT_ASSERT(m_ses.is_network_thread()); - // these stats are propagated to the session - // stats the next time second_tick is called - m_stat += s; + m_stat.received_bytes(bytes_payload, bytes_protocol); + m_ses.received_bytes(bytes_payload, bytes_protocol); + } + + void torrent::trancieve_ip_packet(int bytes, bool ipv6) + { + m_stat.trancieve_ip_packet(bytes, ipv6); + m_ses.trancieve_ip_packet(bytes, ipv6); + } + + void torrent::sent_syn(bool ipv6) + { + m_stat.sent_syn(ipv6); + m_ses.sent_syn(ipv6); + } + + void torrent::received_synack(bool ipv6) + { + m_stat.received_synack(ipv6); + m_ses.received_synack(ipv6); } #if TORRENT_DEBUG_STREAMING > 0 @@ -8666,7 +10131,7 @@ namespace libtorrent void torrent::request_time_critical_pieces() { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); TORRENT_ASSERT(!upload_mode()); // build a list of peers and sort it by download_queue_time @@ -8714,7 +10179,7 @@ namespace libtorrent // now, iterate over all time critical pieces, in order of importance, and // request them from the peers, in order of responsiveness. i.e. request // the most time critical pieces from the fastest peers. - for (std::deque::iterator i = m_time_critical_pieces.begin() + for (std::vector::iterator i = m_time_critical_pieces.begin() , end(m_time_critical_pieces.end()); i != end; ++i) { #if TORRENT_DEBUG_STREAMING > 1 @@ -8840,7 +10305,7 @@ namespace libtorrent std::set torrent::web_seeds(web_seed_entry::type_t type) const { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); std::set ret; for (std::list::const_iterator i = m_web_seeds.begin() , end(m_web_seeds.end()); i != end; ++i) @@ -8863,7 +10328,7 @@ namespace libtorrent void torrent::disconnect_web_seed(peer_connection* p) { std::list::iterator i = std::find_if(m_web_seeds.begin(), m_web_seeds.end() - , (boost::bind(&policy::peer::connection, boost::bind(&web_seed_entry::peer_info, _1)) == p)); + , (boost::bind(&torrent_peer::connection, boost::bind(&web_seed_entry::peer_info, _1)) == p)); // this happens if the web server responded with a redirect // or with something incorrect, so that we removed the web seed // immediately, before we disconnected @@ -8878,94 +10343,216 @@ namespace libtorrent i->peer_info.connection = 0; } - void torrent::remove_web_seed(peer_connection* p) + void torrent::remove_web_seed(peer_connection* p, error_code const& ec + , peer_connection_interface::operation_t op, int error) { std::list::iterator i = std::find_if(m_web_seeds.begin(), m_web_seeds.end() - , (boost::bind(&policy::peer::connection, boost::bind(&web_seed_entry::peer_info, _1)) == p)); + , (boost::bind(&torrent_peer::connection, boost::bind(&web_seed_entry::peer_info, _1)) == p)); TORRENT_ASSERT(i != m_web_seeds.end()); if (i == m_web_seeds.end()) return; - p->set_peer_info(0); + if (i->peer_info.connection) i->peer_info.connection->disconnect(ec, op, error); if (has_picker()) picker().clear_peer(&i->peer_info); m_web_seeds.erase(i); + update_want_tick(); } void torrent::retry_web_seed(peer_connection* p, int retry) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); std::list::iterator i = std::find_if(m_web_seeds.begin(), m_web_seeds.end() - , (boost::bind(&policy::peer::connection, boost::bind(&web_seed_entry::peer_info, _1)) == p)); + , (boost::bind(&torrent_peer::connection, boost::bind(&web_seed_entry::peer_info, _1)) == p)); TORRENT_ASSERT(i != m_web_seeds.end()); if (i == m_web_seeds.end()) return; - if (retry == 0) retry = m_ses.settings().urlseed_wait_retry; + if (retry == 0) retry = m_ses.settings().get_int(settings_pack::urlseed_wait_retry); i->retry = time_now() + seconds(retry); } + torrent_state torrent::get_policy_state() + { + torrent_state ret; + ret.is_paused = is_paused(); + ret.is_finished = is_finished(); + ret.allow_multiple_connections_per_ip = settings().get_bool(settings_pack::allow_multiple_connections_per_ip); + ret.max_peerlist_size = is_paused() + ? settings().get_int(settings_pack::max_paused_peerlist_size) + : settings().get_int(settings_pack::max_peerlist_size); + ret.min_reconnect_time = settings().get_int(settings_pack::min_reconnect_time); + + ret.peer_allocator = m_ses.get_peer_allocator(); + ret.ip = &m_ses.external_address(); + ret.port = m_ses.listen_port(); + return ret; + } + bool torrent::try_connect_peer() { - TORRENT_ASSERT(m_ses.is_network_thread()); - TORRENT_ASSERT(want_more_peers()); - bool ret = m_policy.connect_one_peer(m_ses.session_time()); - return ret; - } + TORRENT_ASSERT(m_ses.is_single_thread()); + TORRENT_ASSERT(want_peers()); - void torrent::add_peer(tcp::endpoint const& adr, int source) - { - TORRENT_ASSERT(m_ses.is_network_thread()); - peer_id id(0); - m_policy.add_peer(adr, id, source, 0); + torrent_state st = get_policy_state(); + need_policy(); + torrent_peer* p = m_policy->connect_one_peer(m_ses.session_time(), &st); + peers_erased(st.erased); + m_ses.inc_stats_counter(counters::connection_attempt_loops, st.loop_counter); - state_updated(); - } - - void torrent::async_verify_piece(int piece_index, boost::function const& f) - { - TORRENT_ASSERT(m_ses.is_network_thread()); -// INVARIANT_CHECK; - - TORRENT_ASSERT(m_storage); - TORRENT_ASSERT(m_storage->refcount() > 0); - TORRENT_ASSERT(piece_index >= 0); - TORRENT_ASSERT(piece_index < m_torrent_file->num_pieces()); - TORRENT_ASSERT(piece_index < (int)m_picker->num_pieces()); - TORRENT_ASSERT(!m_picker || !m_picker->have_piece(piece_index)); -#ifdef TORRENT_DEBUG - if (m_picker) + if (p == NULL) { - int blocks_in_piece = m_picker->blocks_in_piece(piece_index); - for (int i = 0; i < blocks_in_piece; ++i) - { - TORRENT_ASSERT(m_picker->num_peers(piece_block(piece_index, i)) == 0); - } + update_want_peers(); + return false; + } + + if (!connect_to_peer(p)) + { + m_policy->inc_failcount(p); + update_want_peers(); + return false; + } + update_want_peers(); + + return true; + } + + torrent_peer* torrent::add_peer(tcp::endpoint const& adr, int source, int flags) + { + TORRENT_ASSERT(m_ses.is_single_thread()); + +#if !TORRENT_USE_IPV6 + if (!adr.address().is_v4()) return NULL; +#endif + +#ifndef TORRENT_DISABLE_DHT + if (source != peer_info::resume_data) + { + // try to send a DHT ping to this peer + // as well, to figure out if it supports + // DHT (uTorrent and BitComet doesn't + // advertise support) + udp::endpoint node(adr.address(), adr.port()); + session().add_dht_node(node); } #endif - m_storage->async_hash(piece_index, boost::bind(&torrent::on_piece_verified - , shared_from_this(), _1, _2, f)); -#if TORRENT_USE_INVARIANT_CHECKS - check_invariant(); + if (m_apply_ip_filter + && m_ses.get_ip_filter().access(adr.address()) & ip_filter::blocked) + { + if (alerts().should_post()) + alerts().post_alert(peer_blocked_alert(get_handle() + , adr.address(), peer_blocked_alert::ip_filter)); + +#ifndef TORRENT_DISABLE_EXTENSIONS + notify_extension_add_peer(adr, source, torrent_plugin::filtered); #endif - } + return NULL; + } - void torrent::on_piece_verified(int ret, disk_io_job const& j - , boost::function f) - { - TORRENT_ASSERT(m_ses.is_network_thread()); + if (m_ses.get_port_filter().access(adr.port()) & port_filter::blocked) + { + if (alerts().should_post()) + alerts().post_alert(peer_blocked_alert(get_handle() + , adr.address(), peer_blocked_alert::port_filter)); +#ifndef TORRENT_DISABLE_EXTENSIONS + notify_extension_add_peer(adr, source, torrent_plugin::filtered); +#endif + return NULL; + } - // return value: - // 0: success, piece passed hash check - // -1: disk failure - // -2: hash check failed +#if TORRENT_USE_I2P + // if this is an i2p torrent, and we don't allow mixed mode + // no regular peers should ever be added! + if (!settings().get_bool(settings_pack::allow_i2p_mixed) && is_i2p()) + { + if (alerts().should_post()) + alerts().post_alert(peer_blocked_alert(get_handle() + , adr.address(), peer_blocked_alert::i2p_mixed)); + return NULL; + } +#endif + if (settings().get_bool(settings_pack::no_connect_privileged_ports) && adr.port() < 1024) + { + if (alerts().should_post()) + alerts().post_alert(peer_blocked_alert(get_handle() + , adr.address(), peer_blocked_alert::privileged_ports)); +#ifndef TORRENT_DISABLE_EXTENSIONS + notify_extension_add_peer(adr, source, torrent_plugin::filtered); +#endif + return NULL; + } + + need_policy(); + torrent_state st = get_policy_state(); + torrent_peer* p = m_policy->add_peer(adr, source, 0, &st); + peers_erased(st.erased); + if (p) + { + state_updated(); +#ifndef TORRENT_DISABLE_EXTENSIONS + notify_extension_add_peer(adr, source, st.first_time_seen ? torrent_plugin::first_time : 0); +#endif + } + else + { +#ifndef TORRENT_DISABLE_EXTENSIONS + notify_extension_add_peer(adr, source, torrent_plugin::filtered); +#endif + } + update_want_peers(); state_updated(); - - if (ret == -1) handle_disk_error(j); - f(ret); + return p; } - tcp::endpoint torrent::current_tracker() const + bool torrent::ban_peer(torrent_peer* tp) { - return m_tracker_address; + if (!settings().get_bool(settings_pack::ban_web_seeds) && tp->web_seed) + return false; + + need_policy(); + if (!m_policy->ban_peer(tp)) return false; + update_want_peers(); + + m_ses.inc_stats_counter(counters::num_banned_peers); + return true; + } + + void torrent::set_seed(torrent_peer* p, bool s) + { + need_policy(); + m_policy->set_seed(p, s); + } + + void torrent::clear_failcount(torrent_peer* p) + { + need_policy(); + m_policy->set_failcount(p, 0); + update_want_peers(); + } + + std::pair torrent::find_peers(address const& a) + { + need_policy(); + return m_policy->find_peers(a); + } + + void torrent::update_peer_port(int port, torrent_peer* p, int src) + { + need_policy(); + torrent_state st = get_policy_state(); + m_policy->update_peer_port(port, p, src, &st); + peers_erased(st.erased); + update_want_peers(); + } + + // verify piece is used when checking resume data or when the user + // adds a piece + void torrent::verify_piece(int piece) + { + picker().mark_as_checking(piece); + + inc_refcount("verify_piece"); + m_ses.disk_thread().async_hash(m_storage.get(), piece, 0 + , boost::bind(&torrent::on_piece_verified, shared_from_this(), _1) + , (void*)1); } announce_entry* torrent::find_tracker(tracker_request const& r) @@ -8977,12 +10564,74 @@ namespace libtorrent return &*i; } -#if !TORRENT_NO_FPU - void torrent::file_progress(std::vector& fp) const + void torrent::ip_filter_updated() { - fp.clear(); - if (!valid_metadata()) return; + if (!m_apply_ip_filter) return; + if (!m_policy) return; + + torrent_state st = get_policy_state(); + std::vector
banned; + m_policy->apply_ip_filter(m_ses.get_ip_filter(), &st, banned); + + if (alerts().should_post()) + { + for (std::vector
::iterator i = banned.begin() + , end(banned.end()); i != end; ++i) + alerts().post_alert(peer_blocked_alert(get_handle(), *i + , peer_blocked_alert::ip_filter)); + } + + peers_erased(st.erased); + } + + void torrent::port_filter_updated() + { + if (!m_apply_ip_filter) return; + if (!m_policy) return; + + torrent_state st = get_policy_state(); + std::vector
banned; + m_policy->apply_port_filter(m_ses.get_port_filter(), &st, banned); + + if (alerts().should_post()) + { + for (std::vector
::iterator i = banned.begin() + , end(banned.end()); i != end; ++i) + alerts().post_alert(peer_blocked_alert(get_handle(), *i + , peer_blocked_alert::port_filter)); + } + + peers_erased(st.erased); + } + + // this is called when torrent_peers are removed from the policy + // (peer-list). It removes any references we may have to those torrent_peers, + // so we don't leave then dangling + void torrent::peers_erased(std::vector const& peers) + { + if (!has_picker()) return; + + for (std::vector::const_iterator i = peers.begin() + , end(peers.end()); i != end; ++i) + { + m_picker->clear_peer(*i); + } +#if TORRENT_USE_INVARIANT_CHECKS + m_picker->check_peers(); +#endif + } + +#if !TORRENT_NO_FPU + void torrent::file_progress(std::vector& fp) + { + TORRENT_ASSERT(m_ses.is_single_thread()); + if (!valid_metadata()) + { + fp.clear(); + return; + } + if (!need_loaded()) return; fp.resize(m_torrent_file->num_files(), 1.f); if (is_seed()) return; @@ -8990,73 +10639,108 @@ namespace libtorrent file_progress(progress); for (int i = 0; i < m_torrent_file->num_files(); ++i) { - file_entry const& f = m_torrent_file->file_at(i); - if (f.size == 0) fp[i] = 1.f; - else fp[i] = float(progress[i]) / f.size; + boost::int64_t file_size = m_torrent_file->files().file_size(i); + if (file_size == 0) fp[i] = 1.f; + else fp[i] = float(progress[i]) / file_size; } } #endif - void torrent::file_progress(std::vector& fp, int flags) const + void torrent::file_progress(std::vector& fp, int flags) { + TORRENT_ASSERT(m_ses.is_single_thread()); if (!valid_metadata()) { fp.clear(); return; } - - fp.resize(m_torrent_file->num_files(), 0); + if (!need_loaded()) return; + + // if we're a seed, we don't have an m_file_progress anyway + // since we don't need one. We know we have all files if (is_seed()) { - for (int i = 0; i < m_torrent_file->num_files(); ++i) - fp[i] = m_torrent_file->files().file_size(i); + fp.resize(m_torrent_file->num_files()); + file_storage const& fs = m_torrent_file->files(); + for (int i = 0; i < fs.num_files(); ++i) + fp[i] = fs.file_size(i); return; } - if (flags & torrent_handle::piece_granularity) + if (num_have() == 0) { - std::copy(m_file_progress.begin(), m_file_progress.end(), fp.begin()); + // if we don't have any pieces, just return zeroes + fp.clear(); + fp.resize(m_torrent_file->num_files(), 0); return; } + + int num_files = m_torrent_file->num_files(); + if (m_file_progress.empty()) + { + // This is the first time the client asks for file progress. + // allocate it and make sure it's up to date + m_file_progress.resize(num_files, 0); + + int num_pieces = m_torrent_file->num_pieces(); + + // initialize the progress of each file + + const int piece_size = m_torrent_file->piece_length(); + boost::uint64_t off = 0; + boost::uint64_t total_size = m_torrent_file->total_size(); + int file_index = 0; + file_storage const& fs = m_torrent_file->files(); + for (int piece = 0; piece < num_pieces; ++piece, off += piece_size) + { + TORRENT_ASSERT(file_index < fs.num_files()); + size_type file_offset = off - fs.file_offset(file_index); + if (file_offset >= fs.file_size(file_index)) + { + ++file_index; + continue; + } + TORRENT_ASSERT(file_offset <= fs.file_size(file_index)); + + if (!have_piece(piece)) continue; + + int size = (std::min)(boost::uint64_t(piece_size), total_size - off); + + while (size) + { + int add = (std::min)(boost::int64_t(size), fs.file_size(file_index) - file_offset); + m_file_progress[file_index] += add; + + TORRENT_ASSERT(m_file_progress[file_index] + <= m_torrent_file->files().file_size(file_index)); + + size -= add; + if (size > 0) + { + ++file_index; + file_offset = 0; + } + } + } + } + + fp.resize(num_files, 0); + + std::copy(m_file_progress.begin(), m_file_progress.end(), fp.begin()); + + if (flags & torrent_handle::piece_granularity) + return; TORRENT_ASSERT(has_picker()); - for (int i = 0; i < m_torrent_file->num_files(); ++i) + std::vector q = m_picker->get_download_queue(); + + if (!q.empty()) { - peer_request ret = m_torrent_file->files().map_file(i, 0, 0); - size_type size = m_torrent_file->files().file_size(i); - TORRENT_ASSERT(ret.piece >= 0); - TORRENT_ASSERT(ret.piece < m_picker->num_pieces()); - if (ret.piece < 0 || ret.piece >= m_picker->num_pieces()) - { - // this is not supposed to happen. - fp[i] = 0; - continue; - } - - size_type done = 0; - while (size > 0) - { - TORRENT_ASSERT(ret.piece < m_picker->num_pieces()); - TORRENT_ASSERT(ret.piece >= 0); - - size_type bytes_step = (std::min)(size_type(m_torrent_file->piece_size(ret.piece) - - ret.start), size); - - if (m_picker->have_piece(ret.piece)) done += bytes_step; - ++ret.piece; - ret.start = 0; - size -= bytes_step; - } - TORRENT_ASSERT(size == 0); - - fp[i] = done; + if (!const_cast(*this).need_loaded()) return; } - const std::vector& q - = m_picker->get_download_queue(); - file_storage const& fs = m_torrent_file->files(); for (std::vector::const_iterator i = q.begin(), end(q.end()); i != end; ++i) @@ -9088,11 +10772,12 @@ namespace libtorrent if (info[k].state == piece_picker::block_info::state_requested) { block = 0; - policy::peer* p = static_cast(info[k].peer); + torrent_peer* p = static_cast(info[k].peer); if (p && p->connection) { + peer_connection* peer = static_cast(p->connection); boost::optional pbp - = p->connection->downloading_piece_progress(); + = peer->downloading_piece_progress(); if (pbp && pbp->piece_index == i->index && pbp->block_index == k) block = pbp->bytes_downloaded; TORRENT_ASSERT(block <= block_size()); @@ -9144,32 +10829,23 @@ namespace libtorrent void torrent::new_external_ip() { - m_policy.clear_peer_prio(); + if (m_policy) m_policy->clear_peer_prio(); } void torrent::set_state(torrent_status::state_t s) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); + TORRENT_ASSERT(s != 0); // this state isn't used anymore + #if TORRENT_USE_ASSERTS - if (s != torrent_status::checking_files - && s != torrent_status::queued_for_checking) - { - // the only valid transition away from queued_for_checking - // is to checking_files. One exception is to finished - // in case all the files are marked with priority 0 - if (m_queued_for_checking) - { - std::vector pieces; - m_picker->piece_priorities(pieces); - // make sure all pieces have priority 0 - TORRENT_ASSERT(std::accumulate(pieces.begin(), pieces.end(), 0) == 0); - } - } if (s == torrent_status::seeding) TORRENT_ASSERT(is_seed()); if (s == torrent_status::seeding) + { TORRENT_ASSERT(is_seed()); + TORRENT_ASSERT(is_finished()); + } if (s == torrent_status::finished) TORRENT_ASSERT(is_finished()); if (s == torrent_status::downloading && m_state == torrent_status::finished) @@ -9178,27 +10854,27 @@ namespace libtorrent if (int(m_state) == s) return; - if (m_ses.m_alerts.should_post()) + if (m_ses.alerts().should_post()) { - m_ses.m_alerts.post_alert(state_changed_alert(get_handle() + m_ses.alerts().post_alert(state_changed_alert(get_handle() , s, (torrent_status::state_t)m_state)); } if (s == torrent_status::finished - && m_ses.m_alerts.should_post()) + && alerts().should_post()) { alerts().post_alert(torrent_finished_alert( get_handle())); } - m_state = s; #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING debug_log("set_state() %d", m_state); #endif - update_guage(); + update_want_peers(); + update_gauge(); state_updated(); @@ -9237,22 +10913,28 @@ namespace libtorrent // we can't call state_updated() while the session // is building the status update alert - TORRENT_ASSERT(!m_ses.m_posting_torrent_updates); + TORRENT_ASSERT(!m_ses.is_posting_torrent_updates()); - // we're either not subscribing to this torrent, or - // it has already been updated this round, no need to - // add it to the list twice + // we're not subscribing to this torrent, don't add it if (!m_state_subscription) return; - if (m_in_state_updates) + + std::vector& list = m_ses.torrent_list(aux::session_interface::torrent_state_updates); + + // if it has already been updated this round, no need to + // add it to the list twice + if (m_links[aux::session_interface::torrent_state_updates].in_list()) { -#ifndef TORRENT_DISABLE_INVARIANT_CHECKS - TORRENT_ASSERT(m_ses.in_state_updates(shared_from_this())); +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + TORRENT_ASSERT(find(list.begin(), list.end(), this) != list.end()); #endif return; } - m_ses.add_to_update_queue(shared_from_this()); - m_in_state_updates = true; +#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS + TORRENT_ASSERT(find(list.begin(), list.end(), this) == list.end()); +#endif + + m_links[aux::session_interface::torrent_state_updates].insert(list, this); } void torrent::status(torrent_status* st, boost::uint32_t flags) @@ -9263,6 +10945,7 @@ namespace libtorrent st->handle = get_handle(); st->info_hash = info_hash(); + st->is_loaded = is_loaded(); if (flags & torrent_handle::query_name) st->name = name(); @@ -9274,7 +10957,8 @@ namespace libtorrent st->torrent_file = m_torrent_file; st->has_incoming = m_has_incoming; - if (m_error) st->error = convert_from_native(m_error.message()) + ": " + m_error_file; + if (m_error) st->error = convert_from_native(m_error.message()) + + ": " + resolve_filename(m_error_file); st->seed_mode = m_seed_mode; st->moving_storage = m_moving_storage; @@ -9286,13 +10970,22 @@ namespace libtorrent st->upload_mode = m_upload_mode; st->up_bandwidth_queue = 0; st->down_bandwidth_queue = 0; - st->priority = m_priority; + int priority = 0; + for (int i = 0; i < num_classes(); ++i) + { + int const* prio = m_ses.peer_classes().at(class_at(i))->priority; + if (priority < prio[peer_connection::upload_channel]) + priority = prio[peer_connection::upload_channel]; + if (priority < prio[peer_connection::download_channel]) + priority = prio[peer_connection::download_channel]; + } + st->priority = priority; st->num_peers = int(m_connections.size()) - m_num_connecting; - st->list_peers = m_policy.num_peers(); - st->list_seeds = m_policy.num_seeds(); - st->connect_candidates = m_policy.num_connect_candidates(); + st->list_peers = m_policy ? m_policy->num_peers() : 0; + st->list_seeds = m_policy ? m_policy->num_seeds() : 0; + st->connect_candidates = m_policy ? m_policy->num_connect_candidates() : 0; st->seed_rank = seed_rank(settings()); st->all_time_upload = m_total_uploaded; @@ -9385,6 +11078,14 @@ namespace libtorrent st->state = (torrent_status::state_t)m_state; +#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS + if (st->state == torrent_status::finished + || st->state == torrent_status::seeding) + { + TORRENT_ASSERT(st->is_finished); + } +#endif + if (!valid_metadata()) { st->state = torrent_status::downloading_metadata; @@ -9419,19 +11120,22 @@ namespace libtorrent #endif } + int num_pieces = m_torrent_file->num_pieces(); if (has_picker() && (flags & torrent_handle::query_pieces)) { st->sparse_regions = m_picker->sparse_regions(); - int num_pieces = m_picker->num_pieces(); st->pieces.resize(num_pieces, false); for (int i = 0; i < num_pieces; ++i) - if (m_picker->have_piece(i)) st->pieces.set_bit(i); + if (m_picker->has_piece_passed(i)) st->pieces.set_bit(i); } - else if (is_seed()) + else if (m_have_all) { - int num_pieces = m_torrent_file->num_pieces(); st->pieces.resize(num_pieces, true); } + else + { + st->pieces.resize(num_pieces, false); + } st->num_pieces = num_have(); st->num_seeds = num_seeds(); if ((flags & torrent_handle::query_distributed_copies) && m_picker.get()) @@ -9457,7 +11161,7 @@ namespace libtorrent void torrent::add_redundant_bytes(int b, torrent::wasted_reason_t reason) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); TORRENT_ASSERT(b > 0); m_total_redundant_bytes += b; m_ses.add_redundant_bytes(b, reason); @@ -9467,7 +11171,7 @@ namespace libtorrent void torrent::add_failed_bytes(int b) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); TORRENT_ASSERT(b > 0); m_total_failed_bytes += b; m_ses.add_failed_bytes(b); @@ -9477,11 +11181,11 @@ namespace libtorrent int torrent::num_seeds() const { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); INVARIANT_CHECK; int ret = 0; - for (std::set::const_iterator i = m_connections.begin() + for (const_peer_iterator i = m_connections.begin() , end(m_connections.end()); i != end; ++i) if ((*i)->is_seed()) ++ret; return ret; @@ -9491,7 +11195,7 @@ namespace libtorrent , int response_code, error_code const& ec, const std::string& msg , int retry_interval) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); INVARIANT_CHECK; @@ -9515,9 +11219,9 @@ namespace libtorrent deprioritize_tracker(tracker_index); } - if (m_ses.m_alerts.should_post()) + if (m_ses.alerts().should_post()) { - m_ses.m_alerts.post_alert(tracker_error_alert(get_handle() + m_ses.alerts().post_alert(tracker_error_alert(get_handle() , ae?ae->fails:0, response_code, r.url, ec, msg)); } } @@ -9530,9 +11234,9 @@ namespace libtorrent if (ae) ae->fail_limit = 1; } - if (m_ses.m_alerts.should_post()) + if (m_ses.alerts().should_post()) { - m_ses.m_alerts.post_alert(scrape_failed_alert(get_handle(), r.url, ec)); + m_ses.alerts().post_alert(scrape_failed_alert(get_handle(), r.url, ec)); } } // announce to the next working tracker @@ -9542,20 +11246,19 @@ namespace libtorrent } #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING - void torrent::debug_log(const char* fmt, ...) const + void torrent::debug_log(char const* fmt, ...) const { - if (!m_ses.m_logger) return; + if (!m_logger) return; va_list v; va_start(v, fmt); - char usr[1024]; + char usr[400]; vsnprintf(usr, sizeof(usr), fmt, v); va_end(v); - char buf[1280]; - snprintf(buf, sizeof(buf), "%s: %s: %s\n", time_now_string() - , to_hex(info_hash().to_string()).substr(0, 6).c_str(), usr); - (*m_ses.m_logger) << buf; + char buf[450]; + snprintf(buf, sizeof(buf), "%" PRId64 ": %s\n", total_microseconds(time_now_hires() - m_logger_time), usr); + (*m_logger) << buf; } #endif diff --git a/src/torrent_handle.cpp b/src/torrent_handle.cpp index 798f9e7e9..43fccae75 100644 --- a/src/torrent_handle.cpp +++ b/src/torrent_handle.cpp @@ -139,6 +139,7 @@ namespace libtorrent , has_incoming(false) , seed_mode(false) , moving_storage(false) + , is_loaded(true) , info_hash(0) {} @@ -156,41 +157,50 @@ namespace libtorrent // defined in session.cpp void fun_wrap(bool* done, condition_variable* e, mutex* m, boost::function f); +#ifdef TORRENT_PROFILE_CALLS + extern void blocking_call(); + +#define TORRENT_RECORD_BLOCKING_CALL blocking_call(); +#else +#define TORRENT_RECORD_BLOCKING_CALL +#endif + #define TORRENT_ASYNC_CALL(x) \ boost::shared_ptr t = m_torrent.lock(); \ if (!t) return; \ - session_impl& ses = t->session(); \ + session_impl& ses = (session_impl&) t->session(); \ ses.m_io_service.dispatch(boost::bind(&torrent:: x, t)) #define TORRENT_ASYNC_CALL1(x, a1) \ boost::shared_ptr t = m_torrent.lock(); \ if (!t) return; \ - session_impl& ses = t->session(); \ + session_impl& ses = (session_impl&) t->session(); \ ses.m_io_service.dispatch(boost::bind(&torrent:: x, t, a1)) #define TORRENT_ASYNC_CALL2(x, a1, a2) \ boost::shared_ptr t = m_torrent.lock(); \ if (!t) return; \ - session_impl& ses = t->session(); \ + session_impl& ses = (session_impl&) t->session(); \ ses.m_io_service.dispatch(boost::bind(&torrent:: x, t, a1, a2)) #define TORRENT_ASYNC_CALL3(x, a1, a2, a3) \ boost::shared_ptr t = m_torrent.lock(); \ if (!t) return; \ - session_impl& ses = t->session(); \ + session_impl& ses = (session_impl&) t->session(); \ ses.m_io_service.dispatch(boost::bind(&torrent:: x, t, a1, a2, a3)) #define TORRENT_ASYNC_CALL4(x, a1, a2, a3, a4) \ boost::shared_ptr t = m_torrent.lock(); \ if (!t) return; \ - session_impl& ses = t->session(); \ + session_impl& ses = (session_impl&) t->session(); \ ses.m_io_service.dispatch(boost::bind(&torrent:: x, t, a1, a2, a3, a4)) #define TORRENT_SYNC_CALL(x) \ boost::shared_ptr t = m_torrent.lock(); \ if (!t) return; \ bool done = false; \ - session_impl& ses = t->session(); \ + session_impl& ses = (session_impl&) t->session(); \ + TORRENT_RECORD_BLOCKING_CALL \ mutex::scoped_lock l(ses.mut); \ ses.m_io_service.dispatch(boost::bind(&fun_wrap, &done, &ses.cond, &ses.mut, boost::function(boost::bind(&torrent:: x, t)))); \ while (!done) { ses.cond.wait(l); } @@ -199,7 +209,8 @@ namespace libtorrent boost::shared_ptr t = m_torrent.lock(); \ if (t) { \ bool done = false; \ - session_impl& ses = t->session(); \ + session_impl& ses = (session_impl&) t->session(); \ + TORRENT_RECORD_BLOCKING_CALL \ mutex::scoped_lock l(ses.mut); \ ses.m_io_service.dispatch(boost::bind(&fun_wrap, &done, &ses.cond, &ses.mut, boost::function(boost::bind(&torrent:: x, t, a1)))); \ t.reset(); \ @@ -209,7 +220,8 @@ namespace libtorrent boost::shared_ptr t = m_torrent.lock(); \ if (t) { \ bool done = false; \ - session_impl& ses = t->session(); \ + session_impl& ses = (session_impl&) t->session(); \ + TORRENT_RECORD_BLOCKING_CALL \ mutex::scoped_lock l(ses.mut); \ ses.m_io_service.dispatch(boost::bind(&fun_wrap, &done, &ses.cond, &ses.mut, boost::function(boost::bind(&torrent:: x, t, a1, a2)))); \ t.reset(); \ @@ -219,7 +231,8 @@ namespace libtorrent boost::shared_ptr t = m_torrent.lock(); \ if (t) { \ bool done = false; \ - session_impl& ses = t->session(); \ + session_impl& ses = (session_impl&) t->session(); \ + TORRENT_RECORD_BLOCKING_CALL \ mutex::scoped_lock l(ses.mut); \ ses.m_io_service.dispatch(boost::bind(&fun_wrap, &done, &ses.cond, &ses.mut, boost::function(boost::bind(&torrent:: x, t, a1, a2, a3)))); \ t.reset(); \ @@ -229,8 +242,9 @@ namespace libtorrent boost::shared_ptr t = m_torrent.lock(); \ if (!t) return def; \ bool done = false; \ - session_impl& ses = t->session(); \ + session_impl& ses = (session_impl&) t->session(); \ type r; \ + TORRENT_RECORD_BLOCKING_CALL \ mutex::scoped_lock l(ses.mut); \ ses.m_io_service.dispatch(boost::bind(&fun_ret, &r, &done, &ses.cond, &ses.mut, boost::function(boost::bind(&torrent:: x, t)))); \ t.reset(); \ @@ -240,8 +254,9 @@ namespace libtorrent boost::shared_ptr t = m_torrent.lock(); \ if (!t) return def; \ bool done = false; \ - session_impl& ses = t->session(); \ + session_impl& ses = (session_impl&) t->session(); \ type r; \ + TORRENT_RECORD_BLOCKING_CALL \ mutex::scoped_lock l(ses.mut); \ ses.m_io_service.dispatch(boost::bind(&fun_ret, &r, &done, &ses.cond, &ses.mut, boost::function(boost::bind(&torrent:: x, t, a1)))); \ t.reset(); \ @@ -251,8 +266,9 @@ namespace libtorrent boost::shared_ptr t = m_torrent.lock(); \ if (!t) return def; \ bool done = false; \ - session_impl& ses = t->session(); \ + session_impl& ses = (session_impl&) t->session(); \ type r; \ + TORRENT_RECORD_BLOCKING_CALL \ mutex::scoped_lock l(ses.mut); \ ses.m_io_service.dispatch(boost::bind(&fun_ret, &r, &done, &ses.cond, &ses.mut, boost::function(boost::bind(&torrent:: x, t, a1, a2)))); \ t.reset(); \ @@ -299,7 +315,7 @@ namespace libtorrent void torrent_handle::set_upload_limit(int limit) const { TORRENT_ASSERT_PRECOND(limit >= -1); - TORRENT_ASYNC_CALL2(set_upload_limit, limit, true); + TORRENT_ASYNC_CALL1(set_upload_limit, limit); } int torrent_handle::upload_limit() const @@ -311,7 +327,7 @@ namespace libtorrent void torrent_handle::set_download_limit(int limit) const { TORRENT_ASSERT_PRECOND(limit >= -1); - TORRENT_ASYNC_CALL2(set_download_limit, limit, true); + TORRENT_ASYNC_CALL1(set_download_limit, limit); } int torrent_handle::download_limit() const @@ -491,6 +507,11 @@ namespace libtorrent return st; } + void torrent_handle::set_pinned(bool p) const + { + TORRENT_ASYNC_CALL1(set_pinned, p); + } + void torrent_handle::set_sequential_download(bool sd) const { TORRENT_ASYNC_CALL1(set_sequential_download, sd); @@ -517,6 +538,11 @@ namespace libtorrent TORRENT_ASYNC_CALL1(prioritize_pieces, pieces); } + void torrent_handle::prioritize_pieces(std::vector > const& pieces) const + { + TORRENT_ASYNC_CALL1(prioritize_piece_list, pieces); + } + std::vector torrent_handle::piece_priorities() const { std::vector ret; @@ -547,14 +573,19 @@ namespace libtorrent return ret; } +#ifndef TORRENT_NO_DEPRECATE +// ============ start deprecation =============== + + int torrent_handle::get_peer_upload_limit(tcp::endpoint ip) const { return -1; } + int torrent_handle::get_peer_download_limit(tcp::endpoint ip) const { return -1; } + void torrent_handle::set_peer_upload_limit(tcp::endpoint ip, int limit) const {} + void torrent_handle::set_peer_download_limit(tcp::endpoint ip, int limit) const {} + void torrent_handle::set_ratio(float ratio) const {} void torrent_handle::use_interface(const char* net_interface) const { TORRENT_ASYNC_CALL1(use_interface, std::string(net_interface)); } -#ifndef TORRENT_NO_DEPRECATE -// ============ start deprecation =============== - #if !TORRENT_NO_FPU void torrent_handle::file_progress(std::vector& progress) const { @@ -562,35 +593,6 @@ namespace libtorrent } #endif - int torrent_handle::get_peer_upload_limit(tcp::endpoint ip) const - { - TORRENT_SYNC_CALL_RET1(int, -1, get_peer_upload_limit, ip); - return r; - } - - int torrent_handle::get_peer_download_limit(tcp::endpoint ip) const - { - TORRENT_SYNC_CALL_RET1(int, -1, get_peer_download_limit, ip); - return r; - } - - void torrent_handle::set_peer_upload_limit(tcp::endpoint ip, int limit) const - { - TORRENT_ASSERT_PRECOND(limit >= -1); - TORRENT_ASYNC_CALL2(set_peer_upload_limit, ip, limit); - } - - void torrent_handle::set_peer_download_limit(tcp::endpoint ip, int limit) const - { - TORRENT_ASSERT_PRECOND(limit >= -1); - TORRENT_ASYNC_CALL2(set_peer_download_limit, ip, limit); - } - - void torrent_handle::set_ratio(float ratio) const - { - TORRENT_ASSERT_PRECOND(ratio >= 0.f); - } - bool torrent_handle::is_seed() const { TORRENT_SYNC_CALL_RET(bool, false, is_seed); @@ -743,10 +745,10 @@ namespace libtorrent return !m_torrent.expired(); } - boost::intrusive_ptr torrent_handle::torrent_file() const + boost::shared_ptr torrent_handle::torrent_file() const { - TORRENT_SYNC_CALL_RET(boost::intrusive_ptr - , boost::intrusive_ptr(), get_torrent_copy); + TORRENT_SYNC_CALL_RET(boost::shared_ptr + , boost::shared_ptr(), get_torrent_copy); return r; } @@ -756,23 +758,16 @@ namespace libtorrent // forces the torrent to stay loaded while the client holds it torrent_info const& torrent_handle::get_torrent_info() const { -#ifdef BOOST_NO_EXCEPTIONS - const static torrent_info empty(sha1_hash(0)); -#endif - boost::shared_ptr t = m_torrent.lock(); - if (!t) -#ifdef BOOST_NO_EXCEPTIONS - return empty; -#else - throw_invalid_handle(); -#endif - if (!t->valid_metadata()) -#ifdef BOOST_NO_EXCEPTIONS - return empty; -#else - throw_invalid_handle(); -#endif - return t->torrent_file(); + static boost::shared_ptr holder[4]; + static int cursor = 0; + static mutex holder_mutex; + + boost::shared_ptr r = torrent_file(); + + mutex::scoped_lock l(holder_mutex); + holder[cursor++] = r; + cursor = cursor % (sizeof(holder) / sizeof(holder[0])); + return *r; } entry torrent_handle::write_resume_data() const @@ -783,11 +778,13 @@ namespace libtorrent if (t) { bool done = false; - session_impl& ses = t->session(); + session_impl& ses = (session_impl&) t->session(); + TORRENT_RECORD_BLOCKING_CALL mutex::scoped_lock l(ses.mut); + storage_error ec; ses.m_io_service.dispatch(boost::bind(&fun_wrap, &done, &ses.cond , &ses.mut, boost::function(boost::bind( - &piece_manager::write_resume_data, &t->filesystem(), boost::ref(ret))))); + &piece_manager::write_resume_data, &t->storage(), boost::ref(ret), boost::ref(ec))))); t.reset(); while (!done) { ses.cond.wait(l); } } @@ -809,9 +806,9 @@ namespace libtorrent #endif - void torrent_handle::connect_peer(tcp::endpoint const& adr, int source) const + void torrent_handle::connect_peer(tcp::endpoint const& adr, int source, int flags) const { - TORRENT_ASYNC_CALL2(add_peer, adr, source); + TORRENT_ASYNC_CALL3(add_peer, adr, source, flags); } #ifndef TORRENT_NO_DEPRECATE @@ -835,6 +832,16 @@ namespace libtorrent TORRENT_ASYNC_CALL2(force_tracker_request, time_now() + seconds(s), idx); } + void torrent_handle::file_status(std::vector& status) const + { + status.clear(); + + boost::shared_ptr t = m_torrent.lock(); + if (!t || !t->has_storage()) return; + session_impl& ses = (session_impl&) t->session(); + ses.m_disk_thread.files().get_status(&status, &t->storage()); + } + void torrent_handle::scrape_tracker() const { TORRENT_ASYNC_CALL(scrape_tracker); diff --git a/src/torrent_info.cpp b/src/torrent_info.cpp index d81db9290..14f9d4d44 100644 --- a/src/torrent_info.cpp +++ b/src/torrent_info.cpp @@ -39,19 +39,6 @@ POSSIBILITY OF SUCH DAMAGE. #include #include -#include - -#ifdef _MSC_VER -#pragma warning(push, 1) -#endif - -#include -#include - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - #include "libtorrent/config.hpp" #include "libtorrent/torrent_info.hpp" #include "libtorrent/escape_string.hpp" // is_space @@ -62,10 +49,26 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/utf8.hpp" #include "libtorrent/time.hpp" #include "libtorrent/invariant_check.hpp" -#include "libtorrent/session_settings.hpp" +#include "libtorrent/aux_/session_settings.hpp" #include "libtorrent/add_torrent_params.hpp" #include "libtorrent/magnet_uri.hpp" +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif + +#include +#include +#if TORRENT_HAS_BOOST_UNORDERED +#include +#endif + +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + #if TORRENT_USE_I2P #include "libtorrent/parse_url.hpp" #endif @@ -90,6 +93,7 @@ namespace libtorrent TORRENT_EXTRA_EXPORT bool verify_encoding(std::string& target, bool fix_paths = false) { std::string tmp_path; + tmp_path.reserve(target.size()+5); bool valid_encoding = true; for (std::string::iterator i = target.begin() , end(target.end()); i != end; ++i) @@ -178,117 +182,196 @@ namespace libtorrent return valid_encoding; } - // TODO: 1 we might save constructing a std::string if this would take a char const* instead - bool valid_path_element(std::string const& element) + void sanitize_append_path_element(std::string& path, char const* element, int element_len) { - if (element.empty() - || element == "." || element == ".." - || element[0] == '/' || element[0] == '\\' - || element[element.size()-1] == ':') - return false; - return true; - } + if (element_len == 1 && element[0] == '.') return; - TORRENT_EXTRA_EXPORT void trim_path_element(std::string& element) - { - const int max_path_len = TORRENT_MAX_PATH; - - // on windows, the max path is expressed in - // unicode characters, not bytes -#if defined TORRENT_WINDOWS && TORRENT_USE_WSTRING - std::wstring path_element; - utf8_wchar(element, path_element); - if (path_element.size() > max_path_len) - { - // truncate filenames that are too long. But keep extensions! - std::wstring ext; - wchar_t const* ext1 = wcsrchr(path_element.c_str(), '.'); - if (ext1 != NULL) ext = ext1; - - if (ext.size() > 15) - { - path_element.resize(max_path_len); - } - else - { - path_element.resize(max_path_len - ext.size()); - path_element += ext; - } - } - // remove trailing spaces and dots. These aren't allowed in filenames on windows - for (int i = path_element.size() - 1; i >= 0; --i) - { - if (path_element[i] != L' ' && path_element[i] != L'.') break; - path_element.resize(i); - } - if (path_element.empty()) path_element = L"_"; - wchar_utf8(path_element, element); +#ifdef TORRENT_WINDOWS +#define TORRENT_SEPARATOR "\\" #else - std::string& path_element = element; - if (int(path_element.size()) > max_path_len) +#define TORRENT_SEPARATOR "/" +#endif + path.reserve(path.size() + element_len + 2); + int added_separator = 0; + if (!path.empty()) { + path += TORRENT_SEPARATOR; + added_separator = 1; + } - // truncate filenames that are too long. But keep extensions! - std::string ext = extension(path_element); - if (ext.size() > 15) + if (element_len == 0) + { + path += "_"; + return; + } + +#if !TORRENT_USE_UNC_PATHS && defined TORRENT_WINDOWS + // if we're not using UNC paths on windows, there + // are certain filenames we're not allowed to use + const static char const* reserved_names[] = + { + "con", "prn", "aux", "clock$", "nul", + "com0", "com1", "com2", "com3", "com4", + "com5", "com6", "com7", "com8", "com9", + "lpt0", "lpt1", "lpt2", "lpt3", "lpt4", + "lpt5", "lpt6", "lpt7", "lpt8", "lpt9" + }; + int num_names = sizeof(reserved_names)/sizeof(reserved_names[0]); + + // this is not very efficient, but it only affects some specific + // windows builds for now anyway (not even the default windows build) + std::string pe(element, element_len); + char const* file_end = strrchr(pe.c_str(), '.'); + std::string name; + if (file_end) name.assign(pe.c_str(), file_end); + else name = pe; + std::transform(name.begin(), name.end(), name.begin(), &to_lower); + char const* str = std::find(reserved_names, reserved_names + num_names, name); + if (str != reserved + num_names) + { + pe = "_" + pe; + element = pe.c_str(); + element_len = pe.size(); + } +#endif + // this counts the number of unicode characters + // we've added (which is different from the number + // of bytes) + int unicode_chars = 0; + + int added = 0; + // the number of dots we've added + char num_dots = 0; + bool found_extension = false; + for (int i = 0; i < element_len; ++i) + { + if (element[i] == '/' + || element[i] == '\\' +#ifdef TORRENT_WINDOWS + || element[i] == ':' +#endif + ) + continue; + + if (element[i] == '.') ++num_dots; + + int last_len = 0; + + if ((element[i] & 0x80) == 0) { - path_element.resize(max_path_len); + // 1 byte + path += element[i]; + last_len = 1; } - else + else if ((element[i] & 0xe0) == 0xc0) { - path_element.resize(max_path_len - ext.size()); - path_element += ext; + // 2 bytes + if (element_len - i < 2 + || (element[i+1] & 0xc0) != 0x80) + { + path += '?'; + last_len = 1; + } + else + { + path += element[i]; + path += element[i+1]; + last_len = 2; + } + i += 1; } + else if ((element[i] & 0xf0) == 0xe0) + { + // 3 bytes + if (element_len - i < 3 + || (element[i+1] & 0xc0) != 0x80 + || (element[i+2] & 0xc0) != 0x80 + ) + { + path += '?'; + last_len = 1; + } + else + { + path += element[i]; + path += element[i+1]; + path += element[i+2]; + last_len = 3; + } + i += 2; + } + else if ((element[i] & 0xf8) == 0xf0) + { + // 4 bytes + if (element_len - i < 4 + || (element[i+1] & 0xc0) != 0x80 + || (element[i+2] & 0xc0) != 0x80 + || (element[i+3] & 0xc0) != 0x80 + ) + { + path += '?'; + last_len = 1; + } + else + { + path += element[i]; + path += element[i+1]; + path += element[i+2]; + path += element[i+3]; + last_len = 4; + } + i += 3; + } + + added += last_len; + ++unicode_chars; + + // any given path element should not + // be more than 255 characters + // if we exceed 240, pick up any potential + // file extension and add that too +#ifdef TORRENT_WINDOWS + if (unicode_chars >= 240 && !found_extension) +#else + if (added >= 240 && !found_extension) +#endif + { + int dot = -1; + for (int j = element_len-1; j > (std::max)(element_len - 10, i); --j) + { + if (element[j] != '.') continue; + dot = j; + break; + } + // there is no extension + if (dot == -1) break; + found_extension = true; + i = dot - 1; + } + } + + if (added == num_dots && added <= 2) + { + // revert everything + path.erase(path.end()-added-added_separator, path.end()); + return; + } + + if (added == 0 && added_separator) + { + // remove the separator added at the beginning + path.erase(path.end()-1); + return; } // remove trailing spaces and dots. These aren't allowed in filenames on windows - // apply rules consistently across platforms though - for (int i = path_element.size() - 1; i >= 0; --i) + for (int i = path.size() - 1; i >= 0; --i) { - if (path_element[i] != ' ' && path_element[i] != '.') break; - path_element.resize(i); + if (path[i] != ' ' && path[i] != '.') break; + path.resize(i); } - if (path_element.empty()) path_element = "_"; -#endif - } - - TORRENT_EXTRA_EXPORT std::string sanitize_path(std::string const& p) - { - std::string new_path; - std::string split = split_path(p); - for (char const* e = split.c_str(); e != 0; e = next_path_element(e)) - { - std::string pe = e; -#if !TORRENT_USE_UNC_PATHS && defined TORRENT_WINDOWS - // if we're not using UNC paths on windows, there - // are certain filenames we're not allowed to use - const static char const* reserved_names[] = - { - "con", "prn", "aux", "clock$", "nul", - "com0", "com1", "com2", "com3", "com4", - "com5", "com6", "com7", "com8", "com9", - "lpt0", "lpt1", "lpt2", "lpt3", "lpt4", - "lpt5", "lpt6", "lpt7", "lpt8", "lpt9" - }; - int num_names = sizeof(reserved_names)/sizeof(reserved_names[0]); - - char const* file_end = strrchr(pe.c_str(), '.'); - std::string name; - if (file_end) name.assign(pe.c_str(), file_end); - else name = pe; - std::transform(name.begin(), name.end(), name.begin(), &to_lower); - char const** str = std::find(reserved_names, reserved_names + num_names, name); - if (str != reserved_names + num_names) - { - pe += "_"; - } -#endif - if (!valid_path_element(pe)) continue; - trim_path_element(pe); - new_path = combine_path(new_path, pe); - } - return new_path; + if (path.empty()) path = "_"; } bool extract_single_file(lazy_entry const& dict, file_entry& target @@ -317,20 +400,15 @@ namespace libtorrent return false; std::string path = root_dir; + std::string path_element; for (int i = 0, end(p->list_size()); i < end; ++i) { if (p->list_at(i)->type() != lazy_entry::string_t) return false; - std::string path_element = p->list_at(i)->string_value(); - if (path_element.empty()) - path_element = "_"; - if (!valid_path_element(path_element)) continue; if (i == end - 1) *filename = p->list_at(i); - trim_path_element(path_element); - path = combine_path(path, path_element); + sanitize_append_path_element(path + , p->list_at(i)->string_ptr(), p->list_at(i)->string_length()); } - path = sanitize_path(path); - verify_encoding(path, true); // bitcomet pad file if (path.find("_____padding_file_") != std::string::npos) @@ -363,7 +441,6 @@ namespace libtorrent for (int i = 0, end(s_p->list_size()); i < end; ++i) { std::string path_element = s_p->list_at(i)->string_value(); - trim_path_element(path_element); target.symlink_path = combine_path(target.symlink_path, path_element); } } @@ -375,9 +452,46 @@ namespace libtorrent return true; } +#if TORRENT_HAS_BOOST_UNORDERED + struct string_hash_no_case + { + size_t operator()(std::string const& s) const + { + char const* s1 = s.c_str(); + size_t ret = 5381; + int c; + + while ((c = *s1++)) + ret = (ret * 33) ^ to_lower(c); + + return ret; + } + }; + + struct string_eq_no_case + { + bool operator()(std::string const& lhs, std::string const& rhs) const + { + char c1, c2; + char const* s1 = lhs.c_str(); + char const* s2 = rhs.c_str(); + + while (*s1 != 0 && *s2 != 0) + { + c1 = to_lower(*s1); + c2 = to_lower(*s2); + if (c1 != c2) return false; + ++s1; + ++s2; + } + return *s1 == *s2; + } + }; + +#else struct string_less_no_case { - bool operator()(std::string const& lhs, std::string const& rhs) + bool operator()(std::string const& lhs, std::string const& rhs) const { char c1, c2; char const* s1 = lhs.c_str(); @@ -395,6 +509,7 @@ namespace libtorrent return false; } }; +#endif bool extract_files(lazy_entry const& list, file_storage& target , std::string const& root_dir, ptrdiff_t info_ptr_diff) @@ -402,11 +517,6 @@ namespace libtorrent if (list.type() != lazy_entry::list_t) return false; target.reserve(list.list_size()); - // TODO: 1 this logic should be a separate step - // done once the torrent is loaded, and the original - // filenames should be preserved! - std::set files; - for (int i = 0, end(list.list_size()); i < end; ++i) { lazy_entry const* file_hash = 0; @@ -417,36 +527,17 @@ namespace libtorrent , &file_hash, &fee, &mtime)) return false; - // as long as this file already exists - // increase the counter - int cnt = 0; - if (!files.insert(e.path).second) - { - std::string base = remove_extension(e.path); - std::string ext = extension(e.path); - do - { - ++cnt; - char new_ext[50]; - snprintf(new_ext, sizeof(new_ext), ".%d%s", cnt, ext.c_str()); - e.path = base + new_ext; - } while (!files.insert(e.path).second); - } target.add_file(e, file_hash ? file_hash->string_ptr() + info_ptr_diff : 0); // This is a memory optimization! Instead of having // each entry keep a string for its filename, make it // simply point into the info-section buffer int last_index = target.num_files() - 1; - // TODO: 1 once the filename renaming is removed from here - // this check can be removed as well - if (fee && target.file_name(last_index) == fee->string_value()) - { - // this string pointer does not necessarily point into - // the m_info_section buffer. - char const* str_ptr = fee->string_ptr() + info_ptr_diff; - target.rename_file_borrow(last_index, str_ptr, fee->string_length()); - } + + // this string pointer does not necessarily point into + // the m_info_section buffer. + char const* str_ptr = fee->string_ptr() + info_ptr_diff; + target.rename_file_borrow(last_index, str_ptr, fee->string_length()); } return true; } @@ -546,14 +637,21 @@ namespace libtorrent int announce_entry::min_announce_in() const { return total_seconds(min_announce - time_now()); } - void announce_entry::failed(session_settings const& sett, int retry_interval) + void announce_entry::reset() + { + start_sent = false; + next_announce = min_time(); + min_announce = min_time(); + } + + void announce_entry::failed(aux::session_settings const& sett, int retry_interval) { ++fails; // the exponential back-off ends up being: // 7, 15, 27, 45, 95, 127, 165, ... seconds // with the default tracker_backoff of 250 int delay = (std::min)(tracker_retry_delay_min + int(fails) * int(fails) - * tracker_retry_delay_min * sett.tracker_backoff / 100 + * tracker_retry_delay_min * sett.get_int(settings_pack::tracker_backoff) / 100 , int(tracker_retry_delay_max)); delay = (std::max)(delay, retry_interval); next_announce = time_now() + seconds(delay); @@ -581,20 +679,22 @@ namespace libtorrent web_seed_entry::web_seed_entry(std::string const& url_, type_t type_ , std::string const& auth_ , headers_t const& extra_headers_) - : url(url_), type(type_) - , auth(auth_), extra_headers(extra_headers_) + : url(url_) + , auth(auth_) + , extra_headers(extra_headers_) , retry(time_now()) - , supports_keepalive(true) - , resolving(false), removed(false) , peer_info(tcp::endpoint(), true, 0) + , type(type_) + , supports_keepalive(true) + , resolving(false) + , removed(false) { peer_info.web_seed = true; restart_request.piece = -1; } - torrent_info::torrent_info(torrent_info const& t, int flags) - : m_merkle_first_leaf(t.m_merkle_first_leaf) - , m_files(t.m_files) + torrent_info::torrent_info(torrent_info const& t) + : m_files(t.m_files) , m_orig_files(t.m_orig_files) , m_urls(t.m_urls) , m_web_seeds(t.m_web_seeds) @@ -605,6 +705,7 @@ namespace libtorrent , m_created_by(t.m_created_by) , m_creation_date(t.m_creation_date) , m_info_hash(t.m_info_hash) + , m_merkle_first_leaf(t.m_merkle_first_leaf) , m_info_section_size(t.m_info_section_size) , m_multifile(t.m_multifile) , m_private(t.m_private) @@ -637,10 +738,56 @@ namespace libtorrent INVARIANT_CHECK; } + void torrent_info::resolve_duplicate_filenames() + { + INVARIANT_CHECK; + int cnt = 0; + +#if TORRENT_HAS_BOOST_UNORDERED + boost::unordered_set files; +#else + std::set files; +#endif + std::vector const& paths = m_files.paths(); + + // insert all directories first, to make sure no files + // are allowed to collied with them + for (std::vector::const_iterator i = paths.begin() + , end(paths.end()); i != end; ++i) + { + files.insert(combine_path(m_files.name(), *i)); + } + + for (int i = 0; i < m_files.num_files(); ++i) + { + // as long as this file already exists + // increase the counter + std::string filename = m_files.file_path(i); + if (!files.insert(filename).second) + { + std::string base = remove_extension(filename); + std::string ext = extension(filename); + do + { + ++cnt; + char new_ext[50]; + snprintf(new_ext, sizeof(new_ext), ".%d%s", cnt, ext.c_str()); + filename = base + new_ext; + } + while (!files.insert(filename).second); + + copy_on_write(); + m_files.rename_file(i, filename); + } + cnt = 0; + } + } + void torrent_info::remap_files(file_storage const& f) { INVARIANT_CHECK; + TORRENT_ASSERT(is_loaded()); // the new specified file storage must have the exact // same size as the current file storage TORRENT_ASSERT(m_files.total_size() == f.total_size()); @@ -655,9 +802,9 @@ namespace libtorrent #ifndef TORRENT_NO_DEPRECATE // standard constructor that parses a torrent file torrent_info::torrent_info(entry const& torrent_file) - : m_merkle_first_leaf(0) - , m_piece_hashes(0) + : m_piece_hashes(0) , m_creation_date(0) + , m_merkle_first_leaf(0) , m_info_section_size(0) , m_multifile(false) , m_private(false) @@ -688,9 +835,9 @@ namespace libtorrent #ifndef BOOST_NO_EXCEPTIONS torrent_info::torrent_info(lazy_entry const& torrent_file, int flags) - : m_merkle_first_leaf(0) - , m_piece_hashes(0) + : m_piece_hashes(0) , m_creation_date(0) + , m_merkle_first_leaf(0) , m_info_section_size(0) , m_multifile(false) , m_private(false) @@ -704,9 +851,9 @@ namespace libtorrent } torrent_info::torrent_info(char const* buffer, int size, int flags) - : m_merkle_first_leaf(0) - , m_piece_hashes(0) + : m_piece_hashes(0) , m_creation_date(0) + , m_merkle_first_leaf(0) , m_info_section_size(0) , m_multifile(false) , m_private(false) @@ -724,9 +871,9 @@ namespace libtorrent } torrent_info::torrent_info(std::string const& filename, int flags) - : m_merkle_first_leaf(0) - , m_piece_hashes(0) + : m_piece_hashes(0) , m_creation_date(0) + , m_merkle_first_leaf(0) , m_info_section_size(0) , m_multifile(false) , m_private(false) @@ -750,9 +897,9 @@ namespace libtorrent #if TORRENT_USE_WSTRING #ifndef TORRENT_NO_DEPRECATE torrent_info::torrent_info(std::wstring const& filename, int flags) - : m_merkle_first_leaf(0) - , m_piece_hashes(0) + : m_piece_hashes(0) , m_creation_date(0) + , m_merkle_first_leaf(0) , m_info_section_size(0) , m_multifile(false) , m_private(false) @@ -777,6 +924,7 @@ namespace libtorrent void torrent_info::rename_file(int index, std::wstring const& new_filename) { + TORRENT_ASSERT(is_loaded()); copy_on_write(); m_files.rename_file_deprecated(index, new_filename); } @@ -785,9 +933,9 @@ namespace libtorrent #endif torrent_info::torrent_info(lazy_entry const& torrent_file, error_code& ec, int flags) - : m_merkle_first_leaf(0) - , m_piece_hashes(0) + : m_piece_hashes(0) , m_creation_date(0) + , m_merkle_first_leaf(0) , m_info_section_size(0) , m_multifile(false) , m_private(false) @@ -799,9 +947,9 @@ namespace libtorrent } torrent_info::torrent_info(char const* buffer, int size, error_code& ec, int flags) - : m_merkle_first_leaf(0) - , m_piece_hashes(0) + : m_piece_hashes(0) , m_creation_date(0) + , m_merkle_first_leaf(0) , m_info_section_size(0) , m_multifile(false) , m_private(false) @@ -816,9 +964,9 @@ namespace libtorrent } torrent_info::torrent_info(std::string const& filename, error_code& ec, int flags) - : m_merkle_first_leaf(0) - , m_piece_hashes(0) + : m_piece_hashes(0) , m_creation_date(0) + , m_merkle_first_leaf(0) , m_info_section_size(0) , m_multifile(false) , m_private(false) @@ -839,9 +987,9 @@ namespace libtorrent #if TORRENT_USE_WSTRING #ifndef TORRENT_NO_DEPRECATE torrent_info::torrent_info(std::wstring const& filename, error_code& ec, int flags) - : m_merkle_first_leaf(0) - , m_piece_hashes(0) + : m_piece_hashes(0) , m_creation_date(0) + , m_merkle_first_leaf(0) , m_info_section_size(0) , m_multifile(false) , m_private(false) @@ -868,10 +1016,10 @@ namespace libtorrent // just the necessary to use it with piece manager // used for torrents with no metadata torrent_info::torrent_info(sha1_hash const& info_hash, int flags) - : m_merkle_first_leaf(0) - , m_piece_hashes(0) + : m_piece_hashes(0) , m_creation_date(time(0)) , m_info_hash(info_hash) + , m_merkle_first_leaf(0) , m_info_section_size(0) , m_multifile(false) , m_private(false) @@ -881,8 +1029,40 @@ namespace libtorrent torrent_info::~torrent_info() {} + void torrent_info::load(char const* buffer, int size, error_code& ec) + { + lazy_entry e; + if (lazy_bdecode(buffer, buffer + size, e, ec) != 0) + return; + + if (!parse_torrent_file(e, ec, 0)) + return; + } + + void torrent_info::unload() + { + TORRENT_ASSERT(m_info_section.unique()); + + m_info_section.reset(); + m_info_section_size = 0; + + // if we have orig_files, we have to keep + // m_files around, since it means we have + // remapped files, and we won't be able to + // restore that from just reloading the + // torrent file + if (m_orig_files) m_orig_files.reset(); + else m_files.unload(); + + m_piece_hashes = 0; + std::vector().swap(m_web_seeds); + + TORRENT_ASSERT(!is_loaded()); + } + void torrent_info::copy_on_write() { + TORRENT_ASSERT(is_loaded()); INVARIANT_CHECK; if (m_orig_files) return; @@ -969,7 +1149,8 @@ namespace libtorrent ec = errors::torrent_missing_piece_length; return false; } - m_files.set_piece_length(piece_length); + file_storage files; + files.set_piece_length(piece_length); // extract file name (or the directory name if it's a multifile libtorrent) lazy_entry const* name_ent = info.dict_find_string("name.utf-8"); @@ -980,19 +1161,11 @@ namespace libtorrent return false; } - std::string name = name_ent->string_value(); + std::string name; + sanitize_append_path_element(name, name_ent->string_ptr(), name_ent->string_length()); + if (name.empty()) name = to_hex(m_info_hash.to_string()); - name = sanitize_path(name); - - if (!valid_path_element(name)) - { - ec = errors::torrent_invalid_name; - return false; - } - // correct utf-8 encoding errors - verify_encoding(name, true); - // extract file list lazy_entry const* i = info.dict_find_list("files"); if (i == 0) @@ -1030,7 +1203,6 @@ namespace libtorrent for (int i = 0, end(s_p->list_size()); i < end; ++i) { std::string path_element = s_p->list_at(i)->string_value(); - trim_path_element(path_element); e.symlink_path = combine_path(e.symlink_path, path_element); } } @@ -1045,26 +1217,26 @@ namespace libtorrent // bitcomet pad file if (e.path.find("_____padding_file_") != std::string::npos) e.pad_file = true; - m_files.add_file(e, fh ? fh->string_ptr() + info_ptr_diff : 0); + files.add_file(e, fh ? fh->string_ptr() + info_ptr_diff : 0); m_multifile = false; } else { - if (!extract_files(*i, m_files, name, info_ptr_diff)) + if (!extract_files(*i, files, name, info_ptr_diff)) { ec = errors::torrent_file_parse_failed; return false; } m_multifile = true; } - m_files.set_name(name); + files.set_name(name); // extract sha-1 hashes for all pieces // we want this division to round upwards, that's why we have the // extra addition - m_files.set_num_pieces(int((m_files.total_size() + m_files.piece_length() - 1) - / m_files.piece_length())); + files.set_num_pieces(int((files.total_size() + files.piece_length() - 1) + / files.piece_length())); lazy_entry const* pieces = info.dict_find_string("pieces"); lazy_entry const* root_hash = info.dict_find_string("root hash"); @@ -1076,7 +1248,7 @@ namespace libtorrent if (pieces) { - if (pieces->string_length() != m_files.num_pieces() * 20) + if (pieces->string_length() != files.num_pieces() * 20) { ec = errors::torrent_invalid_hashes; return false; @@ -1094,7 +1266,7 @@ namespace libtorrent ec = errors::torrent_invalid_hashes; return false; } - int num_leafs = merkle_num_leafs(m_files.num_pieces()); + int num_leafs = merkle_num_leafs(files.num_pieces()); int num_nodes = merkle_num_nodes(num_leafs); m_merkle_first_leaf = num_nodes - num_leafs; m_merkle_tree.resize(num_nodes); @@ -1104,6 +1276,21 @@ namespace libtorrent m_private = info.dict_find_int_value("private", 0); + // now, commit the files structure we just parsed out + // into the torrent_info object. + // if we already have an m_files that's populated, it + // indicates that we unloaded this torrent_info ones + // and we had modifications to the files, so we unloaded + // the orig_files. In that case, the orig files is what + // needs to be restored + if (m_files.is_loaded()) { + m_orig_files.reset(new file_storage); + const_cast(*m_orig_files).swap(files); + } + else + { + m_files.swap(files); + } return true; } @@ -1226,6 +1413,7 @@ namespace libtorrent return false; } if (!parse_info_section(*info, ec, flags)) return false; + resolve_duplicate_filenames(); // extract the url of the tracker lazy_entry const* i = torrent_file.dict_find_list("announce-list"); diff --git a/src/torrent_peer.cpp b/src/torrent_peer.cpp new file mode 100644 index 000000000..7dc4fcfc3 --- /dev/null +++ b/src/torrent_peer.cpp @@ -0,0 +1,281 @@ +/* + +Copyright (c) 2012-2013, 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. + +*/ + +#include "libtorrent/torrent_peer.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/string_util.hpp" +#include "libtorrent/peer_connection.hpp" +#include "libtorrent/crc32c.hpp" +#include "libtorrent/ip_voter.hpp" + +namespace libtorrent +{ + void apply_mask(boost::uint8_t* b, boost::uint8_t const* mask, int size) + { + for (int i = 0; i < size; ++i) + { + *b &= *mask; + ++b; + ++mask; + } + } + + // 1. if the IP addresses are identical, hash the ports in 16 bit network-order + // binary representation, ordered lowest first. + // 2. if the IPs are in the same /24, hash the IPs ordered, lowest first. + // 3. if the IPs are in the ame /16, mask the IPs by 0xffffff55, hash them + // ordered, lowest first. + // 4. if IPs are not in the same /16, mask the IPs by 0xffff5555, hash them + // ordered, lowest first. + // + // * for IPv6 peers, just use the first 64 bits and widen the masks. + // like this: 0xffff5555 -> 0xffffffff55555555 + // the lower 64 bits are always unmasked + // + // * for IPv6 addresses, compare /32 and /48 instead of /16 and /24 + // + // * the two IP addresses that are used to calculate the rank must + // always be of the same address family + // + // * all IP addresses are in network byte order when hashed + boost::uint32_t peer_priority(tcp::endpoint e1, tcp::endpoint e2) + { + TORRENT_ASSERT(e1.address().is_v4() == e2.address().is_v4()); + + using std::swap; + + boost::uint32_t ret; + if (e1.address() == e2.address()) + { + if (e1.port() > e2.port()) + swap(e1, e2); + boost::uint32_t p; + reinterpret_cast(&p)[0] = htons(e1.port()); + reinterpret_cast(&p)[1] = htons(e2.port()); + ret = crc32c_32(p); + } +#if TORRENT_USE_IPV6 + else if (e1.address().is_v6()) + { + const static boost::uint8_t v6mask[][8] = { + { 0xff, 0xff, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55 }, + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x55, 0x55 }, + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } + }; + + if (e1 > e2) swap(e1, e2); + address_v6::bytes_type b1 = e1.address().to_v6().to_bytes(); + address_v6::bytes_type b2 = e2.address().to_v6().to_bytes(); + int mask = memcmp(&b1[0], &b2[0], 4) ? 0 + : memcmp(&b1[0], &b2[0], 6) ? 1 : 2; + apply_mask(&b1[0], v6mask[mask], 8); + apply_mask(&b2[0], v6mask[mask], 8); + boost::uint64_t addrbuf[4]; + memcpy(&addrbuf[0], &b1[0], 16); + memcpy(&addrbuf[2], &b2[0], 16); + ret = crc32c(addrbuf, 4); + } +#endif + else + { + const static boost::uint8_t v4mask[][4] = { + { 0xff, 0xff, 0x55, 0x55 }, + { 0xff, 0xff, 0xff, 0x55 }, + { 0xff, 0xff, 0xff, 0xff } + }; + + if (e1 > e2) swap(e1, e2); + address_v4::bytes_type b1 = e1.address().to_v4().to_bytes(); + address_v4::bytes_type b2 = e2.address().to_v4().to_bytes(); + int mask = memcmp(&b1[0], &b2[0], 2) ? 0 + : memcmp(&b1[0], &b2[0], 3) ? 1 : 2; + apply_mask(&b1[0], v4mask[mask], 4); + apply_mask(&b2[0], v4mask[mask], 4); + boost::uint64_t addrbuf; + memcpy(&addrbuf, &b1[0], 4); + memcpy(reinterpret_cast(&addrbuf) + 4, &b2[0], 4); + ret = crc32c(&addrbuf, 1); + } + + return ret; + } + + torrent_peer::torrent_peer(boost::uint16_t port, bool conn, int src) + : prev_amount_upload(0) + , prev_amount_download(0) + , connection(0) + , peer_rank(0) +#ifndef TORRENT_DISABLE_GEO_IP + , inet_as(0) +#endif + , last_optimistically_unchoked(0) + , last_connected(0) + , port(port) + , hashfails(0) + , failcount(0) + , connectable(conn) + , optimistically_unchoked(false) + , seed(false) + , fast_reconnects(0) + , trust_points(0) + , source(src) +#ifndef TORRENT_DISABLE_ENCRYPTION + // assume no support in order to + // prefer opening non-encrypyed + // connections. If it fails, we'll + // retry with encryption + , pe_support(false) +#endif +#if TORRENT_USE_IPV6 + , is_v6_addr(false) +#endif +#if TORRENT_USE_I2P + , is_i2p_addr(false) +#endif + , on_parole(false) + , banned(false) + , supports_utp(true) // assume peers support utp + , confirmed_supports_utp(false) + , supports_holepunch(false) + , web_seed(false) +#if TORRENT_USE_ASSERTS + , in_use(false) +#endif + { + TORRENT_ASSERT((src & 0xff) == src); + } + + boost::uint32_t torrent_peer::rank(external_ip const& external, int external_port) const + { +//TODO: how do we deal with our external address changing? + if (peer_rank == 0) + peer_rank = peer_priority( + tcp::endpoint(external.external_address(this->address()), external_port) + , tcp::endpoint(this->address(), this->port)); + return peer_rank; + } + + boost::uint64_t torrent_peer::total_download() const + { + if (connection != 0) + { + TORRENT_ASSERT(prev_amount_download == 0); + return connection->statistics().total_payload_download(); + } + else + { + return boost::uint64_t(prev_amount_download) << 10; + } + } + + boost::uint64_t torrent_peer::total_upload() const + { + if (connection != 0) + { + TORRENT_ASSERT(prev_amount_upload == 0); + return connection->statistics().total_payload_upload(); + } + else + { + return boost::uint64_t(prev_amount_upload) << 10; + } + } + + ipv4_peer::ipv4_peer( + tcp::endpoint const& ep, bool c, int src + ) + : torrent_peer(ep.port(), c, src) + , addr(ep.address().to_v4()) + { +#if TORRENT_USE_IPV6 + is_v6_addr = false; +#endif +#if TORRENT_USE_I2P + is_i2p_addr = false; +#endif + } + +#if TORRENT_USE_I2P + i2p_peer::i2p_peer(char const* dest, bool connectable, int src) + : torrent_peer(0, connectable, src), destination(allocate_string_copy(dest)) + { +#if TORRENT_USE_IPV6 + is_v6_addr = false; +#endif + is_i2p_addr = true; + } + + i2p_peer::~i2p_peer() + { free(destination); } +#endif // TORRENT_USE_I2P + +#if TORRENT_USE_IPV6 + ipv6_peer::ipv6_peer( + tcp::endpoint const& ep, bool c, int src + ) + : torrent_peer(ep.port(), c, src) + , addr(ep.address().to_v6().to_bytes()) + { + is_v6_addr = true; +#if TORRENT_USE_I2P + is_i2p_addr = false; +#endif + } + +#endif // TORRENT_USE_IPV6 + +#if TORRENT_USE_I2P + char const* torrent_peer::dest() const + { + if (is_i2p_addr) + return static_cast(this)->destination; + return ""; + } +#endif + + libtorrent::address torrent_peer::address() const + { +#if TORRENT_USE_IPV6 + if (is_v6_addr) + return libtorrent::address_v6( + static_cast(this)->addr); + else +#endif +#if TORRENT_USE_I2P + if (is_i2p_addr) return libtorrent::address(); + else +#endif + return static_cast(this)->addr; + } + +} + diff --git a/src/torrent_peer_allocator.cpp b/src/torrent_peer_allocator.cpp new file mode 100644 index 000000000..1c031a5ed --- /dev/null +++ b/src/torrent_peer_allocator.cpp @@ -0,0 +1,134 @@ +/* + +Copyright (c) 2003-2013, 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. + +*/ + + +#include "libtorrent/config.hpp" +#include "libtorrent/assert.hpp" +#include "libtorrent/torrent_peer_allocator.hpp" + +namespace libtorrent +{ + + torrent_peer_allocator::torrent_peer_allocator() + : m_ipv4_peer_pool(sizeof(libtorrent::ipv4_peer), 500) +#if TORRENT_USE_IPV6 + , m_ipv6_peer_pool(sizeof(libtorrent::ipv6_peer), 500) +#endif +#if TORRENT_USE_I2P + , m_i2p_peer_pool(sizeof(libtorrent::i2p_peer), 500) +#endif + , m_total_bytes(0) + , m_total_allocations(0) + , m_live_bytes(0) + , m_live_allocations(0) + { + } + + torrent_peer* torrent_peer_allocator::allocate_peer_entry(int type) + { + torrent_peer* p = NULL; + switch(type) + { + case torrent_peer_allocator_interface::ipv4_peer_type: + p = (torrent_peer*)m_ipv4_peer_pool.malloc(); + if (p == NULL) return NULL; + m_ipv4_peer_pool.set_next_size(500); + m_total_bytes += sizeof(libtorrent::ipv4_peer); + m_live_bytes += sizeof(libtorrent::ipv4_peer); + ++m_live_allocations; + ++m_total_allocations; + break; +#if TORRENT_USE_IPV6 + case torrent_peer_allocator_interface::ipv6_peer_type: + p = (torrent_peer*)m_ipv6_peer_pool.malloc(); + if (p == NULL) return NULL; + m_ipv6_peer_pool.set_next_size(500); + m_total_bytes += sizeof(libtorrent::ipv6_peer); + m_live_bytes += sizeof(libtorrent::ipv6_peer); + ++m_live_allocations; + ++m_total_allocations; + break; +#endif +#if TORRENT_USE_I2P + case torrent_peer_allocator_interface::i2p_peer_type: + p = (torrent_peer*)m_i2p_peer_pool.malloc(); + if (p == NULL) return NULL; + m_i2p_peer_pool.set_next_size(500); + m_total_bytes += sizeof(libtorrent::i2p_peer); + m_live_bytes += sizeof(libtorrent::i2p_peer); + ++m_live_allocations; + ++m_total_allocations; + break; +#endif + } + return p; + } + + void torrent_peer_allocator::free_peer_entry(torrent_peer* p) + { +#if TORRENT_USE_IPV6 + if (p->is_v6_addr) + { + TORRENT_ASSERT(m_ipv6_peer_pool.is_from((libtorrent::ipv6_peer*)p)); + static_cast(p)->~ipv6_peer(); + m_ipv6_peer_pool.free(p); + TORRENT_ASSERT(m_live_bytes >= sizeof(ipv6_peer)); + m_live_bytes -= sizeof(ipv6_peer); + TORRENT_ASSERT(m_live_allocations > 0); + --m_live_allocations; + return; + } +#endif +#if TORRENT_USE_I2P + if (p->is_i2p_addr) + { + TORRENT_ASSERT(m_i2p_peer_pool.is_from((libtorrent::i2p_peer*)p)); + static_cast(p)->~i2p_peer(); + m_i2p_peer_pool.free(p); + TORRENT_ASSERT(m_live_bytes >= sizeof(i2p_peer)); + m_live_bytes -= sizeof(i2p_peer); + TORRENT_ASSERT(m_live_allocations > 0); + --m_live_allocations; + return; + } +#endif + TORRENT_ASSERT(m_ipv4_peer_pool.is_from((libtorrent::ipv4_peer*)p)); + static_cast(p)->~ipv4_peer(); + m_ipv4_peer_pool.free(p); + TORRENT_ASSERT(m_live_bytes >= sizeof(ipv4_peer)); + m_live_bytes -= sizeof(ipv4_peer); + TORRENT_ASSERT(m_live_allocations > 0); + --m_live_allocations; + } + +} + diff --git a/src/tracker_manager.cpp b/src/tracker_manager.cpp index b556325bb..fa6c66d04 100644 --- a/src/tracker_manager.cpp +++ b/src/tracker_manager.cpp @@ -56,10 +56,10 @@ namespace namespace libtorrent { timeout_handler::timeout_handler(io_service& ios) - : m_start_time(time_now_hires()) + : m_completion_timeout(0) + , m_start_time(time_now_hires()) , m_read_time(m_start_time) , m_timeout(ios) - , m_completion_timeout(0) , m_read_timeout(0) , m_abort(false) {} @@ -149,9 +149,9 @@ namespace libtorrent , io_service& ios , boost::weak_ptr r) : timeout_handler(ios) + , m_req(req) , m_requester(r) , m_man(man) - , m_req(req) {} boost::shared_ptr tracker_connection::requester() const @@ -200,13 +200,13 @@ namespace libtorrent void tracker_manager::sent_bytes(int bytes) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); m_ses.m_stat.sent_tracker_bytes(bytes); } void tracker_manager::received_bytes(int bytes) { - TORRENT_ASSERT(m_ses.is_network_thread()); + TORRENT_ASSERT(m_ses.is_single_thread()); m_ses.m_stat.received_tracker_bytes(bytes); } @@ -251,7 +251,7 @@ namespace libtorrent { con = new http_tracker_connection( ios, cc, *this, req, c - , m_ses, m_proxy, auth + , m_ses, auth #if TORRENT_USE_I2P , &m_ses.m_i2p_conn #endif @@ -260,8 +260,7 @@ namespace libtorrent else if (protocol == "udp") { con = new udp_tracker_connection( - ios, cc, *this, req , c, m_ses - , m_proxy); + ios, cc, *this, req , c, m_ses, m_ses.proxy()); } else { @@ -276,7 +275,6 @@ namespace libtorrent m_connections.push_back(con); boost::shared_ptr cb = con->requester(); - if (cb) cb->m_manager = this; con->start(); } @@ -322,7 +320,7 @@ namespace libtorrent for (tracker_connections_t::iterator i = m_connections.begin() , end(m_connections.end()); i != end; ++i) { - intrusive_ptr c = *i; + boost::intrusive_ptr c = *i; tracker_request const& req = c->tracker_req(); if (req.event == tracker_request::stopped && !all) continue; diff --git a/src/udp_socket.cpp b/src/udp_socket.cpp index dd9bc1a4f..9ef5776ee 100644 --- a/src/udp_socket.cpp +++ b/src/udp_socket.cpp @@ -39,6 +39,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/error.hpp" #include "libtorrent/string_util.hpp" // for allocate_string_copy #include "libtorrent/broadcast_socket.hpp" // for is_any +#include "libtorrent/settings_pack.hpp" #include #include #include @@ -93,9 +94,6 @@ udp_socket::udp_socket(asio::io_service& ios m_outstanding_timeout = 0; m_outstanding_resolve = 0; m_outstanding_socks = 0; -#if defined BOOST_HAS_PTHREADS - m_thread = 0; -#endif #endif m_buf_size = 2048; @@ -291,8 +289,8 @@ void udp_socket::on_read(error_code const& ec, udp::socket* s) // TODO: it would be nice to detect this on posix systems also #ifdef TORRENT_WINDOWS - if ((ec == error_code(ERROR_MORE_DATA, get_system_category()) - || ec == error_code(WSAEMSGSIZE, get_system_category())) + if ((ec == error_code(ERROR_MORE_DATA, system_category()) + || ec == error_code(WSAEMSGSIZE, system_category())) && m_buf_size < 65536) { // if this function fails to allocate memory, m_buf_size @@ -442,13 +440,13 @@ void udp_socket::on_read_impl(udp::socket* s, udp::endpoint const& ep && e != asio::error::network_unreachable #ifdef WIN32 // ERROR_MORE_DATA means the same thing as EMSGSIZE - && e != error_code(ERROR_MORE_DATA, get_system_category()) - && e != error_code(ERROR_HOST_UNREACHABLE, get_system_category()) - && e != error_code(ERROR_PORT_UNREACHABLE, get_system_category()) - && e != error_code(ERROR_RETRY, get_system_category()) - && e != error_code(ERROR_NETWORK_UNREACHABLE, get_system_category()) - && e != error_code(ERROR_CONNECTION_REFUSED, get_system_category()) - && e != error_code(ERROR_CONNECTION_ABORTED, get_system_category()) + && e != error_code(ERROR_MORE_DATA, system_category()) + && e != error_code(ERROR_HOST_UNREACHABLE, system_category()) + && e != error_code(ERROR_PORT_UNREACHABLE, system_category()) + && e != error_code(ERROR_RETRY, system_category()) + && e != error_code(ERROR_NETWORK_UNREACHABLE, system_category()) + && e != error_code(ERROR_CONNECTION_REFUSED, system_category()) + && e != error_code(ERROR_CONNECTION_ABORTED, system_category()) #endif && e != asio::error::message_size) { @@ -781,8 +779,8 @@ void udp_socket::set_proxy_settings(proxy_settings const& ps) if (m_abort) return; - if (ps.type == proxy_settings::socks5 - || ps.type == proxy_settings::socks5_pw) + if (ps.type == settings_pack::socks5 + || ps.type == settings_pack::socks5_pw) { m_queue_packets = true; // connect to socks5 server and open up the UDP tunnel @@ -858,11 +856,10 @@ void udp_socket::on_name_lookup(error_code const& e, tcp::resolver::iterator i) ++m_outstanding_timeout; ++m_outstanding_connect_queue; #endif - m_cc.enqueue(boost::bind(&udp_socket::on_connect, this, _1) - , boost::bind(&udp_socket::on_timeout, this), seconds(10)); + m_cc.enqueue(this, seconds(10)); } -void udp_socket::on_timeout() +void udp_socket::on_connect_timeout() { #if TORRENT_USE_ASSERTS TORRENT_ASSERT(m_outstanding_timeout > 0); @@ -889,7 +886,7 @@ void udp_socket::on_timeout() m_connection_ticket = -1; } -void udp_socket::on_connect(int ticket) +void udp_socket::on_allow_connect(int ticket) { TORRENT_ASSERT(is_single_thread()); #if TORRENT_USE_ASSERTS @@ -1013,7 +1010,7 @@ void udp_socket::on_connected(error_code const& e, int ticket) char* p = &m_tmp_buf[0]; write_uint8(5, p); // SOCKS VERSION 5 if (m_proxy_settings.username.empty() - || m_proxy_settings.type == proxy_settings::socks5) + || m_proxy_settings.type == settings_pack::socks5) { write_uint8(1, p); // 1 authentication method (no auth) write_uint8(0, p); // no authentication @@ -1426,6 +1423,16 @@ rate_limited_udp_socket::rate_limited_udp_socket(io_service& ios { } +bool rate_limited_udp_socket::has_quota() +{ + ptime now = time_now_hires(); + time_duration delta = now - m_last_tick; + m_last_tick = now; + // add any new quota we've accrued since last time + m_quota += boost::uint64_t(m_rate_limit) * total_microseconds(delta) / 1000000; + return m_quota > 0; +} + bool rate_limited_udp_socket::send(udp::endpoint const& ep, char const* p , int len, error_code& ec, int flags) { @@ -1441,7 +1448,7 @@ bool rate_limited_udp_socket::send(udp::endpoint const& ep, char const* p // if there's no quota, and it's OK to drop, just // drop the packet - if (m_quota < len && (flags & dont_drop) == 0) return false; + if (m_quota < 0 && (flags & dont_drop) == 0) return false; m_quota -= len; if (m_quota < 0) m_quota = 0; diff --git a/src/udp_tracker_connection.cpp b/src/udp_tracker_connection.cpp index 97f324bbd..148690f27 100644 --- a/src/udp_tracker_connection.cpp +++ b/src/udp_tracker_connection.cpp @@ -61,6 +61,7 @@ namespace libtorrent mutex udp_tracker_connection::m_cache_mutex; + // TODO: 2 it would be nice to not have a dependency on session_impl here udp_tracker_connection::udp_tracker_connection( io_service& ios , connection_queue& cc @@ -70,12 +71,12 @@ namespace libtorrent , aux::session_impl& ses , proxy_settings const& proxy) : tracker_connection(man, req, ios, c) - , m_abort(false) - , m_transaction_id(0) , m_ses(ses) + , m_proxy(proxy) + , m_transaction_id(0) , m_attempts(0) , m_state(action_error) - , m_proxy(proxy) + , m_abort(false) { } @@ -97,11 +98,11 @@ namespace libtorrent return; } - session_settings const& settings = m_ses.settings(); + aux::session_settings const& settings = m_ses.settings(); if (m_proxy.proxy_hostnames - && (m_proxy.type == proxy_settings::socks5 - || m_proxy.type == proxy_settings::socks5_pw)) + && (m_proxy.type == settings_pack::socks5 + || m_proxy.type == settings_pack::socks5_pw)) { m_hostname = hostname; m_target.port(port); @@ -112,10 +113,16 @@ namespace libtorrent #if defined TORRENT_ASIO_DEBUGGING add_outstanding_async("udp_tracker_connection::name_lookup"); #endif - tcp::resolver::query q(hostname, to_string(port).elems); - m_ses.m_host_resolver.async_resolve(q - , boost::bind( - &udp_tracker_connection::name_lookup, self(), _1, _2)); + // when stopping, pass in the prefer cache flag, because we + // don't want to get stuck on DNS lookups when shutting down + // if we can avoid it + m_ses.m_host_resolver.async_resolve(hostname + , tracker_req().event == tracker_request::stopped + ? resolver_interface::prefer_cache + : 0 + , boost::bind(&udp_tracker_connection::name_lookup + , self(), _1, _2, port)); + #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING boost::shared_ptr cb = requester(); if (cb) cb->debug_log("*** UDP_TRACKER [ initiating name lookup: \"%s\" ]" @@ -124,9 +131,9 @@ namespace libtorrent } set_timeout(tracker_req().event == tracker_request::stopped - ? settings.stop_tracker_timeout - : settings.tracker_completion_timeout - , settings.tracker_receive_timeout); + ? settings.get_int(settings_pack::stop_tracker_timeout) + : settings.get_int(settings_pack::tracker_completion_timeout) + , settings.get_int(settings_pack::tracker_receive_timeout)); } void udp_tracker_connection::fail(error_code const& ec, int code @@ -163,14 +170,14 @@ namespace libtorrent } void udp_tracker_connection::name_lookup(error_code const& error - , tcp::resolver::iterator i) + , std::vector
const& addresses, int port) { #if defined TORRENT_ASIO_DEBUGGING complete_async("udp_tracker_connection::name_lookup"); #endif if (m_abort) return; if (error == asio::error::operation_aborted) return; - if (error || i == tcp::resolver::iterator()) + if (error || addresses.empty()) { fail(error); return; @@ -192,8 +199,9 @@ namespace libtorrent // we're listening on. To make sure the tracker get our // correct listening address. - std::transform(i, tcp::resolver::iterator(), std::back_inserter(m_endpoints) - , boost::bind(&tcp::resolver::iterator::value_type::endpoint, _1)); + for (std::vector
::const_iterator i = addresses.begin() + , end(addresses.end()); i != end; ++i) + m_endpoints.push_back(tcp::endpoint(*i, port)); if (tracker_req().apply_ip_filter) { @@ -223,8 +231,6 @@ namespace libtorrent m_target = pick_target_endpoint(); - if (cb) cb->m_tracker_address = tcp::endpoint(m_target.address(), m_target.port()); - start_announce(); } @@ -412,7 +418,7 @@ namespace libtorrent mutex::scoped_lock l(m_cache_mutex); connection_cache_entry& cce = m_connection_cache[m_target.address()]; cce.connection_id = connection_id; - cce.expires = time_now() + seconds(m_ses.m_settings.udp_tracker_token_expiry); + cce.expires = time_now() + seconds(m_ses.m_settings.get_int(settings_pack::udp_tracker_token_expiry)); if (tracker_req().kind == tracker_request::announce_request) send_udp_announce(); @@ -635,7 +641,7 @@ namespace libtorrent tracker_request const& req = tracker_req(); const bool stats = req.send_stats; - session_settings const& settings = m_ses.settings(); + aux::session_settings const& settings = m_ses.settings(); std::map::iterator i = m_connection_cache.find(m_target.address()); @@ -657,11 +663,11 @@ namespace libtorrent // ip address address_v4 announce_ip; - if (!m_ses.settings().anonymous_mode - && !settings.announce_ip.empty()) + if (!settings.get_bool(settings_pack::anonymous_mode) + && !settings.get_str(settings_pack::announce_ip).empty()) { error_code ec; - address ip = address::from_string(settings.announce_ip.c_str(), ec); + address ip = address::from_string(settings.get_str(settings_pack::announce_ip).c_str(), ec); if (!ec && ip.is_v4()) announce_ip = ip.to_v4(); } detail::write_uint32(announce_ip.to_ulong(), out); diff --git a/src/upnp.cpp b/src/upnp.cpp index 1c7054b33..30f8842c2 100644 --- a/src/upnp.cpp +++ b/src/upnp.cpp @@ -70,6 +70,7 @@ upnp::upnp(io_service& ios, connection_queue& cc , m_log_callback(lcb) , m_retry_count(0) , m_io_service(ios) + , m_resolver(ios) , m_socket(udp::endpoint(address_v4::from_string("239.255.255.250", ec), 1900) , boost::bind(&upnp::on_reply, self(), _1, _2, _3)) , m_broadcast_timer(ios) @@ -79,6 +80,7 @@ upnp::upnp(io_service& ios, connection_queue& cc , m_closing(false) , m_ignore_non_routers(ignore_nonrouters) , m_cc(cc) + , m_last_if_update(min_time()) { TORRENT_ASSERT(cb); @@ -301,7 +303,8 @@ void upnp::resend_request(error_code const& ec) log(msg, l); if (d.upnp_connection) d.upnp_connection->close(); d.upnp_connection.reset(new http_connection(m_io_service - , m_cc, boost::bind(&upnp::on_upnp_xml, self(), _1, _2 + , m_cc, m_resolver + , boost::bind(&upnp::on_upnp_xml, self(), _1, _2 , boost::ref(d), _5))); d.upnp_connection->get(d.url, seconds(30), 1); } @@ -353,8 +356,9 @@ void upnp::on_reply(udp::endpoint const& from, char* buffer */ error_code ec; - if (!in_local_network(m_io_service, from.address(), ec)) + if (time_now_hires() - seconds(60) > m_last_if_update) { + m_interfaces = enum_net_interfaces(m_io_service, ec); if (ec) { char msg[500]; @@ -362,23 +366,25 @@ void upnp::on_reply(udp::endpoint const& from, char* buffer , print_endpoint(from).c_str(), convert_from_native(ec.message()).c_str()); log(msg, l); } - else - { - char msg[400]; - int num_chars = snprintf(msg, sizeof(msg) - , "ignoring response from: %s. IP is not on local network. " - , print_endpoint(from).c_str()); + m_last_if_update = time_now(); + } - std::vector net = enum_net_interfaces(m_io_service, ec); - for (std::vector::const_iterator i = net.begin() - , end(net.end()); i != end && num_chars < int(sizeof(msg)); ++i) - { - num_chars += snprintf(msg + num_chars, sizeof(msg) - num_chars, "(%s,%s) " - , print_address(i->interface_address).c_str(), print_address(i->netmask).c_str()); - } - log(msg, l); - return; + if (!ec && !in_local_network(m_interfaces, from.address())) + { + char msg[400]; + int num_chars = snprintf(msg, sizeof(msg) + , "ignoring response from: %s. IP is not on local network. " + , print_endpoint(from).c_str()); + + std::vector net = enum_net_interfaces(m_io_service, ec); + for (std::vector::const_iterator i = net.begin() + , end(net.end()); i != end && num_chars < int(sizeof(msg)); ++i) + { + num_chars += snprintf(msg + num_chars, sizeof(msg) - num_chars, "(%s,%s) " + , print_address(i->interface_address).c_str(), print_address(i->netmask).c_str()); } + log(msg, l); + return; } bool non_router = false; @@ -618,7 +624,8 @@ void upnp::try_map_upnp(mutex::scoped_lock& l, bool timer) if (d.upnp_connection) d.upnp_connection->close(); d.upnp_connection.reset(new http_connection(m_io_service - , m_cc, boost::bind(&upnp::on_upnp_xml, self(), _1, _2 + , m_cc, m_resolver + , boost::bind(&upnp::on_upnp_xml, self(), _1, _2 , boost::ref(d), _5))); d.upnp_connection->get(d.url, seconds(30), 1); } @@ -761,21 +768,23 @@ void upnp::update_map(rootdevice& d, int i, mutex::scoped_lock& l) if (d.upnp_connection) d.upnp_connection->close(); d.upnp_connection.reset(new http_connection(m_io_service - , m_cc, boost::bind(&upnp::on_upnp_map_response, self(), _1, _2 + , m_cc, m_resolver + , boost::bind(&upnp::on_upnp_map_response, self(), _1, _2 , boost::ref(d), i, _5), true, default_max_bottled_buffer_size , boost::bind(&upnp::create_port_mapping, self(), _1, boost::ref(d), i))); - d.upnp_connection->start(d.hostname, to_string(d.port).elems + d.upnp_connection->start(d.hostname, d.port , seconds(10), 1); } else if (m.action == mapping_t::action_delete) { if (d.upnp_connection) d.upnp_connection->close(); d.upnp_connection.reset(new http_connection(m_io_service - , m_cc, boost::bind(&upnp::on_upnp_unmap_response, self(), _1, _2 + , m_cc, m_resolver + , boost::bind(&upnp::on_upnp_unmap_response, self(), _1, _2 , boost::ref(d), i, _5), true, default_max_bottled_buffer_size , boost::bind(&upnp::delete_port_mapping, self(), boost::ref(d), i))); - d.upnp_connection->start(d.hostname, to_string(d.port).elems + d.upnp_connection->start(d.hostname, d.port , seconds(10), 1); } @@ -1022,10 +1031,11 @@ void upnp::on_upnp_xml(error_code const& e } d.upnp_connection.reset(new http_connection(m_io_service - , m_cc, boost::bind(&upnp::on_upnp_get_ip_address_response, self(), _1, _2 + , m_cc, m_resolver + , boost::bind(&upnp::on_upnp_get_ip_address_response, self(), _1, _2 , boost::ref(d), _5), true, default_max_bottled_buffer_size , boost::bind(&upnp::get_ip_address, self(), boost::ref(d)))); - d.upnp_connection->start(d.hostname, to_string(d.port).elems + d.upnp_connection->start(d.hostname, d.port , seconds(10), 1); } diff --git a/src/ut_metadata.cpp b/src/ut_metadata.cpp index 270459a7b..2668e34f8 100644 --- a/src/ut_metadata.cpp +++ b/src/ut_metadata.cpp @@ -37,6 +37,7 @@ POSSIBILITY OF SUCH DAMAGE. #endif #include +#include #ifdef _MSC_VER #pragma warning(pop) @@ -56,6 +57,9 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/extensions/ut_metadata.hpp" #include "libtorrent/alert_types.hpp" #include "libtorrent/random.hpp" +#include "libtorrent/io.hpp" +#include "libtorrent/performance_counters.hpp" // for counters + #ifdef TORRENT_STATS #include "libtorrent/aux_/session_impl.hpp" #endif @@ -95,21 +99,44 @@ namespace libtorrent { namespace , m_metadata_progress(0) , m_metadata_size(0) { + // initialize m_metadata_size + if (m_torrent.valid_metadata()) + metadata(); + } + + bool need_loaded() + { return m_torrent.need_loaded(); } + + virtual void on_unload() + { + m_metadata.reset(); + } + + virtual void on_load() + { + // initialize m_metadata_size + TORRENT_ASSERT(m_torrent.is_loaded()); + metadata(); } virtual void on_files_checked() { - // if the torrent is a seed, copy the metadata from - // the torrent before it is deallocated - if (m_torrent.is_seed()) - metadata(); + // initialize m_metadata_size + metadata(); } virtual boost::shared_ptr new_connection( peer_connection* pc); + int get_metadata_size() const + { + TORRENT_ASSERT(m_metadata_size > 0); + return m_metadata_size; + } + buffer::const_interval metadata() const { + if (!m_torrent.need_loaded()) return buffer::const_interval(NULL, NULL); TORRENT_ASSERT(m_torrent.valid_metadata()); if (!m_metadata) { @@ -205,7 +232,7 @@ namespace libtorrent { namespace entry& messages = h["m"]; messages["ut_metadata"] = 2; if (m_torrent.valid_metadata()) - h["metadata_size"] = m_tp.metadata().left(); + h["metadata_size"] = m_tp.get_metadata_size(); } // called when the extension handshake from the other end is received @@ -255,25 +282,28 @@ namespace libtorrent { namespace if (type == 1) { - if (piece < 0 || piece >= int(m_tp.metadata().left() + 16 * 1024 - 1)/(16*1024)) + if (piece < 0 || piece >= int(m_tp.get_metadata_size() + 16 * 1024 - 1)/(16*1024)) { #ifdef TORRENT_VERBOSE_LOGGING m_pc.peer_log("*** UT_METADATA [ invalid piece %d metadata size: %d ]" - , piece, int(m_tp.metadata().left())); + , piece, int(m_tp.get_metadata_size())); #endif - m_pc.disconnect(errors::invalid_metadata_message, 2); + m_pc.disconnect(errors::invalid_metadata_message, peer_connection_interface::op_bittorrent, 2); return; } TORRENT_ASSERT(m_pc.associated_torrent().lock()->valid_metadata()); - e["total_size"] = m_tp.metadata().left(); + e["total_size"] = m_tp.get_metadata_size(); int offset = piece * 16 * 1024; + // unloaded torrents don't have any metadata. Since we're + // about to send the metadata, we need it to be loaded + if (!m_tp.need_loaded()) return; metadata = m_tp.metadata().begin + offset; metadata_piece_size = (std::min)( - int(m_tp.metadata().left() - offset), 16 * 1024); + int(m_tp.get_metadata_size() - offset), 16 * 1024); TORRENT_ASSERT(metadata_piece_size > 0); TORRENT_ASSERT(offset >= 0); - TORRENT_ASSERT(offset + metadata_piece_size <= int(m_tp.metadata().left())); + TORRENT_ASSERT(offset + metadata_piece_size <= int(m_tp.get_metadata_size())); } char msg[200]; @@ -287,8 +317,13 @@ namespace libtorrent { namespace io::write_uint8(m_message_index, header); m_pc.send_buffer(msg, len + 6); + // TODO: we really need to increment the refcounter on the torrent + // while this buffer is still in the peer's send buffer if (metadata_piece_size) m_pc.append_const_send_buffer( metadata, metadata_piece_size); + + m_pc.ses().inc_stats_counter(counters::num_outgoing_extended); + m_pc.ses().inc_stats_counter(counters::num_outgoing_metadata); } virtual bool on_extended(int length @@ -302,7 +337,7 @@ namespace libtorrent { namespace #ifdef TORRENT_VERBOSE_LOGGING m_pc.peer_log("<== UT_METADATA [ packet too big %d ]", length); #endif - m_pc.disconnect(errors::invalid_metadata_message, 2); + m_pc.disconnect(errors::invalid_metadata_message, peer_connection_interface::op_bittorrent, 2); return true; } @@ -315,7 +350,7 @@ namespace libtorrent { namespace #ifdef TORRENT_VERBOSE_LOGGING m_pc.peer_log("<== UT_METADATA [ not a dictionary ]"); #endif - m_pc.disconnect(errors::invalid_metadata_message, 2); + m_pc.disconnect(errors::invalid_metadata_message, peer_connection_interface::op_bittorrent, 2); return true; } @@ -327,7 +362,7 @@ namespace libtorrent { namespace #ifdef TORRENT_VERBOSE_LOGGING m_pc.peer_log("<== UT_METADATA [ missing or invalid keys ]"); #endif - m_pc.disconnect(errors::invalid_metadata_message, 2); + m_pc.disconnect(errors::invalid_metadata_message, peer_connection_interface::op_bittorrent, 2); return true; } int type = type_ent->integer(); @@ -389,6 +424,9 @@ namespace libtorrent { namespace // unknown message, ignore break; } + + m_pc.ses().inc_stats_counter(counters::num_incoming_metadata); + return true; } @@ -515,7 +553,7 @@ namespace libtorrent { namespace if (!m_metadata) { // verify the total_size - if (total_size <= 0 || total_size > m_torrent.session().settings().max_metadata_size) + if (total_size <= 0 || total_size > m_torrent.session().settings().get_int(settings_pack::max_metadata_size)) { #ifdef TORRENT_VERBOSE_LOGGING source.m_pc.peer_log("*** UT_METADATA [ metadata size too big: %d ]", total_size); @@ -589,6 +627,12 @@ namespace libtorrent { namespace return false; } + // free our copy of the metadata and get a reference + // to the torrent's copy instead. No need to keep two + // identical copies around + m_metadata.reset(); + metadata(); + // clear the storage for the bitfield std::vector().swap(m_requested_metadata); diff --git a/src/ut_pex.cpp b/src/ut_pex.cpp index eeefbb783..fc8a0e432 100644 --- a/src/ut_pex.cpp +++ b/src/ut_pex.cpp @@ -51,6 +51,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket_io.hpp" #include "libtorrent/peer_info.hpp" #include "libtorrent/random.hpp" +#include "libtorrent/socket_type.hpp" // for is_utp +#include "libtorrent/performance_counters.hpp" // for counters #include "libtorrent/extensions/ut_pex.hpp" @@ -111,7 +113,7 @@ namespace libtorrent { namespace virtual void tick() { ptime now = time_now(); - if (now - m_last_msg < seconds(60)) return; + if (now - seconds(60) < m_last_msg) return; m_last_msg = now; int num_peers = m_torrent.num_peers(); @@ -162,7 +164,7 @@ namespace libtorrent { namespace // if the peer has told us which port its listening on, // use that port. But only if we didn't connect to the peer. // if we connected to it, use the port we know works - policy::peer *pi = 0; + torrent_peer *pi = 0; if (!p->is_outgoing() && (pi = peer->peer_info_struct()) && pi->port > 0) remote.port(pi->port); @@ -278,18 +280,18 @@ namespace libtorrent { namespace if (length > 500 * 1024) { - m_pc.disconnect(errors::pex_message_too_large, 2); + m_pc.disconnect(errors::pex_message_too_large, peer_connection_interface::op_bittorrent, 2); return true; } if (body.left() < length) return true; ptime now = time_now(); - if (now - m_last_pex[0] < seconds(60)) + if (now - seconds(60) < m_last_pex[0]) { // this client appears to be trying to flood us // with pex messages. Don't allow that. - m_pc.disconnect(errors::too_frequent_pex); + m_pc.disconnect(errors::too_frequent_pex, peer_connection_interface::op_bittorrent); return true; } @@ -303,7 +305,7 @@ namespace libtorrent { namespace int ret = lazy_bdecode(body.begin, body.end, pex_msg, ec); if (ret != 0 || pex_msg.type() != lazy_entry::dict_t) { - m_pc.disconnect(errors::invalid_pex_message, 2); + m_pc.disconnect(errors::invalid_pex_message, peer_connection_interface::op_bittorrent, 2); return true; } @@ -342,14 +344,13 @@ namespace libtorrent { namespace char const* in = p->string_ptr(); char const* fin = pf->string_ptr(); - peer_id pid(0); - policy& p = m_torrent.get_policy(); for (int i = 0; i < num_peers; ++i) { tcp::endpoint adr = detail::read_v4_endpoint(in); char flags = *fin++; - if (int(m_peers.size()) >= m_torrent.settings().max_pex_peers) break; + if (int(m_peers.size()) >= m_torrent.settings().get_int(settings_pack::max_pex_peers)) + break; // ignore local addresses unless the peer is local to us if (is_local(adr.address()) && !is_local(m_pc.remote().address())) continue; @@ -359,7 +360,7 @@ namespace libtorrent { namespace // do we already know about this peer? if (j != m_peers.end() && *j == v) continue; m_peers.insert(j, v); - p.add_peer(adr, pid, peer_info::pex, flags); + m_torrent.add_peer(adr, peer_info::pex, flags); } } @@ -398,22 +399,21 @@ namespace libtorrent { namespace char const* in = p6->string_ptr(); char const* fin = p6f->string_ptr(); - peer_id pid(0); - policy& p = m_torrent.get_policy(); for (int i = 0; i < num_peers; ++i) { tcp::endpoint adr = detail::read_v6_endpoint(in); char flags = *fin++; // ignore local addresses unless the peer is local to us if (is_local(adr.address()) && !is_local(m_pc.remote().address())) continue; - if (int(m_peers6.size()) >= m_torrent.settings().max_pex_peers) break; + if (int(m_peers6.size()) >= m_torrent.settings().get_int(settings_pack::max_pex_peers)) + break; peers6_t::value_type v(adr.address().to_v6().to_bytes(), adr.port()); peers6_t::iterator j = std::lower_bound(m_peers6.begin(), m_peers6.end(), v); // do we already know about this peer? if (j != m_peers6.end() && *j == v) continue; m_peers6.insert(j, v); - p.add_peer(adr, pid, peer_info::pex, flags); + m_torrent.add_peer(adr, peer_info::pex, flags); } } #endif @@ -421,6 +421,8 @@ namespace libtorrent { namespace m_pc.peer_log("<== PEX [ dropped: %d added: %d ]" , num_dropped, num_added); #endif + + m_pc.ses().inc_stats_counter(counters::num_incoming_pex); return true; } @@ -432,7 +434,7 @@ namespace libtorrent { namespace if (!m_message_index) return; ptime now = time_now(); - if (now - m_last_msg < seconds(60)) + if (now - seconds(60) < m_last_msg) { #ifdef TORRENT_VERBOSE_LOGGING m_pc.peer_log("*** PEX [ waiting: %d seconds to next msg ]" @@ -450,7 +452,7 @@ namespace libtorrent { namespace // contention int delay = (std::min)((std::max)(60000 / num_peers, 100), 3000); - if (now - global_last < milliseconds(delay)) + if (now - milliseconds(delay) < global_last) { #ifdef TORRENT_VERBOSE_LOGGING m_pc.peer_log("*** PEX [ global-wait: %d ]", total_seconds(milliseconds(delay) - (now - global_last))); @@ -494,6 +496,9 @@ namespace libtorrent { namespace m_pc.send_buffer(msg, sizeof(msg)); m_pc.send_buffer(&pex_msg[0], pex_msg.size()); + m_pc.ses().inc_stats_counter(counters::num_outgoing_extended); + m_pc.ses().inc_stats_counter(counters::num_outgoing_pex); + #ifdef TORRENT_VERBOSE_LOGGING lazy_entry m; error_code ec; @@ -568,7 +573,7 @@ namespace libtorrent { namespace tcp::endpoint remote = peer->remote(); - policy::peer *pi = 0; + torrent_peer *pi = 0; if (!p->is_outgoing() && (pi = peer->peer_info_struct()) && pi->port > 0) remote.port(pi->port); @@ -599,6 +604,9 @@ namespace libtorrent { namespace m_pc.send_buffer(msg, sizeof(msg)); m_pc.send_buffer(&pex_msg[0], pex_msg.size()); + m_pc.ses().inc_stats_counter(counters::num_outgoing_extended); + m_pc.ses().inc_stats_counter(counters::num_outgoing_pex); + #ifdef TORRENT_VERBOSE_LOGGING m_pc.peer_log("==> PEX_FULL [ added: %d msg_size: %d ]", num_added, int(pex_msg.size())); #endif @@ -655,7 +663,7 @@ namespace libtorrent boost::shared_ptr create_ut_pex_plugin(torrent* t, void*) { if (t->torrent_file().priv() || (t->torrent_file().is_i2p() - && !t->settings().allow_i2p_mixed)) + && !t->settings().get_bool(settings_pack::allow_i2p_mixed))) { return boost::shared_ptr(); } diff --git a/src/utp_socket_manager.cpp b/src/utp_socket_manager.cpp index c89fdf95a..3fc9357e6 100644 --- a/src/utp_socket_manager.cpp +++ b/src/utp_socket_manager.cpp @@ -37,13 +37,16 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket_io.hpp" #include "libtorrent/broadcast_socket.hpp" // for is_teredo #include "libtorrent/random.hpp" +#include "libtorrent/performance_counters.hpp" // #define TORRENT_DEBUG_MTU 1135 namespace libtorrent { - utp_socket_manager::utp_socket_manager(session_settings const& sett, udp_socket& s + utp_socket_manager::utp_socket_manager(aux::session_settings const& sett + , udp_socket& s + , counters& cnt , incoming_utp_callback_t cb) : m_sock(s) , m_cb(cb) @@ -53,9 +56,8 @@ namespace libtorrent , m_last_route_update(min_time()) , m_last_if_update(min_time()) , m_sock_buf_size(0) - { - memset(m_counters, 0, sizeof(m_counters)); - } + , m_counters(cnt) + {} utp_socket_manager::~utp_socket_manager() { @@ -74,18 +76,20 @@ namespace libtorrent s.num_fin_sent = 0; s.num_close_wait = 0; - s.packet_loss = m_counters[packet_loss]; - s.timeout = m_counters[timeout]; - s.packets_in = m_counters[packets_in]; - s.packets_out = m_counters[packets_out]; - s.fast_retransmit = m_counters[fast_retransmit]; - s.packet_resend = m_counters[packet_resend]; - s.samples_above_target = m_counters[samples_above_target]; - s.samples_below_target = m_counters[samples_below_target]; - s.payload_pkts_in = m_counters[payload_pkts_in]; - s.payload_pkts_out = m_counters[payload_pkts_out]; - s.invalid_pkts_in = m_counters[invalid_pkts_in]; - s.redundant_pkts_in = m_counters[redundant_pkts_in]; +#ifndef TORRENT_NO_DEPRECATE + s.packet_loss = m_counters[counters::utp_packet_loss]; + s.timeout = m_counters[counters::utp_timeout]; + s.packets_in = m_counters[counters::utp_packets_in]; + s.packets_out = m_counters[counters::utp_packets_out]; + s.fast_retransmit = m_counters[counters::utp_fast_retransmit]; + s.packet_resend = m_counters[counters::utp_packet_resend]; + s.samples_above_target = m_counters[counters::utp_samples_above_target]; + s.samples_below_target = m_counters[counters::utp_samples_below_target]; + s.payload_pkts_in = m_counters[counters::utp_payload_pkts_in]; + s.payload_pkts_out = m_counters[counters::utp_payload_pkts_out]; + s.invalid_pkts_in = m_counters[counters::utp_invalid_pkts_in]; + s.redundant_pkts_in = m_counters[counters::utp_redundant_pkts_in]; +#endif for (socket_map_t::const_iterator i = m_utp_sockets.begin() , end(m_utp_sockets.end()); i != end; ++i) @@ -122,7 +126,7 @@ namespace libtorrent void utp_socket_manager::mtu_for_dest(address const& addr, int& link_mtu, int& utp_mtu) { - if (time_now() - m_last_route_update > seconds(60)) + if (time_now() - seconds(60) > m_last_route_update) { m_last_route_update = time_now(); error_code ec; @@ -170,8 +174,8 @@ namespace libtorrent mtu -= TORRENT_UDP_HEADER; - if (m_sock.get_proxy_settings().type == proxy_settings::socks5 - || m_sock.get_proxy_settings().type == proxy_settings::socks5_pw) + if (m_sock.get_proxy_settings().type == settings_pack::socks5 + || m_sock.get_proxy_settings().type == settings_pack::socks5_pw) { // this is for the IP layer address proxy_addr = m_sock.proxy_addr().address(); @@ -231,7 +235,7 @@ namespace libtorrent tcp::endpoint socket_ep = m_sock.local_endpoint(ec); // first enumerate the routes in the routing table - if (time_now() - m_last_route_update > seconds(60)) + if (time_now() - seconds(60) > m_last_route_update) { m_last_route_update = time_now(); error_code ec; @@ -262,7 +266,7 @@ namespace libtorrent // for this target. Now figure out what the local address // is for that interface - if (time_now() - m_last_if_update > seconds(60)) + if (time_now() - seconds(60) > m_last_if_update) { m_last_if_update = time_now(); error_code ec; @@ -322,7 +326,7 @@ namespace libtorrent // UTP_LOGV("incoming packet id:%d source:%s\n", id, print_endpoint(ep).c_str()); - if (!m_sett.enable_incoming_utp) + if (!m_sett.get_bool(settings_pack::enable_incoming_utp)) return false; // if not found, see if it's a SYN packet, if it is, @@ -330,7 +334,7 @@ namespace libtorrent if (ph->get_type() == ST_SYN) { // possible SYN flood. Just ignore - if (int(m_utp_sockets.size()) > m_sett.connections_limit * 2) + if (int(m_utp_sockets.size()) > m_sett.get_int(settings_pack::connections_limit) * 2) return false; // UTP_LOGV("not found, new connection id:%d\n", m_new_connection); @@ -451,9 +455,9 @@ namespace libtorrent void utp_socket_manager::inc_stats_counter(int counter) { - TORRENT_ASSERT(counter >= 0); - TORRENT_ASSERT(counter < num_counters); - ++m_counters[counter]; + TORRENT_ASSERT(counter >= counters::utp_packet_loss); + TORRENT_ASSERT(counter <= counters::utp_redundant_pkts_in); + m_counters.inc_stats_counter(counter); } utp_socket_impl* utp_socket_manager::new_utp_socket(utp_stream* str) diff --git a/src/utp_stream.cpp b/src/utp_stream.cpp index 1a55f3d06..02a61274f 100644 --- a/src/utp_stream.cpp +++ b/src/utp_stream.cpp @@ -39,6 +39,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/error.hpp" #include "libtorrent/random.hpp" #include "libtorrent/invariant_check.hpp" +#include "libtorrent/performance_counters.hpp" #include #define TORRENT_UTP_LOG 0 @@ -271,6 +272,7 @@ struct utp_socket_impl , m_nagle(true) , m_slow_start(true) , m_cwnd_full(false) + , m_null_buffers(false) , m_deferred_ack(false) , m_subscribe_drained(false) , m_stalled(false) @@ -282,7 +284,7 @@ struct utp_socket_impl ~utp_socket_impl(); - void tick(ptime const& now); + void tick(ptime now); void init_mtu(int link_mtu, int utp_mtu); bool incoming_packet(boost::uint8_t const* buf, int size , udp::endpoint const& ep, ptime receive_time); @@ -621,6 +623,12 @@ struct utp_socket_impl // flight as allowed by the congestion window (cwnd) bool m_cwnd_full:1; + // this is set to one if the current read operation + // has a null-buffer. i.e. we're not reading into a user-provided + // buffer, we're just signalling when there's something + // to read from our internal receive buffer + bool m_null_buffers:1; + // this is set to true when this socket has added itself to // the utp socket manager's list of deferred acks. Once the // burst of incoming UDP packets is all drained, the utp socket @@ -665,7 +673,7 @@ bool should_delete(utp_socket_impl* s) return s->should_delete(); } -void tick_utp_impl(utp_socket_impl* s, ptime const& now) +void tick_utp_impl(utp_socket_impl* s, ptime now) { s->tick(now); } @@ -782,7 +790,7 @@ void utp_stream::close() std::size_t utp_stream::available() const { - return m_impl->available(); + return m_impl ? m_impl->available() : 0; } utp_stream::endpoint_type utp_stream::remote_endpoint(error_code& ec) const @@ -839,7 +847,7 @@ void utp_stream::on_read(void* self, size_t bytes_transferred, error_code const& , int(bytes_transferred), ec.message().c_str(), kill); TORRENT_ASSERT(s->m_read_handler); - TORRENT_ASSERT(bytes_transferred > 0 || ec); + TORRENT_ASSERT(bytes_transferred > 0 || ec || s->m_impl->m_null_buffers); s->m_io_service.post(boost::bind(s->m_read_handler, ec, bytes_transferred)); s->m_read_handler.clear(); if (kill && s->m_impl) @@ -880,6 +888,7 @@ void utp_stream::on_connect(void* self, error_code const& ec, bool kill) s->m_connect_handler.clear(); if (kill && s->m_impl) { + TORRENT_ASSERT(ec); detach_utp_impl(s->m_impl); s->m_impl = 0; } @@ -941,14 +950,15 @@ void utp_stream::add_write_buffer(void const* buf, size_t len) void utp_stream::set_read_handler(handler_t h) { TORRENT_ASSERT(m_impl->m_userdata); + + m_impl->m_null_buffers = m_impl->m_read_buffer_size == 0; + m_impl->m_read_handler = h; if (m_impl->test_socket_state()) return; UTP_LOGV("%8p: new read handler. %d bytes in buffer\n" , m_impl, m_impl->m_receive_buffer_size); - TORRENT_ASSERT(m_impl->m_read_buffer_size > 0); - // so, the client wants to read. If we already // have some data in the read buffer, move it into the // client's buffer right away @@ -1038,7 +1048,7 @@ size_t utp_stream::read_some(bool clear_buffers) m_impl->m_read_buffer_size = 0; m_impl->m_read_buffer.clear(); } - TORRENT_ASSERT(ret > 0); + TORRENT_ASSERT(ret > 0 || m_impl->m_null_buffers); return ret; } @@ -1150,8 +1160,11 @@ void utp_socket_impl::maybe_trigger_receive_callback() { INVARIANT_CHECK; + if (m_read_handler == 0) return; + // nothing has been read or there's no outstanding read operation - if (m_read == 0 || m_read_handler == 0) return; + if (m_null_buffers && m_receive_buffer_size == 0) return; + else if (!m_null_buffers && m_read == 0) return; UTP_LOGV("%8p: calling read handler read:%d\n", this, m_read); m_read_handler(m_userdata, m_read, m_error, false); @@ -1256,7 +1269,8 @@ void utp_socket_impl::send_syn() ptime now = time_now_hires(); p->send_time = now; - h->timestamp_microseconds = boost::uint32_t(total_microseconds(now - min_time()) & 0xffffffff); + h->timestamp_microseconds = boost::uint32_t( + total_microseconds(now.time_since_epoch()) & 0xffffffff); #if TORRENT_UTP_LOG UTP_LOGV("%8p: send_syn seq_nr:%d id:%d target:%s\n" @@ -1348,7 +1362,8 @@ void utp_socket_impl::send_reset(utp_header* ph) h.seq_nr = random() & 0xffff; h.ack_nr = ph->seq_nr; ptime now = time_now_hires(); - h.timestamp_microseconds = boost::uint32_t(total_microseconds(now - min_time())); + h.timestamp_microseconds = boost::uint32_t( + total_microseconds(now.time_since_epoch()) & 0xffffffff); UTP_LOGV("%8p: send_reset seq_nr:%d id:%d ack_nr:%d\n" , this, int(h.seq_nr), int(m_send_id), int(ph->seq_nr)); @@ -1717,7 +1732,7 @@ bool utp_socket_impl::send_pkt(int flags) p->allocated = m_mtu; buf_holder.reset((char*)p); - m_sm->inc_stats_counter(utp_socket_manager::payload_pkts_out); + m_sm->inc_stats_counter(counters::utp_payload_pkts_out); } else { @@ -1866,7 +1881,7 @@ bool utp_socket_impl::send_pkt(int flags) ptime now = time_now_hires(); p->send_time = now; h->timestamp_microseconds = boost::uint32_t( - total_microseconds(now - min_time()) & 0xffffffff); + total_microseconds(now.time_since_epoch()) & 0xffffffff); #if TORRENT_UTP_LOG UTP_LOG("%8p: sending packet seq_nr:%d ack_nr:%d type:%s " @@ -1891,7 +1906,7 @@ bool utp_socket_impl::send_pkt(int flags) , p->mtu_probe ? utp_socket_manager::dont_fragment : 0); ++m_out_packets; - m_sm->inc_stats_counter(utp_socket_manager::packets_out); + m_sm->inc_stats_counter(counters::utp_packets_out); if (ec == error::message_size) { @@ -2046,8 +2061,8 @@ bool utp_socket_impl::resend_packet(packet* p, bool fast_resend) TORRENT_ASSERT(p->size - p->header_size >= 0); if (p->need_resend) m_bytes_in_flight += p->size - p->header_size; - m_sm->inc_stats_counter(utp_socket_manager::packet_resend); - if (fast_resend) m_sm->inc_stats_counter(utp_socket_manager::fast_retransmit); + m_sm->inc_stats_counter(counters::utp_packet_resend); + if (fast_resend) m_sm->inc_stats_counter(counters::utp_fast_retransmit); #ifdef TORRENT_DEBUG if (fast_resend) ++p->num_fast_resend; @@ -2058,7 +2073,7 @@ bool utp_socket_impl::resend_packet(packet* p, bool fast_resend) h->timestamp_difference_microseconds = m_reply_micro; p->send_time = time_now_hires(); h->timestamp_microseconds = boost::uint32_t( - total_microseconds(p->send_time - min_time()) & 0xffffffff); + total_microseconds(p->send_time.time_since_epoch()) & 0xffffffff); // if the packet has a selective ack header, we'll need // to update it @@ -2084,7 +2099,7 @@ bool utp_socket_impl::resend_packet(packet* p, bool fast_resend) m_sm->send_packet(udp::endpoint(m_remote_address, m_port) , (char const*)p->buf, p->size, ec); ++m_out_packets; - m_sm->inc_stats_counter(utp_socket_manager::packets_out); + m_sm->inc_stats_counter(counters::utp_packets_out); #if TORRENT_UTP_LOG @@ -2148,7 +2163,7 @@ void utp_socket_impl::experienced_loss(int seq_nr) // if we happen to be in slow-start mode, we need to leave it m_slow_start = false; - m_sm->inc_stats_counter(utp_socket_manager::packet_loss); + m_sm->inc_stats_counter(counters::utp_packet_loss); } void utp_socket_impl::maybe_inc_acked_seq_nr() @@ -2235,6 +2250,7 @@ void utp_socket_impl::incoming(boost::uint8_t const* buf, int size, packet* p, p while (!m_read_buffer.empty()) { + UTP_LOGV("%8p: incoming: have user buffer (%d)\n", this, m_read_buffer_size); if (p) { buf = p->buf + p->header_size; @@ -2281,6 +2297,8 @@ void utp_socket_impl::incoming(boost::uint8_t const* buf, int size, packet* p, p m_receive_buffer.push_back(p); m_receive_buffer_size += p->size - p->header_size; + UTP_LOGV("%8p: incoming: saving packet in receive buffer (%d)\n", this, m_receive_buffer_size); + check_receive_buffers(); } @@ -2498,13 +2516,13 @@ bool utp_socket_impl::incoming_packet(boost::uint8_t const* buf, int size utp_header* ph = (utp_header*)buf; - m_sm->inc_stats_counter(utp_socket_manager::packets_in); + m_sm->inc_stats_counter(counters::utp_packets_in); if (ph->get_version() != 1) { UTP_LOG("%8p: ERROR: incoming packet version:%d (ignored)\n" , this, int(ph->get_version())); - m_sm->inc_stats_counter(utp_socket_manager::invalid_pkts_in); + m_sm->inc_stats_counter(counters::utp_invalid_pkts_in); return false; } @@ -2513,7 +2531,7 @@ bool utp_socket_impl::incoming_packet(boost::uint8_t const* buf, int size { UTP_LOG("%8p: ERROR: incoming packet id:%d expected:%d (ignored)\n" , this, int(ph->connection_id), int(m_recv_id)); - m_sm->inc_stats_counter(utp_socket_manager::invalid_pkts_in); + m_sm->inc_stats_counter(counters::utp_invalid_pkts_in); return false; } @@ -2521,7 +2539,7 @@ bool utp_socket_impl::incoming_packet(boost::uint8_t const* buf, int size { UTP_LOG("%8p: ERROR: incoming packet type:%d (ignored)\n" , this, int(ph->get_type())); - m_sm->inc_stats_counter(utp_socket_manager::invalid_pkts_in); + m_sm->inc_stats_counter(counters::utp_invalid_pkts_in); return false; } @@ -2534,7 +2552,7 @@ bool utp_socket_impl::incoming_packet(boost::uint8_t const* buf, int size if (m_state != UTP_STATE_NONE && ph->get_type() == ST_SYN) { UTP_LOG("%8p: ERROR: incoming packet type:ST_SYN (ignored)\n", this); - m_sm->inc_stats_counter(utp_socket_manager::invalid_pkts_in); + m_sm->inc_stats_counter(counters::utp_invalid_pkts_in); return true; } @@ -2550,7 +2568,8 @@ bool utp_socket_impl::incoming_packet(boost::uint8_t const* buf, int size boost::uint32_t their_delay = 0; if (ph->timestamp_microseconds != 0) { - boost::uint32_t timestamp = boost::uint32_t(total_microseconds(receive_time - min_time()) & 0xffffffff); + boost::uint32_t timestamp = boost::uint32_t(total_microseconds( + receive_time.time_since_epoch()) & 0xffffffff); m_reply_micro = timestamp - ph->timestamp_microseconds; boost::uint32_t prev_base = m_their_delay_hist.initialized() ? m_their_delay_hist.base() : 0; their_delay = m_their_delay_hist.add_sample(m_reply_micro, step); @@ -2590,7 +2609,7 @@ bool utp_socket_impl::incoming_packet(boost::uint8_t const* buf, int size { UTP_LOG("%8p: ERROR: incoming packet ack_nr:%d our seq_nr:%d (ignored)\n" , this, int(ph->ack_nr), m_seq_nr); - m_sm->inc_stats_counter(utp_socket_manager::redundant_pkts_in); + m_sm->inc_stats_counter(counters::utp_redundant_pkts_in); return true; } @@ -2612,7 +2631,7 @@ bool utp_socket_impl::incoming_packet(boost::uint8_t const* buf, int size // we've already received this packet UTP_LOGV("%8p: incoming packet seq_nr:%d our ack_nr:%d (ignored)\n" , this, int(ph->seq_nr), m_ack_nr); - m_sm->inc_stats_counter(utp_socket_manager::redundant_pkts_in); + m_sm->inc_stats_counter(counters::utp_redundant_pkts_in); return true; } */ @@ -2629,7 +2648,7 @@ bool utp_socket_impl::incoming_packet(boost::uint8_t const* buf, int size } if (ph->get_type() == ST_DATA) - m_sm->inc_stats_counter(utp_socket_manager::payload_pkts_in); + m_sm->inc_stats_counter(counters::utp_payload_pkts_in); if (m_state != UTP_STATE_NONE && m_state != UTP_STATE_SYN_SENT @@ -2642,7 +2661,7 @@ bool utp_socket_impl::incoming_packet(boost::uint8_t const* buf, int size // to drop the timestamp information. UTP_LOG("%8p: ERROR: incoming packet seq_nr:%d our ack_nr:%d (ignored)\n" , this, int(ph->seq_nr), m_ack_nr); - m_sm->inc_stats_counter(utp_socket_manager::redundant_pkts_in); + m_sm->inc_stats_counter(counters::utp_redundant_pkts_in); return true; } @@ -2745,7 +2764,7 @@ bool utp_socket_impl::incoming_packet(boost::uint8_t const* buf, int size if (ptr - buf + 2 > size) { UTP_LOG("%8p: ERROR: invalid extension header\n", this); - m_sm->inc_stats_counter(utp_socket_manager::invalid_pkts_in); + m_sm->inc_stats_counter(counters::utp_invalid_pkts_in); return true; } int next_extension = *ptr++; @@ -2754,14 +2773,14 @@ bool utp_socket_impl::incoming_packet(boost::uint8_t const* buf, int size { UTP_LOGV("%8p: invalid extension length:%d packet:%d\n" , this, len, int(ptr - buf)); - m_sm->inc_stats_counter(utp_socket_manager::invalid_pkts_in); + m_sm->inc_stats_counter(counters::utp_invalid_pkts_in); return true; } if (ptr - buf + len > ptrdiff_t(size)) { UTP_LOG("%8p: ERROR: invalid extension header size:%d packet:%d\n" , this, len, int(ptr - buf)); - m_sm->inc_stats_counter(utp_socket_manager::invalid_pkts_in); + m_sm->inc_stats_counter(counters::utp_invalid_pkts_in); return true; } switch(extension) @@ -3062,7 +3081,7 @@ bool utp_socket_impl::incoming_packet(boost::uint8_t const* buf, int size , m_adv_wnd , packet_timeout() , int(total_milliseconds(m_timeout - receive_time)) - , int(total_microseconds(receive_time - min_time())) + , int(total_microseconds(receive_time.time_since_epoch())) , (m_seq_nr - m_acked_seq_nr) & ACK_MASK , m_mtu , their_delay_base @@ -3202,12 +3221,12 @@ void utp_socket_impl::do_ledbat(int acked_bytes, int delay, int in_flight, ptime if (delay >= target_delay) { UTP_LOGV("%8p: off_target: %d slow_start -> 0\n", this, target_delay - delay); - m_sm->inc_stats_counter(utp_socket_manager::samples_above_target); + m_sm->inc_stats_counter(counters::utp_samples_above_target); m_slow_start = false; } else { - m_sm->inc_stats_counter(utp_socket_manager::samples_below_target); + m_sm->inc_stats_counter(counters::utp_samples_below_target); } boost::int64_t linear_gain = (window_factor * delay_factor) >> 16; @@ -3287,10 +3306,13 @@ int utp_socket_impl::packet_timeout() const int timeout = (std::max)(m_sm->min_timeout(), m_rtt.mean() + m_rtt.avg_deviation() * 2); if (m_num_timeouts > 0) timeout += (1 << (int(m_num_timeouts) - 1)) * 1000; + + // timeouts over 1 minute are capped + if (timeout > 60000) timeout = 60000; return timeout; } -void utp_socket_impl::tick(ptime const& now) +void utp_socket_impl::tick(ptime now) { INVARIANT_CHECK; @@ -3312,7 +3334,7 @@ void utp_socket_impl::tick(ptime const& now) // TIMEOUT! // set cwnd to 1 MSS - m_sm->inc_stats_counter(utp_socket_manager::timeout); + m_sm->inc_stats_counter(counters::utp_timeout); if (m_outbuf.size()) ++m_num_timeouts; diff --git a/src/web_connection_base.cpp b/src/web_connection_base.cpp index c30ac4c96..683bfcf66 100644 --- a/src/web_connection_base.cpp +++ b/src/web_connection_base.cpp @@ -44,38 +44,35 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/invariant_check.hpp" #include "libtorrent/io.hpp" #include "libtorrent/version.hpp" -#include "libtorrent/aux_/session_impl.hpp" #include "libtorrent/parse_url.hpp" #include "libtorrent/peer_info.hpp" using boost::shared_ptr; -using libtorrent::aux::session_impl; namespace libtorrent { web_connection_base::web_connection_base( - session_impl& ses + aux::session_interface& ses + , aux::session_settings const& sett + , buffer_allocator_interface& allocator + , disk_interface& disk_thread , boost::weak_ptr t , boost::shared_ptr s - , tcp::endpoint const& remote , web_seed_entry& web) - : peer_connection(ses, t, s, remote, &web.peer_info) - , m_parser(http_parser::dont_parse_chunks) - , m_external_auth(web.auth) - , m_extra_headers(web.extra_headers) + : peer_connection(ses, sett, allocator, disk_thread, ses.get_io_service() + , t, s, web.endpoint, &web.peer_info) , m_first_request(true) , m_ssl(false) + , m_external_auth(web.auth) + , m_extra_headers(web.extra_headers) + , m_parser(http_parser::dont_parse_chunks) , m_body_start(0) { INVARIANT_CHECK; // we only want left-over bandwidth - set_priority(1); + // TODO: introduce a web-seed default class which has a low download priority - // since this is a web seed, change the timeout - // according to the settings. - set_timeout(ses.settings().urlseed_timeout); - std::string protocol; error_code ec; boost::tie(protocol, m_basic_auth, m_host, m_port, m_path) @@ -100,6 +97,13 @@ namespace libtorrent m_server_string += m_host; } + int web_connection_base::timeout() const + { + // since this is a web seed, change the timeout + // according to the settings. + return m_settings.get_int(settings_pack::urlseed_timeout); + } + void web_connection_base::start() { set_upload_only(true); @@ -125,13 +129,13 @@ namespace libtorrent } void web_connection_base::add_headers(std::string& request - , proxy_settings const& ps, bool using_proxy) const + , aux::session_settings const& sett, bool using_proxy) const { request += "Host: "; request += m_host; - if (m_first_request || m_ses.settings().always_send_user_agent) { + if (m_first_request || m_settings.get_bool(settings_pack::always_send_user_agent)) { request += "\r\nUser-Agent: "; - request += m_ses.settings().user_agent; + request += m_settings.get_str(settings_pack::user_agent); } if (!m_external_auth.empty()) { request += "\r\nAuthorization: "; @@ -140,9 +144,10 @@ namespace libtorrent request += "\r\nAuthorization: Basic "; request += m_basic_auth; } - if (ps.type == proxy_settings::http_pw) { + if (sett.get_int(settings_pack::proxy_type) == settings_pack::http_pw) { request += "\r\nProxy-Authorization: Basic "; - request += base64encode(ps.username + ":" + ps.password); + request += base64encode(sett.get_str(settings_pack::proxy_username) + + ":" + sett.get_str(settings_pack::proxy_password)); } for (web_seed_entry::headers_t::const_iterator it = m_extra_headers.begin(); it != m_extra_headers.end(); ++it) { @@ -186,7 +191,7 @@ namespace libtorrent INVARIANT_CHECK; if (error) return; - m_statistics.sent_bytes(0, bytes_transferred); + sent_bytes(0, bytes_transferred); } diff --git a/src/web_peer_connection.cpp b/src/web_peer_connection.cpp index 6544e3e53..8661a071d 100644 --- a/src/web_peer_connection.cpp +++ b/src/web_peer_connection.cpp @@ -7,14 +7,14 @@ 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. + * 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 @@ -44,592 +44,609 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/invariant_check.hpp" #include "libtorrent/io.hpp" #include "libtorrent/version.hpp" -#include "libtorrent/aux_/session_impl.hpp" #include "libtorrent/parse_url.hpp" #include "libtorrent/peer_info.hpp" +#include "libtorrent/aux_/session_interface.hpp" +#include "libtorrent/alert_manager.hpp" // for alert_manageralert_manager using boost::shared_ptr; -using libtorrent::aux::session_impl; namespace libtorrent { - enum - { - request_size_overhead = 5000 - }; +enum +{ + request_size_overhead = 5000 +}; - web_peer_connection::web_peer_connection( - session_impl& ses - , boost::weak_ptr t - , boost::shared_ptr s - , tcp::endpoint const& remote - , web_seed_entry& web) - : web_connection_base(ses, t, s, remote, web) - , m_url(web.url) - , m_web(web) - , m_received_body(0) - , m_range_pos(0) - , m_block_pos(0) - , m_chunk_pos(0) - , m_partial_chunk_header(0) - , m_num_responses(0) - { - INVARIANT_CHECK; +struct disk_interface; - if (!ses.settings().report_web_seed_downloads) - ignore_stats(true); +web_peer_connection::web_peer_connection( + aux::session_interface& ses + , aux::session_settings const& sett + , buffer_allocator_interface& allocator + , disk_interface& disk_thread + , boost::weak_ptr t + , boost::shared_ptr s + , web_seed_entry& web) + : web_connection_base(ses, sett, allocator, disk_thread, t, s, web) + , m_url(web.url) + , m_web(web) + , m_received_body(0) + , m_range_pos(0) + , m_chunk_pos(0) + , m_block_pos(0) + , m_partial_chunk_header(0) + , m_num_responses(0) +{ + INVARIANT_CHECK; - shared_ptr tor = t.lock(); - TORRENT_ASSERT(tor); + if (!m_settings.get_bool(settings_pack::report_web_seed_downloads)) + ignore_stats(true); - // we always prefer downloading 1 MiB chunks - // from web seeds, or whole pieces if pieces - // are larger than a MiB - int preferred_size = 1024 * 1024; + shared_ptr tor = t.lock(); + TORRENT_ASSERT(tor); - // if the web server is known not to support keep-alive. - // request even larger blocks at a time - if (!web.supports_keepalive) preferred_size *= 4; + // we always prefer downloading 1 MiB chunks + // from web seeds, or whole pieces if pieces + // are larger than a MiB + int preferred_size = 1024 * 1024; - prefer_whole_pieces((std::max)(preferred_size / tor->torrent_file().piece_length(), 1)); - - // we want large blocks as well, so - // we can request more bytes at once - // this setting will merge adjacent requests - // into single larger ones - request_large_blocks(true); + // if the web server is known not to support keep-alive. + // request even larger blocks at a time + if (!web.supports_keepalive) preferred_size *= 4; + + prefer_whole_pieces((std::max)(preferred_size / tor->torrent_file().piece_length(), 1)); + + // we want large blocks as well, so + // we can request more bytes at once + // this setting will merge adjacent requests + // into single larger ones + request_large_blocks(true); #ifdef TORRENT_VERBOSE_LOGGING - peer_log("*** web_peer_connection %s", web.url.c_str()); + peer_log("*** web_peer_connection %s", m_url.c_str()); #endif - } +} - void web_peer_connection::on_connected() +void web_peer_connection::on_connected() +{ + incoming_have_all(); + if (m_web.restart_request.piece != -1) { - incoming_have_all(); - if (m_web.restart_request.piece != -1) - { - // increase the chances of requesting the block - // we have partial data for already, to finish it - incoming_suggest(m_web.restart_request.piece); - } - web_connection_base::on_connected(); + // increase the chances of requesting the block + // we have partial data for already, to finish it + incoming_suggest(m_web.restart_request.piece); } + web_connection_base::on_connected(); +} - void web_peer_connection::disconnect(error_code const& ec, int error) +void web_peer_connection::disconnect(error_code const& ec, peer_connection_interface::operation_t op, int error) +{ + if (is_disconnecting()) return; + + boost::shared_ptr t = associated_torrent().lock(); + + if (!m_requests.empty() && !m_file_requests.empty() + && !m_piece.empty()) { - if (is_disconnecting()) return; - - boost::shared_ptr t = associated_torrent().lock(); - - if (!m_requests.empty() && !m_file_requests.empty() - && !m_piece.empty()) - { #if 0 - std::cerr << this << " SAVE-RESTART-DATA: data: " << m_piece.size() - << " req: " << m_requests.front().piece - << " off: " << m_requests.front().start + std::cerr << this << " SAVE-RESTART-DATA: data: " << m_piece.size() + << " req: " << m_requests.front().piece + << " off: " << m_requests.front().start + << std::endl; +#endif + m_web.restart_request = m_requests.front(); + if (!m_web.restart_piece.empty()) + { + // we're about to replace a different restart piece + // buffer. So it was wasted download + if (t) t->add_redundant_bytes(m_web.restart_piece.size() + , torrent::piece_closing); + } + m_web.restart_piece.swap(m_piece); + + // we have to do this to not count this data as redundant. The + // upper layer will call downloading_piece_progress and assume + // it's all wasted download. Since we're saving it here, it isn't. + m_requests.clear(); + m_block_pos = 0; + } + + if (!m_web.supports_keepalive && error == 0) + { + // if the web server doesn't support keepalive and we were + // disconnected as a graceful EOF, reconnect right away + if (t) t->session().get_io_service().post( + boost::bind(&torrent::maybe_connect_web_seeds, t)); + } + peer_connection::disconnect(ec, op, error); + if (t) t->disconnect_web_seed(this); +} + +boost::optional +web_peer_connection::downloading_piece_progress() const +{ + if (m_requests.empty()) + return boost::optional(); + + boost::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + piece_block_progress ret; + + ret.piece_index = m_requests.front().piece; + ret.bytes_downloaded = m_block_pos % t->block_size(); + // this is used to make sure that the block_index stays within + // bounds. If the entire piece is downloaded, the block_index + // would otherwise point to one past the end + int correction = m_block_pos ? -1 : 0; + ret.block_index = (m_requests.front().start + m_block_pos + correction) / t->block_size(); + TORRENT_ASSERT(ret.block_index < int(piece_block::invalid.block_index)); + TORRENT_ASSERT(ret.piece_index < int(piece_block::invalid.piece_index)); + + ret.full_block_bytes = t->block_size(); + const int last_piece = t->torrent_file().num_pieces() - 1; + if (ret.piece_index == last_piece && ret.block_index + == t->torrent_file().piece_size(last_piece) / t->block_size()) + ret.full_block_bytes = t->torrent_file().piece_size(last_piece) % t->block_size(); + return ret; +} + +void web_peer_connection::write_request(peer_request const& r) +{ + INVARIANT_CHECK; + + boost::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + TORRENT_ASSERT(t->valid_metadata()); + + bool single_file_request = t->torrent_file().num_files() == 1; + + if (!single_file_request) + { + // handle incorrect .torrent files which are multi-file + // but have web seeds not ending with a slash + if (m_path.empty() || m_path[m_path.size() - 1] != '/') m_path += "/"; + if (m_url.empty() || m_url[m_url.size() - 1] != '/') m_url += "/"; + } + else + { + // handle .torrent files that don't include the filename in the url + if (m_path.empty()) m_path += "/" + t->torrent_file().name(); + else if (m_path[m_path.size() - 1] == '/') + { + std::string tmp = t->torrent_file().files().at(0).path; +#ifdef TORRENT_WINDOWS + convert_path_to_posix(tmp); +#endif + m_path += tmp; + } + else if (!m_url.empty() && m_url[m_url.size() - 1] == '/') + { + std::string tmp = t->torrent_file().files().at(0).path; +#ifdef TORRENT_WINDOWS + convert_path_to_posix(tmp); +#endif + m_url += tmp; + } + } + + torrent_info const& info = t->torrent_file(); + peer_request req = r; + + std::string request; + request.reserve(400); + + int size = r.length; + const int block_size = t->block_size(); + const int piece_size = t->torrent_file().piece_length(); + peer_request pr; + while (size > 0) + { + int request_offset = r.start + r.length - size; + pr.start = request_offset % piece_size; + pr.length = (std::min)(block_size, size); + pr.piece = r.piece + request_offset / piece_size; + m_requests.push_back(pr); + + if (m_web.restart_request == m_requests.front()) + { + m_piece.swap(m_web.restart_piece); + m_block_pos += m_piece.size(); + peer_request& front = m_requests.front(); + TORRENT_ASSERT(front.length > int(m_piece.size())); + +#if 0 + std::cerr << this << " RESTART-DATA: data: " << m_piece.size() + << " req: ( " << front.piece << ", " << front.start + << ", " << (front.start + front.length - 1) << ")" << std::endl; #endif - m_web.restart_request = m_requests.front(); - if (!m_web.restart_piece.empty()) - { - // we're about to replace a different restart piece - // buffer. So it was wasted download - if (t) t->add_redundant_bytes(m_web.restart_piece.size() - , torrent::piece_closing); - } - m_web.restart_piece.swap(m_piece); - // we have to do this to not count this data as redundant. The - // upper layer will call downloading_piece_progress and assume - // it's all wasted download. Since we're saving it here, it isn't. - m_requests.clear(); - m_block_pos = 0; + req.start += m_piece.size(); + req.length -= m_piece.size(); + + // just to keep the accounting straight for the upper layer. + // it doesn't know we just re-wrote the request + incoming_piece_fragment(m_piece.size()); + m_web.restart_request.piece = -1; } - if (!m_web.supports_keepalive && error == 0) - { - // if the web server doesn't support keepalive and we were - // disconnected as a graceful EOF, reconnect right away - if (t) t->session().m_io_service.post( - boost::bind(&torrent::maybe_connect_web_seeds, t)); - } - peer_connection::disconnect(ec, error); - if (t) t->disconnect_web_seed(this); - } - - boost::optional - web_peer_connection::downloading_piece_progress() const - { - if (m_requests.empty()) - return boost::optional(); - - boost::shared_ptr t = associated_torrent().lock(); - TORRENT_ASSERT(t); - - piece_block_progress ret; - - ret.piece_index = m_requests.front().piece; - ret.bytes_downloaded = m_block_pos % t->block_size(); - // this is used to make sure that the block_index stays within - // bounds. If the entire piece is downloaded, the block_index - // would otherwise point to one past the end - int correction = m_block_pos ? -1 : 0; - ret.block_index = (m_requests.front().start + m_block_pos + correction) / t->block_size(); - TORRENT_ASSERT(ret.block_index < int(piece_block::invalid.block_index)); - TORRENT_ASSERT(ret.piece_index < int(piece_block::invalid.piece_index)); - - ret.full_block_bytes = t->block_size(); - const int last_piece = t->torrent_file().num_pieces() - 1; - if (ret.piece_index == last_piece && ret.block_index - == t->torrent_file().piece_size(last_piece) / t->block_size()) - ret.full_block_bytes = t->torrent_file().piece_size(last_piece) % t->block_size(); - return ret; - } - - void web_peer_connection::write_request(peer_request const& r) - { - INVARIANT_CHECK; - - boost::shared_ptr t = associated_torrent().lock(); - TORRENT_ASSERT(t); - - TORRENT_ASSERT(t->valid_metadata()); - - bool single_file_request = t->torrent_file().num_files() == 1; - - if (!single_file_request) - { - // handle incorrect .torrent files which are multi-file - // but have web seeds not ending with a slash - if (m_path.empty() || m_path[m_path.size() - 1] != '/') m_path += "/"; - if (m_url.empty() || m_url[m_url.size() - 1] != '/') m_url += "/"; - } - else - { - // handle .torrent files that don't include the filename in the url - if (m_path.empty()) m_path += "/" + t->torrent_file().name(); - else if (m_path[m_path.size() - 1] == '/') - { - std::string tmp = t->torrent_file().files().at(0).path; -#ifdef TORRENT_WINDOWS - convert_path_to_posix(tmp); -#endif - m_path += tmp; - } - else if (!m_url.empty() && m_url[m_url.size() - 1] == '/') - { - std::string tmp = t->torrent_file().files().at(0).path; -#ifdef TORRENT_WINDOWS - convert_path_to_posix(tmp); -#endif - m_url += tmp; - } - } - - torrent_info const& info = t->torrent_file(); - peer_request req = r; - - std::string request; - request.reserve(400); - - int size = r.length; - const int block_size = t->block_size(); - const int piece_size = t->torrent_file().piece_length(); - peer_request pr; - while (size > 0) - { - int request_offset = r.start + r.length - size; - pr.start = request_offset % piece_size; - pr.length = (std::min)(block_size, size); - pr.piece = r.piece + request_offset / piece_size; - m_requests.push_back(pr); - size -= pr.length; - if (m_web.restart_request == m_requests.front()) - { - m_piece.swap(m_web.restart_piece); - m_block_pos += m_piece.size(); - peer_request& front = m_requests.front(); - TORRENT_ASSERT(front.length > int(m_piece.size())); - -#if 0 - std::cerr << this << " RESTART-DATA: data: " << m_piece.size() - << " req: ( " << front.piece << ", " << front.start - << ", " << (front.start + front.length - 1) << ")" - << std::endl; -#endif - - req.start += m_piece.size(); - req.length -= m_piece.size(); - - // just to keep the accounting straight for the upper layer. - // it doesn't know we just re-wrote the request - incoming_piece_fragment(m_piece.size()); - m_web.restart_request.piece = -1; - } - #if 0 std::cerr << this << " REQ: p: " << pr.piece << " " << pr.start << std::endl; #endif - } - - proxy_settings const& ps = m_ses.proxy(); - bool using_proxy = (ps.type == proxy_settings::http - || ps.type == proxy_settings::http_pw) && !m_ssl; - - if (single_file_request) - { - request += "GET "; - // do not encode single file paths, they are - // assumed to be encoded in the torrent file - request += using_proxy ? m_url : m_path; - request += " HTTP/1.1\r\n"; - add_headers(request, ps, using_proxy); - request += "\r\nRange: bytes="; - request += to_string(size_type(req.piece) * info.piece_length() - + req.start).elems; - request += "-"; - request += to_string(size_type(req.piece) * info.piece_length() - + req.start + req.length - 1).elems; - request += "\r\n\r\n"; - m_first_request = false; - m_file_requests.push_back(0); - } - else - { - std::vector files = info.orig_files().map_block( - req.piece, req.start, req.length); - - for (std::vector::iterator i = files.begin(); - i != files.end(); ++i) - { - file_slice const& f = *i; - if (info.orig_files().pad_file_at(f.file_index)) - { - m_file_requests.push_back(f.file_index); - continue; - } - request += "GET "; - if (using_proxy) - { - // m_url is already a properly escaped URL - // with the correct slashes. Don't encode it again - request += m_url; - std::string path = info.orig_files().file_path(f.file_index); -#ifdef TORRENT_WINDOWS - convert_path_to_posix(path); -#endif - request += escape_path(path.c_str(), path.length()); - } - else - { - // m_path is already a properly escaped URL - // with the correct slashes. Don't encode it again - request += m_path; - - std::string path = info.orig_files().file_path(f.file_index); -#ifdef TORRENT_WINDOWS - convert_path_to_posix(path); -#endif - request += escape_path(path.c_str(), path.length()); - } - request += " HTTP/1.1\r\n"; - add_headers(request, ps, using_proxy); - request += "\r\nRange: bytes="; - request += to_string(f.offset).elems; - request += "-"; - request += to_string(f.offset + f.size - 1).elems; - request += "\r\n\r\n"; - m_first_request = false; - -#if 0 - std::cerr << this << " SEND-REQUEST: f: " << f.file_index - << " s: " << f.offset - << " e: " << (f.offset + f.size - 1) << std::endl; -#endif - TORRENT_ASSERT(f.file_index >= 0); - m_file_requests.push_back(f.file_index); - } - } - -#ifdef TORRENT_VERBOSE_LOGGING - peer_log("==> %s", request.c_str()); -#endif - - // in case the first file on this series of requests is a padfile - // we need to handle it right now, and pretend that we got a response - // with zeros. - buffer::const_interval recv_buffer = receive_buffer(); - handle_padfile(recv_buffer); - if (associated_torrent().expired()) return; - - send_buffer(request.c_str(), request.size(), message_type_request); + size -= pr.length; } - // -------------------------- - // RECEIVE DATA - // -------------------------- + int proxy_type = m_ses.settings().get_int(settings_pack::proxy_type); + bool using_proxy = (proxy_type == settings_pack::http + || proxy_type == settings_pack::http_pw) && !m_ssl; - namespace + if (single_file_request) { - bool range_contains(peer_request const& range, peer_request const& req, int piece_size) - { - size_type range_start = size_type(range.piece) * piece_size + range.start; - size_type req_start = size_type(req.piece) * piece_size + req.start; - return range_start <= req_start - && range_start + range.length >= req_start + req.length; - } + request += "GET "; + // do not encode single file paths, they are + // assumed to be encoded in the torrent file + request += using_proxy ? m_url : m_path; + request += " HTTP/1.1\r\n"; + add_headers(request, m_ses.settings(), using_proxy); + request += "\r\nRange: bytes="; + request += to_string(size_type(req.piece) * info.piece_length() + + req.start).elems; + request += "-"; + request += to_string(size_type(req.piece) * info.piece_length() + + req.start + req.length - 1).elems; + request += "\r\n\r\n"; + m_first_request = false; + m_file_requests.push_back(0); } - - bool web_peer_connection::maybe_harvest_block() + else { - peer_request const& front_request = m_requests.front(); - - if (int(m_piece.size()) < front_request.length) return false; - TORRENT_ASSERT(int(m_piece.size()) == front_request.length); - - // each call to incoming_piece() may result in us becoming - // a seed. If we become a seed, all seeds we're connected to - // will be disconnected, including this web seed. We need to - // check for the disconnect condition after the call. - - boost::shared_ptr t = associated_torrent().lock(); - TORRENT_ASSERT(t); - buffer::const_interval recv_buffer = receive_buffer(); - - incoming_piece(front_request, &m_piece[0]); - m_requests.pop_front(); - if (associated_torrent().expired()) return false; - TORRENT_ASSERT(m_block_pos >= front_request.length); - m_block_pos -= front_request.length; - cut_receive_buffer(m_body_start, t->block_size() + request_size_overhead); - m_body_start = 0; - recv_buffer = receive_buffer(); -// TORRENT_ASSERT(m_received_body <= range_end - range_start); - m_piece.clear(); - TORRENT_ASSERT(m_piece.empty()); - return true; - } - - bool web_peer_connection::received_invalid_data(int index, bool single_peer) - { - if (!single_peer) return peer_connection::received_invalid_data(index, single_peer); - - // when a web seed fails a hash check, do the following: - // 1. if the whole piece only overlaps a single file, mark that file as not - // have for this peer - // 2. if the piece overlaps more than one file, mark the piece as not have - // for this peer - // 3. if it's a single file torrent, just ban it right away - // this handles the case where web seeds may have some files updated but not other - - boost::shared_ptr t = associated_torrent().lock(); - file_storage const& fs = t->torrent_file().files(); - - // single file torrent - if (fs.num_files() == 1) return peer_connection::received_invalid_data(index, single_peer); - - std::vector files = fs.map_block(index, 0, fs.piece_size(index)); - - if (files.size() == 1) + if (!t->need_loaded()) { - // assume the web seed has a different copy of this specific file - // than what we expect, and pretend not to have it. - int fi = files[0].file_index; - int first_piece = int(fs.file_offset(fi) / fs.piece_length()); - // one past last piece - int end_piece = int((fs.file_offset(fi) + fs.file_size(fi) + 1) / fs.piece_length()); - for (int i = first_piece; i < end_piece; ++i) - incoming_dont_have(i); - } - else - { - incoming_dont_have(index); - } - - peer_connection::received_invalid_data(index, single_peer); - - // if we don't think we have any of the files, allow banning the web seed - if (num_have_pieces() == 0) return true; - - // don't disconnect, we won't request anything from this file again - return false; - } - - void web_peer_connection::on_receive(error_code const& error - , std::size_t bytes_transferred) - { - INVARIANT_CHECK; - -#ifdef TORRENT_DEBUG - TORRENT_ASSERT(m_statistics.last_payload_downloaded() - + m_statistics.last_protocol_downloaded() + bytes_transferred < size_t(INT_MAX)); - int dl_target = m_statistics.last_payload_downloaded() - + m_statistics.last_protocol_downloaded() + bytes_transferred; -#endif - - if (error) - { - m_statistics.received_bytes(0, bytes_transferred); -#ifdef TORRENT_VERBOSE_LOGGING - peer_log("*** web_peer_connection error: %s", error.message().c_str()); -#endif -#ifdef TORRENT_DEBUG - TORRENT_ASSERT(m_statistics.last_payload_downloaded() - + m_statistics.last_protocol_downloaded() - == dl_target); -#endif + disconnect(errors::torrent_aborted, op_bittorrent); return; } - boost::shared_ptr t = associated_torrent().lock(); - TORRENT_ASSERT(t); + std::vector files = info.orig_files().map_block(r.piece, r.start + , r.length); - for (;;) + for (std::vector::iterator i = files.begin(); + i != files.end(); ++i) { -#ifdef TORRENT_DEBUG - TORRENT_ASSERT(m_statistics.last_payload_downloaded() - + m_statistics.last_protocol_downloaded() + int(bytes_transferred) - == dl_target); -#endif - - buffer::const_interval recv_buffer = receive_buffer(); - - int payload; - int protocol; - bool header_finished = m_parser.header_finished(); - if (!header_finished) + file_slice const& f = *i; + if (info.orig_files().pad_file_at(f.file_index)) { - bool failed = false; - boost::tie(payload, protocol) = m_parser.incoming(recv_buffer, failed); - m_statistics.received_bytes(0, protocol); - TORRENT_ASSERT(int(bytes_transferred) >= protocol); - bytes_transferred -= protocol; - - if (failed) - { - m_statistics.received_bytes(0, bytes_transferred); -#ifdef TORRENT_VERBOSE_LOGGING - peer_log("*** %s", std::string(recv_buffer.begin, recv_buffer.end).c_str()); -#endif - disconnect(errors::http_parse_error, 2); -#ifdef TORRENT_DEBUG - TORRENT_ASSERT(m_statistics.last_payload_downloaded() - + m_statistics.last_protocol_downloaded() - == dl_target); -#endif - return; - } - - TORRENT_ASSERT(recv_buffer.left() == 0 || *recv_buffer.begin == 'H'); - - TORRENT_ASSERT(recv_buffer.left() <= packet_size()); - - // this means the entire status line hasn't been received yet - if (m_parser.status_code() == -1) - { - TORRENT_ASSERT(payload == 0); - TORRENT_ASSERT(bytes_transferred == 0); -#ifdef TORRENT_DEBUG - TORRENT_ASSERT(m_statistics.last_payload_downloaded() - + m_statistics.last_protocol_downloaded() + int(bytes_transferred) - == dl_target); -#endif - break; - } - - if (!m_parser.header_finished()) - { - TORRENT_ASSERT(payload == 0); - TORRENT_ASSERT(bytes_transferred == 0); -#ifdef TORRENT_DEBUG - TORRENT_ASSERT(m_statistics.last_payload_downloaded() - + m_statistics.last_protocol_downloaded() + int(bytes_transferred) - == dl_target); -#endif - break; - } - - m_body_start = m_parser.body_start(); - m_received_body = 0; + m_file_requests.push_back(f.file_index); + continue; } - // we just completed reading the header - if (!header_finished) + request += "GET "; + if (using_proxy) { - ++m_num_responses; + // m_url is already a properly escaped URL + // with the correct slashes. Don't encode it again + request += m_url; + std::string path = info.orig_files().file_path(f.file_index); +#ifdef TORRENT_WINDOWS + convert_path_to_posix(path); +#endif + request += escape_path(path.c_str(), path.length()); + } + else + { + // m_path is already a properly escaped URL + // with the correct slashes. Don't encode it again + request += m_path; - if (m_parser.connection_close()) - { - incoming_choke(); - if (m_num_responses == 1) - m_web.supports_keepalive = false; - } + std::string path = info.orig_files().file_path(f.file_index); +#ifdef TORRENT_WINDOWS + convert_path_to_posix(path); +#endif + request += escape_path(path.c_str(), path.length()); + } + request += " HTTP/1.1\r\n"; + add_headers(request, m_ses.settings(), using_proxy); + request += "\r\nRange: bytes="; + request += to_string(f.offset).elems; + request += "-"; + request += to_string(f.offset + f.size - 1).elems; + request += "\r\n\r\n"; + m_first_request = false; + +#if 0 + std::cerr << this << " SEND-REQUEST: f: " << f.file_index + << " s: " << f.offset + << " e: " << (f.offset + f.size - 1) << std::endl; +#endif + TORRENT_ASSERT(f.file_index >= 0); + m_file_requests.push_back(f.file_index); + } + } #ifdef TORRENT_VERBOSE_LOGGING - peer_log("*** STATUS: %d %s", m_parser.status_code(), m_parser.message().c_str()); - std::multimap const& headers = m_parser.headers(); - for (std::multimap::const_iterator i = headers.begin() - , end(headers.end()); i != end; ++i) - peer_log(" %s: %s", i->first.c_str(), i->second.c_str()); + peer_log("==> %s", request.c_str()); #endif - // if the status code is not one of the accepted ones, abort - if (!is_ok_status(m_parser.status_code())) - { - // TODO: 3 just make this peer not have the pieces - // associated with the file we just requested. Only - // when it doesn't have any of the file do the following - int retry_time = atoi(m_parser.header("retry-after").c_str()); - if (retry_time <= 0) retry_time = m_ses.settings().urlseed_wait_retry; - // temporarily unavailable, retry later - t->retry_web_seed(this, retry_time); - std::string error_msg = to_string(m_parser.status_code()).elems - + (" " + m_parser.message()); - if (m_ses.m_alerts.should_post()) - { - m_ses.m_alerts.post_alert(url_seed_alert(t->get_handle(), m_url - , error_msg)); - } - m_statistics.received_bytes(0, bytes_transferred); - disconnect(error_code(m_parser.status_code(), get_http_category()), 1); + + // in case the first file on this series of requests is a padfile + // we need to handle it right now, and pretend that we got a response + // with zeros. + buffer::const_interval recv_buffer = receive_buffer(); + handle_padfile(recv_buffer); + if (associated_torrent().expired()) return; + + send_buffer(request.c_str(), request.size(), message_type_request); +} + +// -------------------------- +// RECEIVE DATA +// -------------------------- + +namespace +{ + bool range_contains(peer_request const& range, peer_request const& req, int piece_size) + { + size_type range_start = size_type(range.piece) * piece_size + range.start; + size_type req_start = size_type(req.piece) * piece_size + req.start; + return range_start <= req_start + && range_start + range.length >= req_start + req.length; + } +} + +bool web_peer_connection::maybe_harvest_block() +{ + peer_request const& front_request = m_requests.front(); + + if (int(m_piece.size()) < front_request.length) return false; + TORRENT_ASSERT(int(m_piece.size() == front_request.length)); + + // each call to incoming_piece() may result in us becoming + // a seed. If we become a seed, all seeds we're connected to + // will be disconnected, including this web seed. We need to + // check for the disconnect condition after the call. + + boost::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + buffer::const_interval recv_buffer = receive_buffer(); + + incoming_piece(front_request, &m_piece[0]); + m_requests.pop_front(); + if (associated_torrent().expired()) return false; + TORRENT_ASSERT(m_block_pos >= front_request.length); + m_block_pos -= front_request.length; + cut_receive_buffer(m_body_start, t->block_size() + request_size_overhead); + m_body_start = 0; + recv_buffer = receive_buffer(); +// TORRENT_ASSERT(m_received_body <= range_end - range_start); + m_piece.clear(); + TORRENT_ASSERT(m_piece.empty()); + return true; +} + +bool web_peer_connection::received_invalid_data(int index, bool single_peer) +{ + if (!single_peer) return peer_connection::received_invalid_data(index, single_peer); + + // when a web seed fails a hash check, do the following: + // 1. if the whole piece only overlaps a single file, mark that file as not + // have for this peer + // 2. if the piece overlaps more than one file, mark the piece as not have + // for this peer + // 3. if it's a single file torrent, just ban it right away + // this handles the case where web seeds may have some files updated but not other + + boost::shared_ptr t = associated_torrent().lock(); + file_storage const& fs = t->torrent_file().files(); + + // single file torrent + if (fs.num_files() == 1) return peer_connection::received_invalid_data(index, single_peer); + + std::vector files = fs.map_block(index, 0, fs.piece_size(index)); + + if (files.size() == 1) + { + // assume the web seed has a different copy of this specific file + // than what we expect, and pretend not to have it. + int fi = files[0].file_index; + int first_piece = fs.file_offset(fi) / fs.piece_length(); + // one past last piece + int end_piece = int((fs.file_offset(fi) + fs.file_size(fi) + 1) / fs.piece_length()); + for (int i = first_piece; i < end_piece; ++i) + incoming_dont_have(i); + } + else + { + incoming_dont_have(index); + } + + peer_connection::received_invalid_data(index, single_peer); + + // if we don't think we have any of the files, allow banning the web seed + if (num_have_pieces() == 0) return true; + + // don't disconnect, we won't request anything from this file again + return false; +} + +void web_peer_connection::on_receive(error_code const& error + , std::size_t bytes_transferred) +{ + INVARIANT_CHECK; + #ifdef TORRENT_DEBUG - TORRENT_ASSERT(m_statistics.last_payload_downloaded() - + m_statistics.last_protocol_downloaded() + TORRENT_ASSERT(statistics().last_payload_downloaded() + + statistics().last_protocol_downloaded() + bytes_transferred < size_t(INT_MAX)); + int dl_target = statistics().last_payload_downloaded() + + statistics().last_protocol_downloaded() + bytes_transferred; +#endif + + if (error) + { + received_bytes(0, bytes_transferred); +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("*** web_peer_connection error: %s", error.message().c_str()); +#endif +#ifdef TORRENT_DEBUG + TORRENT_ASSERT(statistics().last_payload_downloaded() + + statistics().last_protocol_downloaded() + == dl_target); +#endif + return; + } + + boost::shared_ptr t = associated_torrent().lock(); + TORRENT_ASSERT(t); + + for (;;) + { +#ifdef TORRENT_DEBUG + TORRENT_ASSERT(statistics().last_payload_downloaded() + + statistics().last_protocol_downloaded() + int(bytes_transferred) + == dl_target); +#endif + + buffer::const_interval recv_buffer = receive_buffer(); + + int payload; + int protocol; + bool header_finished = m_parser.header_finished(); + if (!header_finished) + { + bool failed = false; + boost::tie(payload, protocol) = m_parser.incoming(recv_buffer, failed); + received_bytes(0, protocol); + TORRENT_ASSERT(int(bytes_transferred) >= protocol); + bytes_transferred -= protocol; + + if (failed) + { + received_bytes(0, bytes_transferred); +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("*** %s", std::string(recv_buffer.begin, recv_buffer.end).c_str()); +#endif + disconnect(errors::http_parse_error, op_bittorrent, 2); +#ifdef TORRENT_DEBUG + TORRENT_ASSERT(statistics().last_payload_downloaded() + + statistics().last_protocol_downloaded() + == dl_target); +#endif + return; + } + + TORRENT_ASSERT(recv_buffer.left() == 0 || *recv_buffer.begin == 'H'); + + TORRENT_ASSERT(recv_buffer.left() <= packet_size()); + + // this means the entire status line hasn't been received yet + if (m_parser.status_code() == -1) + { + TORRENT_ASSERT(payload == 0); + TORRENT_ASSERT(bytes_transferred == 0); +#ifdef TORRENT_DEBUG + TORRENT_ASSERT(statistics().last_payload_downloaded() + + statistics().last_protocol_downloaded() + int(bytes_transferred) + == dl_target); +#endif + break; + } + + if (!m_parser.header_finished()) + { + TORRENT_ASSERT(payload == 0); + TORRENT_ASSERT(bytes_transferred == 0); +#ifdef TORRENT_DEBUG + TORRENT_ASSERT(statistics().last_payload_downloaded() + + statistics().last_protocol_downloaded() + int(bytes_transferred) + == dl_target); +#endif + break; + } + + m_body_start = m_parser.body_start(); + m_received_body = 0; + } + + // we just completed reading the header + if (!header_finished) + { + ++m_num_responses; + + if (m_parser.connection_close()) + { + incoming_choke(); + if (m_num_responses == 1) + m_web.supports_keepalive = false; + } + +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("*** STATUS: %d %s", m_parser.status_code(), m_parser.message().c_str()); + std::multimap const& headers = m_parser.headers(); + for (std::multimap::const_iterator i = headers.begin() + , end(headers.end()); i != end; ++i) + peer_log(" %s: %s", i->first.c_str(), i->second.c_str()); +#endif + // if the status code is not one of the accepted ones, abort + if (!is_ok_status(m_parser.status_code())) + { + // TODO: 3 just make this peer not have the pieces + // associated with the file we just requested. Only + // when it doesn't have any of the file do the following + int retry_time = atoi(m_parser.header("retry-after").c_str()); + if (retry_time <= 0) retry_time = m_ses.settings().get_int(settings_pack::urlseed_wait_retry); + // temporarily unavailable, retry later + t->retry_web_seed(this, retry_time); + std::string error_msg = to_string(m_parser.status_code()).elems + + (" " + m_parser.message()); + if (t->alerts().should_post()) + { + t->alerts().post_alert(url_seed_alert(t->get_handle(), m_url + , error_msg)); + } + received_bytes(0, bytes_transferred); + disconnect(error_code(m_parser.status_code(), get_http_category()), op_bittorrent, 1); +#ifdef TORRENT_DEBUG + TORRENT_ASSERT(statistics().last_payload_downloaded() + + statistics().last_protocol_downloaded() + == dl_target); +#endif + return; + } + if (is_redirect(m_parser.status_code())) + { + // this means we got a redirection request + // look for the location header + std::string location = m_parser.header("location"); + received_bytes(0, bytes_transferred); + + if (location.empty()) + { + // we should not try this server again. + t->remove_web_seed(this, errors::missing_location, op_bittorrent, 2); + TORRENT_ASSERT(is_disconnecting()); +#ifdef TORRENT_DEBUG + TORRENT_ASSERT(statistics().last_payload_downloaded() + + statistics().last_protocol_downloaded() == dl_target); #endif return; } - if (is_redirect(m_parser.status_code())) + + bool single_file_request = false; + if (!m_path.empty() && m_path[m_path.size() - 1] != '/') + single_file_request = true; + + // add the redirected url and remove the current one + if (!single_file_request) { - // this means we got a redirection request - // look for the location header - std::string location = m_parser.header("location"); - m_statistics.received_bytes(0, bytes_transferred); - - if (location.empty()) - { - // we should not try this server again. - t->remove_web_seed(this); - disconnect(errors::missing_location, 2); -#ifdef TORRENT_DEBUG - TORRENT_ASSERT(m_statistics.last_payload_downloaded() - + m_statistics.last_protocol_downloaded() - == dl_target); -#endif - return; - } - - bool single_file_request = false; - if (!m_path.empty() && m_path[m_path.size() - 1] != '/') - single_file_request = true; - - // add the redirected url and remove the current one - if (!single_file_request) - { - TORRENT_ASSERT(!m_file_requests.empty()); - int file_index = m_file_requests.front(); + TORRENT_ASSERT(!m_file_requests.empty()); + int file_index = m_file_requests.front(); + if (!t->need_loaded()) + { + disconnect(errors::torrent_aborted, op_bittorrent); + return; + } // TODO: 2 create a mapping of file-index to redirection URLs. Use that to form // URLs instead. Support to reconnect to a new server without destructing this // peer_connection @@ -642,23 +659,26 @@ namespace libtorrent size_t i = location.rfind(path); if (i == std::string::npos) { - t->remove_web_seed(this); - disconnect(errors::invalid_redirection, 2); + t->remove_web_seed(this, errors::invalid_redirection, op_bittorrent, 2); + TORRENT_ASSERT(is_disconnecting()); #ifdef TORRENT_DEBUG - TORRENT_ASSERT(m_statistics.last_payload_downloaded() - + m_statistics.last_protocol_downloaded() + TORRENT_ASSERT(statistics().last_payload_downloaded() + + statistics().last_protocol_downloaded() == dl_target); #endif return; } location.resize(i); } +#ifdef TORRENT_VERBOSE_LOGGING + peer_log("*** LOCATION: %s", location.c_str()); +#endif t->add_web_seed(location, web_seed_entry::url_seed, m_external_auth, m_extra_headers); - t->remove_web_seed(this); - disconnect(errors::redirecting, 2); + t->remove_web_seed(this, errors::redirecting, op_bittorrent, 2); + TORRENT_ASSERT(is_disconnecting()); #ifdef TORRENT_DEBUG - TORRENT_ASSERT(m_statistics.last_payload_downloaded() - + m_statistics.last_protocol_downloaded() + TORRENT_ASSERT(statistics().last_payload_downloaded() + + statistics().last_protocol_downloaded() == dl_target); #endif return; @@ -685,8 +705,8 @@ namespace libtorrent if (recv_buffer.left() == 0) { #ifdef TORRENT_DEBUG - TORRENT_ASSERT(m_statistics.last_payload_downloaded() - + m_statistics.last_protocol_downloaded() + TORRENT_ASSERT(statistics().last_payload_downloaded() + + statistics().last_protocol_downloaded() == dl_target); #endif break; @@ -699,13 +719,13 @@ namespace libtorrent boost::tie(range_start, range_end) = m_parser.content_range(); if (range_start < 0 || range_end < range_start) { - m_statistics.received_bytes(0, bytes_transferred); + received_bytes(0, bytes_transferred); // we should not try this server again. - t->remove_web_seed(this); - disconnect(errors::invalid_range); + t->remove_web_seed(this, errors::invalid_range, op_bittorrent); + TORRENT_ASSERT(is_disconnecting()); #ifdef TORRENT_DEBUG - TORRENT_ASSERT(m_statistics.last_payload_downloaded() - + m_statistics.last_protocol_downloaded() + TORRENT_ASSERT(statistics().last_payload_downloaded() + + statistics().last_protocol_downloaded() == dl_target); #endif return; @@ -719,13 +739,13 @@ namespace libtorrent range_end = m_parser.content_length(); if (range_end == -1) { - m_statistics.received_bytes(0, bytes_transferred); + received_bytes(0, bytes_transferred); // we should not try this server again. - t->remove_web_seed(this); - disconnect(errors::no_content_length, 2); + t->remove_web_seed(this, errors::no_content_length, op_bittorrent, 2); + TORRENT_ASSERT(is_disconnecting()); #ifdef TORRENT_DEBUG - TORRENT_ASSERT(m_statistics.last_payload_downloaded() - + m_statistics.last_protocol_downloaded() + TORRENT_ASSERT(statistics().last_payload_downloaded() + + statistics().last_protocol_downloaded() == dl_target); #endif return; @@ -749,7 +769,7 @@ namespace libtorrent { TORRENT_ASSERT(int(bytes_transferred) >= chunk_start.left() - m_partial_chunk_header); bytes_transferred -= chunk_start.left() - m_partial_chunk_header; - m_statistics.received_bytes(0, chunk_start.left() - m_partial_chunk_header); + received_bytes(0, chunk_start.left() - m_partial_chunk_header); m_partial_chunk_header = chunk_start.left(); if (bytes_transferred == 0) return; break; @@ -761,7 +781,7 @@ namespace libtorrent #endif TORRENT_ASSERT(int(bytes_transferred) >= header_size - m_partial_chunk_header); bytes_transferred -= header_size - m_partial_chunk_header; - m_statistics.received_bytes(0, header_size - m_partial_chunk_header); + received_bytes(0, header_size - m_partial_chunk_header); m_partial_chunk_header = 0; TORRENT_ASSERT(chunk_size != 0 || chunk_start.left() <= header_size || chunk_start.begin[header_size] == 'H'); // cut out the chunk header from the receive buffer @@ -787,11 +807,11 @@ namespace libtorrent if (m_requests.empty() || m_file_requests.empty()) { - m_statistics.received_bytes(0, bytes_transferred); - disconnect(errors::http_error, 2); + received_bytes(0, bytes_transferred); + disconnect(errors::http_error, op_bittorrent, 2); #ifdef TORRENT_DEBUG - TORRENT_ASSERT(m_statistics.last_payload_downloaded() - + m_statistics.last_protocol_downloaded() + TORRENT_ASSERT(statistics().last_payload_downloaded() + + statistics().last_protocol_downloaded() == dl_target); #endif return; @@ -811,13 +831,18 @@ namespace libtorrent , payload_transferred, front_request.piece , front_request.start, front_request.length); #endif - m_statistics.received_bytes(payload_transferred, 0); + received_bytes(payload_transferred, 0); TORRENT_ASSERT(int(bytes_transferred) >= payload_transferred); bytes_transferred -= payload_transferred; m_range_pos += payload_transferred; m_block_pos += payload_transferred; if (m_range_pos > range_end - range_start) m_range_pos = range_end - range_start; + if (!t->need_loaded()) + { + disconnect(errors::torrent_aborted, op_bittorrent); + return; + } int file_index = m_file_requests.front(); peer_request in_range = info.orig_files().map_file(file_index, range_start , int(range_end - range_start)); @@ -841,11 +866,10 @@ namespace libtorrent { incoming_piece_fragment((std::min)(payload_transferred , front_request.length - m_block_pos)); - m_statistics.received_bytes(0, bytes_transferred); + received_bytes(0, bytes_transferred); // this means the end of the incoming request ends _before_ the // first expected byte (fs + m_piece.size()) - - disconnect(errors::invalid_range, 2); + disconnect(errors::invalid_range, op_bittorrent, 2); return; } @@ -970,6 +994,12 @@ namespace libtorrent m_chunk_pos = 0; m_partial_chunk_header = 0; + if (!t->need_loaded()) + { + disconnect(errors::torrent_aborted, op_bittorrent); + return; + } + handle_padfile(recv_buffer); if (associated_torrent().expired()) return; continue; @@ -978,8 +1008,8 @@ namespace libtorrent if (bytes_transferred == 0 || payload_transferred == 0) { #ifdef TORRENT_DEBUG - TORRENT_ASSERT(m_statistics.last_payload_downloaded() - + m_statistics.last_protocol_downloaded() + TORRENT_ASSERT(statistics().last_payload_downloaded() + + statistics().last_protocol_downloaded() == dl_target); #endif break; @@ -988,8 +1018,8 @@ namespace libtorrent } TORRENT_ASSERT(bytes_transferred == 0); #ifdef TORRENT_DEBUG - TORRENT_ASSERT(m_statistics.last_payload_downloaded() - + m_statistics.last_protocol_downloaded() == dl_target); + TORRENT_ASSERT(statistics().last_payload_downloaded() + + statistics().last_protocol_downloaded() == dl_target); #endif } diff --git a/test/Jamfile b/test/Jamfile index 831f67ff7..9c7ef3dcc 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -82,6 +82,7 @@ project full on shared + on ; feature launcher : none valgrind : composite ; @@ -92,6 +93,11 @@ test-suite libtorrent : [ run test_utf8.cpp ] [ run test_gzip.cpp ] [ run test_bitfield.cpp ] + [ run test_connection_queue.cpp ] + [ run test_recheck.cpp ] + [ run test_stat_cache.cpp ] + [ run test_part_file.cpp ] + [ run test_policy.cpp ] [ run test_torrent_info.cpp ] [ run test_time.cpp ] [ run test_file_storage.cpp ] @@ -100,6 +106,7 @@ test-suite libtorrent : [ run test_file.cpp ] [ run test_privacy.cpp ] [ run test_threads.cpp ] + [ run test_tailqueue.cpp ] [ run test_rss.cpp ] [ run test_bandwidth_limiter.cpp ] [ run test_buffer.cpp ] @@ -115,6 +122,10 @@ test-suite libtorrent : [ run test_ip_filter.cpp ] [ run test_hasher.cpp ] [ run test_dht.cpp ] + [ run test_block_cache.cpp ] + [ run test_peer_classes.cpp ] + [ run test_settings_pack.cpp ] + [ run test_fence.cpp ] [ run test_storage.cpp ] [ run test_torrent_parse.cpp ] [ run test_session.cpp ] @@ -135,6 +146,7 @@ test-suite libtorrent : [ run test_web_seed_ban.cpp ] [ run test_bdecode_performance.cpp ] [ run test_pe_crypto.cpp ] + [ run test_dos_blocker.cpp ] [ run test_remap_files.cpp ] [ run test_utp.cpp ] diff --git a/test/Makefile.am b/test/Makefile.am index 44e3400a3..5024bea8a 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -2,6 +2,12 @@ AUTOMAKE_OPTIONS = subdir-objects test_programs = \ test_bitfield \ + test_connection_queue \ + test_torrent_info \ + test_recheck \ + test_stat_cache \ + test_policy \ + test_part_file \ test_file \ test_file_storage \ test_privacy \ @@ -10,6 +16,7 @@ test_programs = \ test_bdecode_performance \ test_bencoding \ test_buffer \ + test_block_cache \ test_checking \ test_fast_extension \ test_hasher \ @@ -19,6 +26,7 @@ test_programs = \ test_lsd \ test_metadata_extension \ test_pe_crypto \ + test_peer_classes \ test_peer_priority \ test_pex \ test_piece_picker \ @@ -28,11 +36,13 @@ test_programs = \ test_http_parser \ test_magnet \ test_packet_buffer \ + test_settings_pack \ test_read_piece \ test_rss \ test_ssl \ test_storage \ test_swarm \ + test_tailqueue \ test_threads \ test_torrent \ test_torrent_parse \ @@ -131,6 +141,12 @@ libtest_la_SOURCES = main.cpp \ ../ed25519/src/verify.c test_bitfield_SOURCES = test_bitfield.cpp +test_connection_queue_SOURCES = test_connection_queue.cpp +test_torrent_info_SOURCES = test_torrent_info.cpp +test_recheck_SOURCES = test_recheck.cpp +test_stat_cache_SOURCES = test_stat_cache.cpp +test_policy_SOURCES = test_policy.cpp +test_part_file_SOURCES = test_part_file.cpp test_file_SOURCES = test_file.cpp test_file_storage_SOURCES = test_file_storage.cpp test_privacy_SOURCES = test_privacy.cpp @@ -140,6 +156,7 @@ test_bdecode_performance_SOURCES = test_bdecode_performance.cpp test_dht_SOURCES = test_dht.cpp test_bencoding_SOURCES = test_bencoding.cpp test_buffer_SOURCES = test_buffer.cpp +test_block_cache_SOURCES = test_block_cache.cpp test_checking_SOURCES = test_checking.cpp test_fast_extension_SOURCES = test_fast_extension.cpp test_hasher_SOURCES = test_hasher.cpp @@ -149,6 +166,7 @@ test_lsd_SOURCES = test_lsd.cpp test_metadata_extension_SOURCES = test_metadata_extension.cpp test_peer_priority_SOURCES = test_peer_priority.cpp test_pe_crypto_SOURCES = test_pe_crypto.cpp +test_peer_classes_SOURCES = test_peer_classes.cpp test_pex_SOURCES = test_pex.cpp test_piece_picker_SOURCES = test_piece_picker.cpp test_xml_SOURCES = test_xml.cpp @@ -159,7 +177,9 @@ test_magnet_SOURCES = test_magnet.cpp test_packet_buffer_SOURCES = test_packet_buffer.cpp test_read_piece_SOURCES = test_read_piece.cpp test_storage_SOURCES = test_storage.cpp +test_settings_pack_SOURCES = test_settings_pack.cpp test_swarm_SOURCES = test_swarm.cpp +test_tailqueue_SOURCES = test_tailqueue.cpp test_rss_SOURCES = test_rss.cpp test_ssl_SOURCES = test_ssl.cpp test_threads_SOURCES = test_threads.cpp diff --git a/test/main.cpp b/test/main.cpp index 9d66ead31..a53af330e 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -35,7 +35,10 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include // for exit() +#include "libtorrent/address.hpp" +#include "libtorrent/socket.hpp" #include "setup_transfer.hpp" // for tests_failure +#include "test.hpp" #include "dht_server.hpp" // for stop_dht #include "peer_server.hpp" // for stop_peer #include "udp_tracker.hpp" // for stop_udp_tracker @@ -50,6 +53,8 @@ int test_main(); #include // fot SetErrorMode #endif +using namespace libtorrent; + void sig_handler(int sig) { char stack_text[10000]; @@ -81,8 +86,6 @@ void sig_handler(int sig) exit(138); } -using namespace libtorrent; - int main() { #ifdef WIN32 diff --git a/test/setup_transfer.cpp b/test/setup_transfer.cpp index a5be8e031..1a107ffce 100644 --- a/test/setup_transfer.cpp +++ b/test/setup_transfer.cpp @@ -44,6 +44,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/thread.hpp" #include #include +#include #include "test.hpp" #include "libtorrent/assert.hpp" @@ -52,14 +53,14 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket_io.hpp" // print_endpoint #include "libtorrent/socket_type.hpp" #include "libtorrent/instantiate_connection.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/atomic.hpp" #ifdef TORRENT_USE_OPENSSL #include #include #endif -#include - #ifndef _WIN32 #include #include @@ -70,10 +71,39 @@ POSSIBILITY OF SUCH DAMAGE. #define DLOG if (DEBUG_WEB_SERVER) fprintf using namespace libtorrent; +namespace lt = libtorrent; static int tests_failure = 0; static std::vector failure_strings; +#if defined TORRENT_WINDOWS +#include +#endif + +address rand_v4() +{ + return address_v4((rand() << 16 | rand()) & 0xffffffff); +} + +#if TORRENT_USE_IPV6 +address rand_v6() +{ + address_v6::bytes_type bytes; + for (int i = 0; i < bytes.size(); ++i) bytes[i] = rand(); + return address_v6(bytes); +} +#endif + +tcp::endpoint rand_tcp_ep() +{ + return tcp::endpoint(rand_v4(), rand() + 1024); +} + +udp::endpoint rand_udp_ep() +{ + return udp::endpoint(rand_v4(), rand() + 1024); +} + void report_failure(char const* err, char const* file, int line) { char buf[500]; @@ -92,7 +122,7 @@ int print_failures() return tests_failure; } -std::auto_ptr wait_for_alert(session& ses, int type, char const* name) +std::auto_ptr wait_for_alert(lt::session& ses, int type, char const* name) { std::auto_ptr ret; ptime end = time_now() + seconds(10); @@ -126,21 +156,21 @@ int load_file(std::string const& filename, std::vector& v, libtorrent::err FILE* f = fopen(filename.c_str(), "rb"); if (f == NULL) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); return -1; } int r = fseek(f, 0, SEEK_END); if (r != 0) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); fclose(f); return -1; } long s = ftell(f); if (s < 0) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); fclose(f); return -1; } @@ -154,7 +184,7 @@ int load_file(std::string const& filename, std::vector& v, libtorrent::err r = fseek(f, 0, SEEK_SET); if (r != 0) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); fclose(f); return -1; } @@ -169,7 +199,7 @@ int load_file(std::string const& filename, std::vector& v, libtorrent::err r = fread(&v[0], 1, v.size(), f); if (r < 0) { - ec.assign(errno, boost::system::get_generic_category()); + ec.assign(errno, boost::system::generic_category()); fclose(f); return -1; } @@ -202,7 +232,7 @@ void save_file(char const* filename, char const* data, int size) } -bool print_alerts(libtorrent::session& ses, char const* name +bool print_alerts(lt::session& ses, char const* name , bool allow_disconnects, bool allow_no_torrents, bool allow_failed_fastresume , bool (*predicate)(libtorrent::alert*), bool no_output) { @@ -247,6 +277,13 @@ bool print_alerts(libtorrent::session& ses, char const* name || (allow_disconnects && pea->error.message() == "End of file.")); } */ + + invalid_request_alert* ira = alert_cast(*i); + if (ira) + { + fprintf(stderr, "peer error: %s\n", ira->message().c_str()); + TEST_CHECK(false); + } delete *i; } return ret; @@ -261,7 +298,7 @@ bool listen_alert(libtorrent::alert* a) return true; } -void wait_for_listen(libtorrent::session& ses, char const* name) +void wait_for_listen(lt::session& ses, char const* name) { listen_done = false; alert const* a = 0; @@ -275,6 +312,27 @@ void wait_for_listen(libtorrent::session& ses, char const* name) TEST_CHECK(listen_done); } +bool downloading_done = false; +bool downloading_alert(libtorrent::alert* a) +{ + state_changed_alert* sc = alert_cast(a); + if (sc && sc->state == torrent_status::downloading) + downloading_done = true; + return true; +} + +void wait_for_downloading(lt::session& ses, char const* name) +{ + downloading_done = false; + alert const* a = 0; + do + { + print_alerts(ses, name, true, true, true, &downloading_alert, false); + if (downloading_done) break; + a = ses.wait_for_alert(milliseconds(500)); + } while (a); +} + void print_ses_rate(float time , libtorrent::torrent_status const* st1 , libtorrent::torrent_status const* st2 @@ -358,7 +416,7 @@ pid_type async_run(char const* cmdline) if (ret == 0) { int error = GetLastError(); - fprintf(stderr, "failed (%d) %s\n", error, error_code(error, get_system_category()).message().c_str()); + fprintf(stderr, "failed (%d) %s\n", error, error_code(error, system_category()).message().c_str()); return 0; } return pi.dwProcessId; @@ -414,6 +472,7 @@ void stop_all_proxies() } } +// returns a port on success and -1 on failure int start_proxy(int proxy_type) { using namespace libtorrent; @@ -435,25 +494,25 @@ int start_proxy(int proxy_type) switch (proxy_type) { - case proxy_settings::socks4: + case settings_pack::socks4: type = "socks4"; auth = " --allow-v4"; cmd = "python ../socks.py"; break; - case proxy_settings::socks5: + case settings_pack::socks5: type = "socks5"; cmd = "python ../socks.py"; break; - case proxy_settings::socks5_pw: + case settings_pack::socks5_pw: type = "socks5"; auth = " --username testuser --password testpass"; cmd = "python ../socks.py"; break; - case proxy_settings::http: + case settings_pack::http: type = "http"; cmd = "python ../http.py"; break; - case proxy_settings::http_pw: + case settings_pack::http_pw: type = "http"; auth = " --username testuser --password testpass"; cmd = "python ../http.py"; @@ -476,9 +535,9 @@ int start_proxy(int proxy_type) using namespace libtorrent; template -boost::intrusive_ptr clone_ptr(boost::intrusive_ptr const& ptr) +boost::shared_ptr clone_ptr(boost::shared_ptr const& ptr) { - return boost::intrusive_ptr(new T(*ptr)); + return boost::make_shared(*ptr); } unsigned char random_byte() @@ -520,7 +579,7 @@ void create_random_files(std::string const& path, const int file_sizes[], int nu free(random_data); } -boost::intrusive_ptr create_torrent(std::ostream* file, int piece_size +boost::shared_ptr create_torrent(std::ostream* file, int piece_size , int num_pieces, bool add_tracker, std::string ssl_certificate) { char const* tracker_url = "http://non-existent-name.com/announce"; @@ -582,54 +641,48 @@ boost::intrusive_ptr create_torrent(std::ostream* file, int piece_ bencode(out, tor); error_code ec; - return boost::intrusive_ptr(new torrent_info( - &tmp[0], tmp.size(), ec)); -} - -void update_settings(session_settings& sess_set, bool allow_multiple_ips) -{ - if (allow_multiple_ips) sess_set.allow_multiple_connections_per_ip = true; - sess_set.ignore_limits_on_local_network = false; - sess_set.mixed_mode_algorithm = session_settings::prefer_tcp; - sess_set.max_failcount = 1; + return boost::make_shared( + &tmp[0], tmp.size(), boost::ref(ec), 0); } boost::tuple -setup_transfer(session* ses1, session* ses2, session* ses3 +setup_transfer(lt::session* ses1, lt::session* ses2, lt::session* ses3 , bool clear_files, bool use_metadata_transfer, bool connect_peers , std::string suffix, int piece_size - , boost::intrusive_ptr* torrent, bool super_seeding + , boost::shared_ptr* torrent, bool super_seeding , add_torrent_params const* p, bool stop_lsd, bool use_ssl_ports - , boost::intrusive_ptr* torrent2) + , boost::shared_ptr* torrent2) { assert(ses1); assert(ses2); if (stop_lsd) { - ses1->stop_lsd(); - ses2->stop_lsd(); - if (ses3) ses3->stop_lsd(); + settings_pack pack; + pack.set_bool(settings_pack::enable_lsd, false); + ses1->apply_settings(pack); + ses2->apply_settings(pack); + if (ses3) ses3->apply_settings(pack); } - session_settings sess_set = ses1->settings(); - update_settings(sess_set, ses3 != NULL); - ses1->set_settings(sess_set); + // This has the effect of applying the global + // rule to all peers, regardless of if they're local or not + ip_filter f; + f.add_rule(address_v4::from_string("0.0.0.0") + , address_v4::from_string("255.255.255.255") + , lt::session::global_peer_class_id); + ses1->set_peer_class_filter(f); + ses2->set_peer_class_filter(f); + if (ses3) ses3->set_peer_class_filter(f); - sess_set = ses2->settings(); - update_settings(sess_set, ses3 != NULL); - ses2->set_settings(sess_set); - - if (ses3) - { - sess_set = ses3->settings(); - update_settings(sess_set, ses3 != NULL); - ses3->set_settings(sess_set); - } - - ses1->set_alert_mask(~(alert::progress_notification | alert::stats_notification)); - ses2->set_alert_mask(~(alert::progress_notification | alert::stats_notification)); - if (ses3) ses3->set_alert_mask(~(alert::progress_notification | alert::stats_notification)); + settings_pack pack; + pack.set_int(settings_pack::alert_mask, ~(alert::progress_notification | alert::stats_notification)); + if (ses3) pack.set_bool(settings_pack::allow_multiple_connections_per_ip, true); + pack.set_int(settings_pack::mixed_mode_algorithm, settings_pack::prefer_tcp); + pack.set_int(settings_pack::max_failcount, 1); + ses1->apply_settings(pack); + ses2->apply_settings(pack); + if (ses3) ses3->apply_settings(pack); peer_id pid; std::generate(&pid[0], &pid[0] + 20, random_byte); @@ -644,7 +697,7 @@ setup_transfer(session* ses1, session* ses2, session* ses3 assert(ses3->id() != ses2->id()); } - boost::intrusive_ptr t; + boost::shared_ptr t; if (torrent == 0) { error_code ec; @@ -678,12 +731,18 @@ setup_transfer(session* ses1, session* ses2, session* ses3 param.flags |= add_torrent_params::flag_seed_mode; error_code ec; torrent_handle tor1 = ses1->add_torrent(param, ec); + if (ec) + { + fprintf(stderr, "ses1.add_torrent: %s\n", ec.message().c_str()); + return boost::make_tuple(torrent_handle(), torrent_handle(), torrent_handle()); + } tor1.super_seeding(super_seeding); // the downloader cannot use seed_mode param.flags &= ~add_torrent_params::flag_seed_mode; TEST_CHECK(!ses1->get_torrents().empty()); + torrent_handle tor2; torrent_handle tor3; @@ -697,7 +756,7 @@ setup_transfer(session* ses1, session* ses2, session* ses3 if (use_metadata_transfer) { - param.ti = 0; + param.ti.reset(); param.info_hash = t->info_hash(); } else if (torrent2) diff --git a/test/setup_transfer.hpp b/test/setup_transfer.hpp index ba275d73e..530ee7e29 100644 --- a/test/setup_transfer.hpp +++ b/test/setup_transfer.hpp @@ -36,6 +36,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/session.hpp" #include #include "test.hpp" +#include "libtorrent/atomic.hpp" namespace libtorrent { @@ -52,6 +53,13 @@ void EXPORT save_file(char const* filename, char const* data, int size); void EXPORT report_failure(char const* err, char const* file, int line); +libtorrent::address EXPORT rand_v4(); +#if TORRENT_USE_IPV6 +libtorrent::address EXPORT rand_v6(); +#endif +libtorrent::tcp::endpoint EXPORT rand_tcp_ep(); +libtorrent::udp::endpoint EXPORT rand_udp_ep(); + std::auto_ptr EXPORT wait_for_alert( libtorrent::session& ses, int type, char const* name = ""); @@ -68,11 +76,12 @@ bool EXPORT print_alerts(libtorrent::session& ses, char const* name , bool no_output = false); void EXPORT wait_for_listen(libtorrent::session& ses, char const* name); +void EXPORT wait_for_downloading(libtorrent::session& ses, char const* name); void EXPORT test_sleep(int millisec); void EXPORT create_random_files(std::string const& path, const int file_sizes[], int num_files); -boost::intrusive_ptr EXPORT create_torrent(std::ostream* file = 0 +boost::shared_ptr EXPORT create_torrent(std::ostream* file = 0 , int piece_size = 16 * 1024, int num_pieces = 13, bool add_tracker = true , std::string ssl_certificate = ""); @@ -82,9 +91,9 @@ boost::tuple* torrent = 0, bool super_seeding = false + , boost::shared_ptr* torrent = 0, bool super_seeding = false , libtorrent::add_torrent_params const* p = 0, bool stop_lsd = true, bool use_ssl_ports = false - , boost::intrusive_ptr* torrent2 = 0); + , boost::shared_ptr* torrent2 = 0); int EXPORT start_web_server(bool ssl = false, bool chunked = false , bool keepalive = true); diff --git a/test/test.hpp b/test/test.hpp index 9b874a8e7..620cd4ef7 100644 --- a/test/test.hpp +++ b/test/test.hpp @@ -33,6 +33,9 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef TEST_HPP #define TEST_HPP +#include "libtorrent/address.hpp" +#include "libtorrent/socket.hpp" + #include #include #include diff --git a/test/test_auto_unchoke.cpp b/test/test_auto_unchoke.cpp index 0615476fc..03b172463 100644 --- a/test/test_auto_unchoke.cpp +++ b/test/test_auto_unchoke.cpp @@ -34,6 +34,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/session_settings.hpp" #include "libtorrent/hasher.hpp" #include "libtorrent/alert_types.hpp" +#include "libtorrent/ip_filter.hpp" #include "libtorrent/thread.hpp" #include #include @@ -44,6 +45,7 @@ POSSIBILITY OF SUCH DAMAGE. void test_swarm() { using namespace libtorrent; + namespace lt = libtorrent; // these are declared before the session objects // so that they are destructed last. This enables @@ -52,41 +54,35 @@ void test_swarm() session_proxy p2; session_proxy p3; - session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48010, 49000), "0.0.0.0", 0); - session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49010, 50000), "0.0.0.0", 0); - session ses3(fingerprint("LT", 0, 1, 0, 0), std::make_pair(50010, 51000), "0.0.0.0", 0); - - ses1.set_alert_mask(alert::all_categories); - ses2.set_alert_mask(alert::all_categories); - ses3.set_alert_mask(alert::all_categories); - // this is to avoid everything finish from a single peer // immediately. To make the swarm actually connect all // three peers before finishing. float rate_limit = 100000; - session_settings settings; - settings.allow_multiple_connections_per_ip = true; - settings.ignore_limits_on_local_network = false; - settings.choking_algorithm = session_settings::auto_expand_choker; - settings.upload_rate_limit = rate_limit; - settings.unchoke_slots_limit = 1; - ses1.set_settings(settings); + settings_pack pack; + pack.set_int(settings_pack::alert_mask, alert::all_categories); + pack.set_bool(settings_pack::allow_multiple_connections_per_ip, true); + pack.set_int(settings_pack::choking_algorithm, settings_pack::auto_expand_choker); + pack.set_int(settings_pack::upload_rate_limit, rate_limit); + pack.set_int(settings_pack::unchoke_slots_limit, 1); + pack.set_int(settings_pack::max_retry_port_bind, 900); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48010"); - settings.upload_rate_limit = rate_limit / 10; - settings.download_rate_limit = rate_limit / 5; - settings.unchoke_slots_limit = 0; - ses2.set_settings(settings); - ses3.set_settings(settings); + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_forced); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_forced); -#ifndef TORRENT_DISABLE_ENCRYPTION - pe_settings pes; - pes.out_enc_policy = pe_settings::forced; - pes.in_enc_policy = pe_settings::forced; - ses1.set_pe_settings(pes); - ses2.set_pe_settings(pes); - ses3.set_pe_settings(pes); -#endif + lt::session ses1(pack, fingerprint("LT", 0, 1, 0, 0)); + + pack.set_int(settings_pack::upload_rate_limit, rate_limit / 10); + pack.set_int(settings_pack::download_rate_limit, rate_limit / 5); + pack.set_int(settings_pack::unchoke_slots_limit, 0); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:49010"); + + lt::session ses2(pack, fingerprint("LT", 0, 1, 0, 0)); + + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:49010"); + + lt::session ses3(pack, fingerprint("LT", 0, 1, 0, 0)); torrent_handle tor1; torrent_handle tor2; @@ -95,6 +91,7 @@ void test_swarm() boost::tie(tor1, tor2, tor3) = setup_transfer(&ses1, &ses2, &ses3, true, false, true, "_unchoke"); session_status st = ses1.status(); + fprintf(stderr, "st.allowed_upload_slots: %d\n", st.allowed_upload_slots); TEST_EQUAL(st.allowed_upload_slots, 1); for (int i = 0; i < 100; ++i) { @@ -118,9 +115,9 @@ void test_swarm() TEST_CHECK(st.allowed_upload_slots >= 2); // make sure the files are deleted - ses1.remove_torrent(tor1, session::delete_files); - ses2.remove_torrent(tor2, session::delete_files); - ses3.remove_torrent(tor3, session::delete_files); + ses1.remove_torrent(tor1, lt::session::delete_files); + ses2.remove_torrent(tor2, lt::session::delete_files); + ses3.remove_torrent(tor3, lt::session::delete_files); // this allows shutting down the sessions in parallel p1 = ses1.abort(); diff --git a/test/test_bandwidth_limiter.cpp b/test/test_bandwidth_limiter.cpp index 3dd0e8c89..b384f07d3 100644 --- a/test/test_bandwidth_limiter.cpp +++ b/test/test_bandwidth_limiter.cpp @@ -39,10 +39,12 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket.hpp" #include "libtorrent/stat.hpp" #include "libtorrent/time.hpp" -#include "libtorrent/session_settings.hpp" +#include "libtorrent/aux_/session_settings.hpp" #include #include +#include +#include #include #include @@ -57,7 +59,7 @@ const float sample_time = 20.f; // seconds bandwidth_channel global_bwc; -struct peer_connection: bandwidth_socket +struct peer_connection: bandwidth_socket, boost::enable_shared_from_this { peer_connection(bandwidth_manager& bwm , bandwidth_channel& torrent_bwc, int prio, bool ignore_limits, std::string name) @@ -101,14 +103,17 @@ void peer_connection::assign_bandwidth(int channel, int amount) void peer_connection::start() { - m_bwm.request_bandwidth(self(), 400000000, m_priority - , &m_bandwidth_channel + bandwidth_channel* channels[] = { + &m_bandwidth_channel , &m_torrent_bandwidth_channel - , &global_bwc); + , &global_bwc + }; + + m_bwm.request_bandwidth(shared_from_this(), 400000000, m_priority, channels, 3); } -typedef std::vector > connections_t; +typedef std::vector > connections_t; void do_change_rate(bandwidth_channel& t1, bandwidth_channel& t2, int limit) { @@ -152,7 +157,9 @@ void run_test(connections_t& v std::for_each(v.begin(), v.end() , boost::bind(&peer_connection::start, _1)); - int tick_interval = session_settings().tick_interval; + libtorrent::aux::session_settings s; + initialize_default_settings(s); + int tick_interval = s.get_int(settings_pack::tick_interval); for (int i = 0; i < int(sample_time * 1000 / tick_interval); ++i) { @@ -173,7 +180,7 @@ void spawn_connections(connections_t& v, bandwidth_manager& bwm { char name[200]; snprintf(name, sizeof(name), "%s%d", prefix, i); - v.push_back(new peer_connection(bwm, bwc, 200, false, name)); + v.push_back(boost::shared_ptr(new peer_connection(bwm, bwc, 200, false, name))); } } @@ -393,7 +400,7 @@ void test_peer_priority(int limit, bool torrent_limit) spawn_connections(v1, manager, t1, 10, "p"); connections_t v; std::copy(v1.begin(), v1.end(), std::back_inserter(v)); - boost::intrusive_ptr p( + boost::shared_ptr p( new peer_connection(manager, t1, 1, false, "no-priority")); v.push_back(p); run_test(v, manager); @@ -429,7 +436,7 @@ void test_no_starvation(int limit) spawn_connections(v1, manager, t1, num_peers, "p"); connections_t v; std::copy(v1.begin(), v1.end(), std::back_inserter(v)); - boost::intrusive_ptr p( + boost::shared_ptr p( new peer_connection(manager, t2, 1, false, "no-priority")); v.push_back(p); run_test(v, manager); diff --git a/test/test_bencoding.cpp b/test/test_bencoding.cpp index 61ce48ac5..268456b12 100644 --- a/test/test_bencoding.cpp +++ b/test/test_bencoding.cpp @@ -485,6 +485,55 @@ int test_main() TEST_CHECK(ent == entry()); } + { + std::string buf; + buf += "l"; + for (int i = 0; i < 1000; ++i) + { + char tmp[20]; + snprintf(tmp, sizeof(tmp), "i%de", i); + buf += tmp; + } + buf += "e"; + + lazy_entry e; + error_code ec; + int ret = lazy_bdecode((char*)&buf[0], (char*)&buf[0] + buf.size(), e, ec); + TEST_EQUAL(ret, 0); + TEST_EQUAL(e.type(), lazy_entry::list_t); + TEST_EQUAL(e.list_size(), 1000); + for (int i = 0; i < 1000; ++i) + { + TEST_EQUAL(e.list_int_value_at(i), i); + } + } + + { + std::string buf; + buf += "d"; + for (int i = 0; i < 1000; ++i) + { + char tmp[30]; + snprintf(tmp, sizeof(tmp), "4:%04di%de", i, i); + buf += tmp; + } + buf += "e"; + + printf("%s\n", buf.c_str()); + lazy_entry e; + error_code ec; + int ret = lazy_bdecode((char*)&buf[0], (char*)&buf[0] + buf.size(), e, ec); + TEST_EQUAL(ret, 0); + TEST_EQUAL(e.type(), lazy_entry::dict_t); + TEST_EQUAL(e.dict_size(), 1000); + for (int i = 0; i < 1000; ++i) + { + char tmp[30]; + snprintf(tmp, sizeof(tmp), "%04d", i); + TEST_EQUAL(e.dict_find_int_value(tmp), i); + } + } + // test parse_int { char b[] = "1234567890e"; diff --git a/test/test_bitfield.cpp b/test/test_bitfield.cpp index 769bdc509..09e5ada7c 100644 --- a/test/test_bitfield.cpp +++ b/test/test_bitfield.cpp @@ -32,6 +32,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "test.hpp" #include "libtorrent/bitfield.hpp" +#include using namespace libtorrent; @@ -50,7 +51,7 @@ void test_iterators(bitfield& test1) test1.set_all(); int num = 0; - printf("expecting %d ones\n", int(test1.size())); + printf("expecting %d ones\n", test1.size()); for (bitfield::const_iterator i = test1.begin(); i != test1.end(); ++i) { printf("%d", *i); @@ -81,7 +82,7 @@ int test_main() test1.clear_bit(2); TEST_EQUAL(test1.count(), 2); int distance = std::distance(test1.begin(), test1.end()); - fprintf(stderr, "%d\n", distance); + printf("distance: %d\n", distance); TEST_CHECK(distance == 10); print_bitfield(test1); diff --git a/test/test_block_cache.cpp b/test/test_block_cache.cpp new file mode 100644 index 000000000..84649e7fa --- /dev/null +++ b/test/test_block_cache.cpp @@ -0,0 +1,469 @@ +/* + +Copyright (c) 2012, 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. + +*/ + +#include "test.hpp" +#include "libtorrent/block_cache.hpp" +#include "libtorrent/io_service.hpp" +#include "libtorrent/alert.hpp" +#include "libtorrent/disk_io_thread.hpp" +#include "libtorrent/storage.hpp" +#include "libtorrent/alert_dispatcher.hpp" + +#include +#include +#include + +using namespace libtorrent; + +struct print_alert : alert_dispatcher +{ + virtual bool post_alert(alert* a) + { + fprintf(stderr, "ALERT: %s\n", a->message().c_str()); + delete a; + return true; + } +}; + +struct test_storage_impl : storage_interface +{ + virtual void initialize(storage_error& ec) {} + + virtual int readv(file::iovec_t const* bufs, int num_bufs + , int piece, int offset, int flags, storage_error& ec) + { + return bufs_size(bufs, num_bufs); + } + virtual int writev(file::iovec_t const* bufs, int num_bufs + , int piece, int offset, int flags, storage_error& ec) + { + return bufs_size(bufs, num_bufs); + } + + virtual bool has_any_file(storage_error& ec) { return false; } + virtual void set_file_priority(std::vector const& prio, storage_error& ec) {} + virtual int move_storage(std::string const& save_path, int flags, storage_error& ec) { return 0; } + virtual bool verify_resume_data(lazy_entry const& rd, storage_error& ec) { return true; } + virtual void write_resume_data(entry& rd, storage_error& ec) const {} + virtual void release_files(storage_error& ec) {} + virtual void rename_file(int index, std::string const& new_filenamem, storage_error& ec) {} + virtual void delete_files(storage_error& ec) {} + virtual void finalize_file(int, storage_error&) {} +}; + +void nop() {} + +#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS +#define INITIALIZE_JOB(j) j.in_use = true; +#else +#define INITIALIZE_JOB(j) +#endif + +#define TEST_SETUP \ + io_service ios; \ + print_alert ad; \ + block_cache bc(0x4000, ios, boost::bind(&nop), &ad); \ + aux::session_settings sett; \ + file_storage fs; \ + fs.add_file("a/test0", 0x4000); \ + fs.add_file("a/test1", 0x4000); \ + fs.add_file("a/test2", 0x4000); \ + fs.add_file("a/test3", 0x4000); \ + fs.add_file("a/test4", 0x4000); \ + fs.add_file("a/test5", 0x4000); \ + fs.add_file("a/test6", 0x4000); \ + fs.add_file("a/test7", 0x4000); \ + fs.set_piece_length(0x8000); \ + fs.set_num_pieces(5); \ + test_storage_impl* st = new test_storage_impl; \ + boost::shared_ptr pm(boost::make_shared(st, boost::shared_ptr(new int), &fs)); \ + bc.set_settings(sett); \ + st->m_settings = &sett; \ + disk_io_job rj; \ + disk_io_job wj; \ + INITIALIZE_JOB(rj) \ + INITIALIZE_JOB(wj) \ + rj.storage = pm; \ + wj.storage = pm; \ + cached_piece_entry* pe = NULL; \ + int ret = 0; \ + cache_status status; \ + file::iovec_t iov[1] + +#define WRITE_BLOCK(p, b) \ + wj.flags = disk_io_job::in_progress; \ + wj.action = disk_io_job::write; \ + wj.d.io.offset = b * 0x4000; \ + wj.d.io.buffer_size = 0x4000; \ + wj.piece = p; \ + wj.buffer = bc.allocate_buffer("write-test"); \ + pe = bc.add_dirty_block(&wj) + +#define READ_BLOCK(p, b, r) \ + rj.action = disk_io_job::read; \ + rj.d.io.offset = b * 0x4000; \ + rj.d.io.buffer_size = 0x4000; \ + rj.piece = p; \ + rj.storage = pm; \ + rj.requester = (void*)r; \ + rj.buffer = 0; \ + ret = bc.try_read(&rj) + +#define RETURN_BUFFER \ + if (rj.d.io.ref.storage) bc.reclaim_block(rj.d.io.ref); \ + else if (rj.buffer) bc.free_buffer(rj.buffer); \ + rj.d.io.ref.storage = 0 + +#define FLUSH(flushing) \ + for (int i = 0; i < sizeof(flushing)/sizeof(flushing[0]); ++i) \ + { \ + pe->blocks[flushing[i]].pending = true; \ + bc.inc_block_refcount(pe, 0, block_cache::ref_flushing); \ + } \ + bc.blocks_flushed(pe, flushing, sizeof(flushing)/sizeof(flushing[0])) + +#define INSERT(p, b) \ + wj.piece = p; \ + wj.requester = (void*)1; \ + pe = bc.allocate_piece(&wj, cached_piece_entry::read_lru1); \ + ret = bc.allocate_iovec(iov, 1); \ + TEST_EQUAL(ret, 0); \ + bc.insert_blocks(pe, b, iov, 1, &wj) + +void test_write() +{ + TEST_SETUP; + + // write block (0,0) + WRITE_BLOCK(0, 0); + + bc.get_stats(&status); + TEST_EQUAL(status.blocks_read_hit, 0); + TEST_EQUAL(status.write_cache_size, 1); + TEST_EQUAL(status.read_cache_size, 0); + TEST_EQUAL(status.pinned_blocks, 0); + TEST_EQUAL(status.arc_mru_size, 0); + TEST_EQUAL(status.arc_mru_ghost_size, 0); + TEST_EQUAL(status.arc_mfu_size, 0); + TEST_EQUAL(status.arc_mfu_ghost_size, 0); + TEST_EQUAL(status.arc_write_size, 1); + TEST_EQUAL(status.arc_volatile_size, 0); + + // try to read it back + READ_BLOCK(0, 0, 1); + TEST_EQUAL(bc.pinned_blocks(), 1); + + // it's supposed to be a cache hit + TEST_CHECK(ret >= 0); + + // return the reference to the buffer we just read + RETURN_BUFFER; + TEST_EQUAL(bc.pinned_blocks(), 0); + + // try to read block (1, 0) + READ_BLOCK(1, 0, 1); + + // that's supposed to be a cache miss + TEST_CHECK(ret < 0); + TEST_EQUAL(bc.pinned_blocks(), 0); + + // just in case it wasn't we're supposed to return the reference + // to the buffer + RETURN_BUFFER; + + tailqueue jobs; + bc.clear(jobs); +} + +void test_flush() +{ + TEST_SETUP; + + // write block (0,0) + WRITE_BLOCK(0, 0); + + // pretend to flush to disk + int flushing[1] = {0}; + FLUSH(flushing); + + tailqueue jobs; + bc.clear(jobs); +} + +void test_insert() +{ + TEST_SETUP; + + INSERT(0, 0); + + bc.get_stats(&status); + TEST_EQUAL(status.blocks_read_hit, 0); + TEST_EQUAL(status.write_cache_size, 0); + TEST_EQUAL(status.read_cache_size, 1); + TEST_EQUAL(status.pinned_blocks, 0); + TEST_EQUAL(status.arc_mru_size, 1); + TEST_EQUAL(status.arc_mru_ghost_size, 0); + TEST_EQUAL(status.arc_mfu_size, 0); + TEST_EQUAL(status.arc_mfu_ghost_size, 0); + TEST_EQUAL(status.arc_write_size, 0); + TEST_EQUAL(status.arc_volatile_size, 0); + + tailqueue jobs; + bc.clear(jobs); +} + +void test_evict() +{ + TEST_SETUP; + + INSERT(0, 0); + + bc.get_stats(&status); + TEST_EQUAL(status.blocks_read_hit, 0); + TEST_EQUAL(status.write_cache_size, 0); + TEST_EQUAL(status.read_cache_size, 1); + TEST_EQUAL(status.pinned_blocks, 0); + TEST_EQUAL(status.arc_mru_size, 1); + TEST_EQUAL(status.arc_mru_ghost_size, 0); + TEST_EQUAL(status.arc_mfu_size, 0); + TEST_EQUAL(status.arc_mfu_ghost_size, 0); + TEST_EQUAL(status.arc_write_size, 0); + TEST_EQUAL(status.arc_volatile_size, 0); + + tailqueue jobs; + // this should make it not be evicted + // just free the buffers + ++pe->piece_refcount; + bc.evict_piece(pe, jobs); + + bc.get_stats(&status); + TEST_EQUAL(status.blocks_read_hit, 0); + TEST_EQUAL(status.write_cache_size, 0); + TEST_EQUAL(status.read_cache_size, 0); + TEST_EQUAL(status.pinned_blocks, 0); + TEST_EQUAL(status.arc_mru_size, 1); + TEST_EQUAL(status.arc_mru_ghost_size, 0); + TEST_EQUAL(status.arc_mfu_size, 0); + TEST_EQUAL(status.arc_mfu_ghost_size, 0); + TEST_EQUAL(status.arc_write_size, 0); + TEST_EQUAL(status.arc_volatile_size, 0); + + --pe->piece_refcount; + bc.evict_piece(pe, jobs); + + bc.get_stats(&status); + TEST_EQUAL(status.blocks_read_hit, 0); + TEST_EQUAL(status.write_cache_size, 0); + TEST_EQUAL(status.read_cache_size, 0); + TEST_EQUAL(status.pinned_blocks, 0); + TEST_EQUAL(status.arc_mru_size, 0); + TEST_EQUAL(status.arc_mru_ghost_size, 1); + TEST_EQUAL(status.arc_mfu_size, 0); + TEST_EQUAL(status.arc_mfu_ghost_size, 0); + TEST_EQUAL(status.arc_write_size, 0); + TEST_EQUAL(status.arc_volatile_size, 0); + + bc.clear(jobs); +} + +// test to have two different requestors read a block and +// make sure it moves into the MFU list +void test_arc_promote() +{ + TEST_SETUP; + + INSERT(0, 0); + + bc.get_stats(&status); + TEST_EQUAL(status.blocks_read_hit, 0); + TEST_EQUAL(status.write_cache_size, 0); + TEST_EQUAL(status.read_cache_size, 1); + TEST_EQUAL(status.pinned_blocks, 0); + TEST_EQUAL(status.arc_mru_size, 1); + TEST_EQUAL(status.arc_mru_ghost_size, 0); + TEST_EQUAL(status.arc_mfu_size, 0); + TEST_EQUAL(status.arc_mfu_ghost_size, 0); + TEST_EQUAL(status.arc_write_size, 0); + TEST_EQUAL(status.arc_volatile_size, 0); + + READ_BLOCK(0, 0, 1); + TEST_EQUAL(bc.pinned_blocks(), 1); + // it's supposed to be a cache hit + TEST_CHECK(ret >= 0); + // return the reference to the buffer we just read + RETURN_BUFFER; + + bc.get_stats(&status); + TEST_EQUAL(status.blocks_read_hit, 1); + TEST_EQUAL(status.write_cache_size, 0); + TEST_EQUAL(status.read_cache_size, 1); + TEST_EQUAL(status.pinned_blocks, 0); + TEST_EQUAL(status.arc_mru_size, 1); + TEST_EQUAL(status.arc_mru_ghost_size, 0); + TEST_EQUAL(status.arc_mfu_size, 0); + TEST_EQUAL(status.arc_mfu_ghost_size, 0); + TEST_EQUAL(status.arc_write_size, 0); + TEST_EQUAL(status.arc_volatile_size, 0); + + READ_BLOCK(0, 0, 2); + TEST_EQUAL(bc.pinned_blocks(), 1); + // it's supposed to be a cache hit + TEST_CHECK(ret >= 0); + // return the reference to the buffer we just read + RETURN_BUFFER; + + bc.get_stats(&status); + TEST_EQUAL(status.blocks_read_hit, 2); + TEST_EQUAL(status.write_cache_size, 0); + TEST_EQUAL(status.read_cache_size, 1); + TEST_EQUAL(status.pinned_blocks, 0); + TEST_EQUAL(status.arc_mru_size, 0); + TEST_EQUAL(status.arc_mru_ghost_size, 0); + TEST_EQUAL(status.arc_mfu_size, 1); + TEST_EQUAL(status.arc_mfu_ghost_size, 0); + TEST_EQUAL(status.arc_write_size, 0); + TEST_EQUAL(status.arc_volatile_size, 0); + + tailqueue jobs; + bc.clear(jobs); +} + +void test_arc_unghost() +{ + TEST_SETUP; + + INSERT(0, 0); + + bc.get_stats(&status); + TEST_EQUAL(status.blocks_read_hit, 0); + TEST_EQUAL(status.write_cache_size, 0); + TEST_EQUAL(status.read_cache_size, 1); + TEST_EQUAL(status.pinned_blocks, 0); + TEST_EQUAL(status.arc_mru_size, 1); + TEST_EQUAL(status.arc_mru_ghost_size, 0); + TEST_EQUAL(status.arc_mfu_size, 0); + TEST_EQUAL(status.arc_mfu_ghost_size, 0); + TEST_EQUAL(status.arc_write_size, 0); + TEST_EQUAL(status.arc_volatile_size, 0); + + tailqueue jobs; + bc.evict_piece(pe, jobs); + + bc.get_stats(&status); + TEST_EQUAL(status.blocks_read_hit, 0); + TEST_EQUAL(status.write_cache_size, 0); + TEST_EQUAL(status.read_cache_size, 0); + TEST_EQUAL(status.pinned_blocks, 0); + TEST_EQUAL(status.arc_mru_size, 0); + TEST_EQUAL(status.arc_mru_ghost_size, 1); + TEST_EQUAL(status.arc_mfu_size, 0); + TEST_EQUAL(status.arc_mfu_ghost_size, 0); + TEST_EQUAL(status.arc_write_size, 0); + TEST_EQUAL(status.arc_volatile_size, 0); + + // the block is now a ghost. If we cache-hit it, + // it should be promoted back to the main list + bc.cache_hit(pe, (void*)1, false); + + bc.get_stats(&status); + TEST_EQUAL(status.blocks_read_hit, 0); + TEST_EQUAL(status.write_cache_size, 0); + // we didn't actually read in any blocks, so the cache size + // is still 0 + TEST_EQUAL(status.read_cache_size, 0); + TEST_EQUAL(status.pinned_blocks, 0); + TEST_EQUAL(status.arc_mru_size, 1); + TEST_EQUAL(status.arc_mru_ghost_size, 0); + TEST_EQUAL(status.arc_mfu_size, 0); + TEST_EQUAL(status.arc_mfu_ghost_size, 0); + TEST_EQUAL(status.arc_write_size, 0); + TEST_EQUAL(status.arc_volatile_size, 0); + + bc.clear(jobs); +} + +void test_iovec() +{ + TEST_SETUP; + + ret = bc.allocate_iovec(iov, 1); + bc.free_iovec(iov, 1); +} + +void test_unaligned_read() +{ + TEST_SETUP; + + INSERT(0, 0); + INSERT(0, 1); + + rj.action = disk_io_job::read; + rj.d.io.offset = 0x2000; + rj.d.io.buffer_size = 0x4000; + rj.piece = 0; + rj.storage = pm; + rj.requester = (void*)1; + rj.buffer = 0; + ret = bc.try_read(&rj); + + // unaligned reads copies the data into a new buffer + // rather than + TEST_EQUAL(bc.pinned_blocks(), 0); + // it's supposed to be a cache hit + TEST_CHECK(ret >= 0); + // return the reference to the buffer we just read + RETURN_BUFFER; + + tailqueue jobs; + bc.clear(jobs); +} + +int test_main() +{ + test_write(); + test_flush(); + test_insert(); + test_evict(); + test_arc_promote(); + test_arc_unghost(); + test_iovec(); + test_unaligned_read(); + + // TODO: test try_evict_blocks + // TODO: test evicting volatile pieces, to see them be removed + // TODO: test evicting dirty pieces + // TODO: test free_piece + // TODO: test abort_dirty + // TODO: test unaligned reads + return 0; +} + diff --git a/test/test_buffer.cpp b/test/test_buffer.cpp index f6ca8d4f9..e989fe573 100644 --- a/test/test_buffer.cpp +++ b/test/test_buffer.cpp @@ -168,8 +168,9 @@ void test_buffer() std::set buffer_list; -void free_buffer(char* m) +void free_buffer(char* m, void* userdata, block_cache_reference ref) { + TEST_CHECK(userdata == (void*)0x1337); std::set::iterator i = buffer_list.find(m); TEST_CHECK(i != buffer_list.end()); @@ -202,7 +203,7 @@ bool compare_chained_buffer(chained_buffer& b, char const* mem, int size) { if (size == 0) return true; std::vector flat(size); - std::list const& iovec2 = b.build_iovec(size); + std::vector 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; @@ -226,7 +227,7 @@ void test_chained_buffer() char* b1 = allocate_buffer(512); std::memcpy(b1, data, 6); - b.append_buffer(b1, 512, 6, (void(*)(char*))&free_buffer); + b.append_buffer(b1, 512, 6, &free_buffer, (void*)0x1337); TEST_EQUAL(buffer_list.size(), 1); TEST_CHECK(b.capacity() == 512); @@ -256,12 +257,12 @@ void test_chained_buffer() char* b2 = allocate_buffer(512); std::memcpy(b2, data, 6); - b.append_buffer(b2, 512, 6, (void(*)(char*))&free_buffer); + b.append_buffer(b2, 512, 6, free_buffer, (void*)0x1337); 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); + b.append_buffer(b3, 512, 6, &free_buffer, (void*)0x1337); TEST_CHECK(buffer_list.size() == 3); TEST_CHECK(b.capacity() == 512 * 3); @@ -296,7 +297,7 @@ void test_chained_buffer() 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); + b.append_buffer(b4, 20, 12, &free_buffer, (void*)0x1337); TEST_CHECK(b.space_in_last_buffer() == 8); ret = b.append(data, 6); @@ -310,7 +311,7 @@ void test_chained_buffer() char* b5 = allocate_buffer(20); std::memcpy(b4, data, 6); - b.append_buffer(b5, 20, 6, (void(*)(char*))&free_buffer); + b.append_buffer(b5, 20, 6, &free_buffer, (void*)0x1337); b.pop_front(22); TEST_CHECK(b.size() == 5); diff --git a/test/test_checking.cpp b/test/test_checking.cpp index 2edbc615e..f04ed0fbc 100644 --- a/test/test_checking.cpp +++ b/test/test_checking.cpp @@ -43,6 +43,7 @@ const int num_files = sizeof(file_sizes)/sizeof(file_sizes[0]); void test_checking(bool read_only_files, bool corrupt_files = false) { using namespace libtorrent; + namespace lt = libtorrent; fprintf(stderr, "==== TEST CHECKING %s%s=====\n" , read_only_files?"read-only-files ":"" @@ -91,7 +92,7 @@ void test_checking(bool read_only_files, bool corrupt_files = false) std::vector buf; bencode(std::back_inserter(buf), t.generate()); - boost::intrusive_ptr ti(new torrent_info(&buf[0], buf.size(), ec)); + boost::shared_ptr ti(new torrent_info(&buf[0], buf.size(), ec)); fprintf(stderr, "generated torrent: %s tmp1_checking/test_torrent_dir\n" , to_hex(ti->info_hash().to_string()).c_str()); @@ -99,6 +100,7 @@ void test_checking(bool read_only_files, bool corrupt_files = false) // overwrite the files with new random data if (corrupt_files) { + fprintf(stderr, "corrupt file test. overwriting files\n"); // increase the size of some files. When they're read only that forces // the checker to open them in write-mode to truncate them static const int file_sizes2[] = @@ -110,6 +112,7 @@ void test_checking(bool read_only_files, bool corrupt_files = false) // make the files read only if (read_only_files) { + fprintf(stderr, "making files read-only\n"); for (int i = 0; i < num_files; ++i) { char name[1024]; @@ -119,6 +122,7 @@ void test_checking(bool read_only_files, bool corrupt_files = false) std::string path = combine_path(combine_path("tmp1_checking", "test_torrent_dir"), dirname); path = combine_path(path, name); + fprintf(stderr, " %s\n", path.c_str()); #ifdef TORRENT_WINDOWS SetFileAttributesA(path.c_str(), FILE_ATTRIBUTE_READONLY); @@ -128,8 +132,11 @@ void test_checking(bool read_only_files, bool corrupt_files = false) } } - session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48000, 49000), "0.0.0.0", 0); - ses1.set_alert_mask(alert::all_categories); + settings_pack pack; + pack.set_int(settings_pack::alert_mask, alert::all_categories); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48000"); + pack.set_int(settings_pack::max_retry_port_bind, 1000); + lt::session ses1(pack, fingerprint("LT", 0, 1, 0, 0)); add_torrent_params p; p.save_path = "tmp1_checking"; @@ -146,8 +153,11 @@ void test_checking(bool read_only_files, bool corrupt_files = false) printf("%d %f %s\n", st.state, st.progress_ppm / 10000.f, st.error.c_str()); - if (st.state != torrent_status::queued_for_checking - && st.state != torrent_status::checking_files + if ( +#ifndef TORRENT_NO_DEPRECATE + st.state != torrent_status::queued_for_checking && +#endif + st.state != torrent_status::checking_files && st.state != torrent_status::checking_resume_data) break; @@ -157,13 +167,31 @@ void test_checking(bool read_only_files, bool corrupt_files = false) if (corrupt_files) { TEST_CHECK(!st.is_seeding); - TEST_CHECK(!st.error.empty()); - // wait a while to make sure libtorrent survived the error - test_sleep(5000); - st = tor1.status(); - TEST_CHECK(!st.is_seeding); - TEST_CHECK(!st.error.empty()); + if (read_only_files) + { + // we expect our checking of the files to trigger + // attempts to truncate them, since the files are + // read-only here, we expect the checking to fail. + TEST_CHECK(!st.error.empty()); + if (!st.error.empty()) + fprintf(stderr, "error: %s\n", st.error.c_str()); + + // wait a while to make sure libtorrent survived the error + test_sleep(1000); + + st = tor1.status(); + TEST_CHECK(!st.is_seeding); + TEST_CHECK(!st.error.empty()); + if (!st.error.empty()) + fprintf(stderr, "error: %s\n", st.error.c_str()); + } + else + { + TEST_CHECK(st.error.empty()); + if (!st.error.empty()) + fprintf(stderr, "error: %s\n", st.error.c_str()); + } } else { @@ -200,6 +228,7 @@ int test_main() test_checking(false); test_checking(true); test_checking(true, true); + test_checking(false, true); return 0; } diff --git a/test/test_connection_queue.cpp b/test/test_connection_queue.cpp new file mode 100644 index 000000000..04fb9602a --- /dev/null +++ b/test/test_connection_queue.cpp @@ -0,0 +1,144 @@ +/* + +Copyright (c) 2012, 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. + +*/ + +#include "test.hpp" +#include "libtorrent/connection_queue.hpp" +#include "libtorrent/connection_interface.hpp" +#include + +using namespace libtorrent; + +int concurrent_connections = 0; +int num_queued = 0; + +enum test_type_t +{ + half_open_test, + timeout_test, + priority_test +}; + +char const* test_name[] = +{ + "half-open", "timeout", "priority" +}; + +struct test_connection : libtorrent::connection_interface +{ + test_connection(io_service& ios, connection_queue& cq, int test_type) + : m_ios(ios), m_cq(cq), m_ticket(-1), m_type(test_type), m_done(false) + { + ++num_queued; + m_cq.enqueue(this, milliseconds(100), 0); + } + + io_service& m_ios; + connection_queue& m_cq; + int m_ticket; + int m_type; + bool m_done; + + void on_allow_connect(int ticket) + { + fprintf(stderr, "%s: [%p] on_allow_connect(%d)\n", test_name[m_type], this, ticket); + --num_queued; + if (ticket < 0) return; + m_ticket = ticket; + if (m_type != timeout_test) + m_ios.post(boost::bind(&test_connection::on_connected, this)); + ++concurrent_connections; + TEST_CHECK(concurrent_connections <= 5); + } + + void on_connect_timeout() + { + fprintf(stderr, "%s: [%p] on_connect_timeout\n", test_name[m_type], this); + TEST_CHECK(m_type == timeout_test); + TEST_CHECK(concurrent_connections <= 5); + --concurrent_connections; + if (m_type == timeout_test) m_done = true; + } + + void on_connected() + { + fprintf(stderr, "%s: [%p] on_connected\n", test_name[m_type], this); + TEST_CHECK(m_type != timeout_test); + TEST_CHECK(concurrent_connections <= 5); + --concurrent_connections; + m_cq.done(m_ticket); + if (m_type == half_open_test) m_done = true; + } + + virtual ~test_connection() + { + if (!m_done) + { + fprintf(stderr, "%s: failed\n", test_name[m_type]); + TEST_CHECK(m_done); + } + } +}; + +int test_main() +{ + + io_service ios; + connection_queue cq(ios); + + // test half-open limit + cq.limit(5); + + std::vector conns; + for (int i = 0; i < 20; ++i) + conns.push_back(new test_connection(ios, cq, half_open_test)); + + ios.run(); + + TEST_CHECK(concurrent_connections == 0); + TEST_CHECK(num_queued == 0); + ios.reset(); + + for (int i = 0; i < 20; ++i) + delete conns[i]; + + conns.clear(); + + for (int i = 0; i < 5; ++i) + conns.push_back(new test_connection(ios, cq, timeout_test)); + + ios.run(); + + for (int i = 0; i < 5; ++i) + delete conns[i]; + + return 0; +} diff --git a/test/test_dht.cpp b/test/test_dht.cpp index 2985a8967..9ca783a62 100644 --- a/test/test_dht.cpp +++ b/test/test_dht.cpp @@ -39,6 +39,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket_io.hpp" // for hash_address #include "libtorrent/broadcast_socket.hpp" // for supports_ipv6 #include "libtorrent/alert_dispatcher.hpp" +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/random.hpp" #include "libtorrent/kademlia/node_id.hpp" #include "libtorrent/kademlia/routing_table.hpp" @@ -92,6 +94,7 @@ std::list > g_sent_packets; struct mock_socket : udp_socket_interface { + bool has_quota() { return true; } bool send_packet(entry& msg, udp::endpoint const& ep, int flags) { g_sent_packets.push_back(std::make_pair(ep, msg)); @@ -99,16 +102,6 @@ struct mock_socket : udp_socket_interface } }; -address rand_v4() -{ - return address_v4((rand() << 16 | rand()) & 0xffffffff); -} - -udp::endpoint rand_ep() -{ - return udp::endpoint(rand_v4(), rand()); -} - sha1_hash generate_next() { sha1_hash ret; @@ -448,7 +441,8 @@ int test_main() address ext = address::from_string("236.0.0.1"); mock_socket s; print_alert ad; - dht::node_impl node(&ad, &s, sett, node_id(0), ext, 0); + counters cnt; + dht::node_impl node(&ad, &s, sett, node_id(0), ext, 0, cnt); // DHT should be running on port 48199 now lazy_entry response; @@ -1011,7 +1005,7 @@ int test_main() // test a node with the same IP:port changing ID add_and_replace(id, diff); id[0] = i; - tbl.node_seen(id, rand_ep(), 20 + (id[19] & 0xff)); + tbl.node_seen(id, rand_udp_ep(), 20 + (id[19] & 0xff)); } printf("num_active_buckets: %d\n", tbl.num_active_buckets()); TEST_EQUAL(tbl.num_active_buckets(), 6); @@ -1031,7 +1025,7 @@ int test_main() { add_and_replace(id, diff); id[0] = i; - tbl.node_seen(id, rand_ep(), 20 + (id[19] & 0xff)); + tbl.node_seen(id, rand_udp_ep(), 20 + (id[19] & 0xff)); } TEST_EQUAL(tbl.num_active_buckets(), 6); @@ -1411,7 +1405,7 @@ int test_main() do { - dht::node_impl node(&ad, &s, sett, node_id::min(), ext, 0); + dht::node_impl node(&ad, &s, sett, node_id::min(), ext, 0, cnt); udp::endpoint initial_node(address_v4::from_string("4.4.4.4"), 1234); std::vector nodesv; @@ -1474,7 +1468,7 @@ int test_main() do { dht::node_id target = to_hash("1234876923549721020394873245098347598635"); - dht::node_impl node(&ad, &s, sett, node_id::min(), ext, 0); + dht::node_impl node(&ad, &s, sett, node_id::min(), ext, 0, cnt); udp::endpoint initial_node(address_v4::from_string("4.4.4.4"), 1234); node.m_table.add_node(initial_node); @@ -1564,7 +1558,7 @@ int test_main() do { - dht::node_impl node(&ad, &s, sett, node_id::min(), ext, 0); + dht::node_impl node(&ad, &s, sett, node_id::min(), ext, 0, cnt); udp::endpoint initial_node(address_v4::from_string("4.4.4.4"), 1234); node.m_table.add_node(initial_node); @@ -1608,7 +1602,7 @@ int test_main() do { - dht::node_impl node(&ad, &s, sett, node_id::min(), ext, 0); + dht::node_impl node(&ad, &s, sett, node_id::min(), ext, 0, cnt); udp::endpoint initial_node(address_v4::from_string("4.4.4.4"), 1234); node.m_table.add_node(initial_node); @@ -1683,7 +1677,7 @@ int test_main() do { - dht::node_impl node(&ad, &s, sett, node_id::min(), ext, 0); + dht::node_impl node(&ad, &s, sett, node_id::min(), ext, 0, cnt); enum { num_test_nodes = 2 }; node_entry nodes[num_test_nodes] = { node_entry(generate_next(), udp::endpoint(address_v4::from_string("4.4.4.4"), 1234)) @@ -1763,7 +1757,7 @@ int test_main() do { - dht::node_impl node(&ad, &s, sett, node_id::min(), ext, 0); + dht::node_impl node(&ad, &s, sett, node_id::min(), ext, 0, cnt); enum { num_test_nodes = 2 }; node_entry nodes[num_test_nodes] = { node_entry(generate_next(), udp::endpoint(address_v4::from_string("4.4.4.4"), 1234)) diff --git a/test/test_dos_blocker.cpp b/test/test_dos_blocker.cpp new file mode 100644 index 000000000..cd581e721 --- /dev/null +++ b/test/test_dos_blocker.cpp @@ -0,0 +1,65 @@ +/* + +Copyright (c) 2013, 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. + +*/ + +#include "test.hpp" +#include "setup_transfer.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/kademlia/dos_blocker.hpp" + +int test_main() +{ +#ifndef TORRENT_DISABLE_DHT + using namespace libtorrent; + using namespace libtorrent::dht; + + dos_blocker b; + + address spammer = address_v4::from_string("10.10.10.10"); + + ptime now = time_now(); + for (int i = 0; i < 1000; ++i) + { + b.incoming(spammer, now); + now += milliseconds(1); + TEST_EQUAL(b.incoming(rand_v4(), now), true); + now += milliseconds(1); + } + + now += milliseconds(1); + + TEST_EQUAL(b.incoming(spammer, now), false); +#endif + + return 0; +} + diff --git a/test/test_fast_extension.cpp b/test/test_fast_extension.cpp index 053fe161d..2a872f516 100644 --- a/test/test_fast_extension.cpp +++ b/test/test_fast_extension.cpp @@ -35,11 +35,13 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket.hpp" #include "libtorrent/io.hpp" #include "libtorrent/alloca.hpp" +#include "libtorrent/time.hpp" #include #include #include using namespace libtorrent; +namespace lt = libtorrent; int read_message(stream_socket& s, char* buffer) { @@ -228,11 +230,11 @@ void do_handshake(stream_socket& s, sha1_hash const& ih, char* buffer) TEST_CHECK(std::memcmp(buffer + 28, ih.begin(), 20) == 0); } -boost::intrusive_ptr setup_peer(stream_socket& s, sha1_hash& ih, boost::shared_ptr& ses) +boost::shared_ptr setup_peer(stream_socket& s, sha1_hash& ih, boost::shared_ptr& ses) { - boost::intrusive_ptr t = ::create_torrent(); + boost::shared_ptr t = ::create_torrent(); ih = t->info_hash(); - ses.reset(new session(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48900, 49000), "0.0.0.0", 0)); + ses.reset(new lt::session(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48900, 49000), "0.0.0.0", 0)); error_code ec; add_torrent_params p; p.flags &= ~add_torrent_params::flag_paused; @@ -258,7 +260,7 @@ void test_reject_fast() std::cerr << " === test reject ===" << std::endl; sha1_hash ih; - boost::shared_ptr ses; + boost::shared_ptr ses; io_service ios; stream_socket s(ios); setup_peer(s, ih, ses); @@ -310,7 +312,7 @@ void test_respect_suggest() std::cerr << " === test suggest ===" << std::endl; sha1_hash ih; - boost::shared_ptr ses; + boost::shared_ptr ses; io_service ios; stream_socket s(ios); setup_peer(s, ih, ses); @@ -370,10 +372,10 @@ void test_multiple_bitfields() std::cerr << " === test multiple bitfields ===" << std::endl; sha1_hash ih; - boost::shared_ptr ses; + boost::shared_ptr ses; io_service ios; stream_socket s(ios); - boost::intrusive_ptr ti = setup_peer(s, ih, ses); + boost::shared_ptr ti = setup_peer(s, ih, ses); char recv_buffer[1000]; do_handshake(s, ih, recv_buffer); @@ -397,10 +399,10 @@ void test_multiple_have_all() std::cerr << " === test multiple have_all ===" << std::endl; sha1_hash ih; - boost::shared_ptr ses; + boost::shared_ptr ses; io_service ios; stream_socket s(ios); - boost::intrusive_ptr ti = setup_peer(s, ih, ses); + boost::shared_ptr ti = setup_peer(s, ih, ses); char recv_buffer[1000]; do_handshake(s, ih, recv_buffer); diff --git a/test/test_fence.cpp b/test/test_fence.cpp new file mode 100644 index 000000000..c08515b9c --- /dev/null +++ b/test/test_fence.cpp @@ -0,0 +1,211 @@ +#include "libtorrent/storage.hpp" +#include "libtorrent/disk_io_job.hpp" +#include "test.hpp" + +using namespace libtorrent; + +void test_disk_job_empty_fence() +{ + libtorrent::disk_job_fence fence; + atomic_count counter(0); + + disk_io_job test_job[10]; + + // issue 5 jobs. None of them should be blocked by a fence + int ret = 0; + // add a fence job + ret = fence.raise_fence(&test_job[5], &test_job[6], &counter); + // since we don't have any outstanding jobs + // we need to post this job + TEST_CHECK(ret == disk_job_fence::fence_post_fence); + + ret = fence.is_blocked(&test_job[7]); + TEST_CHECK(ret == true); + ret = fence.is_blocked(&test_job[8]); + TEST_CHECK(ret == true); + + tailqueue jobs; + + // complete the fence job + fence.job_complete(&test_job[5], jobs); + + // now it's fine to post the blocked jobs + TEST_CHECK(jobs.size() == 2); + TEST_CHECK(jobs.first() == &test_job[7]); + + // the disk_io_fence has an assert in its destructor + // to make sure all outstanding jobs are completed, so we must + // complete them before we're done + fence.job_complete(&test_job[7], jobs); + fence.job_complete(&test_job[8], jobs); +} + +void test_disk_job_fence() +{ + atomic_count counter(0); + libtorrent::disk_job_fence fence; + + disk_io_job test_job[10]; + + // issue 5 jobs. None of them should be blocked by a fence + bool ret = false; + TEST_CHECK(fence.num_outstanding_jobs() == 0); + ret = fence.is_blocked(&test_job[0]); + TEST_CHECK(ret == false); + TEST_CHECK(fence.num_outstanding_jobs() == 1); + ret = fence.is_blocked(&test_job[1]); + TEST_CHECK(ret == false); + ret = fence.is_blocked(&test_job[2]); + TEST_CHECK(ret == false); + ret = fence.is_blocked(&test_job[3]); + TEST_CHECK(ret == false); + ret = fence.is_blocked(&test_job[4]); + TEST_CHECK(ret == false); + + TEST_CHECK(fence.num_outstanding_jobs() == 5); + TEST_CHECK(fence.num_blocked() == 0); + + // add a fence job + ret = fence.raise_fence(&test_job[5], &test_job[6], &counter); + // since we have outstanding jobs, no need + // to post anything + TEST_CHECK(ret == disk_job_fence::fence_post_flush); + + ret = fence.is_blocked(&test_job[7]); + TEST_CHECK(ret == true); + ret = fence.is_blocked(&test_job[8]); + TEST_CHECK(ret == true); + + tailqueue jobs; + + fence.job_complete(&test_job[3], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[2], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[4], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[1], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[0], jobs); + TEST_EQUAL(jobs.size(), 0); + + // the flush job completes + fence.job_complete(&test_job[6], jobs); + + // this was the last job. Now we should be + // able to run the fence job + TEST_EQUAL(jobs.size(), 1); + + TEST_CHECK(jobs.first() == &test_job[5]); + jobs.pop_front(); + + // complete the fence job + fence.job_complete(&test_job[5], jobs); + + // now it's fine to post the blocked jobs + TEST_EQUAL(jobs.size(), 2); + TEST_CHECK(jobs.first() == &test_job[7]); + + // the disk_io_fence has an assert in its destructor + // to make sure all outstanding jobs are completed, so we must + // complete them before we're done + fence.job_complete(&test_job[7], jobs); + fence.job_complete(&test_job[8], jobs); +} + +void test_disk_job_double_fence() +{ + atomic_count counter(0); + libtorrent::disk_job_fence fence; + + disk_io_job test_job[10]; + + // issue 5 jobs. None of them should be blocked by a fence + int ret = 0; + TEST_CHECK(fence.num_outstanding_jobs() == 0); + ret = fence.is_blocked(&test_job[0]); + TEST_CHECK(ret == false); + TEST_CHECK(fence.num_outstanding_jobs() == 1); + ret = fence.is_blocked(&test_job[1]); + TEST_CHECK(ret == false); + ret = fence.is_blocked(&test_job[2]); + TEST_CHECK(ret == false); + ret = fence.is_blocked(&test_job[3]); + TEST_CHECK(ret == false); + ret = fence.is_blocked(&test_job[4]); + TEST_CHECK(ret == false); + + TEST_CHECK(fence.num_outstanding_jobs() == 5); + TEST_CHECK(fence.num_blocked() == 0); + + // add two fence jobs + ret = fence.raise_fence(&test_job[5], &test_job[6], &counter); + // since we have outstanding jobs, no need + // to post anything + TEST_CHECK(ret == disk_job_fence::fence_post_flush); + + ret = fence.raise_fence(&test_job[7], &test_job[8], &counter); + // since we have outstanding jobs, no need + // to post anything + TEST_CHECK(ret == disk_job_fence::fence_post_none); + fprintf(stderr, "ret: %d\n", ret); + + ret = fence.is_blocked(&test_job[9]); + TEST_CHECK(ret == true); + + tailqueue jobs; + + fence.job_complete(&test_job[3], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[2], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[4], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[1], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[0], jobs); + TEST_CHECK(jobs.size() == 0); + fence.job_complete(&test_job[6], jobs); + // this was the last job. Now we should be + // able to run the fence job + TEST_CHECK(jobs.size() == 1); + + TEST_CHECK(jobs.first() == &test_job[5]); + jobs.pop_front(); + + // complete the fence job + fence.job_complete(&test_job[5], jobs); + + // now it's fine to run the next fence job + // first we get the flush job + TEST_CHECK(jobs.size() == 1); + TEST_CHECK(jobs.first() == &test_job[8]); + jobs.pop_front(); + + fence.job_complete(&test_job[8], jobs); + + // then the fence itself + TEST_CHECK(jobs.size() == 1); + TEST_CHECK(jobs.first() == &test_job[7]); + jobs.pop_front(); + + fence.job_complete(&test_job[7], jobs); + + // and now we can run the remaining blocked job + TEST_CHECK(jobs.size() == 1); + TEST_CHECK(jobs.first() == &test_job[9]); + + // the disk_io_fence has an assert in its destructor + // to make sure all outstanding jobs are completed, so we must + // complete them before we're done + fence.job_complete(&test_job[9], jobs); +} + +int test_main() +{ + test_disk_job_fence(); + test_disk_job_double_fence(); + test_disk_job_empty_fence(); + + return 0; +} diff --git a/test/test_file.cpp b/test/test_file.cpp index c21c4ce59..4cc02af19 100644 --- a/test/test_file.cpp +++ b/test/test_file.cpp @@ -274,20 +274,27 @@ int test_main() // file class file f; + ec.clear(); #if TORRENT_USE_UNC_PATHS || !defined WIN32 TEST_CHECK(f.open("con", file::read_write, ec)); #else TEST_CHECK(f.open("test_file", file::read_write, ec)); #endif + if (ec) + fprintf(stderr, "open failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); TEST_CHECK(!ec); if (ec) fprintf(stderr, "%s\n", ec.message().c_str()); file::iovec_t b = {(void*)"test", 4}; - TEST_CHECK(f.writev(0, &b, 1, ec) == 4); + TEST_EQUAL(f.writev(0, &b, 1, ec), 4); + if (ec) + fprintf(stderr, "writev failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); TEST_CHECK(!ec); char test_buf[5] = {0}; b.iov_base = test_buf; b.iov_len = 4; - TEST_CHECK(f.readv(0, &b, 1, ec) == 4); + TEST_EQUAL(f.readv(0, &b, 1, ec), 4); + if (ec) + fprintf(stderr, "readv failed: [%s] %s\n", ec.category().name(), ec.message().c_str()); TEST_CHECK(!ec); TEST_CHECK(strcmp(test_buf, "test") == 0); f.close(); diff --git a/test/test_http_connection.cpp b/test/test_http_connection.cpp index eedb0bfd9..c2950d52e 100644 --- a/test/test_http_connection.cpp +++ b/test/test_http_connection.cpp @@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket_io.hpp" // print_endpoint #include "libtorrent/connection_queue.hpp" #include "libtorrent/http_connection.hpp" +#include "libtorrent/resolver.hpp" #include "setup_transfer.hpp" #include @@ -45,6 +46,7 @@ using namespace libtorrent; io_service ios; connection_queue cq(ios); +resolver res(ios); int connect_handler_called = 0; int handler_called = 0; @@ -117,7 +119,7 @@ void run_test(std::string const& url, int size, int status, int connected << " error: " << (ec?ec->message():"no error") << std::endl; boost::shared_ptr h(new http_connection(ios, cq - , &::http_handler, true, 1024*1024, &::http_connect_handler)); + , res, &::http_handler, true, 1024*1024, &::http_connect_handler)); h->get(url, seconds(1), 0, &ps); ios.reset(); error_code e; @@ -139,7 +141,7 @@ void run_test(std::string const& url, int size, int status, int connected void run_suite(std::string const& protocol, proxy_settings ps, int port) { - if (ps.type != proxy_settings::none) + if (ps.type != settings_pack::none) { ps.port = start_proxy(ps.type); } @@ -175,12 +177,12 @@ void run_suite(std::string const& protocol, proxy_settings ps, int port) { // if we're going through an http proxy, we won't get the same error as if the hostname // resolution failed - if ((ps.type == proxy_settings::http || ps.type == proxy_settings::http_pw) && protocol != "https") + if ((ps.type == settings_pack::http || ps.type == settings_pack::http_pw) && protocol != "https") run_test(protocol + "://non-existent-domain.se/non-existing-file", -1, 502, 1, err(), ps); else run_test(protocol + "://non-existent-domain.se/non-existing-file", -1, -1, 0, err(), ps); } - if (ps.type != proxy_settings::none) + if (ps.type != settings_pack::none) stop_proxy(ps.port); } @@ -209,7 +211,7 @@ int test_main() for (int i = 0; i < 5; ++i) { - ps.type = (proxy_settings::proxy_type)i; + ps.type = (settings_pack::proxy_type_t)i; run_suite("http", ps, port); } stop_web_server(); @@ -218,7 +220,7 @@ int test_main() port = start_web_server(true); for (int i = 0; i < 5; ++i) { - ps.type = (proxy_settings::proxy_type)i; + ps.type = (settings_pack::proxy_type_t)i; run_suite("https", ps, port); } stop_web_server(); @@ -226,7 +228,7 @@ int test_main() // test chunked encoding port = start_web_server(false, true); - ps.type = proxy_settings::none; + ps.type = settings_pack::none; run_suite("http", ps, port); stop_web_server(); diff --git a/test/test_lsd.cpp b/test/test_lsd.cpp index 9276a9d65..9b46c44d5 100644 --- a/test/test_lsd.cpp +++ b/test/test_lsd.cpp @@ -43,6 +43,7 @@ POSSIBILITY OF SUCH DAMAGE. void test_lsd() { using namespace libtorrent; + namespace lt = libtorrent; // these are declared before the session objects // so that they are destructed last. This enables @@ -50,16 +51,19 @@ void test_lsd() session_proxy p1; session_proxy p2; - session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48100, 49000), "0.0.0.0", 0); - session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49100, 50000), "0.0.0.0", 0); + settings_pack pack; + pack.set_bool(settings_pack::allow_multiple_connections_per_ip, true); + pack.set_bool(settings_pack::enable_dht, false); + pack.set_bool(settings_pack::enable_lsd, true); + pack.set_bool(settings_pack::enable_upnp, false); + pack.set_bool(settings_pack::enable_natpmp, false); + pack.set_str(settings_pack::listen_interfaces, "127.0.0.1:48100"); - session_settings settings; - settings.allow_multiple_connections_per_ip = true; - ses1.set_settings(settings); - ses2.set_settings(settings); + lt::session ses1(pack, fingerprint("LT", 0, 1, 0, 0)); + + pack.set_str(settings_pack::listen_interfaces, "127.0.0.1:49100"); + lt::session ses2(pack, fingerprint("LT", 0, 1, 0, 0)); - ses1.start_lsd(); - ses2.start_lsd(); torrent_handle tor1; torrent_handle tor2; diff --git a/test/test_magnet.cpp b/test/test_magnet.cpp index d28cc803a..d9baab415 100644 --- a/test/test_magnet.cpp +++ b/test/test_magnet.cpp @@ -37,6 +37,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/bencode.hpp" using namespace libtorrent; +namespace lt = libtorrent; int test_main() { @@ -44,22 +45,24 @@ int test_main() session_proxy p2; { // test session state load/restore - session* s = new session(fingerprint("LT",0,0,0,0), 0); + lt::session* s = new lt::session(fingerprint("LT",0,0,0,0), 0); - session_settings sett; - sett.user_agent = "test"; - sett.tracker_receive_timeout = 1234; - sett.file_pool_size = 543; - sett.urlseed_wait_retry = 74; - sett.file_pool_size = 754; - sett.initial_picker_threshold = 351; - sett.upnp_ignore_nonrouters = 5326; - sett.coalesce_writes = 623; - sett.auto_scrape_interval = 753; - sett.close_redundant_connections = 245; - sett.auto_scrape_interval = 235; - sett.auto_scrape_min_interval = 62; - s->set_settings(sett); + settings_pack pack; + pack.set_str(settings_pack::user_agent, "test"); + pack.set_int(settings_pack::tracker_receive_timeout, 1234); + pack.set_int(settings_pack::file_pool_size, 543); + pack.set_int(settings_pack::urlseed_wait_retry, 74); + pack.set_int(settings_pack::initial_picker_threshold, 351); + pack.set_bool(settings_pack::upnp_ignore_nonrouters, true); + pack.set_bool(settings_pack::coalesce_writes, true); + pack.set_int(settings_pack::auto_scrape_interval, 753); + pack.set_bool(settings_pack::close_redundant_connections, false); + pack.set_int(settings_pack::auto_scrape_interval, 235); + pack.set_int(settings_pack::auto_scrape_min_interval, 62); + s->apply_settings(pack); + + TEST_EQUAL(pack.get_str(settings_pack::user_agent), "test"); + TEST_EQUAL(pack.get_int(settings_pack::tracker_receive_timeout), 1234); #ifndef TORRENT_DISABLE_DHT dht_settings dhts; @@ -146,7 +149,7 @@ int test_main() p1 = s->abort(); delete s; - s = new session(fingerprint("LT",0,0,0,0), 0); + s = new lt::session(fingerprint("LT",0,0,0,0), 0); std::vector buf; bencode(std::back_inserter(buf), session_state); @@ -188,34 +191,23 @@ int test_main() TEST_CHECK(session_state2.dict_find("settings")->dict_find("optimistic_disk_retry") == 0); s->load_state(session_state2); -#define CMP_SET(x) TEST_CHECK(s->settings().x == sett.x) - CMP_SET(user_agent); +#define CMP_SET(x) fprintf(stderr, #x ": %d %d\n"\ + , s->get_settings().get_int(settings_pack:: x)\ + , pack.get_int(settings_pack:: x)); \ + TEST_EQUAL(s->get_settings().get_int(settings_pack:: x), pack.get_int(settings_pack:: x)) + CMP_SET(tracker_receive_timeout); CMP_SET(file_pool_size); CMP_SET(urlseed_wait_retry); - CMP_SET(file_pool_size); CMP_SET(initial_picker_threshold); - CMP_SET(upnp_ignore_nonrouters); - CMP_SET(coalesce_writes); CMP_SET(auto_scrape_interval); - CMP_SET(close_redundant_connections); CMP_SET(auto_scrape_interval); CMP_SET(auto_scrape_min_interval); - CMP_SET(max_peerlist_size); - CMP_SET(max_paused_peerlist_size); - CMP_SET(min_announce_interval); - CMP_SET(prioritize_partial_pieces); - CMP_SET(auto_manage_startup); - CMP_SET(rate_limit_ip_overhead); - CMP_SET(announce_to_all_trackers); - CMP_SET(announce_to_all_tiers); - CMP_SET(prefer_udp_trackers); - CMP_SET(strict_super_seeding); - CMP_SET(seeding_piece_quota); p2 = s->abort(); delete s; } + // make_magnet_uri { entry info; diff --git a/test/test_metadata_extension.cpp b/test/test_metadata_extension.cpp index d7449ce4c..af8c851e1 100644 --- a/test/test_metadata_extension.cpp +++ b/test/test_metadata_extension.cpp @@ -56,6 +56,7 @@ void test_transfer(int flags , int timeout) { using namespace libtorrent; + namespace lt = libtorrent; fprintf(stderr, "test transfer: timeout=%d %s%s%s\n" , timeout @@ -71,23 +72,22 @@ void test_transfer(int flags // TODO: it would be nice to test reversing // which session is making the connection as well - session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48100, 49000), "0.0.0.0", 0); - session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49100, 50000), "0.0.0.0", 0); + lt::session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48100, 49000), "0.0.0.0", 0); + lt::session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49100, 50000), "0.0.0.0", 0); ses1.add_extension(constructor); ses2.add_extension(constructor); torrent_handle tor1; torrent_handle tor2; -#ifndef TORRENT_DISABLE_ENCRYPTION - pe_settings pes; - pes.prefer_rc4 = (flags & full_encryption); - pes.out_enc_policy = pe_settings::forced; - pes.in_enc_policy = pe_settings::forced; - ses1.set_pe_settings(pes); - ses2.set_pe_settings(pes); -#endif - session* downloader = &ses2; - session* seed = &ses1; + settings_pack pack; + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_forced); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_forced); + pack.set_bool(settings_pack::prefer_rc4, flags & full_encryption); + ses1.apply_settings(pack); + ses2.apply_settings(pack); + + lt::session* downloader = &ses2; + lt::session* seed = &ses1; if (flags & reverse) { @@ -157,9 +157,10 @@ int test_main() test_transfer(full_encryption | reverse, &create_ut_metadata_plugin, timeout); test_transfer(reverse, &create_ut_metadata_plugin, timeout); +#ifndef TORRENT_NO_DEPRECATE for (int f = 0; f <= (clear_files | disconnect | full_encryption); ++f) test_transfer(f, &create_metadata_plugin, timeout * 2); - +#endif for (int f = 0; f <= (clear_files | disconnect | full_encryption); ++f) test_transfer(f, &create_ut_metadata_plugin, timeout); diff --git a/test/test_part_file.cpp b/test/test_part_file.cpp new file mode 100644 index 000000000..cb0c10acb --- /dev/null +++ b/test/test_part_file.cpp @@ -0,0 +1,148 @@ +/* + +Copyright (c) 2012, 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. + +*/ + +#include +#include "test.hpp" +#include "libtorrent/part_file.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/error_code.hpp" + +using namespace libtorrent; + +int test_main() +{ + error_code ec; + std::string cwd = complete("."); + + remove_all(combine_path(cwd, "partfile_test_dir"), ec); + if (ec) fprintf(stderr, "remove_all: %s\n", ec.message().c_str()); + remove_all(combine_path(cwd, "partfile_test_dir2"), ec); + if (ec) fprintf(stderr, "remove_all: %s\n", ec.message().c_str()); + + int piece_size = 16 * 0x4000; + char buf[1024]; + + { + create_directory(combine_path(cwd, "partfile_test_dir"), ec); + if (ec) fprintf(stderr, "create_directory: %s\n", ec.message().c_str()); + create_directory(combine_path(cwd, "partfile_test_dir2"), ec); + if (ec) fprintf(stderr, "create_directory: %s\n", ec.message().c_str()); + + part_file pf(combine_path(cwd, "partfile_test_dir"), "partfile.parts", 100, piece_size); + pf.flush_metadata(ec); + if (ec) fprintf(stderr, "flush_metadata: %s\n", ec.message().c_str()); + + // since we don't have anything in the part file, it will have + // not have been created yet + + TEST_CHECK(!exists(combine_path(combine_path(cwd, "partfile_test_dir"), "partfile.parts"))); + + // write something to the metadata file + for (int i = 0; i < 1024; ++i) buf[i] = i; + + file::iovec_t v = {&buf, 1024}; + pf.writev(&v, 1, 10, 0, ec); + if (ec) fprintf(stderr, "part_file::writev: %s\n", ec.message().c_str()); + + pf.flush_metadata(ec); + if (ec) fprintf(stderr, "flush_metadata: %s\n", ec.message().c_str()); + + // now wwe should have created the partfile + TEST_CHECK(exists(combine_path(combine_path(cwd, "partfile_test_dir"), "partfile.parts"))); + + pf.move_partfile(combine_path(cwd, "partfile_test_dir2"), ec); + TEST_CHECK(!ec); + if (ec) fprintf(stderr, "move_partfile: %s\n", ec.message().c_str()); + + TEST_CHECK(!exists(combine_path(combine_path(cwd, "partfile_test_dir"), "partfile.parts"))); + TEST_CHECK(exists(combine_path(combine_path(cwd, "partfile_test_dir2"), "partfile.parts"))); + + memset(buf, 0, sizeof(buf)); + + pf.readv(&v, 1, 10, 0, ec); + if (ec) fprintf(stderr, "part_file::readv: %s\n", ec.message().c_str()); + + for (int i = 0; i < 1024; ++i) + TEST_CHECK(buf[i] == char(i)); + } + + { + // load the part file back in + part_file pf(combine_path(cwd, "partfile_test_dir2"), "partfile.parts", 100, piece_size); + + memset(buf, 0, sizeof(buf)); + + file::iovec_t v = {&buf, 1024}; + pf.readv(&v, 1, 10, 0, ec); + if (ec) fprintf(stderr, "part_file::readv: %s\n", ec.message().c_str()); + + for (int i = 0; i < 1024; ++i) + TEST_CHECK(buf[i] == char(i)); + + // test exporting the piece to a file + + std::string output_filename = combine_path(combine_path(cwd, "partfile_test_dir") + , "part_file_test_export"); + file output(output_filename, file::read_write, ec); + if (ec) fprintf(stderr, "export open file: %s\n", ec.message().c_str()); + + pf.export_file(output, 10 * piece_size, 1024, ec); + if (ec) fprintf(stderr, "export_file: %s\n", ec.message().c_str()); + + pf.free_piece(10, ec); + if (ec) fprintf(stderr, "free_piece: %s\n", ec.message().c_str()); + + pf.flush_metadata(ec); + if (ec) fprintf(stderr, "flush_metadata: %s\n", ec.message().c_str()); + + // we just removed the last piece. The partfile does not + // contain anything anymore, it should have deleted itself + TEST_CHECK(!exists(combine_path(combine_path(cwd, "partfile_test_dir2"), "partfile.parts"))); + + output.close(); + + // verify that the exported file is what we expect it to be + output.open(output_filename, file::read_only, ec); + if (ec) fprintf(stderr, "exported file open: %s\n", ec.message().c_str()); + + memset(buf, 0, sizeof(buf)); + + output.readv(0, &v, 1, ec); + if (ec) fprintf(stderr, "exported file read: %s\n", ec.message().c_str()); + + for (int i = 0; i < 1024; ++i) + TEST_CHECK(buf[i] == char(i)); + } + + return 0; +} + diff --git a/test/test_pe_crypto.cpp b/test/test_pe_crypto.cpp index d43ce49e7..8770e0198 100644 --- a/test/test_pe_crypto.cpp +++ b/test/test_pe_crypto.cpp @@ -46,32 +46,34 @@ char const* pe_policy(boost::uint8_t policy) { using namespace libtorrent; - if (policy == pe_settings::disabled) return "disabled"; - else if (policy == pe_settings::enabled) return "enabled"; - else if (policy == pe_settings::forced) return "forced"; + if (policy == settings_pack::pe_disabled) return "disabled"; + else if (policy == settings_pack::pe_enabled) return "enabled"; + else if (policy == settings_pack::pe_forced) return "forced"; return "unknown"; } -void display_pe_settings(libtorrent::pe_settings s) +void display_settings(libtorrent::settings_pack const& s) { using namespace libtorrent; fprintf(stderr, "out_enc_policy - %s\tin_enc_policy - %s\n" - , pe_policy(s.out_enc_policy), pe_policy(s.in_enc_policy)); + , pe_policy(s.get_int(settings_pack::out_enc_policy)) + , pe_policy(s.get_int(settings_pack::in_enc_policy))); fprintf(stderr, "enc_level - %s\t\tprefer_rc4 - %s\n" - , s.allowed_enc_level == pe_settings::plaintext ? "plaintext" - : s.allowed_enc_level == pe_settings::rc4 ? "rc4" - : s.allowed_enc_level == pe_settings::both ? "both" : "unknown" - , s.prefer_rc4 ? "true": "false"); + , s.get_int(settings_pack::allowed_enc_level) == settings_pack::pe_plaintext ? "plaintext" + : s.get_int(settings_pack::allowed_enc_level) == settings_pack::pe_rc4 ? "rc4" + : s.get_int(settings_pack::allowed_enc_level) == settings_pack::pe_both ? "both" : "unknown" + , s.get_bool(settings_pack::prefer_rc4) ? "true": "false"); } -void test_transfer(libtorrent::pe_settings::enc_policy policy +void test_transfer(libtorrent::settings_pack::enc_policy policy , int timeout - , libtorrent::pe_settings::enc_level level = libtorrent::pe_settings::both + , libtorrent::settings_pack::enc_level level = libtorrent::settings_pack::pe_both , bool pref_rc4 = false) { using namespace libtorrent; + namespace lt = libtorrent; // these are declared before the session objects // so that they are destructed last. This enables @@ -79,28 +81,26 @@ void test_transfer(libtorrent::pe_settings::enc_policy policy session_proxy p1; session_proxy p2; - session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48800, 49000), "0.0.0.0", 0); - session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49800, 50000), "0.0.0.0", 0); - pe_settings s; + lt::session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48800, 49000), "0.0.0.0", 0); + lt::session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49800, 50000), "0.0.0.0", 0); + settings_pack s; - s.out_enc_policy = libtorrent::pe_settings::enabled; - s.in_enc_policy = libtorrent::pe_settings::enabled; - - s.allowed_enc_level = pe_settings::both; - ses2.set_pe_settings(s); + s.set_int(settings_pack::out_enc_policy, settings_pack::pe_enabled); + s.set_int(settings_pack::in_enc_policy, settings_pack::pe_enabled); + s.set_int(settings_pack::allowed_enc_level, settings_pack::pe_both); + ses2.apply_settings(s); - s.out_enc_policy = policy; - s.in_enc_policy = policy; - s.allowed_enc_level = level; - s.prefer_rc4 = pref_rc4; - ses1.set_pe_settings(s); - - s = ses1.get_pe_settings(); - fprintf(stderr, " Session1 \n"); - display_pe_settings(s); - s = ses2.get_pe_settings(); fprintf(stderr, " Session2 \n"); - display_pe_settings(s); + display_settings(s); + + s.set_int(settings_pack::out_enc_policy, policy); + s.set_int(settings_pack::in_enc_policy, policy); + s.set_int(settings_pack::allowed_enc_level, level); + s.set_bool(settings_pack::prefer_rc4, pref_rc4); + ses1.apply_settings(s); + + fprintf(stderr, " Session1 \n"); + display_settings(s); torrent_handle tor1; torrent_handle tor2; @@ -215,17 +215,17 @@ int test_main() const int timeout = 5; #endif - test_transfer(pe_settings::disabled, timeout); + test_transfer(settings_pack::pe_disabled, timeout); - test_transfer(pe_settings::forced, timeout, pe_settings::plaintext); - test_transfer(pe_settings::forced, timeout, pe_settings::rc4); - test_transfer(pe_settings::forced, timeout, pe_settings::both, false); - test_transfer(pe_settings::forced, timeout, pe_settings::both, true); + test_transfer(settings_pack::pe_forced, timeout, settings_pack::pe_plaintext); + test_transfer(settings_pack::pe_forced, timeout, settings_pack::pe_rc4); + test_transfer(settings_pack::pe_forced, timeout, settings_pack::pe_both, false); + test_transfer(settings_pack::pe_forced, timeout, settings_pack::pe_both, true); - test_transfer(pe_settings::enabled, timeout, pe_settings::plaintext); - test_transfer(pe_settings::enabled, timeout, pe_settings::rc4); - test_transfer(pe_settings::enabled, timeout, pe_settings::both, false); - test_transfer(pe_settings::enabled, timeout, pe_settings::both, true); + test_transfer(settings_pack::pe_enabled, timeout, settings_pack::pe_plaintext); + test_transfer(settings_pack::pe_enabled, timeout, settings_pack::pe_rc4); + test_transfer(settings_pack::pe_enabled, timeout, settings_pack::pe_both, false); + test_transfer(settings_pack::pe_enabled, timeout, settings_pack::pe_both, true); #else fprintf(stderr, "PE test not run because it's disabled\n"); #endif diff --git a/test/test_peer_classes.cpp b/test/test_peer_classes.cpp new file mode 100644 index 000000000..e3aec9847 --- /dev/null +++ b/test/test_peer_classes.cpp @@ -0,0 +1,117 @@ +/* + +Copyright (c) 2012, 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. + +*/ + +#include "test.hpp" +#include "libtorrent/peer_class.hpp" +#include "libtorrent/peer_class_set.hpp" +#include "libtorrent/peer_class_type_filter.hpp" + +using namespace libtorrent; + +std::string class_name(peer_class_t id, peer_class_pool const& p) +{ + peer_class const* c = p.at(id); + TEST_CHECK(c != NULL); + if (c == NULL) return ""; + peer_class_info i; + c->get_info(&i); + return i.label; +} + +int test_main() +{ + peer_class_pool pool; + + peer_class_t id1 = pool.new_peer_class("test1"); + peer_class_t id2 = pool.new_peer_class("test2"); + + // make sure there's no leak + for (int i = 0; i < 1000; ++i) + { + peer_class_t tmp = pool.new_peer_class("temp"); + pool.decref(tmp); + } + + peer_class_t id3 = pool.new_peer_class("test3"); + + TEST_CHECK(id3 == id2 + 1); + + // make sure refcounting works + TEST_CHECK(class_name(id3, pool) == "test3"); + pool.incref(id3); + TEST_CHECK(class_name(id3, pool) == "test3"); + pool.decref(id3); + TEST_CHECK(class_name(id3, pool) == "test3"); + pool.decref(id3); + // it should have been deleted now + TEST_CHECK(pool.at(id3) == NULL); + + // test setting and retrieving upload and download rates + pool.at(id2)->set_upload_limit(1000); + pool.at(id2)->set_download_limit(2000); + + peer_class_info i; + pool.at(id2)->get_info(&i); + TEST_CHECK(i.upload_limit == 1000); + TEST_CHECK(i.download_limit == 2000); + + // test peer_class_type_filter + peer_class_type_filter filter; + + for (int i = 0; i < 5; ++i) + { + TEST_CHECK(filter.apply((libtorrent::peer_class_type_filter::socket_type_t)i + , 0xffffffff) == 0xffffffff); + } + + filter.disallow((libtorrent::peer_class_type_filter::socket_type_t)0, 0); + TEST_CHECK(filter.apply((libtorrent::peer_class_type_filter::socket_type_t)0 + , 0xffffffff) == 0xfffffffe); + TEST_CHECK(filter.apply((libtorrent::peer_class_type_filter::socket_type_t)1 + , 0xffffffff) == 0xffffffff); + filter.allow((libtorrent::peer_class_type_filter::socket_type_t)0, 0); + TEST_CHECK(filter.apply((libtorrent::peer_class_type_filter::socket_type_t)0 + , 0xffffffff) == 0xffffffff); + + TEST_CHECK(filter.apply((libtorrent::peer_class_type_filter::socket_type_t)0, 0) == 0); + filter.add((libtorrent::peer_class_type_filter::socket_type_t)0, 0); + TEST_CHECK(filter.apply((libtorrent::peer_class_type_filter::socket_type_t)0, 0) == 1); + filter.remove((libtorrent::peer_class_type_filter::socket_type_t)0, 0); + TEST_CHECK(filter.apply((libtorrent::peer_class_type_filter::socket_type_t)0, 0) == 0); + + pool.decref(id2); + pool.decref(id1); + TEST_CHECK(pool.at(id2) == NULL); + TEST_CHECK(pool.at(id1) == NULL); + return 0; +} + diff --git a/test/test_pex.cpp b/test/test_pex.cpp index 5a70aa5fc..9c24ac062 100644 --- a/test/test_pex.cpp +++ b/test/test_pex.cpp @@ -37,6 +37,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/hasher.hpp" #include "libtorrent/extensions/ut_pex.hpp" #include "libtorrent/thread.hpp" +#include "libtorrent/ip_filter.hpp" #include #include "test.hpp" @@ -46,6 +47,7 @@ POSSIBILITY OF SUCH DAMAGE. void test_pex() { using namespace libtorrent; + namespace lt = libtorrent; // these are declared before the session objects // so that they are destructed last. This enables @@ -54,9 +56,44 @@ void test_pex() session_proxy p2; session_proxy p3; - session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48200, 49000), "0.0.0.0", 0); - session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49200, 50000), "0.0.0.0", 0); - session ses3(fingerprint("LT", 0, 1, 0, 0), std::make_pair(50200, 51000), "0.0.0.0", 0); + int mask = alert::all_categories + & ~(alert::progress_notification + | alert::performance_warning + | alert::stats_notification); + + // this is to avoid everything finish from a single peer + // immediately. To make the swarm actually connect all + // three peers before finishing. + settings_pack pack; + pack.set_int(settings_pack::alert_mask, mask); + pack.set_int(settings_pack::download_rate_limit, 2000); + pack.set_int(settings_pack::upload_rate_limit, 2000); + pack.set_int(settings_pack::max_retry_port_bind, 800); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48200"); + + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_forced); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_forced); + + lt::session ses1(pack, fingerprint("LT", 0, 1, 0, 0)); + + // treat all IPs the same, i.e. enable rate limiting for local peers + ip_filter f; + f.add_rule(address_v4::from_string("0.0.0.0"), address_v4::from_string("255.255.255.255"), 1 << lt::session::global_peer_class_id); + peer_class_info pc = ses1.get_peer_class(lt::session::global_peer_class_id); + TEST_EQUAL(pc.upload_limit, 2000); + TEST_EQUAL(pc.download_limit, 2000); + ses1.set_peer_class(lt::session::local_peer_class_id, pc); + + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:49200"); + + lt::session ses3(pack, fingerprint("LT", 0, 1, 0, 0)); + ses3.set_peer_class(lt::session::local_peer_class_id, pc); + + // make the peer connecting the two worthless to transfer + // data, to force peer 3 to connect directly to peer 1 through pex + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:50200"); + lt::session ses2(pack, fingerprint("LT", 0, 1, 0, 0)); + ses2.set_peer_class(lt::session::local_peer_class_id, pc); ses1.add_extension(create_ut_pex_plugin); ses2.add_extension(create_ut_pex_plugin); @@ -67,42 +104,7 @@ void test_pex() boost::tie(tor1, tor2, tor3) = setup_transfer(&ses1, &ses2, &ses3, true, false, false, "_pex"); - int mask = alert::all_categories - & ~(alert::progress_notification - | alert::performance_warning - | alert::stats_notification); - ses1.set_alert_mask(mask); - ses2.set_alert_mask(mask); - ses3.set_alert_mask(mask); - - // this is to avoid everything finish from a single peer - // immediately. To make the swarm actually connect all - // three peers before finishing. - session_settings set = ses1.settings(); - set.download_rate_limit = 2000; - set.upload_rate_limit = 2000; - ses1.set_settings(set); - - // make the peer connecting the two worthless to transfer - // data, to force peer 3 to connect directly to peer 1 through pex - set = ses2.settings(); - set.ignore_limits_on_local_network = false; - set.rate_limit_utp = true; - ses2.set_settings(set); - - set = ses3.settings(); - set.download_rate_limit = 0; - set.upload_rate_limit = 0; - ses3.set_settings(set); - -#ifndef TORRENT_DISABLE_ENCRYPTION - pe_settings pes; - pes.out_enc_policy = pe_settings::forced; - pes.in_enc_policy = pe_settings::forced; - ses1.set_pe_settings(pes); - ses2.set_pe_settings(pes); - ses3.set_pe_settings(pes); -#endif + ses2.apply_settings(pack); test_sleep(100); diff --git a/test/test_piece_picker.cpp b/test/test_piece_picker.cpp index 3bc7eaa2b..4f05079da 100644 --- a/test/test_piece_picker.cpp +++ b/test/test_piece_picker.cpp @@ -31,8 +31,9 @@ POSSIBILITY OF SUCH DAMAGE. */ #include "libtorrent/piece_picker.hpp" -#include "libtorrent/policy.hpp" +#include "libtorrent/torrent_peer.hpp" #include "libtorrent/bitfield.hpp" +#include "libtorrent/performance_counters.hpp" #include #include #include @@ -52,11 +53,26 @@ bitfield string2vec(char const* have_str) const int num_pieces = strlen(have_str); bitfield have(num_pieces, false); for (int i = 0; i < num_pieces; ++i) - if (have_str[i] != ' ') have.set_bit(i); + if (have_str[i] != ' ') have.set_bit(i); return have; } -policy::peer* tmp_peer = 0; +ipv4_peer* tmp_peer = 0; + +tcp::endpoint endp; +ipv4_peer tmp0(endp, false, 0); +ipv4_peer tmp1(endp, false, 0); +ipv4_peer tmp2(endp, false, 0); +ipv4_peer tmp3(endp, false, 0); +ipv4_peer tmp4(endp, false, 0); +ipv4_peer tmp5(endp, false, 0); +ipv4_peer tmp6(endp, false, 0); +ipv4_peer tmp7(endp, false, 0); +ipv4_peer tmp8(endp, false, 0); +ipv4_peer tmp9(endp, false, 0); +ipv4_peer peer_struct(endp, true, 0); + +const std::vector empty_vector; // availability is a string where each character is the // availability of that piece, '1', '2' etc. @@ -70,7 +86,7 @@ boost::shared_ptr setup_picker( , char const* partial) { const int num_pieces = strlen(availability); - assert(int(strlen(have_str)) == num_pieces); + TORRENT_ASSERT(int(strlen(have_str)) == num_pieces); boost::shared_ptr p(new piece_picker); p->init(blocks_per_piece, blocks_per_piece, num_pieces); @@ -80,7 +96,10 @@ boost::shared_ptr setup_picker( const int avail = availability[i] - '0'; assert(avail >= 0); - for (int j = 0; j < avail; ++j) p->inc_refcount(i, 0); + const static torrent_peer* peers[10] = { &tmp0, &tmp1, &tmp2 + , &tmp3, &tmp4, &tmp5, &tmp6, &tmp7, &tmp8, &tmp9 }; + TORRENT_ASSERT(avail < 10); + for (int j = 0; j < avail; ++j) p->inc_refcount(i, peers[j]); } bitfield have = string2vec(have_str); @@ -227,11 +246,13 @@ void print_title(char const* name) std::vector pick_pieces(boost::shared_ptr const& p, char const* availability , int num_blocks, int prefer_whole_pieces, void* peer_struct, piece_picker::piece_state_t state - , int options, std::vector const& suggested_pieces) + = piece_picker::fast, int options = piece_picker::rarest_first + , std::vector const& suggested_pieces = empty_vector) { std::vector picked; + counters pc; p->pick_pieces(string2vec(availability), picked, num_blocks, prefer_whole_pieces, peer_struct - , state, options, suggested_pieces, 20); + , state, options, suggested_pieces, 20, pc); print_pick(picked); TEST_CHECK(verify_pick(p, picked)); return picked; @@ -248,56 +269,59 @@ int test_pick(boost::shared_ptr const& p, int options = piece_pick int test_main() { - tcp::endpoint endp; piece_picker::downloading_piece st; - policy::ipv4_peer tmp1(endp, false, 0); - policy::ipv4_peer tmp2(endp, false, 0); - policy::ipv4_peer tmp3(endp, false, 0); - policy::ipv4_peer peer_struct(endp, true, 0); #if TORRENT_USE_ASSERTS + tmp0.in_use = true; tmp1.in_use = true; tmp2.in_use = true; tmp3.in_use = true; + tmp4.in_use = true; + tmp5.in_use = true; + tmp6.in_use = true; + tmp7.in_use = true; + tmp8.in_use = true; + tmp9.in_use = true; peer_struct.in_use = true; #endif tmp_peer = &tmp1; std::vector picked; boost::shared_ptr p; - const std::vector empty_vector; int options = piece_picker::rarest_first; + std::pair dc; + counters pc; // ======================================================== // test abort_download print_title("test abort_download"); p = setup_picker("1111111", " ", "7110000", ""); - picked = pick_pieces(p, "*******", blocks_per_piece, 0, 0, piece_picker::fast + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer, piece_picker::fast , options, empty_vector); TEST_CHECK(p->is_requested(piece_block(0, 0)) == false); TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0,0)) != picked.end()); - p->abort_download(piece_block(piece_block(0,0))); - picked = pick_pieces(p, "*******", blocks_per_piece, 0, 0, piece_picker::fast + p->abort_download(piece_block(0,0), tmp_peer); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer, piece_picker::fast , options, empty_vector); TEST_CHECK(p->is_requested(piece_block(0, 0)) == false); TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0,0)) != picked.end()); p->mark_as_downloading(piece_block(0,0), &tmp1, piece_picker::fast); - picked = pick_pieces(p, "*******", blocks_per_piece, 0, 0, piece_picker::fast + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer, piece_picker::fast , options, empty_vector); TEST_CHECK(p->is_requested(piece_block(0, 0)) == true); TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0,0)) == picked.end()); - p->abort_download(piece_block(0,0)); - picked = pick_pieces(p, "*******", blocks_per_piece, 0, 0, piece_picker::fast + p->abort_download(piece_block(0,0), tmp_peer); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer, piece_picker::fast , options, empty_vector); TEST_CHECK(p->is_requested(piece_block(0, 0)) == false); TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0,0)) != picked.end()); p->mark_as_downloading(piece_block(0,0), &tmp1, piece_picker::fast); p->mark_as_downloading(piece_block(0,1), &tmp1, piece_picker::fast); - p->abort_download(piece_block(0,0)); - picked = pick_pieces(p, "*******", blocks_per_piece, 0, 0, piece_picker::fast + p->abort_download(piece_block(0,0), tmp_peer); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer, piece_picker::fast , options, empty_vector); TEST_CHECK(p->is_requested(piece_block(0, 0)) == false); TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0,0)) != picked.end()); @@ -305,7 +329,13 @@ int test_main() p->mark_as_downloading(piece_block(0,0), &tmp1, piece_picker::fast); p->mark_as_writing(piece_block(0,0), &tmp1); p->write_failed(piece_block(0,0)); - picked = pick_pieces(p, "*******", blocks_per_piece, 0, 0, piece_picker::fast + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer, piece_picker::fast + , options, empty_vector); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(1,0)) != picked.end() + || std::find(picked.begin(), picked.end(), piece_block(2,0)) != picked.end()); + TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0,0)) == picked.end()); + p->restore_piece(0); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer, piece_picker::fast , options, empty_vector); TEST_CHECK(p->is_requested(piece_block(0, 0)) == false); TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0,0)) != picked.end()); @@ -313,8 +343,8 @@ int test_main() p->mark_as_downloading(piece_block(0,0), &tmp1, piece_picker::fast); p->mark_as_writing(piece_block(0,0), &tmp1); p->mark_as_finished(piece_block(0,0), &tmp1); - p->abort_download(piece_block(0,0)); - picked = pick_pieces(p, "*******", blocks_per_piece, 0, 0, piece_picker::fast + p->abort_download(piece_block(0,0), tmp_peer); + picked = pick_pieces(p, "*******", blocks_per_piece, 0, tmp_peer, piece_picker::fast , options, empty_vector); TEST_CHECK(p->is_requested(piece_block(0, 0)) == false); TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(0,0)) == picked.end()); @@ -326,7 +356,7 @@ int test_main() TEST_CHECK(st.requested == 1); TEST_CHECK(st.finished == 1); TEST_CHECK(st.state == piece_picker::fast); - p->abort_download(piece_block(0,0)); + p->abort_download(piece_block(0,0), tmp_peer); p->piece_info(0, st); TEST_CHECK(st.requested == 0); TEST_CHECK(st.finished == 1); @@ -354,6 +384,22 @@ int test_main() p->get_downloaders(d, 0); TEST_CHECK(d[3] == &tmp2); + p = setup_picker("2222", " ", "", ""); + + for (int i = 0; i < 4; ++i) + for (int k = 0; k < blocks_per_piece; ++k) + p->mark_as_downloading(piece_block(i, k), &tmp1, piece_picker::fast); + + p->mark_as_downloading(piece_block(0, 0), &tmp2, piece_picker::fast); + + fprintf(stderr, "num_peers: %d\n", p->num_peers(piece_block(0, 0))); + TEST_EQUAL(p->num_peers(piece_block(0, 0)), 2); + + p->abort_download(piece_block(0, 0), &tmp1); + + fprintf(stderr, "num_peers: %d\n", p->num_peers(piece_block(0, 0))); + TEST_EQUAL(p->num_peers(piece_block(0, 0)), 1); + // ======================================================== // make sure the block that is picked is from piece 1, since it @@ -433,7 +479,7 @@ int test_main() // there are 2 pieces with availability 2 and 5 with availability 3 print_title("test distributed copies"); p = setup_picker("1233333", "* ", "", ""); - std::pair dc = p->distributed_copies(); + dc = p->distributed_copies(); TEST_CHECK(dc == std::make_pair(2, 5000 / 7)); // ======================================================== @@ -518,16 +564,16 @@ int test_main() TEST_CHECK(picked.front().piece_index == first.piece_index); // ======================================================== -/* + // make sure downloading pieces closer to completion have higher priority // piece 3 has only 1 block from being completed, and should be picked - print_title("test downloading piece order"); - p = setup_picker("1111111", " ", "", "013700f"); - picked = pick_pieces(p, "*******", 1, 0, 0, piece_picker::fast - , options | piece_picker::prioritize_partials, empty_vector); - TEST_CHECK(int(picked.size()) > 0); - TEST_CHECK(picked.front() == piece_block(3, 3)); -*/ +// print_title("test downloading piece order"); +// p = setup_picker("1111111", " ", "", "013700f"); +// picked = pick_pieces(p, "*******", 1, 0, 0, piece_picker::fast +// , options | piece_picker::prioritize_partials, empty_vector); +// TEST_CHECK(int(picked.size()) > 0); +// TEST_CHECK(picked.front() == piece_block(3, 3)); + // ======================================================== // test sequential download @@ -763,26 +809,90 @@ int test_main() picked.clear(); p->pick_pieces(string2vec("*******"), picked, 7 * blocks_per_piece, 0, 0 - , piece_picker::fast, piece_picker::prioritize_partials, empty_vector, 20); + , piece_picker::fast, piece_picker::prioritize_partials, empty_vector, 20 + , pc); TEST_CHECK(verify_pick(p, picked, true)); print_pick(picked); - // don't pick both busy pieces, just one - TEST_EQUAL(picked.size(), 7 * blocks_per_piece - 1); + // don't pick both busy pieces, if there are already other blocks picked + TEST_EQUAL(picked.size(), 7 * blocks_per_piece - 2); picked.clear(); p->pick_pieces(string2vec("*******"), picked, 7 * blocks_per_piece, 0, 0 , piece_picker::fast, piece_picker::prioritize_partials - | piece_picker::rarest_first, empty_vector, 20); + | piece_picker::rarest_first, empty_vector, 20 + , pc); TEST_CHECK(verify_pick(p, picked, true)); print_pick(picked); - TEST_EQUAL(picked.size(), 7 * blocks_per_piece - 1); + // don't pick both busy pieces, if there are already other blocks picked + TEST_EQUAL(picked.size(), 7 * blocks_per_piece - 2); picked.clear(); p->pick_pieces(string2vec("*******"), picked, 7 * blocks_per_piece, 0, 0 - , piece_picker::fast, piece_picker::rarest_first, empty_vector, 20); + , piece_picker::fast, piece_picker::rarest_first, empty_vector, 20 + , pc); TEST_CHECK(verify_pick(p, picked, true)); print_pick(picked); - TEST_EQUAL(picked.size(), 7 * blocks_per_piece - 1); + // don't pick both busy pieces, if there are already other blocks picked + TEST_EQUAL(picked.size(), 7 * blocks_per_piece - 2); + + // make sure we still pick from a partial piece even when prefering whole pieces + picked.clear(); + p->pick_pieces(string2vec(" * "), picked, 1, 1, 0 + , piece_picker::fast, piece_picker::rarest_first | piece_picker::align_expanded_pieces, empty_vector, 20 + , pc); + TEST_CHECK(verify_pick(p, picked, true)); + print_pick(picked); + // always only pick one busy piece + TEST_EQUAL(picked.size(), 1); + TEST_CHECK(picked.size() >= 1 && picked[0].piece_index == 1); + + // don't pick locked pieces + picked.clear(); + p->lock_piece(1); + p->pick_pieces(string2vec(" ** "), picked, 7 * blocks_per_piece, 0, 0 + , piece_picker::fast, piece_picker::rarest_first, empty_vector, 20 + , pc); + TEST_CHECK(verify_pick(p, picked, true)); + print_pick(picked); + // always only pick one busy piece + TEST_EQUAL(picked.size(), 3); + TEST_CHECK(picked.size() >= 1 && picked[0].piece_index == 2); + + p->restore_piece(1); + p->mark_as_downloading(piece_block(2,0), &tmp1, piece_picker::fast); + p->mark_as_downloading(piece_block(2,1), &tmp1, piece_picker::fast); + p->mark_as_downloading(piece_block(2,3), &tmp1, piece_picker::fast); + p->mark_as_downloading(piece_block(1,0), &tmp1, piece_picker::slow); + p->mark_as_downloading(piece_block(1,1), &tmp1, piece_picker::slow); + p->mark_as_downloading(piece_block(1,2), &tmp1, piece_picker::slow); + p->mark_as_downloading(piece_block(1,3), &tmp1, piece_picker::slow); + + picked.clear(); + p->pick_pieces(string2vec(" ** "), picked, 2 * blocks_per_piece, 0, 0 + , piece_picker::fast, piece_picker::rarest_first, empty_vector, 20 + , pc); + TEST_CHECK(verify_pick(p, picked, true)); + print_pick(picked); + // always only pick one busy piece + TEST_EQUAL(picked.size(), 1); + + picked.clear(); + p->pick_pieces(string2vec(" ** "), picked, 2 * blocks_per_piece, 0, 0 + , piece_picker::fast, piece_picker::prioritize_partials, empty_vector, 0 + , pc); + TEST_CHECK(verify_pick(p, picked, true)); + print_pick(picked); + // always only pick one busy piece + TEST_EQUAL(picked.size(), 1); + + picked.clear(); + p->pick_pieces(string2vec(" ** "), picked, 2 * blocks_per_piece, 0, 0 + , piece_picker::fast, piece_picker::prioritize_partials, empty_vector, 20 + , pc); + TEST_CHECK(verify_pick(p, picked, true)); + print_pick(picked); + // always only pick one busy piece + TEST_EQUAL(picked.size(), 1); // ======================================================== @@ -823,15 +933,15 @@ int test_main() dc = p->distributed_copies(); std::cout << "distributed copies: " << dc.first << "." << (dc.second / 1000.f) << std::endl; TEST_CHECK(dc == std::make_pair(1, 5000 / 7)); - p->inc_refcount_all(0); + p->inc_refcount_all(&tmp8); dc = p->distributed_copies(); TEST_CHECK(dc == std::make_pair(2, 5000 / 7)); - p->dec_refcount_all(0); + p->dec_refcount_all(&tmp8); dc = p->distributed_copies(); std::cout << "distributed copies: " << dc.first << "." << (dc.second / 1000.f) << std::endl; TEST_CHECK(dc == std::make_pair(1, 5000 / 7)); - p->inc_refcount(0, 0); - p->dec_refcount_all(0); + p->inc_refcount(0, &tmp0); + p->dec_refcount_all(&tmp0); dc = p->distributed_copies(); std::cout << "distributed copies: " << dc.first << "." << (dc.second / 1000.f) << std::endl; TEST_CHECK(dc == std::make_pair(0, 6000 / 7)); @@ -845,7 +955,7 @@ int test_main() dc = p->distributed_copies(); std::cout << "distributed copies: " << dc.first << "." << (dc.second / 1000.f) << std::endl; TEST_CHECK(dc == std::make_pair(1, 5000 / 7)); - p->inc_refcount_all(0); + p->inc_refcount_all(&tmp8); dc = p->distributed_copies(); std::cout << "distributed copies: " << dc.first << "." << (dc.second / 1000.f) << std::endl; TEST_CHECK(dc == std::make_pair(2, 5000 / 7)); @@ -858,25 +968,37 @@ int test_main() p = setup_picker("1233333", " * ", "", ""); TEST_CHECK(test_pick(p) == 0); - p->dec_refcount(0, 0); + p->dec_refcount(0, &tmp0); TEST_CHECK(test_pick(p) == 1); - p->dec_refcount(4, 0); - p->dec_refcount(4, 0); + p->dec_refcount(4, &tmp0); + p->dec_refcount(4, &tmp1); TEST_CHECK(test_pick(p) == 4); // decrease refcount on something that's not in the piece list - p->dec_refcount(5, 0); - p->inc_refcount(5, 0); + p->dec_refcount(5, &tmp0); + p->inc_refcount(5, &tmp0); - bitfield bits(7); - bits.clear_all(); - bits.set_bit(0); - p->inc_refcount(bits, 0); - bits.clear_all(); - bits.set_bit(4); - p->dec_refcount(bits, 0); - TEST_CHECK(test_pick(p) == 0); + bitfield bits = string2vec("* "); + TEST_EQUAL(bits.get_bit(0), true); + TEST_EQUAL(bits.get_bit(1), false); + TEST_EQUAL(bits.get_bit(2), false); + TEST_EQUAL(bits.get_bit(3), false); + TEST_EQUAL(bits.get_bit(4), false); + TEST_EQUAL(bits.get_bit(5), false); + TEST_EQUAL(bits.get_bit(6), false); + p->inc_refcount(bits, &tmp0); + bits = string2vec(" * "); + + TEST_EQUAL(bits.get_bit(0), false); + TEST_EQUAL(bits.get_bit(1), false); + TEST_EQUAL(bits.get_bit(2), false); + TEST_EQUAL(bits.get_bit(3), false); + TEST_EQUAL(bits.get_bit(4), true); + TEST_EQUAL(bits.get_bit(5), false); + TEST_EQUAL(bits.get_bit(6), false); + p->dec_refcount(bits, &tmp2); + TEST_EQUAL(test_pick(p), 0); // ======================================================== @@ -951,6 +1073,26 @@ int test_main() TEST_CHECK(picked.size() == 7 * blocks_per_piece - 1); TEST_CHECK(std::find(picked.begin(), picked.end(), piece_block(2,2)) == picked.end()); + // test aligned whole pieces + print_title("test prefer aligned whole pieces"); + p = setup_picker("2222221222222222", " ", "", ""); + picked = pick_pieces(p, "****************", 1, 4, 0 + , piece_picker::fast, options | piece_picker::align_expanded_pieces, empty_vector); + + // the piece picker should pick piece 5, and then align it to even 4 pieces + // i.e. it should have picked pieces: 4,5,6,7 + print_pick(picked); + TEST_CHECK(picked.size() == 4 * blocks_per_piece); + + std::set picked_pieces; + for (std::vector::iterator i = picked.begin() + , end(picked.end()); i != end; ++i) + picked_pieces.insert(picked_pieces.begin(), i->piece_index); + + TEST_CHECK(picked_pieces.size() == 4); + int expected_pieces[] = {4,5,6,7}; + TEST_CHECK(std::equal(picked_pieces.begin(), picked_pieces.end(), expected_pieces)) + //#error test picking with partial pieces and other peers present so that both backup_pieces and backup_pieces2 are used // ======================================================== @@ -1001,6 +1143,24 @@ int test_main() for (int i = 1; i < int(picked.size()); ++i) TEST_CHECK(picked[i] == piece_block(5, i)); +// ======================================================== + + // test bitfield optimization + print_title("test bitfield optimization"); + // we have less than half of the pieces + p = setup_picker("2122222211221222", " ", "", ""); + // make sure it's not dirty + pick_pieces(p, "****************", 1, 1, 0); + print_availability(p); + p->dec_refcount(string2vec("** ** ** * "), &tmp0); + print_availability(p); + TEST_CHECK(verify_availability(p, "1022112200220222")); + // make sure it's not dirty + pick_pieces(p, "****************", 1, 1, 0); + p->inc_refcount(string2vec(" ** ** * * "), &tmp8); + print_availability(p); + TEST_CHECK(verify_availability(p, "1132123201220322")); + // ======================================================== // test seed optimizaton @@ -1008,40 +1168,41 @@ int test_main() p = setup_picker("0000000000000000", " ", "", ""); // make sure it's not dirty - pick_pieces(p, "****************", 1, 1, 0, piece_picker::fast, options, empty_vector); + pick_pieces(p, "****************", 1, 1, 0); - p->inc_refcount_all((void*)2); + p->inc_refcount_all(&tmp0); print_availability(p); TEST_CHECK(verify_availability(p, "1111111111111111")); // make sure it's not dirty - pick_pieces(p, "****************", 1, 1, 0, piece_picker::fast, options, empty_vector); - p->dec_refcount(string2vec(" **** ** "), (void*)4); + pick_pieces(p, "****************", 1, 1, 0); + p->dec_refcount(string2vec(" **** ** "), &tmp0); print_availability(p); TEST_CHECK(verify_availability(p, "1100001100111111")); // make sure it's not dirty - pick_pieces(p, "****************", 1, 1, 0, piece_picker::fast, options, empty_vector); - p->inc_refcount(string2vec(" **** ** "), (void*)5); + pick_pieces(p, "****************", 1, 1, 0); + p->inc_refcount(string2vec(" **** ** "), &tmp0); TEST_CHECK(verify_availability(p, "1111111111111111")); // make sure it's not dirty - pick_pieces(p, "****************", 1, 1, 0, piece_picker::fast, options, empty_vector); - p->dec_refcount_all((void*)2); + pick_pieces(p, "****************", 1, 1, 0); + p->dec_refcount_all(&tmp0); TEST_CHECK(verify_availability(p, "0000000000000000")); - p->inc_refcount_all((void*)2); + p->inc_refcount_all(&tmp1); print_availability(p); TEST_CHECK(verify_availability(p, "1111111111111111")); // make sure it's not dirty - pick_pieces(p, "****************", 1, 1, 0, piece_picker::fast, options, empty_vector); - p->dec_refcount(3, (void*)4); + pick_pieces(p, "****************", 1, 1, 0); + p->dec_refcount(3, &tmp1); print_availability(p); TEST_CHECK(verify_availability(p, "1110111111111111")); +// ======================================================== + // MISSING TESTS: -// 1. abort_download // 2. write_failed /* diff --git a/test/test_policy.cpp b/test/test_policy.cpp new file mode 100644 index 000000000..39d42dc32 --- /dev/null +++ b/test/test_policy.cpp @@ -0,0 +1,439 @@ +/* + +Copyright (c) 2013, 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. + +*/ + +#include "libtorrent/policy.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/torrent_peer_allocator.hpp" +#include "libtorrent/peer_connection_interface.hpp" +#include "libtorrent/stat.hpp" +#include "libtorrent/ip_voter.hpp" +#include "libtorrent/ip_filter.hpp" +#include "libtorrent/peer_info.hpp" + +#include "test.hpp" +#include "setup_transfer.hpp" +#include +#include + +using namespace libtorrent; + +tcp::endpoint ep(char const* ip, int port) +{ + return tcp::endpoint(address_v4::from_string(ip), port); +} + +struct mock_peer_connection : peer_connection_interface +{ + mock_peer_connection(bool out, tcp::endpoint const& ep) + : m_choked(false) + , m_outgoing(out) + , m_tp(NULL) + , m_remote(ep) + { + for (int i = 0; i < 20; ++i) m_id[i] = rand(); + } + virtual ~mock_peer_connection() {} +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING + virtual void peer_log(char const* fmt, ...) const + { + va_list v; + va_start(v, fmt); + vprintf(fmt, v); + va_end(v); + } +#endif + + libtorrent::stat m_stat; + bool m_choked; + bool m_outgoing; + torrent_peer* m_tp; + tcp::endpoint m_remote; + peer_id m_id; + + virtual void get_peer_info(peer_info& p) const {} + virtual tcp::endpoint const& remote() const { return m_remote; } + virtual tcp::endpoint local_endpoint() const { return ep("127.0.0.1", 8080); } + virtual void disconnect(error_code const& ec + , peer_connection_interface::operation_t op, int error = 0) + { /* remove from mock_torrent list */ m_tp = 0; } + virtual peer_id const& pid() const { return m_id; } + virtual void set_holepunch_mode() {} + virtual torrent_peer* peer_info_struct() const { return m_tp; } + virtual void set_peer_info(torrent_peer* pi) { m_tp = pi; } + virtual bool is_outgoing() const { return m_outgoing; } + virtual void add_stat(size_type downloaded, size_type uploaded) + { m_stat.add_stat(downloaded, uploaded); } + virtual bool fast_reconnect() const { return true; } + virtual bool is_choked() const { return m_choked; } + virtual bool failed() const { return false; } + virtual libtorrent::stat const& statistics() const { return m_stat; } +}; + +struct mock_torrent +{ + mock_torrent() : m_p(NULL) {} + virtual ~mock_torrent() {} + + bool connect_to_peer(torrent_peer* peerinfo, bool ignore_limit = false) + { + TORRENT_ASSERT(peerinfo->connection == NULL); + if (peerinfo->connection) return false; + boost::shared_ptr c(new mock_peer_connection(true, peerinfo->ip())); + m_connections.push_back(c); + m_p->set_connection(peerinfo, c.get()); + return true; + } + +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + void debug_log(const char* fmt, ...) const + { + va_list v; + va_start(v, fmt); + vprintf(fmt, v); + va_end(v); + } +#endif + + policy* m_p; + +private: + + std::vector > m_connections; +}; + +int test_main() +{ + torrent_peer_allocator allocator; + external_ip ext_ip; + + torrent_state st; + st.is_finished = false; + st.is_paused = false; + st.max_peerlist_size = 1000; + st.allow_multiple_connections_per_ip = false; + st.peer_allocator = &allocator; + st.ip = &ext_ip; + st.port = 9999; + + // test multiple peers with the same IP + // when disallowing it + { + mock_torrent t; + policy p; + t.m_p = &p; + TEST_EQUAL(p.num_connect_candidates(), 0); + torrent_peer* peer1 = p.add_peer(ep("10.0.0.2", 3000), 0, 0, &st); + + TEST_EQUAL(p.num_peers(), 1); + TEST_EQUAL(p.num_connect_candidates(), 1); + st.erased.clear(); + + torrent_peer* peer2 = p.add_peer(ep("10.0.0.2", 9020), 0, 0, &st); + TEST_EQUAL(p.num_peers(), 1); + TEST_EQUAL(peer1, peer2); + TEST_EQUAL(p.num_connect_candidates(), 1); + st.erased.clear(); + } + + // test multiple peers with the same IP + // when allowing it + { + mock_torrent t; + st.allow_multiple_connections_per_ip = true; + policy p; + t.m_p = &p; + torrent_peer* peer1 = p.add_peer(ep("10.0.0.2", 3000), 0, 0, &st); + TEST_EQUAL(p.num_connect_candidates(), 1); + TEST_EQUAL(p.num_peers(), 1); + st.erased.clear(); + + torrent_peer* peer2 = p.add_peer(ep("10.0.0.2", 9020), 0, 0, &st); + TEST_EQUAL(p.num_peers(), 2); + TEST_CHECK(peer1 != peer2); + TEST_EQUAL(p.num_connect_candidates(), 2); + st.erased.clear(); + } + + // test adding two peers with the same IP, but different ports, to + // make sure they can be connected at the same time + // with allow_multiple_connections_per_ip enabled + { + mock_torrent t; + st.allow_multiple_connections_per_ip = true; + policy p; + t.m_p = &p; + torrent_peer* peer1 = p.add_peer(ep("10.0.0.2", 3000), 0, 0, &st); + TEST_EQUAL(p.num_connect_candidates(), 1); + st.erased.clear(); + + TEST_EQUAL(p.num_peers(), 1); + torrent_peer* tp = p.connect_one_peer(0, &st); + TEST_CHECK(tp); + t.connect_to_peer(tp); + st.erased.clear(); + + // we only have one peer, we can't + // connect another one + tp = p.connect_one_peer(0, &st); + TEST_CHECK(tp == NULL); + st.erased.clear(); + + torrent_peer* peer2 = p.add_peer(ep("10.0.0.2", 9020), 0, 0, &st); + TEST_EQUAL(p.num_peers(), 2); + TEST_CHECK(peer1 != peer2); + TEST_EQUAL(p.num_connect_candidates(), 1); + st.erased.clear(); + + tp = p.connect_one_peer(0, &st); + TEST_CHECK(tp); + t.connect_to_peer(tp); + TEST_EQUAL(p.num_connect_candidates(), 0); + st.erased.clear(); + } + + // test adding two peers with the same IP, but different ports, to + // make sure they can not be connected at the same time + // with allow_multiple_connections_per_ip disabled + { + mock_torrent t; + st.allow_multiple_connections_per_ip = false; + policy p; + t.m_p = &p; + torrent_peer* peer1 = p.add_peer(ep("10.0.0.2", 3000), 0, 0, &st); + TEST_EQUAL(p.num_connect_candidates(), 1); + TEST_EQUAL(peer1->port, 3000); + st.erased.clear(); + + TEST_EQUAL(p.num_peers(), 1); + torrent_peer* tp = p.connect_one_peer(0, &st); + TEST_CHECK(tp); + t.connect_to_peer(tp); + st.erased.clear(); + + // we only have one peer, we can't + // connect another one + tp = p.connect_one_peer(0, &st); + TEST_CHECK(tp == NULL); + st.erased.clear(); + + torrent_peer* peer2 = p.add_peer(ep("10.0.0.2", 9020), 0, 0, &st); + TEST_EQUAL(p.num_peers(), 1); + TEST_EQUAL(peer2->port, 9020); + TEST_CHECK(peer1 == peer2); + TEST_EQUAL(p.num_connect_candidates(), 0); + st.erased.clear(); + } + + // test incoming connection + // and update_peer_port + { + mock_torrent t; + st.allow_multiple_connections_per_ip = false; + policy p; + t.m_p = &p; + TEST_EQUAL(p.num_connect_candidates(), 0); + boost::shared_ptr c(new mock_peer_connection(true, ep("10.0.0.1", 8080))); + p.new_connection(*c, 0, &st); + TEST_EQUAL(p.num_connect_candidates(), 0); + TEST_EQUAL(p.num_peers(), 1); + st.erased.clear(); + + p.update_peer_port(4000, c->peer_info_struct(), peer_info::incoming, &st); + TEST_EQUAL(p.num_connect_candidates(), 0); + TEST_EQUAL(p.num_peers(), 1); + TEST_EQUAL(c->peer_info_struct()->port, 4000); + st.erased.clear(); + } + + // test incoming connection + // and update_peer_port, causing collission + { + mock_torrent t; + st.allow_multiple_connections_per_ip = true; + policy p; + t.m_p = &p; + + torrent_peer* peer2 = p.add_peer(ep("10.0.0.1", 4000), 0, 0, &st); + TEST_CHECK(peer2); + + TEST_EQUAL(p.num_connect_candidates(), 1); + boost::shared_ptr c(new mock_peer_connection(true, ep("10.0.0.1", 8080))); + p.new_connection(*c, 0, &st); + TEST_EQUAL(p.num_connect_candidates(), 1); + // at this point we have two peers, because we think they have different + // ports + TEST_EQUAL(p.num_peers(), 2); + st.erased.clear(); + + // this peer will end up having the same port as the existing peer in the list + p.update_peer_port(4000, c->peer_info_struct(), peer_info::incoming, &st); + TEST_EQUAL(p.num_connect_candidates(), 0); + // the expected behavior is to replace that one + TEST_EQUAL(p.num_peers(), 1); + TEST_EQUAL(c->peer_info_struct()->port, 4000); + st.erased.clear(); + } + + // test ip filter + { + mock_torrent t; + st.allow_multiple_connections_per_ip = false; + policy p; + t.m_p = &p; + torrent_peer* peer1 = p.add_peer(ep("10.0.0.2", 3000), 0, 0, &st); + TEST_EQUAL(p.num_connect_candidates(), 1); + TEST_EQUAL(peer1->port, 3000); + st.erased.clear(); + + torrent_peer* peer2 = p.add_peer(ep("11.0.0.2", 9020), 0, 0, &st); + TEST_EQUAL(p.num_peers(), 2); + TEST_EQUAL(peer2->port, 9020); + TEST_CHECK(peer1 != peer2); + TEST_EQUAL(p.num_connect_candidates(), 2); + st.erased.clear(); + + // connect both peers + torrent_peer* tp = p.connect_one_peer(0, &st); + TEST_CHECK(tp); + t.connect_to_peer(tp); + st.erased.clear(); + + tp = p.connect_one_peer(0, &st); + TEST_CHECK(tp); + t.connect_to_peer(tp); + TEST_EQUAL(p.num_peers(), 2); + TEST_EQUAL(p.num_connect_candidates(), 0); + st.erased.clear(); + + // now, filter one of the IPs and make sure the peer is removed + ip_filter filter; + filter.add_rule(address_v4::from_string("11.0.0.0"), address_v4::from_string("255.255.255.255"), 1); + std::vector
banned; + p.apply_ip_filter(filter, &st, banned); + // we just erased a peer, because it was filtered by the ip filter + TEST_EQUAL(st.erased.size(), 1); + TEST_EQUAL(p.num_connect_candidates(), 0); + TEST_EQUAL(p.num_peers(), 1); + TEST_EQUAL(banned.size(), 1); + TEST_EQUAL(banned[0], address_v4::from_string("11.0.0.2")); + } + + // test banning peers + { + mock_torrent t; + st.allow_multiple_connections_per_ip = false; + policy p; + t.m_p = &p; + + torrent_peer* peer1 = p.add_peer(ep("10.0.0.1", 4000), 0, 0, &st); + TEST_CHECK(peer1); + st.erased.clear(); + + TEST_EQUAL(p.num_connect_candidates(), 1); + boost::shared_ptr c(new mock_peer_connection(true, ep("10.0.0.1", 8080))); + p.new_connection(*c, 0, &st); + TEST_EQUAL(p.num_connect_candidates(), 0); + TEST_EQUAL(p.num_peers(), 1); + st.erased.clear(); + + // now, ban the peer + bool ok = p.ban_peer(c->peer_info_struct()); + TEST_EQUAL(ok, true); + TEST_EQUAL(peer1->banned, true); + // we still have it in the list + TEST_EQUAL(p.num_peers(), 1); + // it's just not a connect candidate, nor allowed to receive incoming connections + TEST_EQUAL(p.num_connect_candidates(), 0); + + p.connection_closed(*c, 0, &st); + TEST_EQUAL(p.num_peers(), 1); + TEST_EQUAL(p.num_connect_candidates(), 0); + st.erased.clear(); + + c.reset(new mock_peer_connection(true, ep("10.0.0.1", 8080))); + ok = p.new_connection(*c, 0, &st); + // since it's banned, we should not allow this incoming connection + TEST_EQUAL(ok, false); + TEST_EQUAL(p.num_connect_candidates(), 0); + st.erased.clear(); + } + + // test erase_peers when we fill up the peer list + { + mock_torrent t; + st.max_peerlist_size = 100; + st.allow_multiple_connections_per_ip = true; + policy p; + t.m_p = &p; + + for (int i = 0; i < 100; ++i) + { + torrent_peer* peer = p.add_peer(rand_tcp_ep(), 0, 0, &st); + TEST_EQUAL(st.erased.size(), 0); + st.erased.clear(); + TEST_CHECK(peer); + if (peer == NULL || st.erased.size() > 0) + { + fprintf(stderr, "unexpected rejection of peer: %d in list. added peer %p, erased peers %d\n" + , p.num_peers(), peer, int(st.erased.size())); + } + } + TEST_EQUAL(p.num_peers(), 100); + + // trigger the eviction of one peer + torrent_peer* peer = p.add_peer(rand_tcp_ep(), 0, 0, &st); + // we either removed an existing peer, or rejected this one + TEST_CHECK(st.erased.size() == 1 || peer == NULL); + } + +// TODO: test applying a port_filter +// TODO: test erasing peers +// TODO: test using port and ip filter +// TODO: test incrementing failcount (and make sure we no longer consider the peer a connect canidate) +// TODO: test max peerlist size +// TODO: test logic for which connection to keep when receiving an incoming connection to the same peer as we just made an outgoing connection to +// TODO: test update_peer_port with allow_multiple_connections_per_ip +// TODO: test set_seed +// TODO: test has_peer +// TODO: test insert_peer with a full list +// TODO: test add i2p peers +// TODO: test allow_i2p_mixed +// TODO: test insert_peer failing +// TODO: test IPv6 +// TODO: test connect_to_peer() failing +// TODO: test connection_closed +// TODO: test recalculate connect candidates +// TODO: add tests here + + return 0; +} + diff --git a/test/test_primitives.cpp b/test/test_primitives.cpp index 9874d8557..b3ad1a868 100644 --- a/test/test_primitives.cpp +++ b/test/test_primitives.cpp @@ -64,32 +64,51 @@ sha1_hash to_hash(char const* s) return ret; } -address rand_v4() +address_v4 v4(char const* str) { - return address_v4((rand() << 16 | rand()) & 0xffffffff); + error_code ec; + return address_v4::from_string(str, ec); } #if TORRENT_USE_IPV6 -address rand_v6() +address_v6 v6(char const* str) { - address_v6::bytes_type bytes; - for (int i = 0; i < bytes.size(); ++i) bytes[i] = rand() & 0xff; - return address_v6(bytes); + error_code ec; + return address_v6::from_string(str, ec); } #endif +tcp::endpoint ep(char const* ip, int port) +{ + error_code ec; + return tcp::endpoint(address::from_string(ip, ec), port); +} + int test_main() { using namespace libtorrent; - using namespace libtorrent::dht; error_code ec; + sliding_average<4> avg; + TEST_EQUAL(avg.mean(), 0); + TEST_EQUAL(avg.avg_deviation(), 0); + avg.add_sample(500); + TEST_EQUAL(avg.mean(), 500); + TEST_EQUAL(avg.avg_deviation(), 0); + avg.add_sample(501); + TEST_EQUAL(avg.avg_deviation(), 1); + avg.add_sample(0); + avg.add_sample(0); + printf("avg: %d dev: %d\n", avg.mean(), avg.avg_deviation()); + TEST_CHECK(abs(avg.mean() - 250) < 50); + TEST_CHECK(abs(avg.avg_deviation() - 250) < 80); + // make sure the retry interval keeps growing // on failing announces announce_entry ae("dummy"); int last = 0; - session_settings sett; - sett.tracker_backoff = 250; + aux::session_settings sett; + sett.set_int(settings_pack::tracker_backoff, 250); for (int i = 0; i < 10; ++i) { ae.failed(sett, 5); @@ -212,6 +231,71 @@ int test_main() snprintf(msg, sizeof(msg), "too %s format string", "long"); TEST_CHECK(strcmp(msg, "too long ") == 0); + std::string path; + sanitize_append_path_element(path, "a...", 4); + TEST_EQUAL(path, "a"); + + path.clear(); + sanitize_append_path_element(path, "a ", 4); + TEST_EQUAL(path, "a"); + + path.clear(); + sanitize_append_path_element(path, "a...b", 5); + TEST_EQUAL(path, "a...b"); + + path.clear(); + sanitize_append_path_element(path, "a", 1); + sanitize_append_path_element(path, "..", 2); + sanitize_append_path_element(path, "c", 1); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "a\\c"); +#else + TEST_EQUAL(path, "a/c"); +#endif + + path.clear(); + sanitize_append_path_element(path, "/..", 3); + sanitize_append_path_element(path, ".", 1); + sanitize_append_path_element(path, "c", 1); + TEST_EQUAL(path, "c"); + + path.clear(); + sanitize_append_path_element(path, "dev:", 4); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "dev"); +#else + TEST_EQUAL(path, "dev:"); +#endif + + path.clear(); + sanitize_append_path_element(path, "c:", 2); + sanitize_append_path_element(path, "b", 1); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "c\\b"); +#else + TEST_EQUAL(path, "c:/b"); +#endif + + path.clear(); + sanitize_append_path_element(path, "c:", 2); + sanitize_append_path_element(path, ".", 1); + sanitize_append_path_element(path, "c", 1); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "c\\c"); +#else + TEST_EQUAL(path, "c:/c"); +#endif + + path.clear(); + sanitize_append_path_element(path, "\\c", 2); + sanitize_append_path_element(path, ".", 1); + sanitize_append_path_element(path, "c", 1); +#ifdef TORRENT_WINDOWS + TEST_EQUAL(path, "c\\c"); +#else + TEST_EQUAL(path, "c/c"); +#endif + if (supports_ipv6()) { // make sure the assumption we use in policy's peer list hold @@ -255,6 +339,11 @@ int test_main() TEST_CHECK(is_any(address_v4::any())); TEST_CHECK(!is_any(address::from_string("31.53.21.64", ec))); + TEST_CHECK(match_addr_mask( + address::from_string("10.0.1.176", ec), + address::from_string("10.0.1.176", ec), + address::from_string("255.255.255.0", ec))); + TEST_CHECK(match_addr_mask( address::from_string("10.0.1.3", ec), address::from_string("10.0.3.3", ec), @@ -347,6 +436,23 @@ int test_main() std::cerr << h1 << std::endl; #endif TEST_CHECK(h1 == to_hash("3800000000000000000000000000000000000000")); + + h1 = to_hash("7000000000000000000000000000000000000000"); + h1 >>= 32; +#if TORRENT_USE_IOSTREAM + std::cerr << h1 << std::endl; +#endif + TEST_CHECK(h1 == to_hash("0000000070000000000000000000000000000000")); + h1 >>= 33; +#if TORRENT_USE_IOSTREAM + std::cerr << h1 << std::endl; +#endif + TEST_CHECK(h1 == to_hash("0000000000000000380000000000000000000000")); + h1 <<= 33; +#if TORRENT_USE_IOSTREAM + std::cerr << h1 << std::endl; +#endif + TEST_CHECK(h1 == to_hash("0000000070000000000000000000000000000000")); // CIDR distance test h1 = to_hash("0123456789abcdef01232456789abcdef0123456"); @@ -359,6 +465,36 @@ int test_main() h2 = to_hash("0123456789abcdef11232456789abcdef0123456"); TEST_CHECK(common_bits(&h1[0], &h2[0], 20) == 16 * 4 + 3); + // test print_endpoint, parse_endpoint and print_address + TEST_EQUAL(print_endpoint(ep("127.0.0.1", 23)), "127.0.0.1:23"); +#if TORRENT_USE_IPV6 + TEST_EQUAL(print_endpoint(ep("ff::1", 1214)), "[ff::1]:1214"); +#endif + ec.clear(); + TEST_EQUAL(parse_endpoint("127.0.0.1:23", ec), ep("127.0.0.1", 23)); + TEST_CHECK(!ec); + ec.clear(); +#if TORRENT_USE_IPV6 + TEST_EQUAL(parse_endpoint(" \t[ff::1]:1214 \r", ec), ep("ff::1", 1214)); + TEST_CHECK(!ec); +#endif + TEST_EQUAL(print_address(v4("241.124.23.5")), "241.124.23.5"); +#if TORRENT_USE_IPV6 + TEST_EQUAL(print_address(v6("2001:ff::1")), "2001:ff::1"); + parse_endpoint("[ff::1]", ec); + TEST_EQUAL(ec, error_code(errors::invalid_port, get_libtorrent_category())); +#endif + + parse_endpoint("[ff::1:5", ec); + TEST_EQUAL(ec, error_code(errors::expected_close_bracket_in_address, get_libtorrent_category())); + + // test address_to_bytes + TEST_EQUAL(address_to_bytes(address_v4::from_string("10.11.12.13")), "\x0a\x0b\x0c\x0d"); + TEST_EQUAL(address_to_bytes(address_v4::from_string("16.5.127.1")), "\x10\x05\x7f\x01"); + + // test endpoint_to_bytes + TEST_EQUAL(endpoint_to_bytes(udp::endpoint(address_v4::from_string("10.11.12.13"), 8080)), "\x0a\x0b\x0c\x0d\x1f\x90"); + TEST_EQUAL(endpoint_to_bytes(udp::endpoint(address_v4::from_string("16.5.127.1"), 12345)), "\x10\x05\x7f\x01\x30\x39"); return 0; } diff --git a/test/test_priority.cpp b/test/test_priority.cpp index a8ec5ea60..97432abe6 100644 --- a/test/test_priority.cpp +++ b/test/test_priority.cpp @@ -48,11 +48,10 @@ POSSIBILITY OF SUCH DAMAGE. #include using namespace libtorrent; +namespace lt = libtorrent; using boost::tuples::ignore; -int const alert_mask = alert::all_categories -& ~alert::progress_notification -& ~alert::stats_notification; +const int mask = alert::all_categories & ~(alert::performance_warning | alert::stats_notification); int peer_disconnects = 0; @@ -70,7 +69,7 @@ bool on_alert(alert* a) return false; } -void test_transfer() +void test_transfer(settings_pack const& sett) { // in case the previous run was terminated error_code ec; @@ -85,54 +84,51 @@ void test_transfer() session_proxy p1; session_proxy p2; - session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48075, 49000), "0.0.0.0", 0, alert_mask); - session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49075, 50000), "0.0.0.0", 0, alert_mask); - - session_settings sett; - sett.allow_multiple_connections_per_ip = false; - sett.ignore_limits_on_local_network = false; - - sett.unchoke_slots_limit = 0; - ses1.set_settings(sett); - TEST_CHECK(ses1.settings().unchoke_slots_limit == 0); - sett.unchoke_slots_limit = -1; - ses1.set_settings(sett); - TEST_CHECK(ses1.settings().unchoke_slots_limit == -1); - sett.unchoke_slots_limit = 8; - ses1.set_settings(sett); - TEST_CHECK(ses1.settings().unchoke_slots_limit == 8); + lt::session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48075, 49000), "0.0.0.0", 0, mask); + lt::session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49075, 50000), "0.0.0.0", 0, mask); + settings_pack pack = sett; // we need a short reconnect time since we // finish the torrent and then restart it // immediately to complete the second half. // using a reconnect time > 0 will just add // to the time it will take to complete the test - sett.min_reconnect_time = 0; - sett.stop_tracker_timeout = 1; - sett.announce_to_all_trackers = true; - sett.announce_to_all_tiers = true; + pack.set_int(settings_pack::min_reconnect_time, 0); + pack.set_int(settings_pack::stop_tracker_timeout, 1); + pack.set_bool(settings_pack::announce_to_all_trackers, true); + pack.set_bool(settings_pack::announce_to_all_tiers, true); + // make sure we announce to both http and udp trackers - sett.prefer_udp_trackers = false; - sett.enable_outgoing_utp = false; - sett.enable_incoming_utp = false; + pack.set_bool(settings_pack::prefer_udp_trackers, false); + pack.set_bool(settings_pack::enable_outgoing_utp, false); + pack.set_bool(settings_pack::enable_incoming_utp, false); + pack.set_int(settings_pack::alert_mask, mask); - ses1.set_settings(sett); - ses2.set_settings(sett); + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_disabled); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_disabled); -#ifndef TORRENT_DISABLE_ENCRYPTION - pe_settings pes; - pes.out_enc_policy = pe_settings::disabled; - pes.in_enc_policy = pe_settings::disabled; - ses1.set_pe_settings(pes); - ses2.set_pe_settings(pes); -#endif + pack.set_bool(settings_pack::allow_multiple_connections_per_ip, false); + + pack.set_int(settings_pack::unchoke_slots_limit, 0); + ses1.apply_settings(pack); + TEST_CHECK(ses1.get_settings().get_int(settings_pack::unchoke_slots_limit) == 0); + + pack.set_int(settings_pack::unchoke_slots_limit, -1); + ses1.apply_settings(pack); + TEST_CHECK(ses1.get_settings().get_int(settings_pack::unchoke_slots_limit) == -1); + + pack.set_int(settings_pack::unchoke_slots_limit, 8); + ses1.apply_settings(pack); + TEST_CHECK(ses1.get_settings().get_int(settings_pack::unchoke_slots_limit) == 8); + + ses2.apply_settings(pack); torrent_handle tor1; torrent_handle tor2; create_directory("tmp1_priority", ec); std::ofstream file("tmp1_priority/temporary"); - boost::intrusive_ptr t = ::create_torrent(&file, 16 * 1024, 13, false); + boost::shared_ptr t = ::create_torrent(&file, 16 * 1024, 13, false); file.close(); int udp_tracker_port = start_udp_tracker(); @@ -231,7 +227,6 @@ void test_transfer() peer_disconnects = 0; - // wait until force-recheck is complete // when we're done checking, we're likely to be put in downloading state // for a split second before transitioning to finished. This loop waits // for the finished state @@ -259,30 +254,20 @@ void test_transfer() TEST_CHECK(std::equal(priorities.begin(), priorities.end(), priorities2.begin())); tor2.pause(); - alert const* a = ses2.wait_for_alert(seconds(10)); - bool got_paused_alert = false; - while (a) - { - std::auto_ptr holder = ses2.pop_alert(); - std::cerr << "ses2: " << a->message() << std::endl; - if (alert_cast(a)) - { - got_paused_alert = true; - break; - } - a = ses2.wait_for_alert(seconds(10)); - } - TEST_CHECK(got_paused_alert); + wait_for_alert(ses2, torrent_paused_alert::alert_type, "ses2"); std::vector tr = tor2.trackers(); tr.push_back(announce_entry("http://test.com/announce")); tor2.replace_trackers(tr); tr.clear(); + fprintf(stderr, "save resume data\n"); tor2.save_resume_data(); std::vector resume_data; - a = ses2.wait_for_alert(seconds(10)); + + alert const* a = ses2.wait_for_alert(seconds(10)); + ptime start = time_now_hires(); while (a) { std::auto_ptr holder = ses2.pop_alert(); @@ -291,14 +276,21 @@ void test_transfer() { bencode(std::back_inserter(resume_data) , *alert_cast(a)->resume_data); + fprintf(stderr, "saved resume data\n"); break; } + else if (alert_cast(a)) + { + fprintf(stderr, "save resume failed\n"); + break; + } + if (total_seconds(time_now_hires() - start) > 10) + break; + a = ses2.wait_for_alert(seconds(10)); } TEST_CHECK(resume_data.size()); - std::cerr << "saved resume data" << std::endl; - ses2.remove_torrent(tor2); std::cerr << "removed" << std::endl; @@ -313,9 +305,6 @@ void test_transfer() p.save_path = "tmp2_priority"; p.resume_data = resume_data; tor2 = ses2.add_torrent(p, ec); - ses2.set_alert_mask(alert::all_categories - & ~alert::progress_notification - & ~alert::stats_notification); tor2.prioritize_pieces(priorities); std::cout << "resetting priorities" << std::endl; tor2.resume(); @@ -324,24 +313,28 @@ void test_transfer() TEST_CHECK(std::find_if(tr.begin(), tr.end() , boost::bind(&announce_entry::url, _1) == "http://test.com/announce") != tr.end()); - // wait for the files in ses2 to be checked, i.e. the torrent - // to turn into finished state + // wait for torrent 2 to settle in back to finished state (it will + // start as checking) + torrent_status st1; for (int i = 0; i < 5; ++i) { print_alerts(ses1, "ses1", true, true, true, &on_alert); print_alerts(ses2, "ses2", true, true, true, &on_alert); - torrent_status st1 = tor1.status(); - torrent_status st2 = tor2.status(); + st1 = tor1.status(); + st2 = tor2.status(); - TEST_EQUAL(st1.state, torrent_status::seeding); + TEST_CHECK(st1.state == torrent_status::seeding); if (st2.is_finished) break; test_sleep(100); } - TEST_CHECK(!tor2.status().is_seeding); + // torrent 2 should not be seeding yet, it should + // just be 50% finished + TEST_CHECK(!st2.is_seeding); + TEST_CHECK(st2.is_finished); std::fill(priorities.begin(), priorities.end(), 1); tor2.prioritize_pieces(priorities); @@ -360,7 +353,7 @@ void test_transfer() // this loop makes sure ses2 reconnects to the peer now that it's // in download mode again. If this fails, the reconnect logic may // not work or be inefficient - torrent_status st1 = tor1.status(); + st1 = tor1.status(); st2 = tor2.status(); for (int i = 0; i < 130; ++i) { @@ -405,7 +398,12 @@ int test_main() using namespace libtorrent; // test with all kinds of proxies - test_transfer(); + settings_pack p; + + // test no contiguous_recv_buffers + p = settings_pack(); + p.set_bool(settings_pack::contiguous_recv_buffer, false); + test_transfer(p); error_code ec; remove_all("tmp1_priorities", ec); diff --git a/test/test_privacy.cpp b/test/test_privacy.cpp index 9b6120068..af04478d7 100644 --- a/test/test_privacy.cpp +++ b/test/test_privacy.cpp @@ -41,6 +41,7 @@ POSSIBILITY OF SUCH DAMAGE. #include using namespace libtorrent; +namespace lt = libtorrent; char const* proxy_name[] = { "none", @@ -67,7 +68,7 @@ bool alert_predicate(libtorrent::alert* a) enum flags_t { - anonymous_mode = 1, + force_proxy_mode = 1, expect_http_connection = 2, expect_udp_connection = 4, expect_http_reject = 8, @@ -76,13 +77,13 @@ enum flags_t expect_peer_connection = 64, }; -session_proxy test_proxy(proxy_settings::proxy_type proxy_type, int flags) +session_proxy test_proxy(settings_pack::proxy_type_t proxy_type, int flags) { #ifdef TORRENT_DISABLE_DHT // if DHT is disabled, we won't get any requests to it flags &= ~expect_dht_msg; #endif - fprintf(stderr, "\n=== TEST == proxy: %s anonymous-mode: %s\n\n", proxy_name[proxy_type], (flags & anonymous_mode) ? "yes" : "no"); + fprintf(stderr, "\n=== TEST == proxy: %s anonymous-mode: %s\n\n", proxy_name[proxy_type], (flags & force_proxy_mode) ? "yes" : "no"); int http_port = start_web_server(); int udp_port = start_udp_tracker(); int dht_port = start_dht(); @@ -94,39 +95,42 @@ session_proxy test_proxy(proxy_settings::proxy_type proxy_type, int flags) & ~alert::progress_notification & ~alert::stats_notification; - session* s = new libtorrent::session(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48875, 49800), "0.0.0.0", 0, alert_mask); + settings_pack sett; + sett.set_int(settings_pack::stop_tracker_timeout, 2); + sett.set_int(settings_pack::tracker_completion_timeout, 2); + sett.set_int(settings_pack::tracker_receive_timeout, 2); + sett.set_int(settings_pack::half_open_limit, 2); + sett.set_bool(settings_pack::announce_to_all_trackers, true); + sett.set_bool(settings_pack::announce_to_all_tiers, true); + sett.set_bool(settings_pack::force_proxy, flags & force_proxy_mode); + sett.set_int(settings_pack::alert_mask, alert_mask); - session_settings sett; - sett.stop_tracker_timeout = 2; - sett.tracker_completion_timeout = 2; - sett.tracker_receive_timeout = 2; - sett.half_open_limit = 1; - sett.announce_to_all_trackers = true; - sett.announce_to_all_tiers = true; - sett.anonymous_mode = flags & anonymous_mode; - sett.force_proxy = flags & anonymous_mode; + // since multiple sessions may exist simultaneously (because of the + // pipelining of the tests) they actually need to use different ports + static int listen_port = 48875; + char iface[200]; + snprintf(iface, sizeof(iface), "127.0.0.1:%d", listen_port++); + sett.set_str(settings_pack::listen_interfaces, iface); + sett.set_bool(settings_pack::enable_dht, true); // if we don't do this, the peer connection test // will be delayed by several seconds, by first // trying uTP - sett.enable_outgoing_utp = false; - s->set_settings(sett); + sett.set_bool(settings_pack::enable_outgoing_utp, false); // in non-anonymous mode we circumvent/ignore the proxy if it fails // wheras in anonymous mode, we just fail - proxy_settings ps; - ps.hostname = "non-existing.com"; - ps.port = 4444; - ps.type = proxy_type; - s->set_proxy(ps); + sett.set_str(settings_pack::proxy_hostname, "non-existing.com"); + sett.set_int(settings_pack::proxy_type, (settings_pack::proxy_type_t)proxy_type); + sett.set_int(settings_pack::proxy_port, 4444); - s->start_dht(); + lt::session* s = new lt::session(sett, fingerprint("LT", 0, 1, 0, 0)); error_code ec; remove_all("tmp1_privacy", ec); create_directory("tmp1_privacy", ec); std::ofstream file(combine_path("tmp1_privacy", "temporary").c_str()); - boost::intrusive_ptr t = ::create_torrent(&file, 16 * 1024, 13, false); + boost::shared_ptr t = ::create_torrent(&file, 16 * 1024, 13, false); file.close(); char http_tracker_url[200]; @@ -214,26 +218,26 @@ int test_main() // not using anonymous mode // UDP fails open if we can't connect to the proxy // or if the proxy doesn't support UDP - pr[0] = test_proxy(proxy_settings::none, expect_udp_connection | expect_http_connection | expect_dht_msg | expect_peer_connection); - pr[1] = test_proxy(proxy_settings::socks4, expect_udp_connection | expect_dht_msg); - pr[2] = test_proxy(proxy_settings::socks5, expect_udp_connection | expect_dht_msg); - pr[3] = test_proxy(proxy_settings::socks5_pw, expect_udp_connection | expect_dht_msg); - pr[4] = test_proxy(proxy_settings::http, expect_udp_connection | expect_dht_msg); - pr[5] = test_proxy(proxy_settings::http_pw, expect_udp_connection | expect_dht_msg); - pr[6] = test_proxy(proxy_settings::i2p_proxy, expect_udp_connection | expect_dht_msg); + pr[0] = test_proxy(settings_pack::none, expect_udp_connection | expect_http_connection | expect_dht_msg | expect_peer_connection); + pr[1] = test_proxy(settings_pack::socks4, expect_udp_connection | expect_dht_msg); + pr[2] = test_proxy(settings_pack::socks5, expect_udp_connection | expect_dht_msg); + pr[3] = test_proxy(settings_pack::socks5_pw, expect_udp_connection | expect_dht_msg); + pr[4] = test_proxy(settings_pack::http, expect_udp_connection | expect_dht_msg); + pr[5] = test_proxy(settings_pack::http_pw, expect_udp_connection | expect_dht_msg); + pr[6] = test_proxy(settings_pack::i2p_proxy, expect_udp_connection | expect_dht_msg); // using anonymous mode // anonymous mode doesn't require a proxy when one isn't configured. It could be // used with a VPN for instance. This will all changed in 1.0, where anonymous // mode is separated from force_proxy - pr[7] = test_proxy(proxy_settings::none, anonymous_mode | expect_peer_connection); - pr[8] = test_proxy(proxy_settings::socks4, anonymous_mode | expect_udp_reject); - pr[9] = test_proxy(proxy_settings::socks5, anonymous_mode); - pr[10] = test_proxy(proxy_settings::socks5_pw, anonymous_mode); - pr[11] = test_proxy(proxy_settings::http, anonymous_mode | expect_udp_reject); - pr[12] = test_proxy(proxy_settings::http_pw, anonymous_mode | expect_udp_reject); - pr[13] = test_proxy(proxy_settings::i2p_proxy, anonymous_mode); + pr[7] = test_proxy(settings_pack::none, force_proxy_mode | expect_peer_connection); + pr[8] = test_proxy(settings_pack::socks4, force_proxy_mode | expect_udp_reject); + pr[9] = test_proxy(settings_pack::socks5, force_proxy_mode); + pr[10] = test_proxy(settings_pack::socks5_pw, force_proxy_mode); + pr[11] = test_proxy(settings_pack::http, force_proxy_mode | expect_udp_reject); + pr[12] = test_proxy(settings_pack::http_pw, force_proxy_mode | expect_udp_reject); + pr[13] = test_proxy(settings_pack::i2p_proxy, force_proxy_mode); return 0; } diff --git a/test/test_read_piece.cpp b/test/test_read_piece.cpp index 2dbaf65a1..e1eea0779 100644 --- a/test/test_read_piece.cpp +++ b/test/test_read_piece.cpp @@ -45,6 +45,7 @@ enum flags_t void test_read_piece(int flags) { using namespace libtorrent; + namespace lt = libtorrent; fprintf(stderr, "==== TEST READ PIECE =====\n"); @@ -81,13 +82,15 @@ void test_read_piece(int flags) std::vector buf; bencode(std::back_inserter(buf), t.generate()); - boost::intrusive_ptr ti(new torrent_info(&buf[0], buf.size(), ec)); + boost::shared_ptr ti(new torrent_info(&buf[0], buf.size(), ec)); fprintf(stderr, "generated torrent: %s tmp1_read_piece/test_torrent\n" , to_hex(ti->info_hash().to_string()).c_str()); - session ses(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48000, 49000), "0.0.0.0", 0); - ses.set_alert_mask(alert::all_categories); + lt::session ses(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48000, 49000), "0.0.0.0", 0); + settings_pack sett; + sett.set_int(settings_pack::alert_mask, alert::all_categories); + ses.apply_settings(sett); add_torrent_params p; p.save_path = "tmp1_read_piece"; diff --git a/test/test_recheck.cpp b/test/test_recheck.cpp new file mode 100644 index 000000000..7a74ecd2a --- /dev/null +++ b/test/test_recheck.cpp @@ -0,0 +1,103 @@ +/* + +Copyright (c) 2012, 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. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/session_settings.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/thread.hpp" +#include "libtorrent/time.hpp" +#include "libtorrent/file.hpp" +#include "libtorrent/error_code.hpp" +#include +#include + +#include "test.hpp" +#include "setup_transfer.hpp" + +#include +#include + +using namespace libtorrent; +namespace lt = libtorrent; + +const int mask = alert::all_categories & ~(alert::performance_warning | alert::stats_notification); + +void wait_for_complete(lt::session& ses, torrent_handle h) +{ + for (int i = 0; i < 50; ++i) + { + print_alerts(ses, "ses1"); + torrent_status st = h.status(); + fprintf(stderr, "%f %%\n", st.progress_ppm / 10000.f); + if (st.progress_ppm == 1000000) return; + test_sleep(500); + } + TEST_CHECK(false); +} + +int test_main() +{ + error_code ec; + lt::session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48675, 49000), "0.0.0.0", 0, mask); + create_directory("tmp1_recheck", ec); + if (ec) fprintf(stderr, "create_directory: %s\n", ec.message().c_str()); + std::ofstream file("tmp1_recheck/temporary"); + boost::shared_ptr t = ::create_torrent(&file, 4 * 1024 * 1024, 7); + file.close(); + + add_torrent_params param; + param.flags &= ~add_torrent_params::flag_paused; + param.flags &= ~add_torrent_params::flag_auto_managed; + param.ti = t; + param.save_path = "tmp1_recheck"; + param.flags |= add_torrent_params::flag_seed_mode; + torrent_handle tor1 = ses1.add_torrent(param, ec); + if (ec) fprintf(stderr, "add_torrent: %s\n", ec.message().c_str()); + + wait_for_listen(ses1, "ses1"); + + tor1.force_recheck(); + + torrent_status st1 = tor1.status(); + TEST_CHECK(st1.progress_ppm <= 1000000); + wait_for_complete(ses1, tor1); + + tor1.force_recheck(); + + st1 = tor1.status(); + TEST_CHECK(st1.progress_ppm <= 1000000); + wait_for_complete(ses1, tor1); + + return 0; +} diff --git a/test/test_remap_files.cpp b/test/test_remap_files.cpp index 83a5a2a0c..9cc279b9d 100644 --- a/test/test_remap_files.cpp +++ b/test/test_remap_files.cpp @@ -40,12 +40,13 @@ POSSIBILITY OF SUCH DAMAGE. #include using namespace libtorrent; +namespace lt = libtorrent; using boost::tuples::ignore; template -boost::intrusive_ptr clone_ptr(boost::intrusive_ptr const& ptr) +boost::shared_ptr clone_ptr(boost::shared_ptr const& ptr) { - return boost::intrusive_ptr(new T(*ptr)); + return boost::shared_ptr(new T(*ptr)); } int peer_disconnects = 0; @@ -67,8 +68,10 @@ void test_remap_files_gather(storage_mode_t storage_mode = storage_mode_sparse) & ~alert::progress_notification & ~alert::stats_notification; - session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48075, 49000), "0.0.0.0", 0, alert_mask); - session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49075, 50000), "0.0.0.0", 0, alert_mask); + lt::session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48075, 49000) + , "0.0.0.0", 0, alert_mask); + lt::session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49075, 50000) + , "0.0.0.0", 0, alert_mask); torrent_handle tor1; torrent_handle tor2; @@ -105,8 +108,8 @@ void test_remap_files_gather(storage_mode_t storage_mode = storage_mode_sparse) } std::vector buf; bencode(std::back_inserter(buf), ct.generate()); - boost::intrusive_ptr t(new torrent_info(&buf[0], buf.size(), ec)); - boost::intrusive_ptr t2(new torrent_info(&buf[0], buf.size(), ec)); + boost::shared_ptr t(new torrent_info(&buf[0], buf.size(), ec)); + boost::shared_ptr t2(new torrent_info(&buf[0], buf.size(), ec)); // remap the files to a single one file_storage st; @@ -212,15 +215,17 @@ void test_remap_files_scatter(storage_mode_t storage_mode = storage_mode_sparse) & ~alert::progress_notification & ~alert::stats_notification; - session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48075, 49000), "0.0.0.0", 0, alert_mask); - session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49075, 50000), "0.0.0.0", 0, alert_mask); + lt::session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48075, 49000) + , "0.0.0.0", 0, alert_mask); + lt::session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49075, 50000) + , "0.0.0.0", 0, alert_mask); torrent_handle tor1; torrent_handle tor2; create_directory("tmp1_remap2", ec); std::ofstream file("tmp1_remap2/temporary"); - boost::intrusive_ptr t = ::create_torrent(&file, 32 * 1024, 7); + boost::shared_ptr t = ::create_torrent(&file, 32 * 1024, 7); file.close(); file_storage fs; @@ -236,7 +241,7 @@ void test_remap_files_scatter(storage_mode_t storage_mode = storage_mode_sparse) // add up exactly (in case the total size is not divisible by 10). fs.add_file(name, t->total_size() - fs.total_size()); - boost::intrusive_ptr t2 = clone_ptr(t); + boost::shared_ptr t2 = clone_ptr(t); t2->remap_files(fs); @@ -337,8 +342,10 @@ void test_remap_files_prio(storage_mode_t storage_mode = storage_mode_sparse) & ~alert::progress_notification & ~alert::stats_notification; - session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48075, 49000), "0.0.0.0", 0, alert_mask); - session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49075, 50000), "0.0.0.0", 0, alert_mask); + lt::session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48075, 49000) + , "0.0.0.0", 0, alert_mask); + lt::session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49075, 50000) + , "0.0.0.0", 0, alert_mask); torrent_handle tor1; torrent_handle tor2; @@ -368,7 +375,7 @@ void test_remap_files_prio(storage_mode_t storage_mode = storage_mode_sparse) std::vector buf; bencode(std::back_inserter(buf), ct.generate()); - boost::intrusive_ptr t(new torrent_info(&buf[0], buf.size(), ec)); + boost::shared_ptr t(new torrent_info(&buf[0], buf.size(), ec)); int num_new_files = 3; @@ -385,7 +392,7 @@ void test_remap_files_prio(storage_mode_t storage_mode = storage_mode_sparse) // add up exactly (in case the total size is not divisible by 10). fs.add_file(name, t->total_size() - fs.total_size()); - boost::intrusive_ptr t2 = clone_ptr(t); + boost::shared_ptr t2 = clone_ptr(t); t2->remap_files(fs); @@ -467,6 +474,7 @@ int test_main() using namespace libtorrent; error_code ec; + remove_all("tmp1_remap", ec); remove_all("tmp2_remap", ec); @@ -478,7 +486,7 @@ int test_main() remove_all("tmp2_remap2", ec); test_remap_files_scatter(); - + remove_all("tmp1_remap", ec); remove_all("tmp2_remap", ec); remove_all("tmp1_remap2", ec); diff --git a/test/test_rss.cpp b/test/test_rss.cpp index 20ed242eb..4c074aaec 100644 --- a/test/test_rss.cpp +++ b/test/test_rss.cpp @@ -35,9 +35,11 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/fingerprint.hpp" #include "libtorrent/aux_/session_impl.hpp" #include "libtorrent/http_parser.hpp" +#include "libtorrent/settings_pack.hpp" #include "test.hpp" #include "setup_transfer.hpp" // for load_file +#include using namespace libtorrent; @@ -92,9 +94,13 @@ void test_feed(std::string const& filename, rss_expect const& expect) char const header[] = "HTTP/1.1 200 OK\r\n" "\r\n"; - boost::shared_ptr s = boost::shared_ptr(new aux::session_impl( - std::make_pair(100, 200), fingerprint("TT", 0, 0, 0 ,0), NULL, 0)); - s->start_session(); + settings_pack pack; + pack.set_int(settings_pack::max_retry_port_bind, 100); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:100"); + boost::shared_ptr s = boost::make_shared( + fingerprint("TT", 0, 0, 0 ,0)); + settings_pack p; + s->start_session(p); feed_settings sett; sett.auto_download = false; diff --git a/test/test_session.cpp b/test/test_session.cpp index 4a1c20f91..80a0f7d79 100644 --- a/test/test_session.cpp +++ b/test/test_session.cpp @@ -34,13 +34,41 @@ POSSIBILITY OF SUCH DAMAGE. #include "test.hpp" #include "setup_transfer.hpp" +#include "libtorrent/alert_types.hpp" using namespace libtorrent; +namespace lt = libtorrent; int test_main() { - session ses(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48130, 48140), "0.0.0.0", 0); - ses.set_alert_mask(~0); + settings_pack p; + p.set_int(settings_pack::alert_mask, ~0); + lt::session ses(p, fingerprint("LT", 0, 1, 0, 0)); + + settings_pack sett; + sett.set_int(settings_pack::cache_size, 100); + sett.set_int(settings_pack::max_queued_disk_bytes, 1000 * 16 * 1024); + + ses.apply_settings(sett); + + // verify that we get the appropriate performance warning because + // we're allowing a larger queue than we have cache. + + + std::auto_ptr a; + for (;;) + { + a = wait_for_alert(ses, performance_alert::alert_type, "ses1"); + + if (a.get() == NULL) break; + TEST_EQUAL(a.get()->type(), performance_alert::alert_type); + + if (alert_cast(a.get())->warning_code + == performance_alert::too_high_disk_queue_limit) + break; + } + + TEST_CHECK(a.get()); // make sure the destructor waits properly // for the asynchronous call to set the alert diff --git a/test/test_settings_pack.cpp b/test/test_settings_pack.cpp new file mode 100644 index 000000000..6e720944b --- /dev/null +++ b/test/test_settings_pack.cpp @@ -0,0 +1,82 @@ +/* + +Copyright (c) 2012, 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. + +*/ + +#include "test.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/entry.hpp" +#include + +using namespace libtorrent; +using namespace libtorrent::aux; + +int test_main() +{ + settings_pack sp; + + sp.set_int(settings_pack::max_out_request_queue, 1337); + + aux::session_settings sett; + initialize_default_settings(sett); + + entry e; + save_settings_to_dict(sett, e.dict()); + // all default values are supposed to be skipped + // by save_settings + TEST_EQUAL(e.dict().size(), 0); + +#if defined TORRENT_DEBUG && TORRENT_USE_IOSTREAM + if (e.dict().size() > 0) + std::cerr << e << std::endl; +#endif + + apply_pack(&sp, sett); + + TEST_EQUAL(sett.get_int(settings_pack::max_out_request_queue), 1337); + save_settings_to_dict(sett, e.dict()); + TEST_EQUAL(e.dict().size(), 1); + + +#define TEST_NAME(n) \ + TEST_EQUAL(setting_by_name(#n), settings_pack:: n) \ + TEST_CHECK(strcmp(name_for_setting(settings_pack:: n), #n) == 0) + + TEST_NAME(contiguous_recv_buffer); + TEST_NAME(choking_algorithm); + TEST_NAME(seeding_piece_quota); + TEST_NAME(half_open_limit); + TEST_NAME(peer_turnover_interval); + TEST_NAME(mmap_cache); + + return 0; +} + diff --git a/test/test_ssl.cpp b/test/test_ssl.cpp index 064e9afeb..fe9b50c7d 100644 --- a/test/test_ssl.cpp +++ b/test/test_ssl.cpp @@ -113,31 +113,40 @@ void test_ssl(int test_idx, bool use_utp) remove_all("tmp1_ssl", ec); remove_all("tmp2_ssl", ec); - session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48075, 49000), "0.0.0.0", 0, alert_mask); - session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49075, 50000), "0.0.0.0", 0, alert_mask); + int ssl_port = 1024 + rand() % 50000; + settings_pack sett; + sett.set_int(settings_pack::alert_mask, alert_mask); + sett.set_int(settings_pack::max_retry_port_bind, 100); + sett.set_str(settings_pack::listen_interfaces, "0.0.0.0:48075"); + sett.set_bool(settings_pack::enable_incoming_utp, use_utp); + sett.set_bool(settings_pack::enable_outgoing_utp, use_utp); + sett.set_bool(settings_pack::enable_incoming_tcp, !use_utp); + sett.set_bool(settings_pack::enable_outgoing_tcp, !use_utp); + sett.set_bool(settings_pack::enable_dht, false); + sett.set_bool(settings_pack::enable_lsd, false); + sett.set_bool(settings_pack::enable_upnp, false); + sett.set_bool(settings_pack::enable_natpmp, false); + sett.set_int(settings_pack::ssl_listen, ssl_port); + + session ses1(sett, fingerprint("LT", 0, 1, 0, 0), 0); + + if (!test.downloader_has_cert) + // this disables outgoing SSL connections + sett.set_int(settings_pack::ssl_listen, 0); + else + sett.set_int(settings_pack::ssl_listen, ssl_port + 20); + + session ses2(sett, fingerprint("LT", 0, 1, 0, 0), 0); wait_for_listen(ses1, "ses1"); wait_for_listen(ses2, "ses2"); - session_settings sett; - - sett.enable_incoming_utp = use_utp; - sett.enable_outgoing_utp = use_utp; - sett.enable_incoming_tcp = !use_utp; - sett.enable_outgoing_tcp = !use_utp; - - sett.ssl_listen = 1024 + rand() % 50000; - - ses1.set_settings(sett); - sett.ssl_listen += 10; - ses2.set_settings(sett); - torrent_handle tor1; torrent_handle tor2; create_directory("tmp1_ssl", ec); std::ofstream file("tmp1_ssl/temporary"); - boost::intrusive_ptr t = ::create_torrent(&file + boost::shared_ptr t = ::create_torrent(&file , 16 * 1024, 13, false, combine_path("..", combine_path("ssl", "root_ca_cert.pem"))); file.close(); @@ -146,9 +155,6 @@ void test_ssl(int test_idx, bool use_utp) addp.flags &= ~add_torrent_params::flag_paused; addp.flags &= ~add_torrent_params::flag_auto_managed; - wait_for_listen(ses1, "ses1"); - wait_for_listen(ses2, "ses2"); - peer_disconnects = 0; ssl_peer_disconnects = 0; peer_errors = 0; @@ -284,7 +290,7 @@ attack_t attacks[] = const int num_attacks = sizeof(attacks)/sizeof(attacks[0]); bool try_connect(session& ses1, int port - , boost::intrusive_ptr const& t, boost::uint32_t flags) + , boost::shared_ptr const& t, boost::uint32_t flags) { using boost::asio::ssl::context; @@ -483,17 +489,25 @@ void test_malicious_peer() remove_all("tmp3_ssl", ec); // set up session - session ses1(fingerprint("LT", 0, 1, 0, 0) - , std::make_pair(48075, 49000), "0.0.0.0", 0, alert_mask); + + int ssl_port = 1024 + rand() % 50000; + settings_pack sett; + sett.set_int(settings_pack::alert_mask, alert_mask); + sett.set_int(settings_pack::max_retry_port_bind, 100); + sett.set_str(settings_pack::listen_interfaces, "0.0.0.0:48075"); + sett.set_int(settings_pack::ssl_listen, ssl_port); + sett.set_bool(settings_pack::enable_dht, false); + sett.set_bool(settings_pack::enable_lsd, false); + sett.set_bool(settings_pack::enable_upnp, false); + sett.set_bool(settings_pack::enable_natpmp, false); + + session ses1(sett, fingerprint("LT", 0, 1, 0, 0), 0); wait_for_listen(ses1, "ses1"); - session_settings sett; - sett.ssl_listen = 1024 + rand() % 50000; - ses1.set_settings(sett); // create torrent create_directory("tmp3_ssl", ec); std::ofstream file("tmp3_ssl/temporary"); - boost::intrusive_ptr t = ::create_torrent(&file + boost::shared_ptr t = ::create_torrent(&file , 16 * 1024, 13, false, combine_path("..", combine_path("ssl", "root_ca_cert.pem"))); file.close(); @@ -513,12 +527,11 @@ void test_malicious_peer() , combine_path("..", combine_path("ssl", "dhparams.pem")) , "test"); - wait_for_listen(ses1, "ses1"); wait_for_alert(ses1, torrent_finished_alert::alert_type); for (int i = 0; i < num_attacks; ++i) { - bool success = try_connect(ses1, sett.ssl_listen, t, attacks[i].flags); + bool success = try_connect(ses1, ssl_port, t, attacks[i].flags); TEST_EQUAL(attacks[i].expect, success); } } diff --git a/test/test_stat_cache.cpp b/test/test_stat_cache.cpp new file mode 100644 index 000000000..30db026e3 --- /dev/null +++ b/test/test_stat_cache.cpp @@ -0,0 +1,81 @@ +/* + +Copyright (c) 2012, 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. + +*/ + +#include "libtorrent/stat_cache.hpp" +#include "libtorrent/error_code.hpp" +#include "test.hpp" + +using namespace libtorrent; + +int test_main() +{ + error_code ec; + + stat_cache sc; + + sc.init(10); + + for (int i = 0; i < 10; ++i) + { + TEST_CHECK(sc.get_filesize(i) == stat_cache::not_in_cache); + TEST_CHECK(sc.get_filetime(i) == stat_cache::not_in_cache); + } + + // out of bound accesses count as not-in-cache + TEST_CHECK(sc.get_filesize(10) == stat_cache::not_in_cache); + TEST_CHECK(sc.get_filesize(11) == stat_cache::not_in_cache); + + sc.set_error(3); + TEST_CHECK(sc.get_filesize(3) == stat_cache::cache_error); + + sc.set_noexist(3); + TEST_CHECK(sc.get_filesize(3) == stat_cache::no_exist); + + sc.set_cache(3, 101, 5555); + TEST_CHECK(sc.get_filesize(3) == 101); + TEST_CHECK(sc.get_filetime(3) == 5555); + + sc.set_error(11); + TEST_CHECK(sc.get_filesize(10) == stat_cache::not_in_cache); + TEST_CHECK(sc.get_filesize(11) == stat_cache::cache_error); + + sc.set_noexist(13); + TEST_CHECK(sc.get_filesize(12) == stat_cache::not_in_cache); + TEST_CHECK(sc.get_filesize(13) == stat_cache::no_exist); + + sc.set_cache(15, 1000, 3000); + TEST_CHECK(sc.get_filesize(14) == stat_cache::not_in_cache); + TEST_CHECK(sc.get_filesize(15) == 1000); + TEST_CHECK(sc.get_filetime(15) == 3000); + return 0; +} + diff --git a/test/test_storage.cpp b/test/test_storage.cpp index 6ad0dde17..2a92a77a7 100644 --- a/test/test_storage.cpp +++ b/test/test_storage.cpp @@ -39,6 +39,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/create_torrent.hpp" #include "libtorrent/thread.hpp" +#include #include #include "test.hpp" @@ -47,6 +48,7 @@ POSSIBILITY OF SUCH DAMAGE. #include using namespace libtorrent; +namespace lt = libtorrent; const int piece_size = 16 * 1024 * 16; const int block_size = 16 * 1024; @@ -71,17 +73,17 @@ void on_read_piece(int ret, disk_io_job const& j, char const* data, int size) if (ret > 0) TEST_CHECK(std::equal(j.buffer, j.buffer + ret, data)); } -void on_check_resume_data(int ret, disk_io_job const& j, bool* done) +void on_check_resume_data(disk_io_job const* j, bool* done) { - std::cerr << time_now_string() << " on_check_resume_data ret: " << ret; - switch (ret) + std::cerr << time_now_string() << " on_check_resume_data ret: " << j->ret; + switch (j->ret) { case piece_manager::no_error: std::cerr << time_now_string() << " success" << std::endl; break; case piece_manager::fatal_disk_error: - std::cerr << time_now_string() << " disk error: " << j.str - << " file: " << j.error_file << std::endl; + std::cerr << time_now_string() << " disk error: " << j->error.ec.message() + << " file: " << j->error.file << std::endl; break; case piece_manager::need_full_check: std::cerr << time_now_string() << " need full check" << std::endl; @@ -93,343 +95,10 @@ void on_check_resume_data(int ret, disk_io_job const& j, bool* done) *done = true; } -void on_check_files(int ret, disk_io_job const& j, bool* done) +void print_error(char const* call, int ret, storage_error const& ec) { - std::cerr << " on_check_files ret: " << ret; - - switch (ret) - { - case piece_manager::no_error: - std::cerr << time_now_string() << " done" << std::endl; - *done = true; - break; - case piece_manager::fatal_disk_error: - std::cerr << time_now_string() << " disk error: " << j.str - << " file: " << j.error_file << std::endl; - *done = true; - break; - case piece_manager::need_full_check: - std::cerr << time_now_string() << " current slot: " << j.piece - << " have: " << j.offset << std::endl; - break; - case piece_manager::disk_check_aborted: - std::cerr << time_now_string() << " aborted" << std::endl; - *done = true; - break; - } -} - -void on_read(int ret, disk_io_job const& j, bool* done) -{ - std::cerr << time_now_string() << " on_read ret: " << ret << std::endl; - *done = true; - - if (ret < 0) - { - std::cerr << j.error.message() << std::endl; - std::cerr << j.error_file << std::endl; - - } -} - -void on_move_storage(int ret, bool* done, disk_io_job const& j, std::string path) -{ - std::cerr << time_now_string() << " on_move_storage ret: " << ret << " path: " << j.str << std::endl; - TEST_EQUAL(ret, 0); - TEST_EQUAL(j.str, path); - *done = true; -} - -void on_move_storage_exist(int ret, bool* done, disk_io_job const& j, std::string path) -{ - std::cerr << time_now_string() << " on_move_storage_exist ret: " << ret << " path: " << j.str << std::endl; - TEST_EQUAL(ret, piece_manager::file_exist); - TEST_EQUAL(j.str, path); - TEST_EQUAL(j.error, error_code(boost::system::errc::file_exists, get_system_category())); - *done = true; -} - - -void print_error(int ret, boost::scoped_ptr const& s) -{ - std::cerr << time_now_string() << " returned: " << ret - << " error: " << s->error().message() - << " file: " << s->error_file() - << std::endl; -} - -int bufs_size(file::iovec_t const* bufs, int num_bufs); - -// simulate a very slow first read -struct test_storage : storage_interface -{ - test_storage(): m_started(false), m_ready(false) {} - - virtual bool initialize(bool allocate_files) { return true; } - virtual bool has_any_file() { return true; } - virtual void set_file_priority(std::vector const& p) {} - - int write( - const char* buf - , int slot - , int offset - , int size) - { - return size; - } - - int read( - char* buf - , int slot - , int offset - , int size) - { - if (slot == 0 || slot == 5999) - { - libtorrent::mutex::scoped_lock l(m_mutex); - std::cerr << "--- starting job " << slot << " waiting for main thread ---\n" << std::endl; - m_ready = true; - m_ready_condition.signal(l); - - while (!m_started) - m_condition.wait(l); - - m_condition.clear(l); - m_ready_condition.clear(l); - m_ready = false; - m_started = false; - std::cerr << "--- starting ---\n" << std::endl; - } - return size; - } - - size_type physical_offset(int slot, int offset) - { return slot * 16 * 1024 + offset; } - - virtual int sparse_end(int start) const - { return start; } - - virtual int move_storage(std::string const& save_path, int flags) - { return 0; } - - virtual bool verify_resume_data(lazy_entry const& rd, error_code& error) - { return false; } - - virtual bool write_resume_data(entry& rd) const - { return false; } - - virtual bool move_slot(int src_slot, int dst_slot) - { return false; } - - virtual bool swap_slots(int slot1, int slot2) - { return false; } - - virtual bool swap_slots3(int slot1, int slot2, int slot3) - { return false; } - - virtual bool release_files() { return false; } - - virtual bool rename_file(int index, std::string const& new_filename) - { return false; } - - virtual bool delete_files() { return false; } - - virtual ~test_storage() {} - - void wait_for_ready() - { - libtorrent::mutex::scoped_lock l(m_mutex); - while (!m_ready) - m_ready_condition.wait(l); - } - - void start() - { - libtorrent::mutex::scoped_lock l(m_mutex); - m_started = true; - m_condition.signal(l); - } - -private: - event m_ready_condition; - event m_condition; - libtorrent::mutex m_mutex; - bool m_started; - bool m_ready; - -}; - -storage_interface* create_test_storage(file_storage const& fs - , file_storage const* mapped, std::string const& path, file_pool& fp - , std::vector const&) -{ - return new test_storage; -} - -void nop() {} - -int job_counter = 0; -// the number of elevator turns -int turns = 0; -int direction = 0; -int last_job = 0; - -void callback(int ret, disk_io_job const& j) -{ - if (j.piece > last_job && direction <= 0) - { - if (direction == -1) - { - ++turns; - std::cerr << " === ELEVATOR TURN dir: " << direction << std::endl; - } - direction = 1; - } - else if (j.piece < last_job && direction >= 0) - { - if (direction == 1) - { - ++turns; - std::cerr << " === ELEVATOR TURN dir: " << direction << std::endl; - } - direction = -1; - } - last_job = j.piece; - std::cerr << "completed job #" << j.piece << std::endl; - --job_counter; -} - -void add_job(disk_io_thread& dio, int piece, boost::intrusive_ptr& pm) -{ - disk_io_job j; - j.action = disk_io_job::read; - j.storage = pm; - j.piece = piece; - ++job_counter; - dio.add_job(j, boost::bind(&callback, _1, _2)); -} - -void run_elevator_test() -{ - io_service ios; - file_pool fp; - boost::intrusive_ptr ti = ::create_torrent(NULL, 16, 6000); - TEST_CHECK(ti); - - { - error_code ec; - disk_io_thread dio(ios, &nop, fp); - boost::intrusive_ptr pm(new piece_manager(boost::shared_ptr(), ti, "" - , fp, dio, &create_test_storage, storage_mode_sparse, std::vector())); - - // we must disable the read cache in order to - // verify that the elevator algorithm works. - // since any read cache hit will circumvent - // the elevator order - session_settings set; - set.use_read_cache = false; - disk_io_job j; - j.buffer = (char*)new session_settings(set); - j.action = disk_io_job::update_settings; - dio.add_job(j); - - // test the elevator going up - turns = 0; - direction = 1; - last_job = 0; - add_job(dio, 0, pm); // trigger delay in storage - // make sure the job is processed - ((test_storage*)pm->get_storage_impl())->wait_for_ready(); - - boost::uint32_t p = 1234513; - for (int i = 0; i < 100; ++i) - { - p *= 123; - int job = (p % 5998) + 1; - std::cerr << "starting job #" << job << std::endl; - add_job(dio, job, pm); - } - - ((test_storage*)pm->get_storage_impl())->start(); - - for (int i = 0; i < 101; ++i) - { - ios.run_one(ec); - if (ec) std::cerr << "run_one: " << ec.message() << std::endl; - if (job_counter == 0) break; - } - - TEST_CHECK(turns == 0); - TEST_EQUAL(job_counter, 0); - std::cerr << "number of elevator turns: " << turns << std::endl; - - // test the elevator going down - turns = 0; - direction = -1; - last_job = 6000; - add_job(dio, 5999, pm); // trigger delay in storage - // make sure the job is processed - ((test_storage*)pm->get_storage_impl())->wait_for_ready(); - - for (int i = 0; i < 100; ++i) - { - p *= 123; - int job = (p % 5998) + 1; - std::cerr << "starting job #" << job << std::endl; - add_job(dio, job, pm); - } - - ((test_storage*)pm->get_storage_impl())->start(); - - for (int i = 0; i < 101; ++i) - { - ios.run_one(ec); - if (ec) std::cerr << "run_one: " << ec.message() << std::endl; - if (job_counter == 0) break; - } - - TEST_CHECK(turns == 0); - TEST_EQUAL(job_counter, 0); - std::cerr << "number of elevator turns: " << turns << std::endl; - - // test disabling disk-reordering - set.allow_reordered_disk_operations = false; - j.buffer = (char*)new session_settings(set); - j.action = disk_io_job::update_settings; - dio.add_job(j); - - turns = 0; - direction = 0; - add_job(dio, 0, pm); // trigger delay in storage - // make sure the job is processed - ((test_storage*)pm->get_storage_impl())->wait_for_ready(); - - for (int i = 0; i < 100; ++i) - { - p *= 123; - int job = (p % 5998) + 1; - std::cerr << "starting job #" << job << std::endl; - add_job(dio, job, pm); - } - - ((test_storage*)pm->get_storage_impl())->start(); - - for (int i = 0; i < 101; ++i) - { - ios.run_one(ec); - if (ec) std::cerr << "run_one: " << ec.message() << std::endl; - if (job_counter == 0) break; - } - - TEST_EQUAL(job_counter, 0); - std::cerr << "number of elevator turns: " << turns << std::endl; - - // this is not guaranteed, but very very likely - TEST_CHECK(turns > 20); - - dio.abort(); - dio.join(); - } + fprintf(stderr, "%s: %s() returned: %d error: \"%s\" in file: %d operation: %d\n" + , time_now_string(), call, ret, ec.ec.message().c_str(), ec.file, ec.operation); } void run_until(io_service& ios, bool const& done) @@ -448,7 +117,9 @@ void run_until(io_service& ios, bool const& done) } } -void run_storage_tests(boost::intrusive_ptr info +void nop() {} + +void run_storage_tests(boost::shared_ptr info , file_storage& fs , std::string const& test_path , libtorrent::storage_mode_t storage_mode @@ -460,188 +131,106 @@ void run_storage_tests(boost::intrusive_ptr info if (ec) std::cerr << "create_directory '" << combine_path(test_path, "temp_storage") << "': " << ec.message() << std::endl; remove_all(combine_path(test_path, "temp_storage2"), ec); - if (ec) std::cerr << "remove_all '" << combine_path(test_path, "temp_storage2") + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "temp_storage2") << "': " << ec.message() << std::endl; remove_all(combine_path(test_path, "part0"), ec); - if (ec) std::cerr << "remove_all '" << combine_path(test_path, "part0") + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "part0") << "': " << ec.message() << std::endl; int num_pieces = fs.num_pieces(); TEST_CHECK(info->num_pieces() == num_pieces); - session_settings set; - set.disk_io_write_mode = set.disk_io_read_mode - = unbuffered ? session_settings::disable_os_cache_for_aligned_files - : session_settings::enable_os_cache; + aux::session_settings set; + set.set_int(settings_pack::disk_io_write_mode + , unbuffered ? settings_pack::disable_os_cache + : settings_pack::enable_os_cache); + set.set_int(settings_pack::disk_io_read_mode + , unbuffered ? settings_pack::disable_os_cache + : settings_pack::enable_os_cache); char* piece = page_aligned_allocator::malloc(piece_size); { // avoid having two storages use the same files file_pool fp; - disk_buffer_pool dp(16 * 1024); - boost::scoped_ptr s( - default_storage_constructor(fs, 0, test_path, fp, std::vector())); + libtorrent::asio::io_service ios; + disk_buffer_pool dp(16 * 1024, ios, boost::bind(&nop), NULL); + storage_params p; + p.path = test_path; + p.files = &fs; + p.pool = &fp; + p.mode = storage_mode; + boost::scoped_ptr s(new default_storage(p)); s->m_settings = &set; - s->m_disk_pool = &dp; + + storage_error ec; + s->initialize(ec); + TEST_CHECK(!ec); + if (ec) print_error("initialize", 0, ec); int ret = 0; // write piece 1 (in slot 0) - ret = s->write(piece1, 0, 0, half); - if (ret != half) print_error(ret, s); - ret = s->write(piece1 + half, 0, half, half); - if (ret != half) print_error(ret, s); + file::iovec_t iov = { piece1, half}; + ret = s->writev(&iov, 1, 0, 0, 0, ec); + if (ret != half) print_error("writev", ret, ec); + + iov.iov_base = piece1 + half; + iov.iov_len = half; + ret = s->writev(&iov, 1, 0, half, 0, ec); + if (ret != half) print_error("writev", ret, ec); // test unaligned read (where the bytes are aligned) - ret = s->read(piece + 3, 0, 3, piece_size-9); - if (ret != piece_size - 9) print_error(ret, s); + iov.iov_base = piece + 3; + iov.iov_len = piece_size - 9; + ret = s->readv(&iov, 1, 0, 3, 0, ec); + if (ret != piece_size - 9) print_error("readv",ret, ec); TEST_CHECK(std::equal(piece+3, piece + piece_size-9, piece1+3)); // test unaligned read (where the bytes are not aligned) - ret = s->read(piece, 0, 3, piece_size-9); + iov.iov_base = piece; + iov.iov_len = piece_size - 9; + ret = s->readv(&iov, 1, 0, 3, 0, ec); TEST_CHECK(ret == piece_size - 9); - if (ret != piece_size - 9) print_error(ret, s); + if (ret != piece_size - 9) print_error("readv", ret, ec); TEST_CHECK(std::equal(piece, piece + piece_size-9, piece1+3)); // verify piece 1 - ret = s->read(piece, 0, 0, piece_size); + iov.iov_base = piece; + iov.iov_len = piece_size; + ret = s->readv(&iov, 1, 0, 0, 0, ec); TEST_CHECK(ret == piece_size); - if (ret != piece_size) print_error(ret, s); + if (ret != piece_size) print_error("readv", ret, ec); TEST_CHECK(std::equal(piece, piece + piece_size, piece1)); // do the same with piece 0 and 2 (in slot 1 and 2) - ret = s->write(piece0, 1, 0, piece_size); - if (ret != piece_size) print_error(ret, s); - ret = s->write(piece2, 2, 0, piece_size); - if (ret != piece_size) print_error(ret, s); + iov.iov_base = piece0; + iov.iov_len = piece_size; + ret = s->writev(&iov, 1, 1, 0, 0, ec); + if (ret != piece_size) print_error("writev", ret, ec); + + iov.iov_base = piece2; + iov.iov_len = piece_size; + ret = s->writev(&iov, 1, 2, 0, 0, ec); + if (ret != piece_size) print_error("writev", ret, ec); // verify piece 0 and 2 - ret = s->read(piece, 1, 0, piece_size); - if (ret != piece_size) print_error(ret, s); + iov.iov_base = piece; + iov.iov_len = piece_size; + ret = s->readv(&iov, 1, 1, 0, 0, ec); + if (ret != piece_size) print_error("readv", ret, ec); TEST_CHECK(std::equal(piece, piece + piece_size, piece0)); - ret = s->read(piece, 2, 0, piece_size); - if (ret != piece_size) print_error(ret, s); + iov.iov_base = piece; + iov.iov_len = piece_size; + ret = s->readv(&iov, 1, 2, 0, 0, ec); + if (ret != piece_size) print_error("readv", ret, ec); TEST_CHECK(std::equal(piece, piece + piece_size, piece2)); - s->release_files(); + s->release_files(ec); } - // make sure the piece_manager can identify the pieces - { - file_pool fp; - libtorrent::asio::io_service ios; - disk_io_thread io(ios, boost::function(), fp); - boost::shared_ptr dummy(new int); - boost::intrusive_ptr pm = new piece_manager(dummy, info - , test_path, fp, io, default_storage_constructor, storage_mode, std::vector()); - libtorrent::mutex lock; - - error_code ec; - bool done = false; - lazy_entry frd; - pm->async_check_fastresume(&frd, boost::bind(&on_check_resume_data, _1, _2, &done)); - ios.reset(); - run_until(ios, done); - - done = false; - pm->async_check_files(boost::bind(&on_check_files, _1, _2, &done)); - run_until(ios, done); - - done = false; - peer_request r; - r.piece = 0; - r.start = 10; - r.length = 16 * 1024; - pm->async_read(r, boost::bind(&on_read, _1, _2, &done)); - run_until(ios, done); - - // test rename_file - remove(combine_path(test_path, "part0"), ec); - if (ec) std::cerr << "remove: " << ec.message() << std::endl; - remove_all(combine_path(test_path, "test_dir"), ec); - if (ec) std::cerr << "remove: " << ec.message() << std::endl; - TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage", "test1.tmp")))); - TEST_CHECK(!exists(combine_path(test_path, "part0"))); - TEST_CHECK(!exists(combine_path(test_path, combine_path("test_dir", combine_path("subdir", "part0"))))); - - // test that we can create missing directories when we rename a file - done = false; - pm->async_rename_file(0, "test_dir/subdir/part0", boost::bind(&signal_bool, &done, "rename_file")); - run_until(ios, done); - TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage", "test1.tmp")))); - TEST_CHECK(!exists(combine_path(test_path, "temp_storage2"))); - TEST_CHECK(exists(combine_path(test_path, combine_path("test_dir", combine_path("subdir", "part0"))))); - - done = false; - pm->async_rename_file(0, "part0", boost::bind(&signal_bool, &done, "rename_file")); - run_until(ios, done); - TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage", "test1.tmp")))); - TEST_CHECK(!exists(combine_path(test_path, "temp_storage2"))); - TEST_CHECK(exists(combine_path(test_path, "part0"))); - - // test move_storage with two files in the root directory - TEST_CHECK(exists(combine_path(test_path, "temp_storage"))); - - done = false; - pm->async_move_storage(combine_path(test_path, "temp_storage2"), 0 - , boost::bind(&on_move_storage, _1, &done, _2, combine_path(test_path, "temp_storage2"))); - run_until(ios, done); - - if (fs.num_files() > 1) - { - TEST_CHECK(!exists(combine_path(test_path, "temp_storage"))); - TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage2", "temp_storage")))); - } - TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage2", "part0")))); - - done = false; - pm->async_move_storage(test_path, 0, boost::bind(&on_move_storage, _1, &done, _2, test_path)); - run_until(ios, done); - - TEST_CHECK(exists(combine_path(test_path, "part0"))); - TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage2", "temp_storage")))); - TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage2", "part0")))); - - done = false; - pm->async_move_storage(test_path, fail_if_exist, boost::bind(&on_move_storage_exist, _1, &done, _2, test_path)); - run_until(ios, done); - - TEST_CHECK(exists(combine_path(test_path, "part0"))); - TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage2", "temp_storage")))); - TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage2", "part0")))); - - - r.piece = 0; - r.start = 0; - r.length = block_size; - pm->async_read(r, boost::bind(&on_read_piece, _1, _2, piece0, block_size)); - r.piece = 1; - pm->async_read(r, boost::bind(&on_read_piece, _1, _2, piece1, block_size)); - r.piece = 2; - pm->async_read(r, boost::bind(&on_read_piece, _1, _2, piece2, block_size)); - std::cerr << "async_release_files" << std::endl; - done = false; - pm->async_release_files(boost::bind(&signal_bool, &done, "async_release_files")); - run_until(ios, done); - - std::cerr << "async_rename_file" << std::endl; - done = false; - pm->async_rename_file(0, "temp_storage/test1.tmp", boost::bind(&signal_bool, &done, "rename_file")); - run_until(ios, done); - - TEST_CHECK(!exists(combine_path(test_path, "part0"))); - TEST_CHECK(exists(combine_path(test_path, "temp_storage/test1.tmp"))); - - io.abort(); - io.join(); - remove_all(combine_path(test_path, "temp_storage2"), ec); - if (ec) std::cerr << "remove_all '" << combine_path(test_path, "temp_storage2") - << "': " << ec.message() << std::endl; - remove_all(combine_path(test_path, "part0"), ec); - if (ec) std::cerr << "remove_all '" << combine_path(test_path, "part0") - << "': " << ec.message() << std::endl; - } page_aligned_allocator::free(piece); } @@ -650,7 +239,8 @@ void test_remove(std::string const& test_path, bool unbuffered) file_storage fs; error_code ec; remove_all(combine_path(test_path, "temp_storage"), ec); - if (ec) std::cerr << "remove_all '" << combine_path(test_path, "temp_storage") + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "temp_storage") << "': " << ec.message() << std::endl; TEST_CHECK(!exists(combine_path(test_path, "temp_storage"))); fs.add_file("temp_storage/test1.tmp", 8); @@ -666,44 +256,54 @@ void test_remove(std::string const& test_path, bool unbuffered) std::vector buf; bencode(std::back_inserter(buf), t.generate()); - boost::intrusive_ptr info(new torrent_info(&buf[0], buf.size(), ec)); + boost::shared_ptr info(boost::make_shared(&buf[0], buf.size(), boost::ref(ec), 0)); - session_settings set; - set.disk_io_write_mode = set.disk_io_read_mode - = unbuffered ? session_settings::disable_os_cache_for_aligned_files - : session_settings::enable_os_cache; + aux::session_settings set; + set.set_int(settings_pack::disk_io_write_mode + , unbuffered ? settings_pack::disable_os_cache + : settings_pack::enable_os_cache); + set.set_int(settings_pack::disk_io_read_mode + , unbuffered ? settings_pack::disable_os_cache + : settings_pack::enable_os_cache); file_pool fp; - disk_buffer_pool dp(16 * 1024); - boost::scoped_ptr s( - default_storage_constructor(fs, 0, test_path, fp, std::vector())); - s->m_settings = &set; - s->m_disk_pool = &dp; + io_service ios; + disk_buffer_pool dp(16 * 1024, ios, boost::bind(&nop), NULL); - if (s->error()) - { - TEST_ERROR(s->error().message().c_str()); - fprintf(stderr, "default_storage::constructor %s: %s\n", s->error().message().c_str(), s->error_file().c_str()); - } + storage_params p; + p.files = &fs; + p.pool = &fp; + p.path = test_path; + p.mode = storage_mode_allocate; + boost::scoped_ptr s(new default_storage(p)); + s->m_settings = &set; // allocate the files and create the directories - s->initialize(true); - if (s->error()) + storage_error se; + s->initialize(se); + if (se) { - TEST_ERROR(s->error().message().c_str()); - fprintf(stderr, "default_storage::initialize %s: %s\n", s->error().message().c_str(), s->error_file().c_str()); + TEST_ERROR(se.ec.message().c_str()); + fprintf(stderr, "default_storage::initialize %s: %d\n", se.ec.message().c_str(), int(se.file)); } + // directories are not created up-front, unless they contain + // an empty file (all of which are created up-front, along with + // all required directories) // files are created on first write TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage" , combine_path("_folder3", combine_path("subfolder", "test5.tmp")))))); + + // this directory and file is created up-front because it's an empty file TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage" , combine_path("folder2", "test3.tmp"))))); + + // this isn't TEST_CHECK(!exists(combine_path(test_path, combine_path("temp_storage" , combine_path("folder1", "test2.tmp"))))); file::iovec_t b = {&buf[0], 4}; - s->writev(&b, 2, 0, 1); + s->writev(&b, 1, 2, 0, 0, se); TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage" , combine_path("folder1", "test2.tmp"))))); @@ -714,7 +314,7 @@ void test_remove(std::string const& test_path, bool unbuffered) , combine_path("folder1", "test2.tmp"))), &st, ec); TEST_EQUAL(st.file_size, 8); - s->writev(&b, 4, 0, 1); + s->writev(&b, 1, 4, 0, 0, se); TEST_CHECK(exists(combine_path(test_path, combine_path("temp_storage" , combine_path("_folder3", combine_path("subfolder", "test5.tmp")))))); @@ -722,46 +322,29 @@ void test_remove(std::string const& test_path, bool unbuffered) , combine_path("_folder3", "test5.tmp"))), &st, ec); TEST_EQUAL(st.file_size, 8); - s->delete_files(); + s->delete_files(se); + if (se) print_error("delete_files", 0, se.ec); - if (s->error()) + if (se) { - TEST_ERROR(s->error().message().c_str()); - fprintf(stderr, "default_storage::delete_files %s: %s\n", s->error().message().c_str(), s->error_file().c_str()); + TEST_ERROR(se.ec.message().c_str()); + fprintf(stderr, "default_storage::delete_files %s: %d\n", se.ec.message().c_str(), int(se.file)); } TEST_CHECK(!exists(combine_path(test_path, "temp_storage"))); } -namespace -{ - void check_files_fill_array(int ret, disk_io_job const& j, bool* array, bool* done) - { - std::cerr << time_now_string() << " check_files_fill_array ret: " << ret - << " piece: " << j.piece - << " have: " << j.offset - << " str: " << j.str - << " e: " << j.error.message() - << std::endl; - - if (j.offset >= 0) array[j.offset] = true; - if (ret != piece_manager::need_full_check) - { - *done = true; - } - } -} - void test_check_files(std::string const& test_path , libtorrent::storage_mode_t storage_mode , bool unbuffered) { - boost::intrusive_ptr info; + boost::shared_ptr info; error_code ec; const int piece_size = 16 * 1024; remove_all(combine_path(test_path, "temp_storage"), ec); - if (ec) std::cerr << "remove_all '" << combine_path(test_path, "temp_storage") + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "temp_storage") << "': " << ec.message() << std::endl; file_storage fs; fs.add_file("temp_storage/test1.tmp", piece_size); @@ -795,33 +378,31 @@ void test_check_files(std::string const& test_path std::vector buf; bencode(std::back_inserter(buf), t.generate()); - info = new torrent_info(&buf[0], buf.size(), ec); + info = boost::make_shared(&buf[0], buf.size(), boost::ref(ec), 0); + aux::session_settings set; file_pool fp; libtorrent::asio::io_service ios; - disk_io_thread io(ios, boost::function(), fp); - boost::shared_ptr dummy(new int); - boost::intrusive_ptr pm = new piece_manager(dummy, info - , test_path, fp, io, default_storage_constructor, storage_mode, std::vector()); + disk_io_thread io(ios, NULL, NULL); + disk_buffer_pool dp(16 * 1024, ios, boost::bind(&nop), NULL); + storage_params p; + p.files = &fs; + p.path = test_path; + p.pool = &fp; + p.mode = storage_mode; + + boost::shared_ptr dummy; + boost::shared_ptr pm = boost::make_shared(new default_storage(p), dummy, &fs); libtorrent::mutex lock; bool done = false; lazy_entry frd; - pm->async_check_fastresume(&frd, boost::bind(&on_check_resume_data, _1, _2, &done)); + io.async_check_fastresume(pm.get(), &frd, boost::bind(&on_check_resume_data, _1, &done)); + io.submit_jobs(); ios.reset(); run_until(ios, done); - bool pieces[4] = {false, false, false, false}; - done = false; - pm->async_check_files(boost::bind(&check_files_fill_array, _1, _2, pieces, &done)); - run_until(ios, done); - - TEST_EQUAL(pieces[0], true); - TEST_EQUAL(pieces[1], false); - TEST_EQUAL(pieces[2], false); - TEST_EQUAL(pieces[3], true); - io.abort(); - io.join(); + io.set_num_threads(0); } #ifdef TORRENT_NO_DEPRECATE @@ -832,12 +413,13 @@ void run_test(std::string const& test_path, bool unbuffered) { std::cerr << "\n=== " << test_path << " ===\n" << std::endl; - boost::intrusive_ptr info; + boost::shared_ptr info; { error_code ec; remove_all(combine_path(test_path, "temp_storage"), ec); - if (ec) std::cerr << "remove_all '" << combine_path(test_path, "temp_storage") + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "temp_storage") << "': " << ec.message() << std::endl; file_storage fs; fs.add_file("temp_storage/test1.tmp", 17); @@ -865,7 +447,7 @@ void run_test(std::string const& test_path, bool unbuffered) std::vector buf; bencode(std::back_inserter(buf), t.generate()); - info = new torrent_info(&buf[0], buf.size(), ec); + info = boost::make_shared(&buf[0], buf.size(), boost::ref(ec), 0); std::cerr << "=== test 1 === " << (unbuffered?"unbuffered":"buffered") << std::endl; // run_storage_tests writes piece 0, 1 and 2. not 3 @@ -873,6 +455,7 @@ void run_test(std::string const& test_path, bool unbuffered) // make sure the files have the correct size std::string base = combine_path(test_path, "temp_storage"); + fprintf(stderr, "base = \"%s\"\n", base.c_str()); TEST_EQUAL(file_size(combine_path(base, "test1.tmp")), 17); TEST_EQUAL(file_size(combine_path(base, "test2.tmp")), 612); @@ -887,7 +470,8 @@ void run_test(std::string const& test_path, bool unbuffered) printf("file: %d expected: %d last_file_size: %d, piece_size: %d\n", int(file_size(combine_path(base, "test7.tmp"))), int(last_file_size - piece_size), last_file_size, piece_size); TEST_EQUAL(file_size(combine_path(base, "test7.tmp")), last_file_size - piece_size); remove_all(combine_path(test_path, "temp_storage"), ec); - if (ec) std::cerr << "remove_all '" << combine_path(test_path, "temp_storage") + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "temp_storage") << "': " << ec.message() << std::endl; } @@ -905,7 +489,7 @@ void run_test(std::string const& test_path, bool unbuffered) std::vector buf; bencode(std::back_inserter(buf), t.generate()); - info = new torrent_info(&buf[0], buf.size(), ec); + info = boost::make_shared(&buf[0], buf.size(), boost::ref(ec), 0); std::cerr << "=== test 3 ===" << std::endl; @@ -913,7 +497,8 @@ void run_test(std::string const& test_path, bool unbuffered) TEST_EQUAL(file_size(combine_path(test_path, combine_path("temp_storage", "test1.tmp"))), piece_size * 3); remove_all(combine_path(test_path, "temp_storage"), ec); - if (ec) std::cerr << "remove_all '" << combine_path(test_path, "temp_storage") + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "temp_storage") << "': " << ec.message() << std::endl; // ============================================== @@ -926,7 +511,8 @@ void run_test(std::string const& test_path, bool unbuffered) TEST_EQUAL(file_size(combine_path(test_path, combine_path("temp_storage", "test1.tmp"))), 3 * piece_size); remove_all(combine_path(test_path, "temp_storage"), ec); - if (ec) std::cerr << "remove_all '" << combine_path(test_path, "temp_storage") + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "temp_storage") << "': " << ec.message() << std::endl; } @@ -948,13 +534,14 @@ void test_fastresume(std::string const& test_path) error_code ec; std::cout << "\n\n=== test fastresume ===" << std::endl; remove_all(combine_path(test_path, "tmp1"), ec); - if (ec) std::cerr << "remove_all '" << combine_path(test_path, "tmp1") + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "tmp1") << "': " << ec.message() << std::endl; create_directory(combine_path(test_path, "tmp1"), ec); if (ec) std::cerr << "create_directory '" << combine_path(test_path, "tmp1") << "': " << ec.message() << std::endl; std::ofstream file(combine_path(test_path, "tmp1/temporary").c_str()); - boost::intrusive_ptr t = ::create_torrent(&file); + boost::shared_ptr t = ::create_torrent(&file); file.close(); TEST_CHECK(exists(combine_path(test_path, "tmp1/temporary"))); if (!exists(combine_path(test_path, "tmp1/temporary"))) @@ -962,13 +549,14 @@ void test_fastresume(std::string const& test_path) entry resume; { - session ses(fingerprint(" ", 0,0,0,0), 0); - ses.set_alert_mask(alert::all_categories); + settings_pack pack; + pack.set_int(settings_pack::alert_mask, alert::all_categories); + lt::session ses(pack, fingerprint(" ", 0,0,0,0)); error_code ec; add_torrent_params p; - p.ti = new torrent_info(*t); + p.ti = boost::make_shared(boost::cref(*t)); p.save_path = combine_path(test_path, "tmp1"); p.storage_mode = storage_mode_compact; torrent_handle h = ses.add_torrent(p, ec); @@ -1000,21 +588,24 @@ void test_fastresume(std::string const& test_path) std::auto_ptr ra = wait_for_alert(ses, save_resume_data_alert::alert_type); TEST_CHECK(ra.get()); if (ra.get()) resume = *alert_cast(ra.get())->resume_data; - ses.remove_torrent(h, session::delete_files); + ses.remove_torrent(h, lt::session::delete_files); + std::auto_ptr da = wait_for_alert(ses, torrent_deleted_alert::alert_type); } TEST_CHECK(!exists(combine_path(test_path, combine_path("tmp1", "temporary")))); if (exists(combine_path(test_path, combine_path("tmp1", "temporary")))) return; std::cerr << resume.to_string() << "\n"; + TEST_CHECK(resume.dict().find("file sizes") != resume.dict().end()); // make sure the fast resume check fails! since we removed the file { - session ses(fingerprint(" ", 0,0,0,0), 0); - ses.set_alert_mask(alert::all_categories); + settings_pack pack; + pack.set_int(settings_pack::alert_mask, alert::all_categories); + lt::session ses(pack, fingerprint(" ", 0,0,0,0)); add_torrent_params p; - p.ti = new torrent_info(*t); + p.ti = boost::make_shared(boost::cref(*t)); p.save_path = combine_path(test_path, "tmp1"); p.storage_mode = storage_mode_compact; bencode(std::back_inserter(p.resume_data), resume); @@ -1022,7 +613,9 @@ void test_fastresume(std::string const& test_path) std::auto_ptr a = ses.pop_alert(); ptime end = time_now() + seconds(20); - while (a.get() == 0 || alert_cast(a.get()) == 0) + while (time_now() < end + && (a.get() == 0 + || alert_cast(a.get()) == 0)) { if (ses.wait_for_alert(end - time_now()) == 0) { @@ -1037,7 +630,8 @@ void test_fastresume(std::string const& test_path) TEST_CHECK(alert_cast(a.get()) != 0); } remove_all(combine_path(test_path, "tmp1"), ec); - if (ec) std::cerr << "remove_all '" << combine_path(test_path, "tmp1") + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "tmp1") << "': " << ec.message() << std::endl; } @@ -1052,23 +646,24 @@ void test_rename_file_in_fastresume(std::string const& test_path) error_code ec; std::cout << "\n\n=== test rename file in fastresume ===" << std::endl; remove_all(combine_path(test_path, "tmp2"), ec); - if (ec) std::cerr << "remove_all '" << combine_path(test_path, "tmp2") + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "tmp2") << "': " << ec.message() << std::endl; create_directory(combine_path(test_path, "tmp2"), ec); if (ec) std::cerr << "create_directory: " << ec.message() << std::endl; std::ofstream file(combine_path(test_path, "tmp2/temporary").c_str()); - boost::intrusive_ptr t = ::create_torrent(&file); + boost::shared_ptr t = ::create_torrent(&file); file.close(); TEST_CHECK(exists(combine_path(test_path, "tmp2/temporary"))); entry resume; { - session ses(fingerprint(" ", 0,0,0,0), 0); - ses.set_alert_mask(alert::all_categories); - + settings_pack pack; + pack.set_int(settings_pack::alert_mask, alert::all_categories); + lt::session ses(pack, fingerprint(" ", 0,0,0,0)); add_torrent_params p; - p.ti = new torrent_info(*t); + p.ti = boost::make_shared(boost::cref(*t)); p.save_path = combine_path(test_path, "tmp2"); p.storage_mode = storage_mode_compact; torrent_handle h = ses.add_torrent(p, ec); @@ -1076,10 +671,11 @@ void test_rename_file_in_fastresume(std::string const& test_path) h.rename_file(0, "testing_renamed_files"); std::cout << "renaming file" << std::endl; bool renamed = false; - for (int i = 0; i < 100; ++i) + for (int i = 0; i < 10; ++i) { if (print_alerts(ses, "ses", true, true, true, &got_file_rename_alert)) renamed = true; torrent_status s = h.status(); + if (s.state == torrent_status::downloading) break; if (s.state == torrent_status::seeding && renamed) break; test_sleep(100); } @@ -1096,16 +692,18 @@ void test_rename_file_in_fastresume(std::string const& test_path) TEST_CHECK(!exists(combine_path(test_path, "tmp2/temporary"))); TEST_CHECK(exists(combine_path(test_path, "tmp2/testing_renamed_files"))); TEST_CHECK(resume.dict().find("mapped_files") != resume.dict().end()); + TEST_CHECK(resume.dict().find("file sizes") != resume.dict().end()); std::cerr << resume.to_string() << "\n"; // make sure the fast resume check succeeds, even though we renamed the file { - session ses(fingerprint(" ", 0,0,0,0), 0); - ses.set_alert_mask(alert::all_categories); + settings_pack pack; + pack.set_int(settings_pack::alert_mask, alert::all_categories); + lt::session ses(pack, fingerprint(" ", 0,0,0,0)); add_torrent_params p; - p.ti = new torrent_info(*t); + p.ti = boost::make_shared(boost::cref(*t)); p.save_path = combine_path(test_path, "tmp2"); p.storage_mode = storage_mode_compact; bencode(std::back_inserter(p.resume_data), resume); @@ -1133,15 +731,13 @@ void test_rename_file_in_fastresume(std::string const& test_path) std::cerr << resume.to_string() << "\n"; remove_all(combine_path(test_path, "tmp2"), ec); - if (ec) std::cerr << "remove_all '" << combine_path(test_path, "tmp2") + if (ec && ec != boost::system::errc::no_such_file_or_directory) + std::cerr << "remove_all '" << combine_path(test_path, "tmp2") << "': " << ec.message() << std::endl; } int test_main() { - - run_elevator_test(); - // initialize test pieces for (char* p = piece0, *end(piece0 + piece_size); p < end; ++p) *p = random_byte(); @@ -1170,6 +766,7 @@ int test_main() std::for_each(test_paths.begin(), test_paths.end(), boost::bind(&test_fastresume, _1)); std::for_each(test_paths.begin(), test_paths.end(), boost::bind(&test_rename_file_in_fastresume, _1)); + std::for_each(test_paths.begin(), test_paths.end(), boost::bind(&run_test, _1, true)); std::for_each(test_paths.begin(), test_paths.end(), boost::bind(&run_test, _1, false)); diff --git a/test/test_string.cpp b/test/test_string.cpp index 12df3aae6..65503e9f5 100644 --- a/test/test_string.cpp +++ b/test/test_string.cpp @@ -208,6 +208,50 @@ int test_main() TEST_CHECK(url_has_argument("http://127.0.0.1/test?foo=24&bar=23&a=e", "a") == "e"); TEST_CHECK(url_has_argument("http://127.0.0.1/test?foo=24&bar=23&a=e", "b") == ""); + // resolve_file_url + +#ifdef TORRENT_WINDOWS + std::string p = "c:/blah/foo/bar\\"; + convert_path_to_windows(p); + TEST_EQUAL(p, "c:\\blah\\foo\\bar\\"); + TEST_EQUAL(resolve_file_url("file:///c:/blah/foo/bar"), "c:\\blah\\foo\\bar"); + TEST_EQUAL(resolve_file_url("file:///c:/b%3fah/foo/bar"), "c:\\b?ah\\foo\\bar"); + TEST_EQUAL(resolve_file_url("file://\\c:\\b%3fah\\foo\\bar"), "c:\\b?ah\\foo\\bar"); +#else + TEST_EQUAL(resolve_file_url("file:///c/blah/foo/bar"), "/c/blah/foo/bar"); + TEST_EQUAL(resolve_file_url("file:///c/b%3fah/foo/bar"), "/c/b?ah/foo/bar"); +#endif + + std::vector list; + parse_comma_separated_string(" a,b, c, d ,e \t,foobar\n\r,[::1]", list); + TEST_EQUAL(list.size(), 7); + TEST_EQUAL(list[0], "a"); + TEST_EQUAL(list[1], "b"); + TEST_EQUAL(list[2], "c"); + TEST_EQUAL(list[3], "d"); + TEST_EQUAL(list[4], "e"); + TEST_EQUAL(list[5], "foobar"); + TEST_EQUAL(list[6], "[::1]"); + + std::vector > list2; + parse_comma_separated_string_port(" a:4,b:35, c : 1000, d: 351 ,e \t:42,foobar:1337\n\r,[2001::1]:6881", list2); + TEST_EQUAL(list2.size(), 7); + TEST_EQUAL(list2[0].first, "a"); + TEST_EQUAL(list2[1].first, "b"); + TEST_EQUAL(list2[2].first, "c"); + TEST_EQUAL(list2[3].first, "d"); + TEST_EQUAL(list2[4].first, "e"); + TEST_EQUAL(list2[5].first, "foobar"); + TEST_EQUAL(list2[6].first, "2001::1"); + + TEST_EQUAL(list2[0].second, 4); + TEST_EQUAL(list2[1].second, 35); + TEST_EQUAL(list2[2].second, 1000); + TEST_EQUAL(list2[3].second, 351); + TEST_EQUAL(list2[4].second, 42); + TEST_EQUAL(list2[5].second, 1337); + TEST_EQUAL(list2[6].second, 6881); + // test string_tokenize char test_tokenize[] = "a b c \"foo bar\" d\ne f"; diff --git a/test/test_swarm.cpp b/test/test_swarm.cpp index b2565bbd2..05fc2a192 100644 --- a/test/test_swarm.cpp +++ b/test/test_swarm.cpp @@ -42,9 +42,29 @@ POSSIBILITY OF SUCH DAMAGE. #include "setup_transfer.hpp" #include -void test_swarm(bool super_seeding = false, bool strict = false, bool seed_mode = false, bool time_critical = false) +enum test_flags_t +{ + super_seeding = 1, + strict_super_seeding = 2, + seed_mode = 4, + time_critical = 8, + suggest = 16, + explicit_cache = 32 +}; + +void test_swarm(int flags = 0) { using namespace libtorrent; + namespace lt = libtorrent; + + fprintf(stderr, "\n\n ==== TEST SWARM === %s%s%s%s%s%s ===\n\n\n" + , (flags & super_seeding) ? "super-seeding ": "" + , (flags & strict_super_seeding) ? "strict-super-seeding ": "" + , (flags & seed_mode) ? "seed-mode ": "" + , (flags & time_critical) ? "time-critical ": "" + , (flags & suggest) ? "suggest ": "" + , (flags & explicit_cache) ? "explicit-cache ": "" + ); // in case the previous run was terminated error_code ec; @@ -59,53 +79,62 @@ void test_swarm(bool super_seeding = false, bool strict = false, bool seed_mode session_proxy p2; session_proxy p3; - session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48000, 49000), "0.0.0.0", 0); - session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49000, 50000), "0.0.0.0", 0); - session ses3(fingerprint("LT", 0, 1, 0, 0), std::make_pair(50000, 51000), "0.0.0.0", 0); + int mask = alert::all_categories + & ~(alert::progress_notification + | alert::performance_warning + | alert::stats_notification); + + settings_pack pack; + pack.set_int(settings_pack::alert_mask, mask); + pack.set_bool(settings_pack::allow_multiple_connections_per_ip, true); + + if (flags & strict_super_seeding) + pack.set_bool(settings_pack::strict_super_seeding, true); + + if (flags & suggest) + pack.set_int(settings_pack::suggest_mode, settings_pack::suggest_read_cache); + + if (flags & explicit_cache) + pack.set_bool(settings_pack::explicit_read_cache, true); + + if (flags & explicit_cache) + { + pack.set_bool(settings_pack::explicit_read_cache, true); + pack.set_int(settings_pack::explicit_cache_interval, 5); + } // this is to avoid everything finish from a single peer // immediately. To make the swarm actually connect all // three peers before finishing. float rate_limit = 100000; - session_settings settings; - settings.allow_multiple_connections_per_ip = true; - settings.ignore_limits_on_local_network = false; - settings.strict_super_seeding = strict; + pack.set_int(settings_pack::upload_rate_limit, rate_limit); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48000"); + pack.set_int(settings_pack::max_retry_port_bind, 1000); - settings.upload_rate_limit = rate_limit; - ses1.set_settings(settings); + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_forced); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_forced); - settings.download_rate_limit = rate_limit / 2; - settings.upload_rate_limit = rate_limit; - ses2.set_settings(settings); - ses3.set_settings(settings); + lt::session ses1(pack, fingerprint("LT", 0, 1, 0, 0)); -#ifndef TORRENT_DISABLE_ENCRYPTION - pe_settings pes; - pes.out_enc_policy = pe_settings::forced; - pes.in_enc_policy = pe_settings::forced; - ses1.set_pe_settings(pes); - ses2.set_pe_settings(pes); - ses3.set_pe_settings(pes); -#endif + ses1.apply_settings(pack); + + pack.set_int(settings_pack::download_rate_limit, rate_limit / 2); + pack.set_int(settings_pack::upload_rate_limit, rate_limit); + lt::session ses2(pack, fingerprint("LT", 0, 1, 0, 0)); + lt::session ses3(pack, fingerprint("LT", 0, 1, 0, 0)); torrent_handle tor1; torrent_handle tor2; torrent_handle tor3; add_torrent_params p; - p.flags |= add_torrent_params::flag_seed_mode; + if (flags & seed_mode) p.flags |= add_torrent_params::flag_seed_mode; // test using piece sizes smaller than 16kB boost::tie(tor1, tor2, tor3) = setup_transfer(&ses1, &ses2, &ses3, true - , false, true, "_swarm", 8 * 1024, 0, super_seeding, &p); + , false, true, "_swarm", 8 * 1024, 0, flags & super_seeding, &p); - int mask = alert::all_categories & ~(alert::progress_notification | alert::performance_warning | alert::stats_notification); - ses1.set_alert_mask(mask); - ses2.set_alert_mask(mask); - ses3.set_alert_mask(mask); - - if (time_critical) + if (flags & time_critical) { tor2.set_piece_deadline(2, 0); tor2.set_piece_deadline(5, 1000); @@ -157,9 +186,9 @@ void test_swarm(bool super_seeding = false, bool strict = false, bool seed_mode if (tor2.status().is_seeding && tor3.status().is_seeding) std::cerr << "done\n"; // make sure the files are deleted - ses1.remove_torrent(tor1, session::delete_files); - ses2.remove_torrent(tor2, session::delete_files); - ses3.remove_torrent(tor3, session::delete_files); + ses1.remove_torrent(tor1, lt::session::delete_files); + ses2.remove_torrent(tor2, lt::session::delete_files); + ses3.remove_torrent(tor3, lt::session::delete_files); std::auto_ptr a = ses1.pop_alert(); ptime end = time_now() + seconds(20); @@ -212,18 +241,24 @@ int test_main() using namespace libtorrent; // with time critical pieces - test_swarm(false, false, false, true); + test_swarm(time_critical); // with seed mode - test_swarm(false, false, true); + test_swarm(seed_mode); test_swarm(); // with super seeding - test_swarm(true); + test_swarm(super_seeding); // with strict super seeding - test_swarm(true, true); + test_swarm(super_seeding | strict_super_seeding); + + // with suggest pieces + test_swarm(suggest); + + // test explicit cache + test_swarm(suggest | explicit_cache); return 0; } diff --git a/test/test_tailqueue.cpp b/test/test_tailqueue.cpp new file mode 100644 index 000000000..d929d0b11 --- /dev/null +++ b/test/test_tailqueue.cpp @@ -0,0 +1,162 @@ +/* + +Copyright (c) 2012, 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. + +*/ + +#include "test.hpp" +#include "libtorrent/tailqueue.hpp" + +using namespace libtorrent; + +struct test_node : tailqueue_node +{ + test_node(char n) : name(n) {} + char name; +}; + +void check_chain(tailqueue& chain, char const* expected) +{ + tailqueue_iterator i = chain.iterate(); + while (i.get()) + { + TEST_EQUAL(((test_node*)i.get())->name, *expected); + i.next(); + ++expected; + } + TEST_EQUAL(expected[0], 0); +} + +void free_chain(tailqueue& q) +{ + test_node* chain = (test_node*)q.get_all(); + while(chain) + { + test_node* del = (test_node*)chain; + chain = (test_node*)chain->next; + delete del; + } +} + +void build_chain(tailqueue& q, char const* str) +{ + free_chain(q); + + char const* expected = str; + + while(*str) + { + q.push_back(new test_node(*str)); + ++str; + } + check_chain(q, expected); +} + +int test_main() +{ + tailqueue t1; + tailqueue t2; + + // test prepend + build_chain(t1, "abcdef"); + build_chain(t2, "12345"); + + t1.prepend(t2); + check_chain(t1, "12345abcdef"); + check_chain(t2, ""); + + // test append + build_chain(t1, "abcdef"); + build_chain(t2, "12345"); + + t1.append(t2); + check_chain(t1, "abcdef12345"); + check_chain(t2, ""); + + // test swap + build_chain(t1, "abcdef"); + build_chain(t2, "12345"); + + t1.swap(t2); + + check_chain(t1, "12345"); + check_chain(t2, "abcdef"); + + // test pop_front + build_chain(t1, "abcdef"); + + delete t1.pop_front(); + + check_chain(t1, "bcdef"); + + // test push_back + + build_chain(t1, "abcdef"); + t1.push_back(new test_node('1')); + check_chain(t1, "abcdef1"); + + // test push_front + + build_chain(t1, "abcdef"); + t1.push_front(new test_node('1')); + check_chain(t1, "1abcdef"); + + // test size + + build_chain(t1, "abcdef"); + TEST_EQUAL(t1.size(), 6); + + // test empty + + free_chain(t1); + TEST_EQUAL(t1.empty(), true); + build_chain(t1, "abcdef"); + TEST_EQUAL(t1.empty(), false); + + // test get_all + build_chain(t1, "abcdef"); + test_node* n = (test_node*)t1.get_all(); + TEST_EQUAL(t1.empty(), true); + TEST_EQUAL(t1.size(), 0); + + char const* expected = "abcdef"; + while (n) + { + test_node* del = n; + TEST_EQUAL(n->name, *expected); + n = (test_node*)n->next; + ++expected; + delete del; + } + + free_chain(t1); + free_chain(t2); + return 0; +} + diff --git a/test/test_threads.cpp b/test/test_threads.cpp index 74ed3ed96..ee90ce0e1 100644 --- a/test/test_threads.cpp +++ b/test/test_threads.cpp @@ -33,10 +33,9 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include "libtorrent/thread.hpp" -#include +#include "libtorrent/atomic.hpp" #include "test.hpp" -using boost::detail::atomic_count; using namespace libtorrent; void fun(condition_variable* s, libtorrent::mutex* m, int* waiting, int i) diff --git a/test/test_torrent.cpp b/test/test_torrent.cpp index b3b303beb..f5b1bc9b3 100644 --- a/test/test_torrent.cpp +++ b/test/test_torrent.cpp @@ -38,17 +38,22 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/alert_types.hpp" #include "libtorrent/thread.hpp" #include +#include #include #include "test.hpp" #include "setup_transfer.hpp" using namespace libtorrent; +namespace lt = libtorrent; -void test_running_torrent(boost::intrusive_ptr info, size_type file_size) +void test_running_torrent(boost::shared_ptr info, size_type file_size) { - session ses(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48130, 48140), "0.0.0.0", 0); - ses.set_alert_mask(alert::storage_notification); + settings_pack pack; + pack.set_int(settings_pack::alert_mask, alert::storage_notification); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:48130"); + pack.set_int(settings_pack::max_retry_port_bind, 10); + lt::session ses(pack, fingerprint("LT", 0, 1, 0, 0)); std::vector zeroes; zeroes.resize(1000, 0); @@ -62,43 +67,56 @@ void test_running_torrent(boost::intrusive_ptr info, size_type fil error_code ec; torrent_handle h = ses.add_torrent(p, ec); + if (ec) + { + fprintf(stderr, "add_torrent: %s\n", ec.message().c_str()); + return; + } std::vector ones(info->num_files(), 1); h.prioritize_files(ones); - test_sleep(500); +// test_sleep(500); torrent_status st = h.status(); - std::cout << "total_wanted: " << st.total_wanted << " : " << file_size * 3 << std::endl; - TEST_CHECK(st.total_wanted == file_size * 3); - std::cout << "total_wanted_done: " << st.total_wanted_done << " : 0" << std::endl; - TEST_CHECK(st.total_wanted_done == 0); + TEST_EQUAL(st.total_wanted, file_size * 3); + TEST_EQUAL(st.total_wanted_done, 0); - std::vector prio(3, 1); + std::vector prio(info->num_files(), 1); prio[0] = 0; h.prioritize_files(prio); - std::cout << "prio: " << prio.size() << std::endl; - std::cout << "ret prio: " << h.file_priorities().size() << std::endl; - TEST_CHECK(h.file_priorities().size() == info->num_files()); - - test_sleep(500); st = h.status(); - std::cout << "total_wanted: " << st.total_wanted << " : " << file_size * 2 << std::endl; - TEST_CHECK(st.total_wanted == file_size * 2); - std::cout << "total_wanted_done: " << st.total_wanted_done << " : 0" << std::endl; - TEST_CHECK(st.total_wanted_done == 0); + TEST_EQUAL(st.total_wanted, file_size * 2); + TEST_EQUAL(st.total_wanted_done, 0); + TEST_EQUAL(h.file_priorities().size(), info->num_files()); + if (!st.is_seeding) + { + TEST_EQUAL(h.file_priorities()[0], 0); + if (info->num_files() > 1) + TEST_EQUAL(h.file_priorities()[1], 1); + if (info->num_files() > 2) + TEST_EQUAL(h.file_priorities()[2], 1); + } - prio[1] = 0; - h.prioritize_files(prio); + if (info->num_files() > 1) + { + prio[1] = 0; + h.prioritize_files(prio); + st = h.status(); - test_sleep(500); - st = h.status(); - - std::cout << "total_wanted: " << st.total_wanted << " : " << file_size << std::endl; - TEST_CHECK(st.total_wanted == file_size); - std::cout << "total_wanted_done: " << st.total_wanted_done << " : 0" << std::endl; - TEST_CHECK(st.total_wanted_done == 0); + TEST_EQUAL(st.total_wanted, file_size); + TEST_EQUAL(st.total_wanted_done, 0); + if (!st.is_seeding) + { + TEST_EQUAL(h.file_priorities().size(), info->num_files()); + TEST_EQUAL(h.file_priorities()[0], 0); + if (info->num_files() > 1) + TEST_EQUAL(h.file_priorities()[1], 0); + if (info->num_files() > 2) + TEST_EQUAL(h.file_priorities()[2], 1); + } + } if (info->num_pieces() > 0) { @@ -109,7 +127,10 @@ void test_running_torrent(boost::intrusive_ptr info, size_type fil for (int i = 0; i < int(piece.size()); ++i) piece[i] = (i % 26) + 'A'; h.add_piece(0, &piece[0]); - test_sleep(10000); + + // wait until the piece is done writing and hashing + // TODO: wait for an alert rather than just waiting 10 seconds. This is kind of silly + test_sleep(2000); st = h.status(); TEST_CHECK(st.pieces.size() > 0 && st.pieces[0] == true); @@ -129,6 +150,7 @@ void test_running_torrent(boost::intrusive_ptr info, size_type fil TEST_CHECK(memcmp(&piece[0], rpa->buffer.get(), piece.size()) == 0); TEST_CHECK(rpa->size == info->piece_size(0)); TEST_CHECK(rpa->piece == 0); + TEST_CHECK(hasher(&piece[0], piece.size()).final() == info->hash_for_piece(0)); break; } a = ses.wait_for_alert(seconds(10)); @@ -140,7 +162,7 @@ void test_running_torrent(boost::intrusive_ptr info, size_type fil int test_main() { - { +/* { remove("test_torrent_dir2/tmp1"); remove("test_torrent_dir2/tmp2"); remove("test_torrent_dir2/tmp3"); @@ -167,12 +189,12 @@ int test_main() std::back_insert_iterator > out(tmp); bencode(out, t.generate()); error_code ec; - boost::intrusive_ptr info(new torrent_info(&tmp[0], tmp.size(), ec)); + boost::shared_ptr info(boost::make_shared(&tmp[0], tmp.size(), boost::ref(ec), 0)); TEST_CHECK(info->num_pieces() > 0); test_running_torrent(info, file_size); } - +*/ { file_storage fs; @@ -184,7 +206,7 @@ int test_main() std::back_insert_iterator > out(tmp); bencode(out, t.generate()); error_code ec; - boost::intrusive_ptr info(new torrent_info(&tmp[0], tmp.size(), ec)); + boost::shared_ptr info(boost::make_shared(&tmp[0], tmp.size(), boost::ref(ec), 0)); test_running_torrent(info, 0); } diff --git a/test/test_torrent_info.cpp b/test/test_torrent_info.cpp index 178743da9..2c819572a 100644 --- a/test/test_torrent_info.cpp +++ b/test/test_torrent_info.cpp @@ -41,6 +41,7 @@ int test_main() { file_storage fs; + int total_size = 100 * 0x4000; fs.add_file("test/temporary.txt", 0x4000); fs.add_file("test/A/tmp", 0x4000); @@ -75,17 +76,12 @@ int test_main() "test/A/tmp", "test/Temporary.1.txt", // duplicate of temporary.txt "test/TeMPorArY.2.txT", // duplicate of temporary.txt - - // a file may not have the same name as a directory - // however, detecting this is not supported currently. - // it is in the next major release - "test/a", - + "test/a.1", // a file may not have the same name as a directory "test/b.exe", "test/B.1.ExE", // duplicate of b.exe "test/B.2.exe", // duplicate of b.exe "test/test/TEMPORARY.TXT", // a file with the same name in a seprate directory is fine - "test/A.1", // duplicate of directory a + "test/A.2", // duplicate of directory a }; for (int i = 0; i < ti.num_files(); ++i) diff --git a/test/test_torrent_parse.cpp b/test/test_torrent_parse.cpp index 6c246ee8a..dbdf3fa14 100644 --- a/test/test_torrent_parse.cpp +++ b/test/test_torrent_parse.cpp @@ -73,6 +73,7 @@ test_torrent_t test_torrents[] = { "root_hash.torrent" }, { "empty_path_multi.torrent" }, { "duplicate_web_seeds.torrent" }, + { "invalid_name2.torrent" }, { "invalid_name3.torrent" }, { "symlink1.torrent" }, }; @@ -90,7 +91,6 @@ test_failing_torrent_t test_error_torrents[] = { "negative_piece_len.torrent", errors::torrent_missing_piece_length }, { "no_name.torrent", errors::torrent_missing_name }, { "invalid_name.torrent", errors::torrent_missing_name }, - { "invalid_name2.torrent", errors::torrent_invalid_name }, { "invalid_info.torrent", errors::torrent_missing_info }, { "string.torrent", errors::torrent_is_no_dict }, { "negative_size.torrent", errors::torrent_invalid_length }, @@ -108,7 +108,7 @@ namespace libtorrent { // defined in torrent_info.cpp TORRENT_EXPORT bool verify_encoding(std::string& target, bool path = true); - TORRENT_EXPORT std::string sanitize_path(std::string const& p); + TORRENT_EXTRA_EXPORT void sanitize_append_path_element(std::string& p, char const* element, int len); } // TODO: test remap_files @@ -191,23 +191,45 @@ int test_main() TEST_EQUAL(merkle_num_nodes(8), 15); TEST_EQUAL(merkle_num_nodes(16), 31); - // test sanitize_path + // test sanitize_append_path_element + std::string path; + + path.clear(); + sanitize_append_path_element(path, + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_", 250); + sanitize_append_path_element(path, + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcde.test", 250); #ifdef TORRENT_WINDOWS - TEST_EQUAL(sanitize_path("/a/b/c"), "a\\b\\c"); - TEST_EQUAL(sanitize_path("a/../c"), "a\\c"); + TEST_EQUAL(path, + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_\\" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_.test"); #else - TEST_EQUAL(sanitize_path("/a/b/c"), "a/b/c"); - TEST_EQUAL(sanitize_path("a/../c"), "a/c"); + TEST_EQUAL(path, + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_/" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_abcdefghi_" + "abcdefghi_abcdefghi_abcdefghi_abcdefghi_.test"); #endif - TEST_EQUAL(sanitize_path("/.././c"), "c"); - TEST_EQUAL(sanitize_path("dev:"), ""); - TEST_EQUAL(sanitize_path("c:/b"), "b"); + + path.clear(); + sanitize_append_path_element(path, "/a/", 3); + sanitize_append_path_element(path, "b", 1); + sanitize_append_path_element(path, "c", 1); #ifdef TORRENT_WINDOWS - TEST_EQUAL(sanitize_path("c:\\.\\c"), "c"); - TEST_EQUAL(sanitize_path("\\c"), "c"); + TEST_EQUAL(path, "a\\b\\c"); #else - TEST_EQUAL(sanitize_path("//./c"), "c"); + TEST_EQUAL(path, "a/b/c"); #endif // test torrent parsing @@ -238,9 +260,9 @@ int test_main() torrent_info ti2(&buf[0], buf.size(), ec); std::cerr << ti2.name() << std::endl; #ifdef TORRENT_WINDOWS - TEST_CHECK(ti2.name() == "test1\\test2\\test3"); + TEST_CHECK(ti2.name() == "ctest1test2test3"); #else - TEST_CHECK(ti2.name() == "test1/test2/test3"); + TEST_CHECK(ti2.name() == "test1test2test3"); #endif info["name.utf-8"] = "test2/../test3/.././../../test4"; @@ -249,11 +271,7 @@ int test_main() bencode(std::back_inserter(buf), torrent); torrent_info ti3(&buf[0], buf.size(), ec); std::cerr << ti3.name() << std::endl; -#ifdef TORRENT_WINDOWS - TEST_CHECK(ti3.name() == "test2\\test3\\test4"); -#else - TEST_CHECK(ti3.name() == "test2/test3/test4"); -#endif + TEST_CHECK(ti3.name() == "test2..test3.......test4"); // verify_encoding std::string test = "\b?filename=4"; @@ -316,56 +334,14 @@ int test_main() fprintf(stderr, "%s\n", test.c_str()); TEST_CHECK(test == "filename_____foobar"); - // trim_path_element - - fprintf(stderr, "TORRENT_MAX_PATH: %d\n", TORRENT_MAX_PATH); - - // 1100 characters - test = "abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789" - "abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789" - "abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789" - "abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789" - "abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789" - "abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789" - "abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789" - "abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789" - "abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789" - "abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789" - "abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij012345.txt"; - std::string comparison = test; - trim_path_element(test); - if (comparison.size() > TORRENT_MAX_PATH) - { - comparison.resize(TORRENT_MAX_PATH - 4); - comparison += ".txt"; // the extension is supposed to be preserved - } - TEST_EQUAL(test, comparison); - - // extensions > 15 characters are ignored - test = "abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789" - "abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789" - "abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789" - "abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789" - "abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789" - "abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789" - "abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789" - "abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789" - "abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789" - "abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789" - "abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij.123456789abcdefghij0123456789"; - comparison = test; - trim_path_element(test); - if (comparison.size() > TORRENT_MAX_PATH) - comparison.resize(TORRENT_MAX_PATH); - TEST_EQUAL(test, comparison); - std::string root_dir = parent_path(current_working_directory()); for (int i = 0; i < sizeof(test_torrents)/sizeof(test_torrents[0]); ++i) { fprintf(stderr, "loading %s\n", test_torrents[i].file); - boost::intrusive_ptr ti(new torrent_info(combine_path(combine_path(root_dir, "test_torrents"), test_torrents[i].file), ec)); + std::string filename = combine_path(combine_path(root_dir, "test_torrents"), test_torrents[i].file); + boost::shared_ptr ti(new torrent_info(filename, ec)); TEST_CHECK(!ec); - if (ec) fprintf(stderr, " -> failed %s\n", ec.message().c_str()); + if (ec) fprintf(stderr, " loading(\"%s\") -> failed %s\n", filename.c_str(), ec.message().c_str()); if (std::string(test_torrents[i].file) == "whitespace_url.torrent") { @@ -430,6 +406,13 @@ int test_main() TEST_EQUAL(ti->url_seeds()[0], "http://test.com/test%20file/foo%20bar/"); #endif } + else if (std::string(test_torrents[i].file) == "invalid_name2.torrent") + { + // if, after all invalid characters are removed from the name, it ends up + // being empty, it's set to the info-hash. Some torrents also have an empty name + // in which case it's also set to the info-hash + TEST_EQUAL(ti->name(), "b61560c2918f463768cd122b6d2fdd47b77bdb35"); + } else if (std::string(test_torrents[i].file) == "invalid_name3.torrent") { TEST_EQUAL(ti->name(), "foobar"); @@ -474,7 +457,7 @@ int test_main() { error_code ec; fprintf(stderr, "loading %s\n", test_error_torrents[i].file); - boost::intrusive_ptr ti(new torrent_info(combine_path(combine_path(root_dir, "test_torrents"), test_error_torrents[i].file), ec)); + boost::shared_ptr ti(new torrent_info(combine_path(combine_path(root_dir, "test_torrents"), test_error_torrents[i].file), ec)); fprintf(stderr, "E: \"%s\"\nexpected: \"%s\"\n", ec.message().c_str(), test_error_torrents[i].error.message().c_str()); TEST_CHECK(ec.message() == test_error_torrents[i].error.message()); } diff --git a/test/test_tracker.cpp b/test/test_tracker.cpp index fdec63443..808fa7e54 100644 --- a/test/test_tracker.cpp +++ b/test/test_tracker.cpp @@ -40,6 +40,7 @@ POSSIBILITY OF SUCH DAMAGE. #include using namespace libtorrent; +namespace lt = libtorrent; int test_main() { @@ -52,19 +53,19 @@ int test_main() & ~alert::progress_notification & ~alert::stats_notification; - session* s = new libtorrent::session(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48875, 49800), "0.0.0.0", 0, alert_mask); + lt::session* s = new lt::session(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48875, 49800), "0.0.0.0", 0, alert_mask); - session_settings sett; - sett.half_open_limit = 1; - sett.announce_to_all_trackers = true; - sett.announce_to_all_tiers = true; - s->set_settings(sett); + settings_pack pack; + pack.set_int(settings_pack::half_open_limit, 1); + pack.set_bool(settings_pack::announce_to_all_trackers, true); + pack.set_bool(settings_pack::announce_to_all_tiers, true); + s->apply_settings(pack); error_code ec; remove_all("tmp1_tracker", ec); create_directory("tmp1_tracker", ec); std::ofstream file(combine_path("tmp1_tracker", "temporary").c_str()); - boost::intrusive_ptr t = ::create_torrent(&file, 16 * 1024, 13, false); + boost::shared_ptr t = ::create_torrent(&file, 16 * 1024, 13, false); file.close(); char tracker_url[200]; @@ -107,14 +108,15 @@ int test_main() // test that we move on to try the next tier if the first one fails // ======================================== - s = new libtorrent::session(fingerprint("LT", 0, 1, 0, 0), std::make_pair(39775, 39800), "0.0.0.0", 0, alert_mask); + s = new lt::session(fingerprint("LT", 0, 1, 0, 0), std::make_pair(39775, 39800), "0.0.0.0", 0, alert_mask); - sett.half_open_limit = 1; - sett.announce_to_all_trackers = true; - sett.announce_to_all_tiers = false; - sett.tracker_completion_timeout = 2; - sett.tracker_receive_timeout = 1; - s->set_settings(sett); + pack.clear(); + pack.set_int(settings_pack::half_open_limit, 1); + pack.set_bool(settings_pack::announce_to_all_trackers, true); + pack.set_bool(settings_pack::announce_to_all_tiers, false); + pack.set_int(settings_pack::tracker_completion_timeout, 2); + pack.set_int(settings_pack::tracker_receive_timeout, 1); + s->apply_settings(pack); remove_all("tmp2_tracker", ec); create_directory("tmp2_tracker", ec); diff --git a/test/test_trackers_extension.cpp b/test/test_trackers_extension.cpp index 38547fc0a..9336a913b 100644 --- a/test/test_trackers_extension.cpp +++ b/test/test_trackers_extension.cpp @@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/session.hpp" #include "libtorrent/hasher.hpp" #include "libtorrent/thread.hpp" +#include #include #include "test.hpp" @@ -49,6 +50,7 @@ using boost::tuples::ignore; int test_main() { using namespace libtorrent; + namespace lt = libtorrent; // these are declared before the session objects // so that they are destructed last. This enables @@ -56,8 +58,8 @@ int test_main() session_proxy p1; session_proxy p2; - session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48130, 49000), "0.0.0.0", 0); - session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49130, 50000), "0.0.0.0", 0); + lt::session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48130, 49000), "0.0.0.0", 0); + lt::session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49130, 50000), "0.0.0.0", 0); ses1.add_extension(create_lt_trackers_plugin); ses2.add_extension(create_lt_trackers_plugin); @@ -96,7 +98,7 @@ int test_main() std::vector buf; bencode(std::back_inserter(buf), torrent); - boost::intrusive_ptr ti(new torrent_info(&buf[0], buf.size(), ec)); + boost::shared_ptr ti(boost::make_shared(&buf[0], buf.size(), boost::ref(ec))); TEST_CHECK(!ec); atp.ti = ti; diff --git a/test/test_transfer.cpp b/test/test_transfer.cpp index 0c0fd177e..813030849 100644 --- a/test/test_transfer.cpp +++ b/test/test_transfer.cpp @@ -47,11 +47,11 @@ POSSIBILITY OF SUCH DAMAGE. #include using namespace libtorrent; +namespace lt = libtorrent; + using boost::tuples::ignore; -int const alert_mask = alert::all_categories -& ~alert::progress_notification -& ~alert::stats_notification; +const int mask = alert::all_categories & ~(alert::performance_warning | alert::stats_notification); int peer_disconnects = 0; @@ -70,117 +70,73 @@ bool on_alert(alert* a) } // simulate a full disk -struct test_storage : storage_interface +struct test_storage : default_storage { - test_storage(file_storage const& fs, std::string const& p, file_pool& fp) - : m_lower_layer(default_storage_constructor(fs, 0, p, fp, std::vector())) + test_storage(storage_params const& params) + : default_storage(params) , m_written(0) , m_limit(16 * 1024 * 2) {} virtual void set_file_priority(std::vector const& p) {} - virtual bool initialize(bool allocate_files) - { return m_lower_layer->initialize(allocate_files); } - - virtual bool has_any_file() - { return m_lower_layer->has_any_file(); } - - virtual int readv(file::iovec_t const* bufs, int slot, int offset, int num_bufs, int flags) - { return m_lower_layer->readv(bufs, slot, offset, num_bufs, flags); } - - virtual int writev(file::iovec_t const* bufs, int slot, int offset, int num_bufs, int flags) + void set_limit(int lim) { - int ret = m_lower_layer->writev(bufs, slot, offset, num_bufs, flags); - if (ret > 0) m_written += ret; - if (m_written > m_limit) - { -#if BOOST_VERSION == 103500 - set_error("", error_code(boost::system::posix_error::no_space_on_device, get_posix_category())); -#elif BOOST_VERSION > 103500 - set_error("", error_code(boost::system::errc::no_space_on_device, get_posix_category())); -#else - set_error("", error_code(ENOSPC, get_posix_category())); -#endif - return -1; - } - return ret; + mutex::scoped_lock l(m_mutex); + m_limit = lim; } - virtual size_type physical_offset(int piece_index, int offset) - { return m_lower_layer->physical_offset(piece_index, offset); } - - virtual int read(char* buf, int slot, int offset, int size) - { return m_lower_layer->read(buf, slot, offset, size); } - - virtual int write(const char* buf, int slot, int offset, int size) + int writev( + file::iovec_t const* bufs + , int num_bufs + , int piece_index + , int offset + , int flags + , storage_error& se) { - int ret = m_lower_layer->write(buf, slot, offset, size); - if (ret > 0) m_written += ret; - if (m_written > m_limit) + mutex::scoped_lock l(m_mutex); + if (m_written >= m_limit) { + std::cerr << "storage written: " << m_written << " limit: " << m_limit << std::endl; + error_code ec; #if BOOST_VERSION == 103500 - set_error("", error_code(boost::system::posix_error::no_space_on_device, get_posix_category())); + ec = error_code(boost::system::posix_error::no_space_on_device, get_posix_category()); #elif BOOST_VERSION > 103500 - set_error("", error_code(boost::system::errc::no_space_on_device, get_posix_category())); + ec = error_code(boost::system::errc::no_space_on_device, get_posix_category()); #else - set_error("", error_code(ENOSPC, get_posix_category())); + ec = error_code(ENOSPC, get_posix_category()); #endif - return -1; + se.ec = ec; + return 0; } - return ret; + + for (int i = 0; i < num_bufs; ++i) + m_written += bufs[i].iov_len; + l.unlock(); + return default_storage::writev(bufs, num_bufs, piece_index, offset, flags, se); } - virtual int sparse_end(int start) const - { return m_lower_layer->sparse_end(start); } - - virtual int move_storage(std::string const& save_path, int flags) - { return m_lower_layer->move_storage(save_path, flags); } - - virtual bool verify_resume_data(lazy_entry const& rd, error_code& error) - { return m_lower_layer->verify_resume_data(rd, error); } - - virtual bool write_resume_data(entry& rd) const - { return m_lower_layer->write_resume_data(rd); } - - virtual bool move_slot(int src_slot, int dst_slot) - { return m_lower_layer->move_slot(src_slot, dst_slot); } - - virtual bool swap_slots(int slot1, int slot2) - { return m_lower_layer->swap_slots(slot1, slot2); } - - virtual bool swap_slots3(int slot1, int slot2, int slot3) - { return m_lower_layer->swap_slots3(slot1, slot2, slot3); } - - virtual bool release_files() { return m_lower_layer->release_files(); } - - virtual bool rename_file(int index, std::string const& new_filename) - { return m_lower_layer->rename_file(index, new_filename); } - - virtual bool delete_files() { return m_lower_layer->delete_files(); } - virtual ~test_storage() {} - boost::scoped_ptr m_lower_layer; int m_written; int m_limit; + mutex m_mutex; }; -storage_interface* test_storage_constructor(file_storage const& fs - , file_storage const*, std::string const& path, file_pool& fp, std::vector const&) +storage_interface* test_storage_constructor(storage_params const& params) { - return new test_storage(fs, path, fp); + return new test_storage(params); } -void test_transfer(int proxy_type, bool test_disk_full = false - , bool test_allowed_fast = false +void test_transfer(int proxy_type, settings_pack const& sett + , bool test_disk_full = false , storage_mode_t storage_mode = storage_mode_sparse) { static int listen_port = 0; char const* test_name[] = {"no", "SOCKS4", "SOCKS5", "SOCKS5 password", "HTTP", "HTTP password"}; - fprintf(stderr, "\n\n ==== TESTING %s proxy ==== disk-full: %s allow-fast: %s\n\n\n" - , test_name[proxy_type], test_disk_full ? "true": "false", test_allowed_fast ? "true" : "false"); + fprintf(stderr, "\n\n ==== TESTING %s proxy ==== disk-full: %s\n\n\n" + , test_name[proxy_type], test_disk_full ? "true": "false"); // in case the previous run was terminated error_code ec; @@ -195,83 +151,77 @@ void test_transfer(int proxy_type, bool test_disk_full = false session_proxy p1; session_proxy p2; - session ses1(fingerprint("LT", 0, 1, 0, 0) - , std::make_pair(48075 + listen_port, 49000), "0.0.0.0", 0, alert_mask); - session ses2(fingerprint("LT", 0, 1, 0, 0) - , std::make_pair(49075 + listen_port, 50000), "0.0.0.0", 0, alert_mask); + lt::session ses1(fingerprint("LT", 0, 1, 0, 0) + , std::make_pair(48075 + listen_port, 49000), "0.0.0.0", 0, mask); + lt::session ses2(fingerprint("LT", 0, 1, 0, 0) + , std::make_pair(49075 + listen_port, 50000), "0.0.0.0", 0, mask); listen_port += 10; - proxy_settings ps; + int proxy_port = 0; if (proxy_type) { - ps.port = start_proxy(proxy_type); - ps.username = "testuser"; - ps.password = "testpass"; - ps.type = (proxy_settings::proxy_type)proxy_type; + proxy_port = start_proxy(proxy_type); + + settings_pack pack; + pack.set_str(settings_pack::proxy_username, "testuser"); + pack.set_str(settings_pack::proxy_password, "testpass"); + pack.set_int(settings_pack::proxy_type, (settings_pack::proxy_type_t)proxy_type); + pack.set_int(settings_pack::proxy_port, proxy_port); // test resetting the proxy in quick succession. // specifically the udp_socket connecting to a new // socks5 proxy while having one connection attempt // in progress. - ps.hostname = "5.6.7.8"; - ses1.set_proxy(ps); + pack.set_str(settings_pack::proxy_hostname, "5.6.7.8"); + ses1.apply_settings(pack); - ps.hostname = "127.0.0.1"; - ses1.set_proxy(ps); - ses2.set_proxy(ps); + pack.set_str(settings_pack::proxy_hostname, "127.0.0.1"); + ses1.apply_settings(pack); + ses2.apply_settings(pack); } - session_settings sett; - sett.allow_multiple_connections_per_ip = false; - sett.ignore_limits_on_local_network = false; - - if (test_allowed_fast) - { - sett.allowed_fast_set_size = 2000; - sett.unchoke_slots_limit = 0; - } - - sett.unchoke_slots_limit = 0; - ses1.set_settings(sett); - TEST_CHECK(ses1.settings().unchoke_slots_limit == 0); - sett.unchoke_slots_limit = -1; - ses1.set_settings(sett); - TEST_CHECK(ses1.settings().unchoke_slots_limit == -1); - sett.unchoke_slots_limit = 8; - ses1.set_settings(sett); - TEST_CHECK(ses1.settings().unchoke_slots_limit == 8); - + settings_pack pack = sett; // we need a short reconnect time since we // finish the torrent and then restart it // immediately to complete the second half. // using a reconnect time > 0 will just add // to the time it will take to complete the test - sett.min_reconnect_time = 0; - sett.stop_tracker_timeout = 1; - sett.announce_to_all_trackers = true; - sett.announce_to_all_tiers = true; + pack.set_int(settings_pack::min_reconnect_time, 0); + pack.set_int(settings_pack::stop_tracker_timeout, 1); + pack.set_bool(settings_pack::announce_to_all_trackers, true); + pack.set_bool(settings_pack::announce_to_all_tiers, true); + // make sure we announce to both http and udp trackers - sett.prefer_udp_trackers = false; - sett.enable_outgoing_utp = false; - sett.enable_incoming_utp = false; + pack.set_bool(settings_pack::prefer_udp_trackers, false); + pack.set_bool(settings_pack::enable_outgoing_utp, false); + pack.set_bool(settings_pack::enable_incoming_utp, false); + pack.set_int(settings_pack::alert_mask, mask); - ses1.set_settings(sett); - ses2.set_settings(sett); + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_disabled); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_disabled); -#ifndef TORRENT_DISABLE_ENCRYPTION - pe_settings pes; - pes.out_enc_policy = pe_settings::disabled; - pes.in_enc_policy = pe_settings::disabled; - ses1.set_pe_settings(pes); - ses2.set_pe_settings(pes); -#endif + pack.set_bool(settings_pack::allow_multiple_connections_per_ip, false); + + pack.set_int(settings_pack::unchoke_slots_limit, 0); + ses1.apply_settings(pack); + TEST_CHECK(ses1.get_settings().get_int(settings_pack::unchoke_slots_limit) == 0); + + pack.set_int(settings_pack::unchoke_slots_limit, -1); + ses1.apply_settings(pack); + TEST_CHECK(ses1.get_settings().get_int(settings_pack::unchoke_slots_limit) == -1); + + pack.set_int(settings_pack::unchoke_slots_limit, 8); + ses1.apply_settings(pack); + TEST_CHECK(ses1.get_settings().get_int(settings_pack::unchoke_slots_limit) == 8); + + ses2.apply_settings(pack); torrent_handle tor1; torrent_handle tor2; create_directory("tmp1_transfer", ec); std::ofstream file("tmp1_transfer/temporary"); - boost::intrusive_ptr t = ::create_torrent(&file, 16 * 1024, 13, false); + boost::shared_ptr t = ::create_torrent(&file, 16 * 1024, 13, false); file.close(); TEST_CHECK(exists(combine_path("tmp1_transfer", "temporary"))); @@ -284,7 +234,7 @@ void test_transfer(int proxy_type, bool test_disk_full = false params.storage_mode = storage_mode; wait_for_listen(ses1, "ses1"); - wait_for_listen(ses2, "ses1"); + wait_for_listen(ses2, "ses2"); peer_disconnects = 0; @@ -300,6 +250,9 @@ void test_transfer(int proxy_type, bool test_disk_full = false bool test_move_storage = false; tracker_responses = 0; + int upload_mode_timer = 0; + + wait_for_downloading(ses2, "ses2"); for (int i = 0; i < 200; ++i) { @@ -322,11 +275,15 @@ void test_transfer(int proxy_type, bool test_disk_full = false std::cerr << "moving storage" << std::endl; } + // wait 10 loops before we restart the torrent. This lets + // us catch all events that failed (and would put the torrent + // back into upload mode) before we restart it. + // TODO: 3 factor out the disk-full test into its own unit test - if (test_disk_full && st2.upload_mode) + if (test_disk_full && st2.upload_mode && ++upload_mode_timer > 10) { test_disk_full = false; - ((test_storage*)tor2.get_storage_impl())->m_limit = 16 * 1024 * 1024; + ((test_storage*)tor2.get_storage_impl())->set_limit(16 * 1024 * 1024); // if we reset the upload mode too soon, there may be more disk // jobs failing right after, putting us back in upload mode. So, @@ -340,23 +297,23 @@ void test_transfer(int proxy_type, bool test_disk_full = false print_alerts(ses2, "ses2", true, true, true, &on_alert); tor2.set_upload_mode(false); + + // at this point we probably disconnected the seed + // so we need to reconnect as well + fprintf(stderr, "%s: reconnecting peer\n", time_now_string()); + error_code ec; + tor2.connect_peer(tcp::endpoint(address::from_string("127.0.0.1", ec) + , ses1.listen_port())); + TEST_CHECK(tor2.status().is_finished == false); - TEST_EQUAL(peer_disconnects, 2); + fprintf(stderr, "disconnects: %d\n", peer_disconnects); + TEST_CHECK(peer_disconnects >= 2); fprintf(stderr, "%s: discovered disk full mode. Raise limit and disable upload-mode\n", time_now_string()); peer_disconnects = 0; - test_sleep(100); continue; } - if (!test_disk_full && st2.is_finished) break; - - if (st2.state != torrent_status::downloading) - { - static char const* state_str[] = - {"checking (q)", "checking", "dl metadata" - , "downloading", "finished", "seeding", "allocating", "checking (r)"}; - std::cerr << "st2 state: " << state_str[st2.state] << std::endl; - } + if (!test_disk_full && st2.is_seeding) break; TEST_CHECK(st1.state == torrent_status::seeding || st1.state == torrent_status::checking_files); @@ -378,30 +335,42 @@ void test_transfer(int proxy_type, bool test_disk_full = false p1 = ses1.abort(); p2 = ses2.abort(); - if (proxy_type) stop_proxy(ps.port); + if (proxy_type) stop_proxy(proxy_port); } int test_main() { using namespace libtorrent; + settings_pack p; + + // test no contiguous_recv_buffers + p = settings_pack(); + p.set_bool(settings_pack::contiguous_recv_buffer, false); + test_transfer(0, p); + // test with all kinds of proxies + p = settings_pack(); for (int i = 0; i < 6; ++i) - test_transfer(i); - + test_transfer(i, p); + // test with a (simulated) full disk - test_transfer(0, true, true); - + test_transfer(0, p, true); + // test allowed fast - test_transfer(0, false, true); + p = settings_pack(); + p.set_int(settings_pack::allowed_fast_set_size, 2000); + test_transfer(0, p, false); + + test_transfer(0, p, false); // test storage_mode_allocate fprintf(stderr, "full allocation mode\n"); - test_transfer(0, false, false, storage_mode_allocate); + test_transfer(0, p, false, storage_mode_allocate); #ifndef TORRENT_NO_DEPRECATE fprintf(stderr, "compact mode\n"); - test_transfer(0, false, false, storage_mode_compact); + test_transfer(0, p, false, storage_mode_compact); #endif error_code ec; diff --git a/test/test_url_seed.cpp b/test/test_url_seed.cpp index 849652b95..720bd43aa 100644 --- a/test/test_url_seed.cpp +++ b/test/test_url_seed.cpp @@ -36,7 +36,7 @@ POSSIBILITY OF SUCH DAMAGE. using namespace libtorrent; -const int proxy = libtorrent::proxy_settings::none; +const int proxy = libtorrent::settings_pack::none; int test_main() { diff --git a/test/test_utp.cpp b/test/test_utp.cpp index 7620d8869..44a0bb91c 100644 --- a/test/test_utp.cpp +++ b/test/test_utp.cpp @@ -47,6 +47,7 @@ POSSIBILITY OF SUCH DAMAGE. #include using namespace libtorrent; +namespace lt = libtorrent; using boost::tuples::ignore; void test_transfer() @@ -62,45 +63,28 @@ void test_transfer() session_proxy p1; session_proxy p2; - session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48885, 49930), "0.0.0.0", 0); - session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49885, 50930), "0.0.0.0", 0); + lt::session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48885, 49930), "0.0.0.0", 0); + lt::session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49885, 50930), "0.0.0.0", 0); - session_settings sett; - - sett.enable_outgoing_tcp = false; - sett.min_reconnect_time = 1; - sett.announce_to_all_trackers = true; - sett.announce_to_all_tiers = true; - // make sure we announce to both http and udp trackers - sett.prefer_udp_trackers = false; - - // speed up loopback connections (by using the full MTU) - sett.utp_dynamic_sock_buf = true; - - // for performance testing -// sett.disable_hash_checks = true; -// sett.utp_delayed_ack = 0; - - // disable this to use regular size packets over loopback -// sett.utp_dynamic_sock_buf = false; - - ses1.set_settings(sett); - ses2.set_settings(sett); - -#ifndef TORRENT_DISABLE_ENCRYPTION - pe_settings pes; - pes.out_enc_policy = pe_settings::disabled; - pes.in_enc_policy = pe_settings::disabled; - ses1.set_pe_settings(pes); - ses2.set_pe_settings(pes); -#endif + settings_pack pack; + pack.set_int(settings_pack::out_enc_policy, settings_pack::pe_disabled); + pack.set_int(settings_pack::in_enc_policy, settings_pack::pe_disabled); + pack.set_bool(settings_pack::enable_outgoing_tcp, false); + pack.set_bool(settings_pack::enable_incoming_tcp, false); + pack.set_bool(settings_pack::announce_to_all_trackers, true); + pack.set_bool(settings_pack::announce_to_all_tiers, true); + pack.set_bool(settings_pack::prefer_udp_trackers, false); + pack.set_bool(settings_pack::utp_dynamic_sock_buf, true); + pack.set_int(settings_pack::min_reconnect_time, 1); + ses1.apply_settings(pack); + ses2.apply_settings(pack); torrent_handle tor1; torrent_handle tor2; create_directory("./tmp1_utp", ec); std::ofstream file("./tmp1_utp/temporary"); - boost::intrusive_ptr t = ::create_torrent(&file, 128 * 1024, 6, false); + boost::shared_ptr t = ::create_torrent(&file, 128 * 1024, 6, false); file.close(); // for performance testing diff --git a/test/test_web_seed.cpp b/test/test_web_seed.cpp index 56cf29d27..cc1c8cbe0 100644 --- a/test/test_web_seed.cpp +++ b/test/test_web_seed.cpp @@ -36,7 +36,7 @@ POSSIBILITY OF SUCH DAMAGE. using namespace libtorrent; -const int proxy = libtorrent::proxy_settings::none; +const int proxy = libtorrent::settings_pack::none; int test_main() { diff --git a/test/test_web_seed_ban.cpp b/test/test_web_seed_ban.cpp index 7d987c38b..015e4cffa 100644 --- a/test/test_web_seed_ban.cpp +++ b/test/test_web_seed_ban.cpp @@ -36,7 +36,7 @@ POSSIBILITY OF SUCH DAMAGE. using namespace libtorrent; -const int proxy = libtorrent::proxy_settings::none; +const int proxy = libtorrent::settings_pack::none; int test_main() { diff --git a/test/test_web_seed_chunked.cpp b/test/test_web_seed_chunked.cpp index 83baf9305..507591a17 100644 --- a/test/test_web_seed_chunked.cpp +++ b/test/test_web_seed_chunked.cpp @@ -36,7 +36,7 @@ POSSIBILITY OF SUCH DAMAGE. using namespace libtorrent; -const int proxy = libtorrent::proxy_settings::none; +const int proxy = libtorrent::settings_pack::none; int test_main() { diff --git a/test/test_web_seed_http.cpp b/test/test_web_seed_http.cpp index 9915ddd33..fa37c1e91 100644 --- a/test/test_web_seed_http.cpp +++ b/test/test_web_seed_http.cpp @@ -36,7 +36,7 @@ POSSIBILITY OF SUCH DAMAGE. using namespace libtorrent; -const int proxy = libtorrent::proxy_settings::http; +const int proxy = libtorrent::settings_pack::http; int test_main() { diff --git a/test/test_web_seed_http_pw.cpp b/test/test_web_seed_http_pw.cpp index f9e241d4c..2ff669e24 100644 --- a/test/test_web_seed_http_pw.cpp +++ b/test/test_web_seed_http_pw.cpp @@ -36,7 +36,7 @@ POSSIBILITY OF SUCH DAMAGE. using namespace libtorrent; -const int proxy = libtorrent::proxy_settings::http_pw; +const int proxy = libtorrent::settings_pack::http_pw; int test_main() { diff --git a/test/test_web_seed_socks4.cpp b/test/test_web_seed_socks4.cpp index 9d3f9264a..313af4364 100644 --- a/test/test_web_seed_socks4.cpp +++ b/test/test_web_seed_socks4.cpp @@ -36,7 +36,7 @@ POSSIBILITY OF SUCH DAMAGE. using namespace libtorrent; -const int proxy = libtorrent::proxy_settings::socks4; +const int proxy = libtorrent::settings_pack::socks4; int test_main() { diff --git a/test/test_web_seed_socks5.cpp b/test/test_web_seed_socks5.cpp index d78b2f8a9..48e35e4ec 100644 --- a/test/test_web_seed_socks5.cpp +++ b/test/test_web_seed_socks5.cpp @@ -36,7 +36,7 @@ POSSIBILITY OF SUCH DAMAGE. using namespace libtorrent; -const int proxy = libtorrent::proxy_settings::socks5; +const int proxy = libtorrent::settings_pack::socks5; int test_main() { diff --git a/test/test_web_seed_socks5_pw.cpp b/test/test_web_seed_socks5_pw.cpp index da8a429b0..a9527afba 100644 --- a/test/test_web_seed_socks5_pw.cpp +++ b/test/test_web_seed_socks5_pw.cpp @@ -36,7 +36,7 @@ POSSIBILITY OF SUCH DAMAGE. using namespace libtorrent; -const int proxy = libtorrent::proxy_settings::socks5_pw; +const int proxy = libtorrent::settings_pack::socks5_pw; int test_main() { diff --git a/test/upnp.xml b/test/upnp.xml deleted file mode 100644 index 2fc9362da..000000000 --- a/test/upnp.xml +++ /dev/null @@ -1 +0,0 @@ -10http://127.0.0.1:59585urn:schemas-upnp-org:device:InternetGatewayDevice:1http://192.168.0.1:80D-Link RouterD-Linkhttp://www.dlink.comInternet Access RouterD-Link Routeruuid:upnp-InternetGatewayDevice-1_0-12345678900001123456789001urn:schemas-upnp-org:service:Layer3Forwarding:1urn:upnp-org:serviceId:L3Forwarding1/Layer3Forwarding/Layer3Forwarding/Layer3Forwarding.xmlurn:schemas-upnp-org:device:WANDevice:1WANDeviceD-Linkhttp://www.dlink.comInternet Access RouterD-Link Router1http://support.dlink.com12345678900001uuid:upnp-WANDevice-1_0-12345678900001123456789001urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1urn:upnp-org:serviceId:WANCommonInterfaceConfig/WANCommonInterfaceConfig/WANCommonInterfaceConfig/WANCommonInterfaceConfig.xmlurn:schemas-upnp-org:device:WANConnectionDevice:1WAN Connection DeviceD-Linkhttp://www.dlink.comInternet Access RouterD-Link Router1http://support.dlink.com12345678900001uuid:upnp-WANConnectionDevice-1_0-12345678900001123456789001urn:schemas-upnp-org:service:WANIPConnection:1urn:upnp-org:serviceId:WANIPConnection/WANIPConnection/WANIPConnection/WANIPConnection.xml \ No newline at end of file diff --git a/test/web_seed_suite.cpp b/test/web_seed_suite.cpp index ce3af1239..69c640164 100644 --- a/test/web_seed_suite.cpp +++ b/test/web_seed_suite.cpp @@ -29,8 +29,7 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "setup_transfer.hpp" -#include "libtorrent/alert_types.hpp" + #include "libtorrent/session.hpp" #include "libtorrent/hasher.hpp" #include "libtorrent/file_pool.hpp" @@ -40,14 +39,18 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/create_torrent.hpp" #include "libtorrent/thread.hpp" #include "libtorrent/alert_types.hpp" -#include "libtorrent/file.hpp" -#include #include "setup_transfer.hpp" +#include +#include +#include +#include "setup_transfer.hpp" + #include using namespace libtorrent; +namespace lt = libtorrent; int peer_disconnects = 0; @@ -62,7 +65,7 @@ bool on_alert(alert* a) } const int num_pieces = 9; - +/* static sha1_hash file_hash(std::string const& name) { std::vector buf; @@ -72,16 +75,18 @@ static sha1_hash file_hash(std::string const& name) hasher h(&buf[0], buf.size()); return h.final(); } - +*/ static char const* proxy_name[] = {"", "_socks4", "_socks5", "_socks5_pw", "_http", "_http_pw", "_i2p"}; // proxy: 0=none, 1=socks4, 2=socks5, 3=socks5_pw 4=http 5=http_pw -static void test_transfer(session& ses, boost::intrusive_ptr torrent_file +static void test_transfer(lt::session& ses, boost::shared_ptr torrent_file , int proxy, int port, char const* protocol, bool url_seed , bool chunked_encoding, bool test_ban, bool keepalive) { using namespace libtorrent; + TORRENT_ASSERT(torrent_file->web_seeds().size() > 0); + std::string save_path = "tmp2_web_seed"; save_path += proxy_name[proxy]; @@ -97,25 +102,33 @@ static void test_transfer(session& ses, boost::intrusive_ptr torre , chunked_encoding ? "chunked": "none", test_ban ? "yes" : "no" , keepalive ? "yes" : "no"); - proxy_settings ps; + int proxy_port = 0; if (proxy) { - ps.port = start_proxy(proxy); - ps.hostname = "127.0.0.1"; - ps.username = "testuser"; - ps.password = "testpass"; - ps.type = (proxy_settings::proxy_type)proxy; - ses.set_proxy(ps); + proxy_port = start_proxy(proxy); + if (proxy_port < 0) + { + fprintf(stderr, "failed to start proxy"); + return; + } + settings_pack pack; + pack.set_str(settings_pack::proxy_hostname, "127.0.0.1"); + pack.set_str(settings_pack::proxy_username, "testuser"); + pack.set_str(settings_pack::proxy_password, "testpass"); + pack.set_int(settings_pack::proxy_type, (settings_pack::proxy_type_t)proxy); + pack.set_int(settings_pack::proxy_port, proxy_port); + ses.apply_settings(pack); } else { - ps.port = 0; - ps.hostname.clear(); - ps.username.clear(); - ps.password.clear(); - ps.type = proxy_settings::none; - ses.set_proxy(ps); + settings_pack pack; + pack.set_str(settings_pack::proxy_hostname, ""); + pack.set_str(settings_pack::proxy_username, ""); + pack.set_str(settings_pack::proxy_password, ""); + pack.set_int(settings_pack::proxy_type, settings_pack::none); + pack.set_int(settings_pack::proxy_port, 0); + ses.apply_settings(pack); } add_torrent_params p; @@ -127,6 +140,9 @@ static void test_transfer(session& ses, boost::intrusive_ptr torre p.storage_mode = storage_mode_compact; #endif torrent_handle th = ses.add_torrent(p, ec); + printf("adding torrent, save_path = \"%s\" cwd = \"%s\" torrent = \"%s\"\n" + , save_path.c_str(), current_working_directory().c_str() + , torrent_file->name().c_str()); std::vector empty; th.replace_trackers(empty); @@ -155,7 +171,7 @@ static void test_transfer(session& ses, boost::intrusive_ptr torre rate_sum += s.download_payload_rate; ses_rate_sum += ss.payload_download_rate; - cs = ses.get_cache_status(); + ses.get_cache_info(&cs); if (cs.blocks_read < 1) cs.blocks_read = 1; if (cs.blocks_written < 1) cs.blocks_written = 1; @@ -165,19 +181,31 @@ static void test_transfer(session& ses, boost::intrusive_ptr torre if (test_ban && th.url_seeds().empty() && th.http_seeds().empty()) { + fprintf(stderr, "testing ban: URL seed removed\n"); // when we don't have any web seeds left, we know we successfully banned it break; } + if (s.is_seeding) + { + fprintf(stderr, "SEEDING\n"); + fprintf(stderr, "session.payload: %d session.redundant: %d\n" + , int(ses.status().total_payload_download), int(ses.status().total_redundant_bytes)); + fprintf(stderr, "torrent.payload: %d torrent.redundant: %d\n" + , int(s.total_payload_download), int(s.total_redundant_bytes)); + + TEST_EQUAL(s.total_payload_download - s.total_redundant_bytes, total_size - pad_file_size); + // we need to sleep here a bit to let the session sync with the torrent stats + // commented out because it takes such a long time +// TEST_EQUAL(ses.status().total_payload_download - ses.status().total_redundant_bytes +// , total_size - pad_file_size); + break; + } + // if the web seed connection is disconnected, we're going to fail // the test. make sure to do so quickly if (keepalive && peer_disconnects >= 1) break; - if (s.is_seeding /* && ss.download_rate == 0.f*/) - { - TEST_EQUAL(s.total_payload_download - s.total_redundant_bytes, total_size - pad_file_size); - break; - } test_sleep(100); } @@ -185,6 +213,8 @@ static void test_transfer(session& ses, boost::intrusive_ptr torre // the url seed (i.e. banned it) TEST_CHECK(!test_ban || (th.url_seeds().empty() && th.http_seeds().empty())); + // if the web seed senr corrupt data and we banned it, we probably didn't + // end up using all the cache anyway if (!test_ban) { torrent_status st = th.status(); @@ -194,18 +224,21 @@ static void test_transfer(session& ses, boost::intrusive_ptr torre { for (int i = 0; i < 50; ++i) { - cs = ses.get_cache_status(); - if (cs.read_cache_size == 0 && cs.total_used_buffers == 0) + ses.get_cache_info(&cs); + if (cs.read_cache_size == (torrent_file->total_size() + 0x3fff) / 0x4000 + && cs.total_used_buffers == (torrent_file->total_size() + 0x3fff) / 0x4000) break; fprintf(stderr, "cache_size: %d/%d\n", int(cs.read_cache_size), int(cs.total_used_buffers)); test_sleep(100); } - TEST_EQUAL(cs.read_cache_size, 0); - TEST_EQUAL(cs.total_used_buffers, 0); + TEST_EQUAL(cs.read_cache_size, (torrent_file->total_size() + 0x3fff) / 0x4000); + TEST_EQUAL(cs.total_used_buffers, (torrent_file->total_size() + 0x3fff) / 0x4000); } } std::cerr << "total_size: " << total_size + << " read cache size: " << cs.read_cache_size + << " total used buffer: " << cs.total_used_buffers << " rate_sum: " << rate_sum << " session_rate_sum: " << ses_rate_sum << " session total download: " << ses.status().total_payload_download @@ -221,16 +254,24 @@ static void test_transfer(session& ses, boost::intrusive_ptr torre // otherwise, we are supposed to have TEST_CHECK(th.status().is_seeding == !test_ban); - if (proxy) stop_proxy(ps.port); + if (proxy) stop_proxy(proxy_port); - ses.remove_torrent(th); + th.flush_cache(); - // call this to synchronize with the network thread - ses.status(); + // synchronize to make sure the files have been created on disk + wait_for_alert(ses, cache_flushed_alert::alert_type, "ses"); print_alerts(ses, " >> ses", true, true, false, &on_alert, true); - TEST_CHECK(exists(combine_path(save_path, torrent_file->files().file_path(0))) || test_ban); + if (!test_ban) + { + std::string first_file_path = combine_path(save_path, torrent_file->files().file_path(0)); + fprintf(stderr, "checking file: %s\n", first_file_path.c_str()); + TEST_CHECK(exists(first_file_path)); + } + + ses.remove_torrent(th); + remove_all(save_path, ec); } @@ -247,13 +288,6 @@ int EXPORT run_http_suite(int proxy, char const* protocol, bool test_url_seed error_code ec; create_directories(combine_path(save_path, "torrent_dir"), ec); - if (ec) - { - fprintf(stderr, "FAILED TO CREATE DIRECTORY: (%d) %s\n" - , ec.value(), ec.message().c_str()); - TEST_CHECK(!ec); - return 1; - } file_storage fs; std::srand(10); @@ -297,13 +331,13 @@ int EXPORT run_http_suite(int proxy, char const* protocol, bool test_url_seed t.add_http_seed(tmp); } fprintf(stderr, "testing: %s\n", tmp); - +/* for (int i = 0; i < fs.num_files(); ++i) { file_entry f = fs.at(i); fprintf(stderr, " %04x: %d %s\n", int(f.offset), f.pad_file, f.path.c_str()); } - +*/ // calculate the hash for all pieces set_piece_hashes(t, save_path, ec); @@ -334,8 +368,11 @@ int EXPORT run_http_suite(int proxy, char const* protocol, bool test_url_seed std::vector buf; bencode(std::back_inserter(buf), t.generate()); - boost::intrusive_ptr torrent_file(new torrent_info(&buf[0], buf.size(), ec)); + boost::shared_ptr torrent_file(boost::make_shared(&buf[0], buf.size(), boost::ref(ec), 0)); + + // TODO: file hashes don't work with the new torrent creator reading async +/* // no point in testing the hashes since we know the data is corrupt if (!test_ban) { @@ -351,17 +388,17 @@ int EXPORT run_http_suite(int proxy, char const* protocol, bool test_url_seed TEST_EQUAL(h1, h2); } } - +*/ { - session ses(fingerprint(" ", 0,0,0,0), 0); - session_settings settings; - settings.max_queued_disk_bytes = 256 * 1024; - ses.set_settings(settings); - ses.set_alert_mask(~(alert::progress_notification | alert::stats_notification)); - error_code ec; - ses.listen_on(std::make_pair(51000, 52000), ec); - if (ec) fprintf(stderr, "listen_on failed: %s\n", ec.message().c_str()); - + libtorrent::session ses(fingerprint(" ", 0,0,0,0), 0); + + settings_pack pack; + pack.set_int(settings_pack::max_queued_disk_bytes, 256 * 1024); + pack.set_str(settings_pack::listen_interfaces, "0.0.0.0:51000"); + pack.set_int(settings_pack::max_retry_port_bind, 1000); + pack.set_int(settings_pack::alert_mask, ~(alert::progress_notification | alert::stats_notification)); + ses.apply_settings(pack); + test_transfer(ses, torrent_file, proxy, port, protocol, test_url_seed , chunked_encoding, test_ban, keepalive); @@ -378,4 +415,3 @@ int EXPORT run_http_suite(int proxy, char const* protocol, bool test_url_seed return 0; } - diff --git a/tools/Jamfile b/tools/Jamfile index 3142ec287..5a5d36de7 100644 --- a/tools/Jamfile +++ b/tools/Jamfile @@ -34,6 +34,7 @@ project tools ; exe parse_hash_fails : parse_hash_fails.cpp ; +exe parse_access_log : parse_access_log.cpp ; exe parse_request_log : parse_request_log.cpp ; exe dht : dht_put.cpp : ../ed25519/src ; diff --git a/tools/dht_put.cpp b/tools/dht_put.cpp index 89bf30b39..994b49b44 100644 --- a/tools/dht_put.cpp +++ b/tools/dht_put.cpp @@ -42,6 +42,7 @@ POSSIBILITY OF SUCH DAMAGE. #include using namespace libtorrent; +namespace lt = libtorrent; #ifdef TORRENT_DISABLE_DHT @@ -71,7 +72,7 @@ void usage() exit(1); } -std::auto_ptr wait_for_alert(session& s, int alert_type) +std::auto_ptr wait_for_alert(lt::session& s, int alert_type) { std::auto_ptr ret; bool found = false; @@ -121,7 +122,7 @@ void put_string(entry& e, boost::array& sig, boost::uint64_t& seq , sig.data()); } -void bootstrap(session& s) +void bootstrap(lt::session& s) { printf("bootstrapping\n"); wait_for_alert(s, dht_bootstrap_alert::alert_type); @@ -157,10 +158,11 @@ int main(int argc, char* argv[]) return 0; } - session s; - s.set_alert_mask(0xffffffff); + settings_pack sett; + sett.set_int(settings_pack::alert_mask, 0xffffffff); + lt::session s(sett); - s.add_dht_router(std::pair("54.205.98.145", 10000)); + s.add_dht_router(std::pair("router.utorrent.com", 6881)); FILE* f = fopen(".dht", "rb"); if (f != NULL) @@ -310,7 +312,7 @@ int main(int argc, char* argv[]) } entry e; - s.save_state(e, session::save_dht_state); + s.save_state(e, lt::session::save_dht_state); std::vector state; bencode(std::back_inserter(state), e); f = fopen(".dht", "wb+"); diff --git a/tools/parse_access_log.cpp b/tools/parse_access_log.cpp new file mode 100644 index 000000000..67e97e848 --- /dev/null +++ b/tools/parse_access_log.cpp @@ -0,0 +1,180 @@ +/* + +Copyright (c) 2011, 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. + +*/ + +#include "libtorrent/config.hpp" +#include "libtorrent/io.hpp" +#include +#include +#include +#include +#include +#include +#include + +using namespace libtorrent; +using namespace libtorrent::detail; // for write_* and read_* + +void print_usage() +{ + fprintf(stderr, "usage: parse_access_log log-file\n\n" + "prints a gnuplot readable data file to stdout\n"); + exit(1); +} + +struct file_op +{ + boost::uint64_t timestamp; + boost::uint64_t offset; + boost::uint8_t event; +}; + +int main(int argc, char* argv[]) +{ + if (argc != 2) print_usage(); + + FILE* log_file = fopen(argv[1], "r"); + if (log_file == 0) + { + fprintf(stderr, "failed to open logfile: %s\n%d: %s\n" + , argv[1], errno, strerror(errno)); + return 1; + } + + FILE* writes_file = fopen("writes.log", "w+"); + FILE* reads_file = fopen("reads.log", "w+"); + + FILE* writes_elev_file = fopen("writes_elevator.log", "w+"); + FILE* reads_elev_file = fopen("reads_elevator.log", "w+"); + + + typedef std::map op_map; + op_map outstanding_ops; + + boost::uint64_t first_timestamp = 0; + + for (;;) + { + char entry[21]; + char* ptr = entry; + int ret = fread(&entry, 1, sizeof(entry), log_file); + if (ret != sizeof(entry)) break; + + file_op op; + op.timestamp = read_uint64(ptr); + op.offset = read_uint64(ptr); + boost::uint32_t event_id = read_uint32(ptr); + op.event = read_uint8(ptr); + + if (first_timestamp == 0) first_timestamp = op.timestamp; + + bool write = op.event & 1; + bool complete = op.event & 2; + FILE* out_file = 0; + if (complete) + { + op_map::iterator i = outstanding_ops.find(event_id); + if (i != outstanding_ops.end()) + { + if (i->second.timestamp > op.timestamp) + { + fprintf(stderr, "end-event stamped before " + "start-event: %" PRId64 " started at: %f\n" + , op.offset, double(i->second.timestamp) / 1000000.f); + i->second.timestamp = op.timestamp; + } + + out_file = write ? writes_file : reads_file; + double start_time = double(i->second.timestamp - first_timestamp) / 1000000.0; + double end_time = double(op.timestamp - first_timestamp) / 1000000.0; + double duration_time = double(op.timestamp - i->second.timestamp) / 1000000.0; + fprintf(out_file, "%f\t%" PRId64 "\t%f\n" + , start_time, op.offset, duration_time); + + out_file = write ? writes_elev_file : reads_elev_file; + fprintf(out_file, "%f\t%" PRId64 "\n", end_time, op.offset); + + outstanding_ops.erase(i); + } + else + { + fprintf(stderr, "no start event for (%u): %" PRId64 " ended at: %f\n" + , event_id, op.offset, double(op.timestamp) / 1000000.f); + } + } + else + { + op_map::iterator i = outstanding_ops.find(event_id); + if (i != outstanding_ops.end()) + { + fprintf(stderr, "duplicate start event for (%u): %" PRId64 " at: %f" + "(current start is at: %f)\n" + , event_id, op.offset, double(i->second.timestamp - first_timestamp) / 1000000.f + , double(op.timestamp - first_timestamp) / 1000000.f); + } + else + { + outstanding_ops[event_id] = op; + } + } + } + + fclose(writes_file); + fclose(reads_file); + fclose(writes_elev_file); + fclose(reads_elev_file); + fclose(log_file); + + FILE* gnuplot = fopen("file_access.gnuplot", "w+"); + + char const* gnuplot_file = + "set term png size 1400,1024\n" + "set output \"file_access.png\"\n" + "set xlabel \"time (s)\"\n" + "set ylabel \"file offset\"\n" + "set style line 1 lc rgb \"#ff8888\"\n" + "set style line 2 lc rgb \"#88ff88\"\n" + "set style arrow 1 nohead ls 1\n" + "set style arrow 2 nohead ls 2\n" + "plot \"writes.log\" using 1:2:3:(0) title \"writes\" with vectors arrowstyle 1, " + "\"reads.log\" using 1:2:3:(0) title \"reads\" with vectors arrowstyle 2\n"; + + fwrite(gnuplot_file, strlen(gnuplot_file), 1, gnuplot); + fclose(gnuplot); + + system("gnuplot file_access.gnuplot"); + + assert(outstanding_ops.empty()); + + return 0; +} + + diff --git a/tools/parse_disk_buffer_log.py b/tools/parse_disk_buffer_log.py index e914b752f..509703fa1 100755 --- a/tools/parse_disk_buffer_log.py +++ b/tools/parse_disk_buffer_log.py @@ -79,12 +79,13 @@ print >>out, "set key box" print >>out, 'plot', count = 1 + len(keys) keys.reverse() +comma = '' for k in keys: expr = "$%d" % count for i in xrange(2, count): expr += "+$%d" % i count -= 1 - print >>out, ' "disk_buffer_log.dat" using 1:(%s) title "%s" with filledcurves x1 lt rgb "#%s",' % (expr, k, colors[count-1]), -print >>out, 'x=0' + print >>out, ' %s"disk_buffer_log.dat" using 1:(%s) title "%s" with filledcurves x1 lt rgb "#%s"' % (comma, expr, k, colors[count-1]), + comma = ',' out.close() os.system('gnuplot disk_buffer.gnuplot') diff --git a/tools/parse_sample.py b/tools/parse_sample.py index 6310eb38c..afb254a78 100644 --- a/tools/parse_sample.py +++ b/tools/parse_sample.py @@ -54,6 +54,9 @@ for l in f: print output if 'invariant_checker_impl' in fun: fold = indentation + if 'free_multiple_buffers' in fun: fold = indentation + if 'libtorrent::condition::wait' in fun: fold = indentation + if 'allocate_buffer' in fun: fold = indentation if '::find_POD' in fun: fold = indentation if 'SHA1_Update' in fun: fold = indentation if 'boost::detail::function::basic_vtable' in fun: fold = indentation @@ -65,6 +68,8 @@ for l in f: if 'mp_exptmod' == fun: fold = indentation if '::check_invariant()' in fun: fold = indentation if 'libtorrent::condition::wait' in fun: fold = indentation + if '_sigtramp' in fun: fold = indentation + if 'time_now_hires' in fun: fold = indentation if 'libtorrent::sleep' in fun: fold = indentation if 'puts' == fun: fold = indentation if 'boost::asio::basic_stream_socket' in fun: fold = indentation @@ -89,6 +94,7 @@ for l in f: if 'BN_mod_exp' == fun: fold = indentation if 'BN_CTX_free' == fun: fold = indentation if 'cerror' == fun: fold = indentation + if '0xffffffff' == fun: fold = indentation list = [] for k in fun_samples: diff --git a/tools/parse_session_stats.py b/tools/parse_session_stats.py index 7406d7e21..e6b4c7052 100755 --- a/tools/parse_session_stats.py +++ b/tools/parse_session_stats.py @@ -3,10 +3,10 @@ # subject to 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) -import os, sys, time, os +import os, sys, time, os, math from multiprocessing.pool import ThreadPool -thread_pool = ThreadPool(4) +thread_pool = ThreadPool(8) stat = open(sys.argv[1]) line = stat.readline() @@ -20,6 +20,7 @@ output_dir = 'session_stats_report' line_graph = 0 histogram = 1 stacked = 2 +diff = 3 graph_colors = [] @@ -47,12 +48,42 @@ for i in range(0,len(pattern) * 3): line_colors = list(graph_colors) line_colors.reverse() +gradient16_colors = [] +for i in range(0, 16): + f = i / 16. + pi = 3.1415927 + r = max(int(255 * (math.sin(f*pi)+0.2)), 0) + g = max(int(255 * (math.sin((f-0.5)*pi)+0.2)), 0) + b = max(int(255 * (math.sin((f+0.5)*pi)+0.2)), 0) + c = '#%02x%02x%02x' % (min(r, 255), min(g, 255), min(b, 255)) + gradient16_colors.append(c) + +gradient18_colors = [] +for i in range(0, 18): + f = i / 18. + pi = 3.1415927 + r = max(int(255 * (math.sin(f*pi)+0.2)), 0) + g = max(int(255 * (math.sin((f-0.5)*pi)+0.2)), 0) + b = max(int(255 * (math.sin((f+0.5)*pi)+0.2)), 0) + c = '#%02x%02x%02x' % (min(r, 255), min(g, 255), min(b, 255)) + gradient18_colors.append(c) + +gradient6_colors = [] +for i in range(0, 6): + f = i / 6. + c = '#%02x%02x%02x' % (min(int(255 * (-2 * f + 2)), 255), min(int(255 * (2 * f)), 255), 100) + gradient6_colors.append(c) + def plot_fun(script): - os.system('gnuplot "%s" 2>/dev/null' % script); + ret = os.system('gnuplot "%s" 2>/dev/null' % script) + if ret != 0 and ret != 256: + print 'system: %d\n' % ret + raise Exception("abort") + sys.stdout.write('.') sys.stdout.flush() -def gen_report(name, unit, lines, short_unit, generation, log_file, type=line_graph): +def gen_report(name, unit, lines, short_unit, generation, log_file, options): try: os.mkdir(output_dir) except: pass @@ -76,16 +107,33 @@ def gen_report(name, unit, lines, short_unit, generation, log_file, type=line_gr out = open(script, 'wb') print >>out, "set term png size 1200,700" print >>out, 'set output "%s"' % filename - print >>out, 'set yrange [0:*]' + if not 'allow-negative' in options: + print >>out, 'set yrange [0:*]' print >>out, "set tics nomirror" print >>out, "set key box" - if type == histogram: - binwidth = 0.005; + print >>out, "set key left top" + + colors = graph_colors + if options['type'] == line_graph: + colors = line_colors + + try: + if options['colors'] == 'gradient16': + colors = gradient16_colors + elif options['colors'] == 'gradient6': + colors = gradient6_colors + if options['colors'] == 'gradient18': + colors = gradient18_colors + except: pass + + if options['type'] == histogram: + binwidth = options['binwidth'] + numbins = int(options['numbins']) print >>out, 'binwidth=%f' % binwidth print >>out, 'set boxwidth binwidth' print >>out, 'bin(x,width)=width*floor(x/width) + binwidth/2' - print >>out, 'set xrange [0:%f]' % (binwidth * 100) + print >>out, 'set xrange [0:%f]' % (binwidth * numbins) print >>out, 'set xlabel "%s"' % unit print >>out, 'set ylabel "number"' @@ -100,7 +148,7 @@ def gen_report(name, unit, lines, short_unit, generation, log_file, type=line_gr print >>out, '' print >>out, '' - elif type == stacked: + elif options['type'] == stacked: print >>out, 'set xrange [0:*]' print >>out, 'set ylabel "%s"' % unit print >>out, 'set xlabel "time (s)"' @@ -109,7 +157,6 @@ def gen_report(name, unit, lines, short_unit, generation, log_file, type=line_gr print >>out, 'plot', column = 2 first = True - prev = '' graph = '' plot_expression = '' color = 0 @@ -124,10 +171,32 @@ def gen_report(name, unit, lines, short_unit, generation, log_file, type=line_gr graph += '+' axis = 'x1y1' graph += '$%d' % column - plot_expression = ' "%s" using 1:(%s) title "%s" axes %s with filledcurves y1=0 lc rgb "%s"' % (log_file, graph, k, axis, graph_colors[color % len(graph_colors)]) + plot_expression + plot_expression = ' "%s" using 1:(%s) title "%s" axes %s with filledcurves y1=0 lc rgb "%s"' % (log_file, graph, k, axis, colors[color % len(colors)]) + plot_expression first = False color += 1 print >>out, plot_expression + elif options['type'] == diff: + print >>out, 'set xrange [0:*]' + print >>out, 'set ylabel "%s"' % unit + print >>out, 'set xlabel "time (s)"' + print >>out, 'set format y "%%.1s%%c%s";' % short_unit + column = 2 + first = True + graph = '' + title = '' + for k in lines: + try: + column = keys.index(k) + 2 + except: + print '"%s" not found' % k + continue; + if not first: + graph += '-' + title += ' - ' + graph += '$%d' % column + title += k + first = False + print >>out, 'plot "%s" using 1:(%s) title "%s" with step' % (log_file, graph, title) else: print >>out, 'set xrange [0:*]' print >>out, 'set ylabel "%s"' % unit @@ -145,7 +214,7 @@ def gen_report(name, unit, lines, short_unit, generation, log_file, type=line_gr continue; if not first: print >>out, ', ', axis = 'x1y1' - print >>out, ' "%s" using 1:%d title "%s" axes %s with steps lc rgb "%s"' % (log_file, column, k, axis, line_colors[color % len(line_colors)]), + print >>out, ' "%s" using 1:%d title "%s" axes %s with steps lc rgb "%s"' % (log_file, column, k, axis, colors[color % len(colors)]), first = False color += 1 print >>out, '' @@ -190,67 +259,111 @@ def gen_html(reports, generations): reports = [ ('torrents', 'num', '', 'number of torrents in different torrent states', ['downloading torrents', 'seeding torrents', \ 'checking torrents', 'stopped torrents', 'upload-only torrents', 'error torrents', 'queued seed torrents', \ - 'queued download torrents'], stacked), + 'queued download torrents'], {'type':stacked}), ('torrents_want_peers', 'num', '', 'number of torrents that want more peers', ['torrents want more peers']), - ('peers', 'num', '', 'num connected peers', ['peers', 'connecting peers', 'connection attempts', 'banned peers', 'total peers']), - ('peers_max', 'num', '', 'num connected peers', ['peers', 'connecting peers', 'connection attempts', 'banned peers', 'max connections', 'total peers']), + ('peers', 'num', '', 'num connected peers', ['peers', 'connecting peers'], {'type':stacked}), + ('peers_max', 'num', '', 'num connected peers', ['peers', 'connecting peers', 'max connections', 'total peers']), ('peer_churn', 'num', '', 'connecting and disconnecting peers', ['connecting peers', 'connection attempts']), + ('new_peers', 'num', '', '', ['incoming connections', 'connection attempts']), + ('connection_attempts', 'num', '', '', ['connection attempt loops', 'connection attempts']), ('peer_limits', 'num', '', 'number of connections per limit', ['average peers per limit']), + ('pieces', 'num', '', 'number completed pieces', ['total pieces', 'pieces flushed', 'pieces passed', 'pieces failed']), ('connect_candidates', 'num', '', 'number of peers we know of that we can connect to', ['connect candidates']), ('peers_list_size', 'num', '', 'number of known peers (not necessarily connected)', ['num list peers']), ('overall_rates', 'rate', 'B/s', 'download and upload rates', ['uploaded bytes', 'downloaded bytes', 'upload rate', 'download rate', 'smooth upload rate', 'smooth download rate']), - ('disk_write_queue', 'Bytes', 'B', 'bytes queued up by peers, to be written to disk', ['disk write queued bytes', 'disk queue limit', 'disk queue low watermark']), + ('disk_write_queue', 'Bytes', 'B', 'bytes queued up by peers, to be written to disk', ['disk write queued bytes']), ('peers_requests', 'num', '', 'incoming piece request rate', ['piece requests', 'piece rejects', 'max piece requests', 'invalid piece requests', 'choked piece requests', 'cancelled piece requests']), - ('peers_upload', 'num', '', 'number of peers by state wrt. uploading', ['peers up interested', 'peers up unchoked', 'peers up requests', 'peers disk-up', 'peers up send buffer', 'peers bw-up', 'max unchoked']), + ('peers_upload_max', 'num', '', 'number of peers by state wrt. uploading', ['peers up interested', 'peers up unchoked', 'peers up requests', 'peers disk-up', 'peers up send buffer', 'peers bw-up', 'max unchoked']), + ('peers_upload', 'num', '', 'number of peers by state wrt. uploading', ['peers up interested', 'peers up unchoked', 'peers up requests', 'peers disk-up', 'peers up send buffer', 'peers bw-up']), ('peers_download', 'num', '', 'number of peers by state wrt. downloading', ['peers down interesting', 'peers down unchoked', 'peers down requests', 'peers disk-down', 'peers bw-down','num end-game peers']), ('peer_errors', 'num', '', 'number of peers by error that disconnected them', ['error peers', 'peer disconnects', 'peers eof', 'peers connection reset', 'connect timeouts', 'uninteresting peers disconnect', 'banned for hash failure', 'no memory peer errors', 'too many peers', 'transport timeout peers', 'connection refused peers', 'connection aborted peers', 'permission denied peers', 'no buffer peers', 'host unreachable peers', 'broken pipe peers', 'address in use peers', 'access denied peers', 'invalid argument peers', 'operation aborted peers']), ('peer_errors_incoming', 'num', '', 'number of peers by incoming or outgoing connection', ['error incoming peers', 'error outgoing peers']), ('peer_errors_transport', 'num', '', 'number of peers by transport protocol', ['error tcp peers', 'error utp peers']), ('peer_errors_encryption', 'num', '', 'number of peers by encryption level', ['error encrypted peers', 'error rc4 peers', 'peer disconnects']), ('incoming requests', 'num', '', 'incoming 16kiB block requests', ['pending incoming block requests', 'average pending incoming block requests']), - ('waste', '% of all downloaded bytes', '%%', 'proportion of all downloaded bytes that were wasted', ['% failed payload bytes', '% wasted payload bytes', '% protocol bytes'], stacked), - ('waste by source', '% of all wasted bytes', '%%', 'what\' causing the waste', [ 'redundant timed-out', 'redundant cancelled', 'redundant unknown', 'redundant seed', 'redundant end-game', 'redundant closing'], stacked), - ('average_disk_time_absolute', 'job time', 's', 'running averages of timings of disk operations', ['disk read time', 'disk write time', 'disk hash time', 'disk job time', 'disk sort time']), - ('average_disk_queue_time', 'job queued time', 's', 'running averages of disk queue time', ['disk queue time', 'disk job time']), - ('disk_time', '% of total disk job time', '%%', 'proportion of time spent by the disk thread', ['% read time', '% write time', '% hash time', '% sort time'], stacked), - ('disk_cache_hits', 'blocks (16kiB)', '', '', ['disk block read', 'read cache hits', 'disk block written', 'disk read back']), - ('disk_cache', 'blocks (16kiB)', '', 'disk cache size and usage', ['disk buffer allocations', 'read disk cache size', 'disk cache size', 'cache size']), + ('disk_write_time', 'write time', 's', 'distribution of write jobs timing', ['disk write time'], {'type': histogram, 'binwidth': 0.1, 'numbins': 400}), + ('disk_read_time', 'read time', 's', 'distribution of read jobs timing', ['disk read time'], {'type': histogram, 'binwidth': 0.1, 'numbins': 400}), + ('waste', '% of all downloaded bytes', '%%', 'proportion of all downloaded bytes that were wasted', ['% failed payload bytes', '% wasted payload bytes', '% protocol bytes'], {'type':stacked}), + ('waste by source', '% of all wasted bytes', '%%', 'what\' causing the waste', [ 'redundant timed-out', 'redundant cancelled', 'redundant unknown', 'redundant seed', 'redundant end-game', 'redundant closing'], {'type':stacked}), + ('average_disk_time_absolute', 'job time', 's', 'running averages of timings of disk operations', ['disk read time', 'disk write time', 'disk hash time']), + ('disk_time', '% of total disk job time', '%%', 'proportion of time spent by the disk thread', ['% read time', '% write time', '% hash time'], {'type': stacked}), + ('disk_cache_hits', 'blocks (16kiB)', '', '', ['disk block read', 'read cache hits'], {'type':stacked}), + ('disk_cache', 'blocks (16kiB)', '', 'disk cache size and usage', ['disk buffer allocations', 'read disk cache size', 'disk cache size', 'cache size', 'pinned blocks', 'cache trim low watermark']), ('disk_readback', '% of written blocks', '%%', 'portion of written blocks that had to be read back for hash verification', ['% read back']), - ('disk_queue', 'number of queued disk jobs', '', 'queued disk jobs', ['disk queue size', 'disk read queue size', 'read job queue size limit']), + ('disk_queue', 'number of queued disk jobs', '', 'queued disk jobs', ['disk queue size', 'disk read queue size', 'allocated jobs', 'allocated read jobs', 'allocated write jobs']), ('disk_iops', 'operations/s', '', 'number of disk operations per second', ['read ops/s', 'write ops/s', 'smooth read ops/s', 'smooth write ops/s']), ('disk pending reads', 'Bytes', '', 'number of bytes peers are waiting for to be read from the disk', ['pending reading bytes']), + ('disk fences', 'num', '', 'number of jobs currently blocked by a fence job', ['blocked jobs']), + ('fence jobs', 'num', '', 'active fence jobs per type', ['move_storage', 'release_files', 'delete_files', 'check_fastresume', 'save_resume_data', 'rename_file', 'stop_torrent', 'file_priority', 'clear_piece'], {'type':stacked}), + ('disk threads', 'num', '', 'number of disk threads currently writing', ['num writing threads', 'num running threads']), ('mixed mode', 'rate', 'B/s', 'rates by transport protocol', ['TCP up rate','TCP down rate','uTP up rate','uTP down rate','TCP up limit','TCP down limit']), ('connection_type', 'num', '', 'peers by transport protocol', ['utp peers','tcp peers']), ('uTP delay', 'buffering delay', 's', 'network delays measured by uTP', ['uTP peak send delay','uTP peak recv delay', 'uTP avg send delay', 'uTP avg recv delay']), - ('uTP send delay histogram', 'buffering delay', 's', 'send delays measured by uTP', ['uTP avg send delay'], histogram), - ('uTP recv delay histogram', 'buffering delay', 's', 'receive delays measured by uTP', ['uTP avg recv delay'], histogram), - ('uTP stats', 'num', '', 'number of uTP sockets by state', ['uTP idle', 'uTP syn-sent', 'uTP connected', 'uTP fin-sent', 'uTP close-wait'], stacked), - ('system memory', '', '', 'virtual memory page count', ['active resident pages', 'inactive resident pages', 'pinned resident pages', 'free pages'], stacked), + ('uTP send delay histogram', 'buffering delay', 's', 'send delays measured by uTP', ['uTP avg send delay'], {'type': histogram, 'binwidth': 0.05, 'numbins': 100}), + ('uTP recv delay histogram', 'buffering delay', 's', 'receive delays measured by uTP', ['uTP avg recv delay'], {'type': histogram, 'binwidth': 0.05, 'numbins': 100}), + ('uTP stats', 'num', '', 'number of uTP sockets by state', ['uTP idle', 'uTP syn-sent', 'uTP connected', 'uTP fin-sent', 'uTP close-wait'], {'type': stacked}), + ('system memory', '', '', 'virtual memory page count', ['active resident pages', 'inactive resident pages', 'pinned resident pages', 'free pages'], {'type': stacked}), ('memory paging', '', '', 'vm disk activity', ['pageins', 'pageouts']), ('page faults', '', '', '', ['page faults']), ('CPU usage', '%', '', '', ['network thread system time', 'network thread user+system time']), ('boost.asio messages', 'events/s', '', 'number of messages posted per second', [ \ 'read_counter', 'write_counter', 'tick_counter', 'lsd_counter', \ 'lsd_peer_counter', 'udp_counter', 'accept_counter', 'disk_queue_counter', \ - 'disk_read_counter', 'disk_write_counter'], stacked), + 'disk_counter'], {'type': stacked}), ('send_buffer_sizes', 'num', '', '', ['up 8', 'up 16', 'up 32', 'up 64', 'up 128', 'up 256', \ 'up 512', 'up 1024', 'up 2048', 'up 4096', 'up 8192', 'up 16384', 'up 32768', 'up 65536', \ - 'up 131072', 'up 262144', 'up 524288', 'up 1048576'], stacked), + 'up 131072', 'up 262144', 'up 524288', 'up 1048576'], {'type': stacked, 'colors':'gradient18'}), ('recv_buffer_sizes', 'num', '', '', ['down 8', 'down 16', 'down 32', 'down 64', 'down 128', \ 'down 256', 'down 512', 'down 1024', 'down 2048', 'down 4096', 'down 8192', 'down 16384', \ - 'down 32768', 'down 65536', 'down 131072', 'down 262144', 'down 524288', 'down 1048576'], stacked), + 'down 32768', 'down 65536', 'down 131072', 'down 262144', 'down 524288', 'down 1048576'], {'type': stacked, 'colors':'gradient18'}), + ('ARC', 'num pieces', '', '', ['arc LRU ghost pieces', 'arc LRU pieces', 'arc LRU volatile pieces', 'arc LFU pieces', 'arc LFU ghost pieces'], {'allow-negative': True}), + ('torrent churn', 'num torrents', '', '', ['loaded torrents', 'pinned torrents', 'loaded torrent churn']), + ('pinned torrents', 'num torrents', '', '', ['pinned torrents']), + ('loaded torrents', 'num torrents', '', '', ['loaded torrents', 'pinned torrents']), + ('requests', '', '', '', ['outstanding requests']), + ('incoming messages', 'num', '', 'number of received bittorrent messages, by type', [ \ + 'num_incoming_choke', 'num_incoming_unchoke', 'num_incoming_interested', \ + 'num_incoming_not_interested', 'num_incoming_have', 'num_incoming_bitfield', \ + 'num_incoming_request', 'num_incoming_piece', 'num_incoming_cancel', \ + 'num_incoming_dht_port', 'num_incoming_suggest', 'num_incoming_have_all', \ + 'num_incoming_have_none', 'num_incoming_reject', 'num_incoming_allowed_fast', \ + 'num_incoming_ext_handshake', 'num_incoming_pex', 'num_incoming_metadata', 'num_incoming_extended' \ + ], {'type': stacked}), + ('outgoing messages', 'num', '', 'number of sent bittorrent messages, by type', [ \ + 'num_outgoing_choke', 'num_outgoing_unchoke', 'num_outgoing_interested', \ + 'num_outgoing_not_interested', 'num_outgoing_have', 'num_outgoing_bitfield', \ + 'num_outgoing_request', 'num_outgoing_piece', 'num_outgoing_cancel', \ + 'num_outgoing_dht_port', 'num_outgoing_suggest', 'num_outgoing_have_all', \ + 'num_outgoing_have_none', 'num_outgoing_reject', 'num_outgoing_allowed_fast', \ + 'num_outgoing_ext_handshake', 'num_outgoing_pex', 'num_outgoing_metadata', 'num_outgoing_extended' \ + ], {'type': stacked}), + ('request in balance', 'num', '', 'request and piece message balance', [ \ + 'num_incoming_request', 'num_outgoing_piece', \ + 'num_outgoing_reject' \ + ], {'type': diff}), + ('request out balance', 'num', '', 'request and piece message balance', [ \ + 'num_outgoing_request', 'num_incoming_piece', \ + 'num_incoming_reject' \ + ], {'type': diff}), # ('absolute_waste', 'num', '', ['failed bytes', 'redundant bytes', 'download rate']), #somewhat uninteresting stats ('tick_rate', 'time between ticks', 's', '', ['tick interval', 'tick residual']), - ('peer_dl_rates', 'num', '', 'peers split into download rate buckets', ['peers down 0', 'peers down 0-2', 'peers down 2-5', 'peers down 5-10', 'peers down 50-100', 'peers down 100-'], stacked), - ('peer_dl_rates2', 'num', '', 'peers split into download rate buckets (only downloading peers)', ['peers down 0-2', 'peers down 2-5', 'peers down 5-10', 'peers down 50-100', 'peers down 100-'], stacked), - ('peer_ul_rates', 'num', '', 'peers split into upload rate buckets', ['peers up 0', 'peers up 0-2', 'peers up 2-5', 'peers up 5-10', 'peers up 50-100', 'peers up 100-'], stacked), - ('peer_ul_rates2', 'num', '', 'peers split into upload rate buckets (only uploading peers)', ['peers up 0-2', 'peers up 2-5', 'peers up 5-10', 'peers up 50-100', 'peers up 100-'], stacked), - ('piece_picker_end_game', 'blocks', '', '', ['end game piece picker blocks', 'piece picker blocks', \ - 'piece picks', 'reject piece picks', 'unchoke piece picks', 'incoming redundant piece picks', \ - 'incoming piece picks', 'end game piece picks', 'snubbed piece picks'], stacked), - ('piece_picker', 'blocks', '', '', ['piece picks', 'reject piece picks', 'unchoke piece picks', 'incoming redundant piece picks', 'incoming piece picks', 'end game piece picks', 'snubbed piece picks'], stacked), + ('peer_dl_rates', 'num', '', 'peers split into download rate buckets', ['peers down 0', 'peers down 0-2', 'peers down 2-5', 'peers down 5-10', 'peers down 50-100', 'peers down 100-'], {'type':stacked, 'colors':'gradient6'}), + ('peer_dl_rates2', 'num', '', 'peers split into download rate buckets (only downloading peers)', ['peers down 0-2', 'peers down 2-5', 'peers down 5-10', 'peers down 50-100', 'peers down 100-'], {'type':stacked, 'colors':'gradient6'}), + ('peer_ul_rates', 'num', '', 'peers split into upload rate buckets', ['peers up 0', 'peers up 0-2', 'peers up 2-5', 'peers up 5-10', 'peers up 50-100', 'peers up 100-'], {'type':stacked, 'colors':'gradient6'}), + ('peer_ul_rates2', 'num', '', 'peers split into upload rate buckets (only uploading peers)', ['peers up 0-2', 'peers up 2-5', 'peers up 5-10', 'peers up 50-100', 'peers up 100-'], {'type':stacked, 'colors':'gradient6'}), + ('piece_picker_invocations', 'invocations of piece picker', '', '', ['reject piece picks', \ + 'unchoke piece picks', 'incoming redundant piece picks', \ + 'incoming piece picks', 'end game piece picks', 'snubbed piece picks', 'interesting piece picks', 'hash fail piece picks'], \ + {'type':stacked}), + ('piece_picker_loops', 'loops through piece picker', '', '', [ \ + 'piece_picker_partial_loops', 'piece_picker_suggest_loops', 'piece_picker_sequential_loops', 'piece_picker_reverse_rare_loops', + 'piece_picker_rare_loops', 'piece_picker_rand_start_loops', 'piece_picker_rand_loops', 'piece_picker_busy_loops'], {'type': stacked}), + ('picker_partials', 'pieces', '', '', ['num downloading partial pieces', 'num full partial pieces', 'num finished partial pieces', \ + 'num 0-priority partial pieces'], {'type':stacked}), + ('picker_full_partials_distribution', 'full pieces', '', '', ['num full partial pieces'], {'type': histogram, 'binwidth': 5, 'numbins': 120}), + ('picker_partials_distribution', 'partial pieces', '', '', ['num downloading partial pieces'], {'type': histogram, 'binwidth': 5, 'numbins': 120}) ] print 'generating graphs' @@ -263,10 +376,12 @@ scripts = [] while os.path.exists(os.path.join(log_file_path, log_file)): print '[%s] %04d\r[' % (' ' * len(reports), g), for i in reports: - type = line_graph - try: type = i[5] - except: pass - script = gen_report(i[0], i[1], i[4], i[2], g, os.path.join(log_file_path, log_file), type) + try: options = i[5] + except: options = {} + if not 'type' in options: + options['type'] = line_graph + + script = gen_report(i[0], i[1], i[4], i[2], g, os.path.join(log_file_path, log_file), options) if script != None: scripts.append(script) generations.append(g) g += 1 @@ -275,6 +390,9 @@ while os.path.exists(os.path.join(log_file_path, log_file)): # run gnuplot on all scripts, in parallel thread_pool.map(plot_fun, scripts) + print '' # newline + scripts = [] + print '\ngenerating html' gen_html(reports, generations) diff --git a/tools/run_benchmark.py b/tools/run_benchmark.py index e34fcc662..2e1bebce5 100644 --- a/tools/run_benchmark.py +++ b/tools/run_benchmark.py @@ -38,6 +38,9 @@ except: pass def run_test(name, test_cmd, client_arg, num_peers): output_dir = 'logs_%s' % name + + try: shutil.rmtree(output_dir) + except: pass try: os.mkdir(output_dir) except: pass @@ -49,7 +52,7 @@ def run_test(name, test_cmd, client_arg, num_peers): except: pass start = time.time(); - client_cmd = '../examples/client_test -p %d cpu_benchmark.torrent -0 -k -z -H -X -q 120 %s -h -c %d -T %d -C %d -f %s/events.log' \ + client_cmd = '../examples/client_test -p %d cpu_benchmark.torrent -k -z -H -X -q 120 %s -h -c %d -T %d -C %d -f %s/events.log' \ % (port, client_arg, num_peers*2, num_peers*2, cache_size * 16, output_dir) test_cmd = '../examples/connection_tester %s -c %d -d 127.0.0.1 -p %d -t cpu_benchmark.torrent' % (test_cmd, num_peers, port) @@ -65,9 +68,13 @@ def run_test(name, test_cmd, client_arg, num_peers): end = time.time(); - c.communicate('q') + try: c.communicate('q') + except: pass c.wait() + client_out.close(); + test_out.close(); + print 'runtime %d seconds' % (end - start) print 'analyzing proile...' os.system('gprof ../examples/client_test >%s/gprof.out' % output_dir) diff --git a/tools/run_tests.py b/tools/run_tests.py index 8b17e1c1f..2716f130b 100755 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -107,8 +107,7 @@ def run_tests(toolset, tests, features, options, test_dir, time_limit): options_copy.remove('launcher=valgrind') cmdline = ['bjam', '--out-xml=%s' % xml_file, '-l%d' % time_limit, \ '-q', '--abbreviate-paths', toolset] + options_copy + feature_list - -# print ' '.join(cmdline) +# print 'calling ', cmdline p = subprocess.Popen(cmdline, stdout=subprocess.PIPE, cwd=test_dir) output = ''