Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package libtorrent for openSUSE:Factory 
checked in at 2026-01-05 14:52:34
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/libtorrent (Old)
 and      /work/SRC/openSUSE:Factory/.libtorrent.new.1928 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "libtorrent"

Mon Jan  5 14:52:34 2026 rev:27 rq:1325243 version:0.16.6

Changes:
--------
--- /work/SRC/openSUSE:Factory/libtorrent/libtorrent.changes    2025-12-03 
14:15:03.284964974 +0100
+++ /work/SRC/openSUSE:Factory/.libtorrent.new.1928/libtorrent.changes  
2026-01-05 14:54:02.580485338 +0100
@@ -1,0 +2,11 @@
+Sat Jan  3 18:10:37 UTC 2026 - Jan Engelhardt <[email protected]>
+
+- Update to release 0.16.6
+  * Session saves are now handled in a separate thread.
+  * Downloading of magnet links now save the metadata file to
+    session directory by default.
+  * New Timestamp Helper Commands: Added ``.or_zero`` and
+    ``.elapsed`` commands for ``d.timestamp.started`` and
+    ``d.timestamp.finished``.
+
+-------------------------------------------------------------------

Old:
----
  libtorrent-0.16.5.tar.gz

New:
----
  libtorrent-0.16.6.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ libtorrent.spec ++++++
--- /var/tmp/diff_new_pack.N3GpKH/_old  2026-01-05 14:54:16.597068352 +0100
+++ /var/tmp/diff_new_pack.N3GpKH/_new  2026-01-05 14:54:16.597068352 +0100
@@ -16,9 +16,9 @@
 #
 
 
-%define lname  libtorrent35
+%define lname  libtorrent36
 Name:           libtorrent
-Version:        0.16.5
+Version:        0.16.6
 Release:        0
 Summary:        A BitTorrent library written in C++
 License:        SUSE-GPL-2.0+-with-openssl-exception

++++++ _scmsync.obsinfo ++++++
--- /var/tmp/diff_new_pack.N3GpKH/_old  2026-01-05 14:54:16.641070181 +0100
+++ /var/tmp/diff_new_pack.N3GpKH/_new  2026-01-05 14:54:16.645070347 +0100
@@ -1,5 +1,5 @@
-mtime: 1764752201
-commit: 994a38c7ddd491b1ef7906eedf9419b7f1e842e940708f20791fa05da5a20a0d
+mtime: 1767464045
+commit: 46e19fbca4be3b97cec57ec6bcd86a581e3fc10ab1c84f92f8dd753039de3178
 url: https://src.opensuse.org/jengelh/libtorrent
 revision: master
 

++++++ build.specials.obscpio ++++++

++++++ build.specials.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/.gitignore new/.gitignore
--- old/.gitignore      1970-01-01 01:00:00.000000000 +0100
+++ new/.gitignore      2026-01-03 19:14:19.000000000 +0100
@@ -0,0 +1 @@
+.osc

++++++ libtorrent-0.16.5.tar.gz -> libtorrent-0.16.6.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.5/configure 
new/libtorrent-0.16.6/configure
--- old/libtorrent-0.16.5/configure     2025-12-02 18:15:57.000000000 +0100
+++ new/libtorrent-0.16.6/configure     2026-01-02 15:37:45.000000000 +0100
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.72 for libtorrent 0.16.5.
+# Generated by GNU Autoconf 2.72 for libtorrent 0.16.6.
 #
 # Report bugs to <[email protected]>.
 #
@@ -614,8 +614,8 @@
 # Identity of this package.
 PACKAGE_NAME='libtorrent'
 PACKAGE_TARNAME='libtorrent'
-PACKAGE_VERSION='0.16.5'
-PACKAGE_STRING='libtorrent 0.16.5'
+PACKAGE_VERSION='0.16.6'
+PACKAGE_STRING='libtorrent 0.16.6'
 PACKAGE_BUGREPORT='[email protected]'
 PACKAGE_URL=''
 
@@ -1416,7 +1416,7 @@
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-'configure' configures libtorrent 0.16.5 to adapt to many kinds of systems.
+'configure' configures libtorrent 0.16.6 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1487,7 +1487,7 @@
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of libtorrent 0.16.5:";;
+     short | recursive ) echo "Configuration of libtorrent 0.16.6:";;
    esac
   cat <<\_ACEOF
 
@@ -1649,7 +1649,7 @@
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-libtorrent configure 0.16.5
+libtorrent configure 0.16.6
 generated by GNU Autoconf 2.72
 
 Copyright (C) 2023 Free Software Foundation, Inc.
@@ -2367,7 +2367,7 @@
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by libtorrent $as_me 0.16.5, which was
+It was created by libtorrent $as_me 0.16.6, which was
 generated by GNU Autoconf 2.72.  Invocation command line was
 
   $ $0$ac_configure_args_raw
@@ -4060,7 +4060,7 @@
 
 # Define the identity of the package.
  PACKAGE='libtorrent'
- VERSION='0.16.5'
+ VERSION='0.16.6'
 
 
 printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
@@ -13703,14 +13703,16 @@
 
 
 
+# When releasing the first 1.x.y version, we need to start with 1.1.0 (or 
higher) as we've already
+# used 0.16.x.
 
-printf "%s\n" "#define PEER_NAME \"-lt1005-\"" >>confdefs.h
+printf "%s\n" "#define PEER_NAME \"-lt1006-\"" >>confdefs.h
 
 
-printf "%s\n" "#define PEER_VERSION \"lt\\x10\\x05\"" >>confdefs.h
+printf "%s\n" "#define PEER_VERSION \"lt\\x10\\x06\"" >>confdefs.h
 
 
-LIBTORRENT_CURRENT=35
+LIBTORRENT_CURRENT=36
 LIBTORRENT_REVISION=0
 LIBTORRENT_AGE=0
 
@@ -23877,7 +23879,7 @@
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by libtorrent $as_me 0.16.5, which was
+This file was extended by libtorrent $as_me 0.16.6, which was
 generated by GNU Autoconf 2.72.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -23945,7 +23947,7 @@
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config='$ac_cs_config_escaped'
 ac_cs_version="\\
-libtorrent config.status 0.16.5
+libtorrent config.status 0.16.6
 configured by $0, generated by GNU Autoconf 2.72,
   with options \\"\$ac_cs_config\\"
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.5/configure.ac 
new/libtorrent-0.16.6/configure.ac
--- old/libtorrent-0.16.5/configure.ac  2025-12-02 18:15:46.000000000 +0100
+++ new/libtorrent-0.16.6/configure.ac  2026-01-02 15:37:32.000000000 +0100
@@ -1,4 +1,4 @@
-AC_INIT([[libtorrent]],[[0.16.5]],[[[email protected]]])
+AC_INIT([[libtorrent]],[[0.16.6]],[[[email protected]]])
 
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_MACRO_DIRS([scripts])
@@ -6,10 +6,12 @@
 
 LT_INIT([[disable-static]])
 
-AC_DEFINE([[PEER_NAME]], [["-lt1005-"]], [[Identifier that is part of the 
default peer id.]])
-AC_DEFINE([[PEER_VERSION]], [["lt\x10\x05"]], [[4 byte client and version 
identifier for DHT.]])
+# When releasing the first 1.x.y version, we need to start with 1.1.0 (or 
higher) as we've already
+# used 0.16.x.
+AC_DEFINE([[PEER_NAME]], [["-lt1006-"]], [[Identifier that is part of the 
default peer id.]])
+AC_DEFINE([[PEER_VERSION]], [["lt\x10\x06"]], [[4 byte client and version 
identifier for DHT.]])
 
-LIBTORRENT_CURRENT=35
+LIBTORRENT_CURRENT=36
 LIBTORRENT_REVISION=0
 LIBTORRENT_AGE=0
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.5/src/data/hash_torrent.cc 
new/libtorrent-0.16.6/src/data/hash_torrent.cc
--- old/libtorrent-0.16.5/src/data/hash_torrent.cc      2025-12-02 
18:15:46.000000000 +0100
+++ new/libtorrent-0.16.6/src/data/hash_torrent.cc      2026-01-02 
15:37:32.000000000 +0100
@@ -140,7 +140,7 @@
       }
 
       if (handle.error_number().is_valid() && handle.error_number().value() != 
rak::error_number::e_noent) {
-        LT_LOG_THIS(DEBUG, "Return on handle errno == E_NOENT: position:%u.", 
m_position);
+        LT_LOG_THIS(DEBUG, "Return on handle errno != E_NOENT: position:%u.", 
m_position);
         return;
       }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.5/src/net/curl_socket.cc 
new/libtorrent-0.16.6/src/net/curl_socket.cc
--- old/libtorrent-0.16.5/src/net/curl_socket.cc        2025-12-02 
18:15:46.000000000 +0100
+++ new/libtorrent-0.16.6/src/net/curl_socket.cc        2026-01-02 
15:37:32.000000000 +0100
@@ -3,6 +3,7 @@
 #include "curl_socket.h"
 
 #include <cassert>
+#include <unistd.h>
 #include <curl/multi.h>
 
 #include "net/curl_stack.h"
@@ -11,20 +12,33 @@
 
 namespace torrent::net {
 
+CurlSocket::CurlSocket(int fd, CurlStack* stack, CURL* easy_handle)
+  : m_stack(stack),
+    m_easy_handle(easy_handle) {
+  m_fileDesc = fd;
+}
+
 int
-CurlSocket::receive_socket([[maybe_unused]] void* easy_handle, curl_socket_t 
fd, int what, CurlStack* stack, CurlSocket* socket) {
+CurlSocket::receive_socket(CURL* easy_handle, curl_socket_t fd, int what, 
CurlStack* stack, CurlSocket* socket) {
   // We always return 0, even when stack is not running, as we depend on these 
calls to delete
   // sockets.
 
   if (what == CURL_POLL_REMOVE) {
     if (socket == nullptr)
+      throw internal_error("CurlSocket::receive_socket() called with 
CURL_POLL_REMOVE and null socket.");
+
+    // LibCurl already called close_socket().
+    if (socket->m_fileDesc == -1) {
+      delete socket;
       return 0;
+    }
 
-    // We also probably need the special code here as we're not
-    // guaranteed that the fd will be closed, afaik.
-    socket->close();
+    if (socket->m_fileDesc != fd)
+      throw internal_error("CurlSocket::receive_socket() CURL_POLL_REMOVE fd 
mismatch.");
 
+    socket->close();
     delete socket;
+
     return 0;
   }
 
@@ -32,11 +46,11 @@
     if (!stack->is_running())
       return 0;
 
-    // TODO: Assign nullptr here....
-    // TODO: Might not be getting removed ? verify fd, etc.
+    socket = new CurlSocket(fd, stack, easy_handle);
 
-    socket = new CurlSocket(fd, stack);
     curl_multi_assign(stack->handle(), fd, socket);
+    curl_easy_setopt(easy_handle, CURLOPT_CLOSESOCKETDATA, socket);
+    curl_easy_setopt(easy_handle, CURLOPT_CLOSESOCKETFUNCTION, 
&CurlSocket::close_socket);
 
     this_thread::poll()->open(socket);
     this_thread::poll()->insert_error(socket);
@@ -62,6 +76,29 @@
   return 0;
 }
 
+// When receive_socket() is called with CURL_POLL_REMOVE, we call 
CurlSocket::close() which
+// deregisters this callback.
+int
+CurlSocket::close_socket(CurlSocket* socket, curl_socket_t fd) {
+  if (socket == nullptr)
+    throw internal_error("CurlSocket::close_socket() called with null 
socket.");
+
+  if (fd != socket->m_fileDesc)
+    throw internal_error("CurlSocket::close_socket() fd mismatch.");
+
+  socket->close();
+
+  if (::close(fd) != 0)
+    throw internal_error("CurlSocket::close_socket() error closing fd.");
+
+  // We assume the socket is deleted in receive_socket() after 
CURL_POLL_REMOVE.
+  //
+  // TODO: We need to verify that libcurl calls CURL_POLL_REMOVE after this.
+
+  // delete socket;
+  return 0;
+}
+
 CurlSocket::~CurlSocket() {
   assert(m_fileDesc == -1 && "CurlSocket::~CurlSocket() m_fileDesc != -1.");
 }
@@ -69,10 +106,14 @@
 void
 CurlSocket::close() {
   if (m_fileDesc == -1)
-    throw internal_error("CurlSocket::close() m_fileDesc == -1.");
+    return;
 
   this_thread::poll()->remove_and_close(this);
 
+  // Deregister close callback for when receive_socket() is called with 
CURL_POLL_REMOVE, as this
+  // CurlSocket is deleted.
+  curl_easy_setopt(m_easy_handle, CURLOPT_CLOSESOCKETFUNCTION, nullptr);
+
   m_stack = nullptr;
   m_fileDesc = -1;
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.5/src/net/curl_socket.h 
new/libtorrent-0.16.6/src/net/curl_socket.h
--- old/libtorrent-0.16.5/src/net/curl_socket.h 2025-12-02 18:15:46.000000000 
+0100
+++ new/libtorrent-0.16.6/src/net/curl_socket.h 2026-01-02 15:37:32.000000000 
+0100
@@ -11,26 +11,28 @@
 
 class CurlSocket : public torrent::Event {
 public:
-  CurlSocket(int fd, CurlStack* stack) : m_stack(stack) { m_fileDesc = fd; }
+  CurlSocket(int fd, CurlStack* stack, CURL* easy_handle);
   ~CurlSocket() override;
 
-  const char*        type_name() const override { return "curl_socket"; }
+  const char*         type_name() const override { return "curl_socket"; }
 
-  void               close();
+  void                close();
 
-  void               event_read() override;
-  void               event_write() override;
-  void               event_error() override;
+  void                event_read() override;
+  void                event_write() override;
+  void                event_error() override;
 
-  static int         receive_socket(void* easy_handle, curl_socket_t fd, int 
what, CurlStack* userp, CurlSocket* socketp);
+  static int          receive_socket(CURL* easy_handle, curl_socket_t fd, int 
what, CurlStack* userp, CurlSocket* socketp);
+  static int          close_socket(CurlSocket* socket, curl_socket_t item);
 
 private:
   CurlSocket(const CurlSocket&) = delete;
   CurlSocket& operator=(const CurlSocket&) = delete;
 
-  void               handle_action(int ev_bitmask);
+  void                handle_action(int ev_bitmask);
 
-  CurlStack*         m_stack{};
+  CurlStack*          m_stack{};
+  CURL*               m_easy_handle{};
 };
 
 } // namespace torrent::net
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.5/src/net/listen.h 
new/libtorrent-0.16.6/src/net/listen.h
--- old/libtorrent-0.16.5/src/net/listen.h      2025-12-02 18:15:46.000000000 
+0100
+++ new/libtorrent-0.16.6/src/net/listen.h      2026-01-02 15:37:32.000000000 
+0100
@@ -13,6 +13,8 @@
 public:
   ~Listen() override { close(); }
 
+  const char*         type_name() const override { return "listen"; }
+
   static bool         open_single(Listen* listen, const sockaddr* bind_address,
                                   uint16_t first, uint16_t last, int backlog, 
bool block_ipv4in6);
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.5/src/protocol/handshake_manager.cc 
new/libtorrent-0.16.6/src/protocol/handshake_manager.cc
--- old/libtorrent-0.16.5/src/protocol/handshake_manager.cc     2025-12-02 
18:15:46.000000000 +0100
+++ new/libtorrent-0.16.6/src/protocol/handshake_manager.cc     2026-01-02 
15:37:32.000000000 +0100
@@ -110,7 +110,11 @@
   manager->connection_manager()->inc_socket_count();
 
   auto handshake = std::make_unique<Handshake>(fd, this, 
config::network_config()->encryption_options());
-  handshake->initialize_incoming(sa);
+
+  if (sa_is_v4mapped(sa))
+    handshake->initialize_incoming(sa_from_v4mapped(sa).get());
+  else
+    handshake->initialize_incoming(sa);
 
   base_type::push_back(std::move(handshake));
 }
@@ -130,18 +134,14 @@
     encryption_options &= ~net::NetworkConfig::encryption_enable_retry;
   }
 
-  create_outgoing(sa, download, encryption_options);
+  if (sa_is_v4mapped(sa))
+    create_outgoing(sa_from_v4mapped(sa).get(), download, encryption_options);
+  else
+    create_outgoing(sa, download, encryption_options);
 }
 
 void
 HandshakeManager::create_outgoing(const sockaddr* sa, DownloadMain* download, 
int encryption_options) {
-  auto connect_address = [sa]() {
-      if (sa_is_v4mapped(sa))
-        return sa_from_v4mapped(sa);
-      else
-        return sa_copy(sa);
-    }();
-
   int connection_options = PeerList::connect_keep_handshakes;
 
   if (!(encryption_options & net::NetworkConfig::encryption_retrying))
@@ -150,10 +150,11 @@
   PeerInfo* peer_info = download->peer_list()->connected(sa, 
connection_options);
 
   if (peer_info == NULL || peer_info->failed_counter() > max_failed) {
-    LT_LOG_SAP(connect_address, "rejected outgoing connection: no peer info or 
too many failures", 0);
+    LT_LOG_SA(sa, "rejected outgoing connection: no peer info or too many 
failures", 0);
     return;
   }
 
+  auto connect_address = sa_copy(sa);
   auto proxy_address = config::network_config()->proxy_address();
 
   if (proxy_address->sa_family != AF_UNSPEC) {
@@ -177,8 +178,12 @@
   else
     message = ConnectionManager::handshake_outgoing;
 
-  LT_LOG_SAP(connect_address, "created outgoing connection: fd:%i address:%s 
encryption:%x message:%x",
-             fd, sa_pretty_str(sa).c_str(), encryption_options, message);
+  if (proxy_address->sa_family != AF_UNSPEC) {
+    LT_LOG_SA(sa, "created outgoing connection via proxy: fd:%i proxy:%s 
encryption:%x message:%x",
+              fd, sa_addr_str(proxy_address.get()).c_str(), 
encryption_options, message);
+  } else {
+    LT_LOG_SA(sa, "created outgoing connection: fd:%i encryption:%x 
message:%x", fd, encryption_options, message);
+  }
 
   manager->connection_manager()->inc_socket_count();
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.5/src/torrent/common.h 
new/libtorrent-0.16.6/src/torrent/common.h
--- old/libtorrent-0.16.5/src/torrent/common.h  2025-12-02 18:15:46.000000000 
+0100
+++ new/libtorrent-0.16.6/src/torrent/common.h  2026-01-02 15:37:32.000000000 
+0100
@@ -134,9 +134,9 @@
 std::chrono::microseconds cached_time() LIBTORRENT_EXPORT;
 std::chrono::seconds      cached_seconds() LIBTORRENT_EXPORT;
 
-void                      callback(void* target, std::function<void ()>&& fn);
-void                      cancel_callback(void* target);
-void                      cancel_callback_and_wait(void* target);
+void                      callback(void* target, std::function<void ()>&& fn) 
LIBTORRENT_EXPORT;
+void                      cancel_callback(void* target) LIBTORRENT_EXPORT;
+void                      cancel_callback_and_wait(void* target) 
LIBTORRENT_EXPORT;
 
 net::Poll*                poll() LIBTORRENT_EXPORT;
 net::Resolver*            resolver() LIBTORRENT_EXPORT;
@@ -161,9 +161,9 @@
 torrent::utils::Thread* thread() LIBTORRENT_EXPORT;
 std::thread::id         thread_id() LIBTORRENT_EXPORT;
 
-void                    callback(void* target, std::function<void ()>&& fn);
-void                    cancel_callback(void* target);
-void                    cancel_callback_and_wait(void* target);
+void                    callback(void* target, std::function<void ()>&& fn) 
LIBTORRENT_EXPORT;
+void                    cancel_callback(void* target) LIBTORRENT_EXPORT;
+void                    cancel_callback_and_wait(void* target) 
LIBTORRENT_EXPORT;
 
 uint32_t                hash_queue_size() LIBTORRENT_EXPORT;
 
@@ -174,9 +174,9 @@
 torrent::utils::Thread* thread() LIBTORRENT_EXPORT;
 std::thread::id         thread_id() LIBTORRENT_EXPORT;
 
-void                    callback(void* target, std::function<void ()>&& fn);
-void                    cancel_callback(void* target);
-void                    cancel_callback_and_wait(void* target);
+void                    callback(void* target, std::function<void ()>&& fn) 
LIBTORRENT_EXPORT;
+void                    cancel_callback(void* target) LIBTORRENT_EXPORT;
+void                    cancel_callback_and_wait(void* target) 
LIBTORRENT_EXPORT;
 
 torrent::net::HttpStack* http_stack() LIBTORRENT_EXPORT;
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.5/src/torrent/connection_manager.cc 
new/libtorrent-0.16.6/src/torrent/connection_manager.cc
--- old/libtorrent-0.16.5/src/torrent/connection_manager.cc     2025-12-02 
18:15:46.000000000 +0100
+++ new/libtorrent-0.16.6/src/torrent/connection_manager.cc     2026-01-02 
15:37:32.000000000 +0100
@@ -25,11 +25,17 @@
   if (config::network_config()->is_block_ipv6() && sa_is_inet6(sa))
     return 0;
 
-  if (config::network_config()->is_block_ipv4in6() && sa_is_v4mapped(sa))
-    return 0;
+  if (sa_is_v4mapped(sa)) {
+    if (config::network_config()->is_block_ipv4in6())
+      return 0;
+
+    if (m_slot_filter)
+      return m_slot_filter(sa_from_v4mapped(sa).get());
 
-  if (m_slot_filter)
-    return m_slot_filter(sa);
+  } else {
+    if (m_slot_filter)
+      return m_slot_filter(sa);
+  }
 
   return 1;
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.5/src/torrent/download.cc 
new/libtorrent-0.16.6/src/torrent/download.cc
--- old/libtorrent-0.16.5/src/torrent/download.cc       2025-12-02 
18:15:46.000000000 +0100
+++ new/libtorrent-0.16.6/src/torrent/download.cc       2026-01-02 
15:37:32.000000000 +0100
@@ -129,8 +129,16 @@
   m_ptr->main()->tracker_controller().disable();
 }
 
+// When session data is loaded, it includes a bitfield and list of files+mtime.
+//
+// As the download is opened it checks the mtimes of the files, if the file is 
missing or has wrong mtime then
+// the file's range is added to hashing_ranges() and the bitfield range is 
cleared.
+//
+// Hash check with try_quick never does any actual hashing, it only checks if 
the underlying file
+// exists and if it does it fails the whole try_quick hash check.
+
 bool
-Download::hash_check(bool tryQuick) {
+Download::hash_check(bool try_quick) {
   if (m_ptr->hash_checker()->is_checking())
     throw internal_error("Download::hash_check(...) called but the hash is 
already being checked.");
 
@@ -142,7 +150,7 @@
 
   Bitfield* bitfield = m_ptr->data()->mutable_completed_bitfield();
 
-  LT_LOG_THIS(INFO, "Checking hash: allocated:%i try_quick:%i.", 
!bitfield->empty(), (int)tryQuick);
+  LT_LOG_THIS(INFO, "Checking hash: allocated:%i try_quick:%i.", 
!bitfield->empty(), (int)try_quick);
 
   if (bitfield->empty()) {
     // The bitfield still hasn't been allocated, so no resume data was
@@ -151,11 +159,19 @@
     bitfield->unset_all();
 
     m_ptr->hash_checker()->hashing_ranges().insert(0, 
m_ptr->main()->file_list()->size_chunks());
+
+  } else if (!try_quick) {
+    // Clear ranges we're about to recheck so mark_completed can re-set them.
+    for (auto range : m_ptr->hash_checker()->hashing_ranges())
+      bitfield->unset_range(range.first, range.second);
+
+    // TODO: Consider adding a sanity check above instead, and print out the 
files (size+range) of
+    // the invalid marked bit.
   }
 
   m_ptr->main()->file_list()->update_completed();
 
-  return m_ptr->hash_checker()->start(tryQuick);
+  return m_ptr->hash_checker()->start(try_quick);
 }
 
 // Propably not correct, need to clear content, etc.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.5/src/torrent/event.cc 
new/libtorrent-0.16.6/src/torrent/event.cc
--- old/libtorrent-0.16.5/src/torrent/event.cc  2025-12-02 18:15:46.000000000 
+0100
+++ new/libtorrent-0.16.6/src/torrent/event.cc  2026-01-02 15:37:32.000000000 
+0100
@@ -2,14 +2,25 @@
 
 #include "event.h"
 
+#include <cassert>
+
 #include "torrent/exceptions.h"
 #include "torrent/net/fd.h"
 
 namespace torrent {
 
+Event::~Event() {
+  assert(m_poll_event == nullptr && "Event::~Event() called with m_poll_event 
!= nullptr.");
+}
+
 const char*
 Event::type_name() const {
-  return "default";
+  throw internal_error("Event::type_name() must be overridden in derived 
class.");
+}
+
+std::string
+Event::print_name_fd_str() const {
+ return "name:" + std::string(type_name()) + " fd:" + 
std::to_string(file_descriptor());
 }
 
 void
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.5/src/torrent/event.h 
new/libtorrent-0.16.6/src/torrent/event.h
--- old/libtorrent-0.16.5/src/torrent/event.h   2025-12-02 18:15:46.000000000 
+0100
+++ new/libtorrent-0.16.6/src/torrent/event.h   2026-01-02 15:37:32.000000000 
+0100
@@ -1,19 +1,26 @@
 #ifndef LIBTORRENT_TORRENT_EVENT_H
 #define LIBTORRENT_TORRENT_EVENT_H
 
+#include <memory>
 #include <torrent/common.h>
 
 namespace torrent {
 
+namespace net {
+class PollEvent;
+class PollInternal;
+}
+
 class LIBTORRENT_EXPORT Event {
 public:
-  virtual             ~Event() = default;
+  virtual             ~Event();
 
   bool                is_open() const;
 
   int                 file_descriptor() const;
 
-  // TODO: Require all to define their own typename.
+  std::string         print_name_fd_str() const;
+
   virtual const char* type_name() const;
 
   // TODO: Make these protected.
@@ -21,12 +28,16 @@
   virtual void        event_write() = 0;
   virtual void        event_error() = 0;
 
-
 protected:
+  friend class net::Poll;
+  friend class net::PollInternal;
+
   void                close_file_descriptor();
   void                set_file_descriptor(int fd);
 
-  // TODO: Rename to m_fd.
+  std::shared_ptr<net::PollEvent> m_poll_event;
+
+  // TODO: replace by m_poll_event->fd()
   int                 m_fileDesc{-1};
 
   // TODO: Deprecate.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.5/src/torrent/hash_string.cc 
new/libtorrent-0.16.6/src/torrent/hash_string.cc
--- old/libtorrent-0.16.5/src/torrent/hash_string.cc    2025-12-02 
18:15:46.000000000 +0100
+++ new/libtorrent-0.16.6/src/torrent/hash_string.cc    2026-01-02 
15:37:32.000000000 +0100
@@ -1,39 +1,3 @@
-// libTorrent - BitTorrent library
-// Copyright (C) 2005-2011, Jari Sundell
-//
-// This program is free software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation; either version 2 of the License, or
-// (at your option) any later version.
-// 
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-// 
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-//
-// In addition, as a special exception, the copyright holders give
-// permission to link the code of portions of this program with the
-// OpenSSL library under certain conditions as described in each
-// individual source file, and distribute linked combinations
-// including the two.
-//
-// You must obey the GNU General Public License in all respects for
-// all of the code used other than OpenSSL.  If you modify file(s)
-// with this exception, you may extend this exception to your version
-// of the file(s), but you are not obligated to do so.  If you do not
-// wish to do so, delete this exception statement from your version.
-// If you delete this exception statement from all source files in the
-// program, then also delete it here.
-//
-// Contact:  Jari Sundell <[email protected]>
-//
-//           Skomakerveien 33
-//           3185 Skoppum, NORWAY
-
 #include "config.h"
 
 #include <rak/string_manip.h>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.5/src/torrent/net/poll_epoll.cc 
new/libtorrent-0.16.6/src/torrent/net/poll_epoll.cc
--- old/libtorrent-0.16.5/src/torrent/net/poll_epoll.cc 2025-12-02 
18:15:46.000000000 +0100
+++ new/libtorrent-0.16.6/src/torrent/net/poll_epoll.cc 2026-01-02 
15:37:32.000000000 +0100
@@ -14,6 +14,9 @@
 #include "torrent/utils/log.h"
 #include "torrent/utils/thread.h"
 
+#define LT_LOG(log_fmt, ...)                                        \
+  lt_log_print(LOG_CONNECTION_FD, "epoll: " log_fmt, __VA_ARGS__);
+
 #define LT_LOG_EVENT(log_fmt, ...)                                      \
   lt_log_print(LOG_CONNECTION_FD, "epoll->%i : %s : " log_fmt, 
event->file_descriptor(), event->type_name(), __VA_ARGS__);
 
@@ -23,62 +26,57 @@
 
 namespace torrent::net {
 
+class PollEvent {
+public:
+  PollEvent(Event* event) : event(event) {}
+  ~PollEvent() = default;
+
+  uint32_t            mask{};
+  Event*              event{};
+};
+
 class PollInternal {
 public:
-  using Table = std::vector<std::pair<uint32_t, Event*>>;
+  using Table = std::map<unsigned int, std::shared_ptr<PollEvent>>;
 
-  inline uint32_t     event_mask(Event* e);
-  inline uint32_t     event_mask_any(int fd);
-  inline void         set_event_mask(Event* e, uint32_t m);
+  uint32_t            event_mask(Event* event);
+  void                set_event_mask(Event* event, uint32_t mask);
 
+  void                flush();
   void                modify(torrent::Event* event, unsigned short op, 
uint32_t mask);
 
   int                 m_fd;
 
-  unsigned int        m_max_events;
+  unsigned int        m_max_sockets{};
+  unsigned int        m_max_events{};
   unsigned int        m_waiting_events{};
 
   Table                                 m_table;
   std::unique_ptr<struct epoll_event[]> m_events;
 };
 
-inline uint32_t
-PollInternal::event_mask(Event* e) {
-  if (e->file_descriptor() == -1)
-    throw internal_error("PollInternal::event_mask() invalid file descriptor 
for event: name:" + std::string(e->type_name()));
-
-  if (static_cast<unsigned int>(e->file_descriptor()) >= m_table.size())
-    throw internal_error("PollInternal::event_mask() file descriptor out of 
range: name:" + std::string(e->type_name()) + " fd:" + 
std::to_string(e->file_descriptor()));
-
-  if (m_table[e->file_descriptor()].second != e)
-    throw internal_error("PollInternal::event_mask() event mismatch: name:" + 
std::string(e->type_name()) + " fd:" + std::to_string(e->file_descriptor()));
+uint32_t
+PollInternal::event_mask(Event* event) {
+  if (event->file_descriptor() == -1)
+    throw internal_error("PollInternal::event_mask() invalid file descriptor 
for event: " + event->print_name_fd_str());
 
-  return m_table[e->file_descriptor()].first;
-}
+  auto itr = m_table.find(event->file_descriptor());
 
-inline uint32_t
-PollInternal::event_mask_any(int fd) {
-  if (fd == -1)
-    throw internal_error("PollInternal::event_mask_any() invalid file 
descriptor for event");
+  if (itr == m_table.end())
+    throw internal_error("PollInternal::event_mask() event not found: " + 
event->print_name_fd_str());
 
-  if (static_cast<unsigned int>(fd) >= m_table.size())
-    throw internal_error("PollInternal::event_mask_any() file descriptor out 
of range: fd:" + std::to_string(fd));
+  if (event != itr->second->event)
+    throw internal_error("PollInternal::event_mask() event mismatch: " + 
event->print_name_fd_str());
 
-  return m_table[fd].first;
+  return itr->second->mask;
 }
 
-inline void
-PollInternal::set_event_mask(Event* e, uint32_t m) {
-  if (e->file_descriptor() == -1)
-    throw internal_error("PollInternal::set_event_mask() invalid file 
descriptor for event: name:" + std::string(e->type_name()));
-
-  if (static_cast<unsigned int>(e->file_descriptor()) >= m_table.size())
-    throw internal_error("PollInternal::set_event_mask() file descriptor out 
of range: name:" + std::string(e->type_name()) + " fd:" + 
std::to_string(e->file_descriptor()));
-
-  if (m_table[e->file_descriptor()].second != e)
-    throw internal_error("PollInternal::set_event_mask() event mismatch: 
name:" + std::string(e->type_name()) + " fd:" + 
std::to_string(e->file_descriptor()));
+void
+PollInternal::set_event_mask(Event* event, uint32_t mask) {
+  if (event->file_descriptor() == -1)
+    throw internal_error("PollInternal::set_event_mask() invalid file 
descriptor for event: " + event->print_name_fd_str());
 
-  m_table[e->file_descriptor()] = Table::value_type(m, e);
+  event->m_poll_event->mask = mask;
 }
 
 void
@@ -88,9 +86,11 @@
 
   LT_LOG_EVENT("modify event : op:%hx mask:%hx", op, mask);
 
-  epoll_event e;
-  e.data.u64 = 0;
-  e.data.fd = event->file_descriptor();
+  if (event->m_poll_event == nullptr)
+    throw internal_error("PollInternal::modify() event has no PollEvent 
associated: " + event->print_name_fd_str());
+
+  epoll_event e{};
+  e.data.ptr = event->m_poll_event.get();
   e.events = mask;
 
   set_event_mask(event, mask);
@@ -114,7 +114,7 @@
     if (errno || epoll_ctl(m_fd, retry, event->file_descriptor(), &e)) {
       char errmsg[1024];
       snprintf(errmsg, sizeof(errmsg),
-               "Poll::modify(...) epoll_ctl(%d, %d -> %d, %d, [%p:%x]) = %d: 
%s",
+               "PollInternal::modify() epoll_ctl(%d, %d -> %d, %d, [%p:%x]) = 
%d: %s",
                m_fd, op, retry, event->file_descriptor(), event, mask, errno, 
strerror(errno));
 
       throw internal_error(errmsg);
@@ -127,28 +127,29 @@
   auto socket_open_max = sysconf(_SC_OPEN_MAX);
 
   if (socket_open_max == -1)
-    throw internal_error("Poll::create() : sysconf(_SC_OPEN_MAX) failed: " + 
std::string(std::strerror(errno)));
+    throw internal_error("Poll::create() : sysconf(_SC_OPEN_MAX) failed : " + 
std::string(std::strerror(errno)));
 
   int fd = epoll_create(socket_open_max);
 
   if (fd == -1)
-    return nullptr;
+    throw internal_error("Poll::create() : kqueue() failed : " + 
std::string(std::strerror(errno)));
 
   auto poll = new Poll();
 
-  poll->m_internal = std::make_unique<PollInternal>();
-  poll->m_internal->m_table.resize(socket_open_max);
-  poll->m_internal->m_fd = fd;
-  poll->m_internal->m_max_events = 1024;
-  poll->m_internal->m_events = std::make_unique<struct 
epoll_event[]>(poll->m_internal->m_max_events);
+  poll->m_internal                = std::make_unique<PollInternal>();
+  poll->m_internal->m_fd          = fd;
+  poll->m_internal->m_max_sockets = static_cast<unsigned int>(socket_open_max);
+  poll->m_internal->m_max_events  = 1024;
+  poll->m_internal->m_events      = std::make_unique<struct 
epoll_event[]>(poll->m_internal->m_max_events);
 
   return std::unique_ptr<Poll>(poll);
 }
 
 Poll::~Poll() {
-  m_internal->m_table.clear();
+  assert(m_internal->m_table.empty() && "Poll::~Poll() called with non-empty 
event table.");
 
   ::close(m_internal->m_fd);
+  m_internal->m_fd = -1;
 }
 
 unsigned int
@@ -189,18 +190,14 @@
   unsigned int count = 0;
 
   for (epoll_event *itr = m_internal->m_events.get(), *last = 
m_internal->m_events.get() + m_internal->m_waiting_events; itr != last; ++itr) {
-    if (itr->data.fd < 0)
-      throw internal_error("Poll::process() received negative file descriptor: 
" + std::to_string(itr->data.fd));
-
-    if (static_cast<size_t>(itr->data.fd) >= m_internal->m_table.size())
-      throw internal_error("Poll::process() received invalid file descriptor: 
" + std::to_string(itr->data.fd));
-
     if (utils::Thread::self()->callbacks_should_interrupt_polling())
       utils::Thread::self()->process_callbacks(true);
 
-    auto evItr = m_internal->m_table.begin() + itr->data.fd;
+    auto* poll_event = static_cast<PollEvent*>(itr->data.ptr);
+    auto* event      = poll_event->event;
 
-    if (evItr->second == nullptr) {
+    if (event == nullptr) {
+      // TODO: This should fail.
       LT_LOG_DEBUG_DATA_FD("event is null, skipping : events:%hx", 
itr->events);
       continue;
     }
@@ -211,22 +208,30 @@
     // TODO: Make it so that it checks that read/write is wanted, that
     // it wasn't removed from one of them but not closed.
 
-    if (itr->events & EPOLLERR && evItr->first & EPOLLERR) {
-      count++;
-      evItr->second->event_error();
+    if (itr->events & EPOLLERR && poll_event->mask & EPOLLERR) {
+      if (!(poll_event->mask & EPOLLERR))
+        throw internal_error("Poll::process() received error event for event 
not in error: " + poll_event->event->print_name_fd_str());
+
+      auto event_info = event->print_name_fd_str();
+
+      event->event_error();
+
+      if (poll_event->mask != 0)
+        throw internal_error("Poll::process() event_error called but event 
mask not cleared: " + event_info);
 
       // We assume that the event gets closed if we get an error.
+      count++;
       continue;
     }
 
-    if (itr->events & EPOLLIN && evItr->first & EPOLLIN) {
+    if (itr->events & EPOLLIN && poll_event->mask & EPOLLIN) {
+      event->event_read();
       count++;
-      evItr->second->event_read();
     }
 
-    if (itr->events & EPOLLOUT && evItr->first & EPOLLOUT) {
+    if (itr->events & EPOLLOUT && poll_event->mask & EPOLLOUT) {
+      event->event_write();
       count++;
-      evItr->second->event_write();
     }
   }
 
@@ -236,35 +241,47 @@
 
 uint32_t
 Poll::open_max() const {
-  return m_internal->m_table.size();
+  return m_internal->m_max_sockets;
 }
 
 void
 Poll::open(Event* event) {
   LT_LOG_EVENT("open event", 0);
 
-  if (m_internal->event_mask_any(event->file_descriptor()) != 0)
-    throw internal_error("Poll::open() called but the file descriptor is 
active");
+  if (event->file_descriptor() == -1)
+    throw internal_error("Poll::open() invalid file descriptor for event: " + 
event->print_name_fd_str());
 
-  m_internal->m_table[event->file_descriptor()] = 
PollInternal::Table::value_type(0, event);
+  if (event->m_poll_event != nullptr)
+    throw internal_error("Poll::open() called but the event is already 
associated with a poll: " + event->print_name_fd_str());
+
+  if (m_internal->m_table.find(event->file_descriptor()) != 
m_internal->m_table.end())
+    throw internal_error("Poll::open() event already exists: " + 
event->print_name_fd_str());
+
+  event->m_poll_event = std::make_shared<PollEvent>(event);
+
+  m_internal->m_table[event->file_descriptor()] = event->m_poll_event;
 }
 
 void
 Poll::close(Event* event) {
   LT_LOG_EVENT("close event", 0);
 
+  auto* poll_event = event->m_poll_event.get();
+
+  if (poll_event == nullptr)
+    return;
+
+  if (poll_event->event != event)
+    throw internal_error("Poll::close() event mismatch: " + 
event->print_name_fd_str());
+
   if (m_internal->event_mask(event) != 0)
-    throw internal_error("Poll::close() called but the file descriptor is 
active");
+    throw internal_error("Poll::close() called but the file descriptor is 
active: " + event->print_name_fd_str());
 
-  m_internal->m_table[event->file_descriptor()] = 
PollInternal::Table::value_type();
+  if (m_internal->m_table.erase(event->file_descriptor()) == 0)
+    throw internal_error("Poll::close() event not found: " + 
event->print_name_fd_str());
 
-  // Clear the event list just in case we open a new socket with the
-  // same fd while in the middle of calling Poll::perform.
-  //
-  // Removed.
-  //
-  // Shouldn't be needed as we unset the read/write/error flags in 
m_internal->m_events using
-  // remove_read/write/error.
+  poll_event->event = nullptr;
+  event->m_poll_event.reset();
 }
 
 bool
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.5/src/torrent/net/poll_kqueue.cc 
new/libtorrent-0.16.6/src/torrent/net/poll_kqueue.cc
--- old/libtorrent-0.16.5/src/torrent/net/poll_kqueue.cc        2025-12-02 
18:15:46.000000000 +0100
+++ new/libtorrent-0.16.6/src/torrent/net/poll_kqueue.cc        2026-01-02 
15:37:32.000000000 +0100
@@ -7,8 +7,9 @@
 #include <algorithm>
 #include <cassert>
 #include <cerrno>
-#include <sys/event.h>
+#include <map>
 #include <unistd.h>
+#include <sys/event.h>
 
 #include "utils/log.h"
 #include "utils/thread.h"
@@ -31,24 +32,33 @@
 
 namespace torrent::net {
 
+class PollEvent {
+public:
+  PollEvent(Event* e) : event(e) {}
+  ~PollEvent() = default;
+
+  uint32_t            mask{};
+  Event*              event{};
+};
+
 class PollInternal {
 public:
-  using Table = std::vector<std::pair<uint32_t, Event*>>;
+  using Table = std::map<unsigned int, std::shared_ptr<PollEvent>>;
+
+  static constexpr uint32_t flag_read  = 0x1;
+  static constexpr uint32_t flag_write = 0x2;
+  static constexpr uint32_t flag_error = 0x4;
 
-  static constexpr uint32_t flag_read  = (1 << 0);
-  static constexpr uint32_t flag_write = (1 << 1);
-  static constexpr uint32_t flag_error = (1 << 2);
-
-  inline uint32_t     event_mask(Event* e);
-  inline uint32_t     event_mask_any(int fd);
-  inline void         set_event_mask(Event* e, uint32_t m);
+  uint32_t            event_mask(Event* event);
+  void                set_event_mask(Event* event, uint32_t mask);
 
   void                flush();
   void                modify(torrent::Event* event, unsigned short op, short 
mask);
 
   int                 m_fd;
 
-  unsigned int        m_max_events;
+  unsigned int        m_max_sockets{};
+  unsigned int        m_max_events{};
   unsigned int        m_waiting_events{};
   unsigned int        m_changed_events{};
 
@@ -57,43 +67,30 @@
   std::unique_ptr<struct kevent[]> m_changes;
 };
 
-inline uint32_t
-PollInternal::event_mask(Event* e) {
-  if (e->file_descriptor() == -1)
-    throw internal_error("PollInternal::event_mask() invalid file descriptor 
for event: name:" + std::string(e->type_name()));
-
-  if (static_cast<unsigned int>(e->file_descriptor()) >= m_table.size())
-    throw internal_error("PollInternal::event_mask() file descriptor out of 
range: name:" + std::string(e->type_name()) + " fd:" + 
std::to_string(e->file_descriptor()));
+uint32_t
+PollInternal::event_mask(Event* event) {
+  // TODO: Replace `file_descriptor()` with m_event_poll.
 
-  if (e != m_table[e->file_descriptor()].second)
-    throw internal_error("PollInternal::event_mask() event mismatch: name:" + 
std::string(e->type_name()) + " fd:" + std::to_string(e->file_descriptor()));
+  if (event->file_descriptor() == -1)
+    throw internal_error("PollInternal::event_mask() invalid file descriptor 
for event: " + event->print_name_fd_str());
 
-  return m_table[e->file_descriptor()].first;
-}
+  auto itr = m_table.find(event->file_descriptor());
 
-inline uint32_t
-PollInternal::event_mask_any(int fd) {
-  if (fd == -1)
-    throw internal_error("PollInternal::event_mask_any() invalid file 
descriptor for event");
+  if (itr == m_table.end())
+    throw internal_error("PollInternal::event_mask() event not found: " + 
event->print_name_fd_str());
 
-  if (static_cast<unsigned int>(fd) >= m_table.size())
-    throw internal_error("PollInternal::event_mask_any() file descriptor out 
of range: fd:" + std::to_string(fd));
+  if (event != itr->second->event)
+    throw internal_error("PollInternal::event_mask() event mismatch: " + 
event->print_name_fd_str());
 
-  return m_table[fd].first;
+  return itr->second->mask;
 }
 
-inline void
-PollInternal::set_event_mask(Event* e, uint32_t m) {
-  if (e->file_descriptor() == -1)
-    throw internal_error("PollInternal::set_event_mask() invalid file 
descriptor for event: name:" + std::string(e->type_name()));
-
-  if (static_cast<unsigned int>(e->file_descriptor()) >= m_table.size())
-    throw internal_error("PollInternal::set_event_mask() file descriptor out 
of range: name:" + std::string(e->type_name()) + " fd:" + 
std::to_string(e->file_descriptor()));
-
-  if (e != m_table[e->file_descriptor()].second)
-    throw internal_error("PollInternal::set_event_mask() event mismatch: 
name:" + std::string(e->type_name()) + " fd:" + 
std::to_string(e->file_descriptor()));
+void
+PollInternal::set_event_mask(Event* event, uint32_t mask) {
+  if (event->file_descriptor() == -1)
+    throw internal_error("PollInternal::set_event_mask() invalid file 
descriptor for event: " + event->print_name_fd_str());
 
-  m_table[e->file_descriptor()] = Table::value_type(m, e);
+  event->m_poll_event->mask = mask;
 }
 
 void
@@ -119,8 +116,7 @@
 
   struct kevent* itr = m_changes.get() + (m_changed_events++);
 
-  assert(event == m_table[event->file_descriptor()].second);
-  EV_SET(itr, event->file_descriptor(), mask, op, 0, 0, event);
+  EV_SET(itr, event->file_descriptor(), mask, op, 0, 0, 
event->m_poll_event.get());
 }
 
 std::unique_ptr<Poll>
@@ -128,32 +124,30 @@
   auto socket_open_max = sysconf(_SC_OPEN_MAX);
 
   if (socket_open_max == -1)
-    throw internal_error("Poll::create() : sysconf(_SC_OPEN_MAX) failed: " + 
std::string(std::strerror(errno)));
+    throw internal_error("Poll::create() : sysconf(_SC_OPEN_MAX) failed : " + 
std::string(std::strerror(errno)));
 
-  int fd = kqueue();
+  int fd = ::kqueue();
 
   if (fd == -1)
-    return nullptr;
+    throw internal_error("Poll::create() : kqueue() failed : " + 
std::string(std::strerror(errno)));
 
   auto poll = new Poll();
 
-  poll->m_internal = std::make_unique<PollInternal>();
-  poll->m_internal->m_table.resize(socket_open_max);
-  poll->m_internal->m_fd = fd;
-  poll->m_internal->m_max_events = 1024;
-  poll->m_internal->m_events = std::make_unique<struct 
kevent[]>(poll->m_internal->m_max_events);
-
-  // TODO: Dynamically resize.
-  // !!!!! check if correct size
-  poll->m_internal->m_changes = std::make_unique<struct 
kevent[]>(socket_open_max);
+  poll->m_internal                = std::make_unique<PollInternal>();
+  poll->m_internal->m_fd          = fd;
+  poll->m_internal->m_max_sockets = static_cast<unsigned int>(socket_open_max);
+  poll->m_internal->m_max_events  = 1024;
+  poll->m_internal->m_events      = std::make_unique<struct 
kevent[]>(poll->m_internal->m_max_events);
+  poll->m_internal->m_changes     = std::make_unique<struct 
kevent[]>(poll->m_internal->m_max_events);
 
   return std::unique_ptr<Poll>(poll);
 }
 
 Poll::~Poll() {
-  m_internal->m_table.clear();
+  assert(m_internal->m_table.empty() && "Poll::~Poll() called with non-empty 
event table.");
 
   ::close(m_internal->m_fd);
+  m_internal->m_fd = -1;
 }
 
 unsigned int
@@ -162,7 +156,7 @@
 
   if (status == -1) {
     if (errno != EINTR)
-      throw internal_error("Poll::work() " + 
std::string(std::strerror(errno)));
+      throw internal_error("Poll::do_poll() error: " + 
std::string(std::strerror(errno)));
 
     return 0;
   }
@@ -201,42 +195,45 @@
   unsigned int count = 0;
 
   for (struct kevent *itr = m_internal->m_events.get(), *last = 
m_internal->m_events.get() + m_internal->m_waiting_events; itr != last; ++itr) {
-    if (itr->ident >= m_internal->m_table.size())
-      throw internal_error("Poll::process() received ident out of range: " + 
std::to_string(itr->ident));
-
     if (utils::Thread::self()->callbacks_should_interrupt_polling())
       utils::Thread::self()->process_callbacks(true);
 
-    auto ev_itr = m_internal->m_table.begin() + itr->ident;
+    auto* poll_event = static_cast<PollEvent*>(itr->udata);
+    auto* event      = poll_event->event;
 
-    if (ev_itr->second == nullptr) {
-      LT_LOG_DEBUG_IDENT("event is null, skipping : flags:%hx fflag:%hx 
filter:%hx", itr->flags, itr->fflags, itr->filter);
+    if (event == nullptr) {
+      // TODO: This should fail.
+      LT_LOG_DEBUG_IDENT("event is null, skipping : udata:%p", itr->udata);
       continue;
     }
 
     if ((itr->flags & EV_ERROR)) {
-      if (ev_itr->first & PollInternal::flag_error)
-        ev_itr->second->event_error();
+      if (!(poll_event->mask & PollInternal::flag_error))
+        throw internal_error("Poll::process() received error event for event 
not in error: " + poll_event->event->print_name_fd_str());
 
-      count++;
+      auto event_info = event->print_name_fd_str();
+
+      event->event_error();
+
+      if (poll_event->mask != 0)
+        throw internal_error("Poll::process() event_error called but event 
mask not cleared: " + event_info);
 
       // We assume that the event gets closed if we get an error.
+      count++;
       continue;
     }
 
-    // Also check current mask.
-
-    if (itr->filter == EVFILT_READ && ev_itr->first & PollInternal::flag_read) 
{
+    if (itr->filter == EVFILT_READ && (poll_event->mask & 
PollInternal::flag_read)) {
       count++;
-      ev_itr->second->event_read();
+      event->event_read();
     }
     else if (itr->filter == EVFILT_READ) {
       LT_LOG_DEBUG_IDENT("spurious read event, skipping", 0);
     }
 
-    if (itr->filter == EVFILT_WRITE && ev_itr->first & 
PollInternal::flag_write) {
+    if (itr->filter == EVFILT_WRITE && (poll_event->mask & 
PollInternal::flag_write)) {
       count++;
-      ev_itr->second->event_write();
+      event->event_write();
     }
     else if (itr->filter == EVFILT_WRITE) {
       LT_LOG_DEBUG_IDENT("spurious write event, skipping", 0);
@@ -250,29 +247,49 @@
 
 uint32_t
 Poll::open_max() const {
-  return m_internal->m_table.size();
+  return m_internal->m_max_sockets;
 }
 
 void
 Poll::open(Event* event) {
   LT_LOG_EVENT("open event", 0);
 
-  if (m_internal->event_mask_any(event->file_descriptor()) != 0)
-    throw internal_error("Poll::open() called but the file descriptor is 
active");
+  if (event->file_descriptor() == -1)
+    throw internal_error("Poll::open() invalid file descriptor for event: " + 
event->print_name_fd_str());
+
+  if (event->m_poll_event != nullptr)
+    throw internal_error("Poll::open() called but the event is already 
associated with a poll: " + event->print_name_fd_str());
 
-  m_internal->m_table[event->file_descriptor()] = 
PollInternal::Table::value_type(0, event);
+  if (m_internal->m_table.find(event->file_descriptor()) != 
m_internal->m_table.end())
+    throw internal_error("Poll::open() event already exists: " + 
event->print_name_fd_str());
+
+  event->m_poll_event = std::make_shared<PollEvent>(event);
+
+  m_internal->m_table[event->file_descriptor()] = event->m_poll_event;
 }
 
 void
 Poll::close(Event* event) {
   LT_LOG_EVENT("close event", 0);
 
+  auto* poll_event = event->m_poll_event.get();
+
+  if (poll_event == nullptr)
+    return;
+
+  if (poll_event->event != event)
+    throw internal_error("Poll::close() event mismatch: " + 
event->print_name_fd_str());
+
   if (m_internal->event_mask(event) != 0)
-    throw internal_error("Poll::close() called but the file descriptor is 
active");
+    throw internal_error("Poll::close() called but the file descriptor is 
active: " + event->print_name_fd_str());
 
-  m_internal->m_table[event->file_descriptor()] = 
PollInternal::Table::value_type();
+  if (m_internal->m_table.erase(event->file_descriptor()) == 0)
+    throw internal_error("Poll::close() event not found: " + 
event->print_name_fd_str());
 
   m_internal->flush();
+
+  poll_event->event = nullptr;
+  event->m_poll_event.reset();
 }
 
 bool
@@ -366,7 +383,7 @@
 
   remove_read(event);
   remove_write(event);
-  // remove_error(event);
+  remove_error(event);
 
   close(event);
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.5/src/torrent/utils/log.h 
new/libtorrent-0.16.6/src/torrent/utils/log.h
--- old/libtorrent-0.16.5/src/torrent/utils/log.h       2025-12-02 
18:15:46.000000000 +0100
+++ new/libtorrent-0.16.6/src/torrent/utils/log.h       2026-01-02 
15:37:32.000000000 +0100
@@ -20,13 +20,6 @@
   LOG_INFO,
   LOG_DEBUG,
 
-  // LOG_DHT_CRITICAL,
-  // LOG_DHT_ERROR,
-  // LOG_DHT_WARN,
-  // LOG_DHT_NOTICE,
-  // LOG_DHT_INFO,
-  // LOG_DHT_DEBUG,
-
   LOG_PEER_CRITICAL,
   LOG_PEER_ERROR,
   LOG_PEER_WARN,
@@ -102,6 +95,7 @@
   LOG_RPC_EVENTS,
   LOG_RPC_DUMP,
 
+  LOG_SESSION_EVENTS,
   LOG_STORAGE,
   LOG_SYSTEM,
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libtorrent-0.16.5/src/torrent/utils/option_strings.cc 
new/libtorrent-0.16.6/src/torrent/utils/option_strings.cc
--- old/libtorrent-0.16.5/src/torrent/utils/option_strings.cc   2025-12-02 
18:15:46.000000000 +0100
+++ new/libtorrent-0.16.6/src/torrent/utils/option_strings.cc   2026-01-02 
15:37:32.000000000 +0100
@@ -183,6 +183,7 @@
   "rpc_events",
   "rpc_dump",
 
+  "session_events",
   "storage",
   "system",
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.5/src/torrent/utils/thread.cc 
new/libtorrent-0.16.6/src/torrent/utils/thread.cc
--- old/libtorrent-0.16.5/src/torrent/utils/thread.cc   2025-12-02 
18:15:46.000000000 +0100
+++ new/libtorrent-0.16.6/src/torrent/utils/thread.cc   2026-01-02 
15:37:32.000000000 +0100
@@ -21,6 +21,15 @@
 
 thread_local Thread* Thread::m_self{nullptr};
 
+Thread::~Thread() = default;
+
+Thread* Thread::self() { return m_self; }
+
+void Thread::init_thread() {}
+void Thread::init_thread_pre_start() {}
+void Thread::init_thread_post_local() {}
+void Thread::cleanup_thread() {}
+
 Thread::Thread() :
     m_instrumentation_index(INSTRUMENTATION_POLLING_DO_POLL_OTHERS - 
INSTRUMENTATION_POLLING_DO_POLL),
     m_poll(net::Poll::create()),
@@ -32,13 +41,6 @@
   m_scheduler->set_cached_time(m_cached_time);
 }
 
-Thread::~Thread() = default;
-
-Thread*
-Thread::self() {
-  return m_self;
-}
-
 void
 Thread::start_thread() {
   if (m_poll == nullptr)
@@ -47,6 +49,8 @@
   if (!is_initialized())
     throw internal_error("Called Thread::start_thread on an uninitialized 
object.");
 
+  init_thread_pre_start();
+
   if (pthread_create(&m_thread, nullptr, &Thread::enter_event_loop, this))
     throw internal_error("Failed to create thread.");
 
@@ -141,6 +145,7 @@
 
     m_poll->open(m_interrupt_receiver.get());
     m_poll->insert_read(m_interrupt_receiver.get());
+    m_poll->insert_error(m_interrupt_receiver.get());
 
     while (true) {
       process_events();
@@ -171,10 +176,9 @@
     lt_log_print(LOG_THREAD_NOTICE, "%s: Shutting down thread.", name());
   }
 
-  // Some test, and perhaps other code, segfaults on this.
-  // TODO: Test
-  //m_poll->remove_read(m_interrupt_receiver.get());
-  //m_poll->close(m_interrupt_receiver.get());
+  m_poll->remove_read(m_interrupt_receiver.get());
+  m_poll->remove_error(m_interrupt_receiver.get());
+  m_poll->close(m_interrupt_receiver.get());
 
   auto previous_state = STATE_ACTIVE;
 
@@ -210,10 +214,6 @@
 }
 
 void
-Thread::init_thread_post_local() {
-}
-
-void
 Thread::cleanup_thread_local() {
   lt_log_print(LOG_THREAD_NOTICE, "%s : cleaning up thread local data", 
name());
 
@@ -224,12 +224,6 @@
 }
 
 void
-Thread::set_cached_time(std::chrono::microseconds t) {
-  m_cached_time = t;
-  m_scheduler->set_cached_time(t);
-}
-
-void
 Thread::process_events() {
   // TODO: We should call process_callbacks() here before and after 
call_events, however due to the
   // many different cached times in the code, we need to let each thread 
manage this themselves.
@@ -283,6 +277,12 @@
   }
 }
 
+void
+Thread::set_cached_time(std::chrono::microseconds t) {
+  m_cached_time = t;
+  m_scheduler->set_cached_time(t);
+}
+
 } // namespace torrent::utils
 
 namespace torrent::this_thread {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libtorrent-0.16.5/src/torrent/utils/thread.h 
new/libtorrent-0.16.6/src/torrent/utils/thread.h
--- old/libtorrent-0.16.5/src/torrent/utils/thread.h    2025-12-02 
18:15:46.000000000 +0100
+++ new/libtorrent-0.16.6/src/torrent/utils/thread.h    2026-01-02 
15:37:32.000000000 +0100
@@ -64,16 +64,7 @@
   //
   auto                signal_bitfield()    { return &m_signal_bitfield; }
 
-  virtual void        init_thread() = 0;
-  void                init_thread_local();
-  virtual void        init_thread_post_local();
-
-  // It is assumed that any thread-specific resources no longer are accessed 
at the time
-  // cleanup_thread is called, or that those resources remain safe to call.
-  //
-  // This mean that e.g. tracker::Manager never gets called once 
thread_tracker is stopped.
-  virtual void        cleanup_thread() = 0;
-  void                cleanup_thread_local();
+  virtual void        init_thread();
 
   void                start_thread();
   void                stop_thread_wait();
@@ -97,8 +88,6 @@
   net::Resolver*      resolver()  { return m_resolver.get(); }
   Scheduler*          scheduler() { return m_scheduler.get(); }
 
-  void                set_cached_time(std::chrono::microseconds t);
-
   bool                callbacks_should_interrupt_polling() const { return 
m_callbacks_should_interrupt_polling.load(); }
 
   static void*        enter_event_loop(void* thread);
@@ -106,10 +95,23 @@
   virtual void                      call_events() = 0;
   virtual std::chrono::microseconds next_timeout() = 0;
 
+  virtual void        init_thread_pre_start();
+  void                init_thread_local();
+  virtual void        init_thread_post_local();
+
+  // It is assumed that any thread-specific resources no longer are accessed 
at the time
+  // cleanup_thread is called, or that those resources remain safe to call.
+  //
+  // This mean that e.g. tracker::Manager never gets called once 
thread_tracker is stopped.
+  virtual void        cleanup_thread();
+  void                cleanup_thread_local();
+
   void                process_events();
   void                process_events_without_cached_time();
   void                process_callbacks(bool only_interrupt = false);
 
+  void                set_cached_time(std::chrono::microseconds t);
+
   static thread_local Thread*  m_self;
 
   // TODO: Remove m_thread.
@@ -135,6 +137,7 @@
   std::multimap<const void*, std::function<void ()>> m_callbacks;
   std::multimap<const void*, std::function<void ()>> m_interrupt_callbacks;
   std::atomic<bool>                                  
m_callbacks_should_interrupt_polling{false};
+
   std::mutex                                         
m_callbacks_processing_lock;
   std::atomic<bool>                                  
m_callbacks_processing{false};
 };
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libtorrent-0.16.5/test/torrent/test_tracker_controller.cc 
new/libtorrent-0.16.6/test/torrent/test_tracker_controller.cc
--- old/libtorrent-0.16.5/test/torrent/test_tracker_controller.cc       
2025-12-02 18:15:47.000000000 +0100
+++ new/libtorrent-0.16.6/test/torrent/test_tracker_controller.cc       
2026-01-02 15:37:32.000000000 +0100
@@ -80,7 +80,7 @@
 
 void
 TestTrackerController::test_basic() {
-  torrent::TrackerController tracker_controller(NULL);
+  torrent::TrackerController tracker_controller(nullptr);
 
   CPPUNIT_ASSERT(tracker_controller.flags() == 0);
   CPPUNIT_ASSERT(!tracker_controller.is_active());

Reply via email to