libtorrent icon indicating copy to clipboard operation
libtorrent copied to clipboard

Torrents without network activity stop sending updates

Open HanabishiRecca opened this issue 1 month ago • 22 comments

libtorrent: current RC_2_0 (195f94d4a1283b58b042bb72a4f862d52fcc0f13)

For seeding torrents without activity, libtorrent simply stops sending any state update alerts, like if the torrent is paused. This manifests as clients (qBittorrent in my case) simply stop updating torrent info until some event is happened. E.g. transfer rate freezes at "1 B/s" forever, other fields like active/seeding time are never updated as well.

Conditions to reproduce that behavior is fairly strict: the torrent should not have any trackers and active peer connections, as they naturally cause info updates. I described it in detail in https://github.com/qbittorrent/qBittorrent/pull/23431#issuecomment-3536811579, https://github.com/qbittorrent/qBittorrent/pull/23431#issuecomment-3536919070.

Some research lead me to this part:

https://github.com/arvidn/libtorrent/blob/195f94d4a1283b58b042bb72a4f862d52fcc0f13/src/torrent.cpp#L10325-L10327

Removing the condition mitigates the "1 B/s" problem, allows torrent info to update one more time and actually zero out the counter.

But unfortunately it doesn't fix the rest fields, things like active time still remain frozen. And as for now I haven't managed to figure out why.

HanabishiRecca avatar Nov 16 '25 16:11 HanabishiRecca

But unfortunately it doesn't fix the rest fields, things like active time still remain frozen. And as for now I haven't managed to figure out why.

Don't you talk about non-active torrent in this case? So why does active time should be changed?

glassez avatar Nov 16 '25 17:11 glassez

Because it does tick constantly in background. The change is simply not sent to the client / not reflected by the UI. So any external event causes it to abruptly jump forward.

HanabishiRecca avatar Nov 16 '25 18:11 HanabishiRecca

Here is a little video demonstration.

https://github.com/user-attachments/assets/13a001b9-6d12-4ace-80cf-00c77d13745c

HanabishiRecca avatar Nov 16 '25 18:11 HanabishiRecca

lt 1.2.x

https://github.com/user-attachments/assets/798028e2-cff8-4d24-bbca-ca5f0fa52f90

stalkerok avatar Nov 17 '25 10:11 stalkerok

Ok, I've found something. Turns out the blocker here is check for the internal m_inactive value inside torrent::want_tick().

https://github.com/arvidn/libtorrent/blob/195f94d4a1283b58b042bb72a4f862d52fcc0f13/src/torrent.cpp#L8158-L8159

It is also defined as having download/upload rate > 0.

https://github.com/arvidn/libtorrent/blob/195f94d4a1283b58b042bb72a4f862d52fcc0f13/src/torrent.cpp#L10357-L10365

So we basically have "no transfer = no updates" situation here. Removing this check along with the condition described earlier fixes the problem completely.

--- a/src/torrent.cpp
+++ b/src/torrent.cpp
@@ -8156,7 +8156,7 @@
 			return true;
 
 		// if we don't get ticks we won't become inactive
-		if (!m_paused && !m_inactive) return true;
+		if (!m_paused) return true;
 
 		return false;
 	}
@@ -10322,9 +10322,7 @@
 		// we need to save the resume data too
 		set_need_save_resume(torrent_handle::if_counters_changed);
 
-		// 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();
+		state_updated();
 
 		// this section determines whether the torrent is active or not. When it
 		// changes state, it may also trigger the auto-manage logic to reconsider

The disadvantage is, it makes all running torrents to update info constantly. Which in theory could lead to some performance penalty with large amount of running tasks.

HanabishiRecca avatar Nov 17 '25 12:11 HanabishiRecca

Thinking more about it though, the rest fields do not seem that important to necessary be updated in real time. So I guess fixing only the stuck "1 B/s" problem could be a fair compromise. I.e. only

--- a/src/torrent.cpp
+++ b/src/torrent.cpp
@@ -10322,9 +10322,7 @@
 		// we need to save the resume data too
 		set_need_save_resume(torrent_handle::if_counters_changed);
 
-		// 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();
+		state_updated();
 
 		// this section determines whether the torrent is active or not. When it
 		// changes state, it may also trigger the auto-manage logic to reconsider

@stalkerok, the same change easily applies for RC_1_2 too.

HanabishiRecca avatar Nov 17 '25 12:11 HanabishiRecca

Thinking more about it though, the rest fields do not seem that important to necessary be updated in real time.

IMO, libtorrent should not force this and just mark status as "dirty" whenever some item is updated and let application decide when to request it.

glassez avatar Nov 18 '25 08:11 glassez

The problem is, the active time ticks constantly. Libtorrent doesn't know how client formats it inside UI, so updates every second. I.e. all running torrents are basically always "dirty", which makes approach no different from my solution in https://github.com/arvidn/libtorrent/issues/8057#issuecomment-3541445947.

HanabishiRecca avatar Nov 18 '25 10:11 HanabishiRecca

The problem is, the active time ticks constantly.

But isn't this only true for "active" torrents (in terms of libtorrent, i.e. non-paused either forcefully or by automanagement)? Do "queued" torrents increase their active/seeding time?

glassez avatar Nov 18 '25 10:11 glassez

https://github.com/arvidn/libtorrent/blob/195f94d4a1283b58b042bb72a4f862d52fcc0f13/include/libtorrent/torrent_status.hpp#L574-L584

active means not paused and added to session.

I referred to them as "running" before, meaning "not paused".

HanabishiRecca avatar Nov 18 '25 10:11 HanabishiRecca

This boils down to making session_handle::post_torrent_updates() efficient. I've tried to make it only post updates for torrents that actually have something new to report. These counters, that constantly count-up, are problematic, because almost all torrents are always "active".

Some work has gone into save_resume_data(), which is similar. There you can specify which categories of stats you care about, and if none of them have changed, no resume data needs to be saved. Saving resume data is generally more expensive, so avoiding it is a greater gain.

I agree that it's not good to stop posting updates before transfer counters reach 0, and I also think it would be nice to be able to avoid posting updates for every torrent every second, as counters keep counting up.

The smoothed transfer rate counters have a similar issue. If all transfers stop, there are no events, other than the clock, that makes them change. In a way I think it would be nice if the smoothing would happen on the client side. But this should be relatively easy to fix on the libtorrent side.

The older finished_time, active_time and seeding_time were deprecated and replaced by finished_duration, active_duration, seeding_duration. However, it doesn't look like I defined those as only updating when the state is changing. Internally, m_seeding_time, m_active_time and m_finished_time are only tracking the counters until the last state change. So you add the number of seconds in the current state to the counter.

That should probably be how these counters are reported to the client as well. It's not trivial to transition to that though.

arvidn avatar Nov 29 '25 19:11 arvidn

Yeah, that's why I suggest to only apply the patch from https://github.com/arvidn/libtorrent/issues/8057#issuecomment-3541604165. It grants 1 more update for speed counters. Other counters are not that important.

HanabishiRecca avatar Nov 29 '25 20:11 HanabishiRecca

Your patch will make the torrent be included in updates every second. Even if the smoothed download rate stays at zero. I think the torrent would have to remember the rate in the last update and skip posting if it was zero and still is.

arvidn avatar Nov 29 '25 21:11 arvidn

Wdym by "included in updates"? My testing shows that it only updates 1 more time and then stops.

As I already described above, another condition inside want_tick() stops it from updating further.

https://github.com/arvidn/libtorrent/blob/195f94d4a1283b58b042bb72a4f862d52fcc0f13/src/torrent.cpp#L8158-L8159

HanabishiRecca avatar Nov 29 '25 22:11 HanabishiRecca

want_tick() will return false once the torrent becomes inactive. That won't happen right away though, I don't think.

Thinking a bit more about this, I would actually expect the existing code to have the desired effect. The upload and download rates are updated in m_stat.second_tick(). That's the call where they, eventually, will become zero. But as long as they're non-zero before the call to second_tick(), we still need to send one more update. As far as I can tell, the last update they should be zero. Unless m_stat is updated somewhere else as well, so it can turn from non-zero -> zero somewhere else, without triggering state_updated().

Maybe this could make a difference.

diff --git a/src/torrent.cpp b/src/torrent.cpp
index 69ac7b414..5235c120c 100644
--- a/src/torrent.cpp
+++ b/src/torrent.cpp
@@ -10229,8 +10229,13 @@ namespace {
                        // let the stats fade out to 0
                        // check the rate before ticking the stats so that the last update is sent
                        // with the rate equal to zero
-                       if (m_stat.low_pass_upload_rate() > 0 || m_stat.low_pass_download_rate() > 0)
+                       if (m_stat.low_pass_upload_rate() > 0
+                               || m_stat.low_pass_download_rate() > 0
+                               || m_stat.upload_rate() > 0
+                               || m_stat.download_rate() > 0)
+                       {
                                state_updated();
+                       }
                        m_stat.second_tick(tick_interval_ms);

                        // the low pass transfer rate may just have dropped to 0

arvidn avatar Nov 30 '25 01:11 arvidn

Maybe this could make a difference.

This patch won't do anything because this code is only executed for paused torrents. Our problem is with running torrents.

Although, I guess the same change could be applied on line 10326.

https://github.com/arvidn/libtorrent/blob/195f94d4a1283b58b042bb72a4f862d52fcc0f13/src/torrent.cpp#L10325-L10327

I'll try that instead.

HanabishiRecca avatar Nov 30 '25 11:11 HanabishiRecca

And it doesn't work anyway unfortunately.

want_tick() will return false once the torrent becomes inactive. That won't happen right away though, I don't think.

It happens right away in my testing btw. With the codition removed (at line 10326), only 1 extra update gets through. Precisely enough to zero out the counter.

HanabishiRecca avatar Nov 30 '25 14:11 HanabishiRecca

This patch won't do anything because this code is only executed for paused torrents. Our problem is with running torrents.

I consider myself obliged to note that "running torrents" in terms of libtorrent and "running torrents" in terms of qBittorrent are not exactly the same thing. I hope you understand the difference when you mention it here, otherwise it could lead to a lot of misunderstanding between the participants of this discussion.

glassez avatar Nov 30 '25 17:11 glassez

I have already explained in https://github.com/arvidn/libtorrent/issues/8057#issuecomment-3546810489 what I mean by that.

I referred to them as "running" before, meaning "not paused".

I.e. m_paused == false in terms of code. While torrent_status is seeding for example.

HanabishiRecca avatar Nov 30 '25 18:11 HanabishiRecca

I meant to apply that change further down, the same code your patch removes.

--- a/src/torrent.cpp
+++ b/src/torrent.cpp
@@ -10323,8 +10323,13 @@ namespace {
                set_need_save_resume(torrent_handle::if_counters_changed);

                // 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)
+               if (m_stat.low_pass_upload_rate() > 0
+                       || m_stat.low_pass_download_rate() > 0
+                       || m_stat.upload_rate() > 0
+                       || m_stat.download_rate() > 0)
+               {
                        state_updated();
+               }

                // this section determines whether the torrent is active or not. When it
                // changes state, it may also trigger the auto-manage logic to reconsider

It's a long shot. The idea being that perhaps the low pass filtered rates go to zero before the non-filtered ones. It's not clear how, but if they were to, we would be missing an update.

arvidn avatar Nov 30 '25 23:11 arvidn

Yeah, I did just that, it doesn't work.

HanabishiRecca avatar Nov 30 '25 23:11 HanabishiRecca

thats how old torrent seeders disappear (till big qbittorent update) and then re-appear.

ilayanyatto72733 avatar Dec 05 '25 10:12 ilayanyatto72733