Queue Share and Speed Limits - Kheopsian/Hydranos GitHub Wiki

🇬🇧 English · 🇫🇷 Français

Queue, Share and Speed Limits

Applies to Hydranos 4.4.

For anyone who wants to cap how many torrents run at once, stop or remove seeds after a ratio or a seeding time, or limit upload and download speed. You will learn which key does what, where to set it (web UI, native API, qBittorrent API), its exact default for race and hoard, and what is still missing in 4.4. Terms: Concepts and Glossary. Engines and roles: Engines: Race and Hoard.

Every setting on this page is per engine: [race], [hoard], or the session of an extra engine block (an extra engine inherits its role's values unless its own session sets them). Except start_paused, every key here is applied live when you change it from the web UI or an API: save it and the next pass of the worker uses it, no restart. A hand edit of default.toml is not: it is read at the next start, or when the next UI or API change rewrites the file (Configuration: Engines).

Where to set them

Level Web UI Native API qBittorrent API (shim)
Engine (queue, slow torrents, share limits, speed caps, choker) Config tab › Advanced settings (default.toml) › Session Race / Session Hoard, then Save settings POST /api/settings; /api/engines/:id/share-limits; /api/engines/:id/rate-limits app/setPreferences (writes every local engine)
One torrent (share limits, speed caps) right-click › Share limits… / Limit rate… /api/torrents/:info_hash/share-limits; /api/torrents/:info_hash/limits; selection actions share_limits, limits (POST /api/selection/:action) torrents/setShareLimits; torrents/setUploadLimit, setDownloadLimit; add fields ratioLimit, seedingTimeLimit, inactiveSeedingTimeLimit, upLimit, dlLimit
Whole client (speed cap only) — read only (GET /api/engines/:id/rate-limits) transfer/setUploadLimit, transfer/setDownloadLimit; setPreferences up_limit / dl_limit

The settings editor lists queueing, active_seeds, active_limit, the slow-torrent, share-limit and speed-cap keys and choking even when your default.toml lacks them, at their default value; saving one creates it in [race] or [hoard]. active_downloads and max_uploads_per_torrent appear only if the file already has them (the shipped template has active_downloads in [hoard] only). The editor only covers [race] and [hoard]: for an extra engine, use the native routes (they write into its block) or edit the file.

The download queue

Each local engine runs a queue manager. Its first pass comes about a minute after the engine starts (a 30-second settle, then the 30-second wait), then one every 30 seconds, reading the limits from the live config on each pass.

Key Meaning Template race Template hoard Absent from the file
active_downloads Downloads running at once. Always applies, with or without queueing. not set 10 0 = no ceiling
queueing qBittorrent's queue switch: turns on active_seeds and active_limit. false false false
active_seeds Seeds running at once, read only while queueing = true. -1 -1 no ceiling
active_limit Downloads + seeds running at once, read only while queueing = true. -1 -1 no ceiling

0 or a negative value means "no ceiling" for all three. queueing exists as a guard: 3.x put active_seeds = 50 / active_limit = 100 on race, and a file carrying them would otherwise stop all but 50 seeds at the upgrade. Those two keys do nothing until you set queueing = true.

How a pass chooses

  • A torrent you stopped (Stop, a share limit's stop) is invisible to the queue: it neither takes a slot nor gets started.
  • A torrent in error is left out. A torrent being checked holds a download slot.
  • Downloads are ranked by the swarm's seeder count, from the tracker announce cache, most seeders first: the likeliest to finish gets the slot. Inside the ceiling, a waiting one is started; past it, a running one is stopped. It keeps its progress and shows queued (tooltip Waiting for a slot; queuedDL over the shim) until a slot frees.
  • The seeder count is the last one a tracker answered to an announce. A queued torrent is stopped, so it does not announce and its count stays where it was when it was stopped; a torrent never announced counts 0. Scrape figures are not used for the ranking. A dead release therefore ranks last and waits until fewer torrents above it need a slot; without the slow rule below, it can wait as long as better-seeded downloads keep arriving.
  • Seeds (only under queueing) are kept by age, oldest added first. Their ceiling is active_seeds, and at most active_limit minus the downloads now running. A seed past it is stopped and remembered as parked (queuedUP over the shim). The queue only ever restarts seeds it parked itself; lift the ceiling and every parked seed starts again.
  • Ranking by age rather than by a swarm figure is deliberate: a ranking that moves would stop and start seeds on every pass, and each stop sends a stopped announce.
flowchart TD
    P["Queue pass, every 30 s, live config"] --> L{"Any ceiling?<br/>active_downloads > 0, or queueing<br/>with active_seeds / active_limit > 0"}
    L -->|no| N["Nothing touched<br/>(seeds it parked earlier start again)"]
    L -->|yes| F["Skip: torrents you stopped, torrents in error"]
    F --> D["Incomplete torrents (checking included),<br/>most swarm seeders first"]
    D --> S1{"dont_count_slow_torrents and this one runs<br/>under its threshold for the timer?"}
    S1 -->|yes| K1["Keeps running, holds no slot"]
    S1 -->|no| C1{"Under min(active_downloads, active_limit)?"}
    C1 -->|yes| R1["Runs: started if waiting"]
    C1 -->|no| Q1["Stopped if running: queued"]
    R1 --> SE
    Q1 --> SE
    K1 --> SE
    SE{"queueing with active_seeds<br/>or active_limit?"} -->|no| E["End of pass"]
    SE -->|yes| D2["Running or parked seeds, oldest added first"]
    D2 --> S2{"Slow rule: uploading under<br/>its threshold for the timer?"}
    S2 -->|yes| K2["Keeps running, holds no slot"]
    S2 -->|no| C2{"Under min(active_seeds,<br/>active_limit minus running downloads)?"}
    C2 -->|yes| R2["Runs: a parked seed is started"]
    C2 -->|no| Q2["Stopped and parked: queued"]
Loading

Slow torrents

qBittorrent's "do not count slow torrents in these limits". A running torrent that stays under its threshold for the timer keeps running but no longer counts in active_downloads, active_seeds or active_limit, so the next candidate gets the slot. It works with or without queueing (for active_downloads).

Key Meaning Absent from the file
dont_count_slow_torrents The switch. false
slow_torrent_dl_rate_threshold A download under this rate, in KiB/s, is slow. 0 = never slow. 2
slow_torrent_ul_rate_threshold A seed uploading under this rate, in KiB/s, is slow. 0 = never slow. 2
slow_torrent_inactive_timer Seconds under the threshold before it stops counting. 60

These four keys are not in the shipped template; the settings editor shows them at the values above (the switch as a toggle, the three figures as number fields; then Save settings). With the rule on, the ceiling caps the fast torrents, not the running ones: a release stuck at 99 % waiting for a seeder stops holding one of your slots, and more torrents than the ceiling can be running at once.

  • Only a running download can turn slow, and a stalled one (stalledDL: running, no data coming) is running: after the timer it frees its slot. A queued torrent is not running, so the rule never applies to it; it gets a slot by its seeder rank only.
  • The timer restarts whenever the torrent's rate reaches the threshold. A slow torrent that speeds up counts again at the next pass, at its place in the seeder ranking: inside the ceiling it keeps running, and the lowest-ranked running download past the ceiling is stopped (queued); outside it, it is stopped and queued itself.

Force download, Start and queue positions

  • Force download / Stop forcing (right-click › State, shown for incomplete hoard torrents) marks the torrent as forced: it appears under the Forced filter chip of the Hoard list. The queue runs a forced torrent past the ceiling and does not count it against the slots: a queued forced torrent is started at the next pass (within 30 s) and is never stopped by the queue. A torrent you stopped yourself stays stopped until you Start it.
  • Start on a queued torrent that is not forced starts it, but the next pass (within 30 s) stops it again if it does not rank inside the ceiling. Force download is the way to jump the queue.
  • Over the shim, torrents/setForceStart with value=true does what Force download does, on every copy of the torrent, and starts it, a stopped torrent included: it runs past the ceiling of any engine. value=false lifts the mark and leaves the torrent to the queue. Rows always report force_start: false.
  • torrents/topPrio, bottomPrio, increasePrio, decreasePrio answer 409: Torrent queueing is not enabled when no engine holding the torrents runs a queue, Hydranos orders its queue itself (most seeders first for downloads, oldest first for seeds): positions cannot be set; force start runs a torrent past it when one does.

Example: keep 5 downloads running

[race]
active_downloads = 5                 # live when saved from the UI or an API; no `queueing` needed
dont_count_slow_torrents = true      # a stalled release frees its slot
slow_torrent_dl_rate_threshold = 2   # KiB/s
slow_torrent_inactive_timer = 60     # seconds

What to expect:

  • Coming from qBittorrent behind Sonarr/Radarr? Your imported torrents and their grabs are in hoard: put these keys under [hoard] instead (the template already has active_downloads there, so Session Hoard shows it). See Migrating from qBittorrent.
  • Race has no download ceiling by default (hoard has 10). Set it in the file as above (the Session Race editor shows active_downloads only once the file has it), or over the shim (below). Saved from the UI or an API, it applies at the next pass; a hand edit of the file waits for the next start or the next save from the UI.
  • The 5 that run are the 5 with the most seeders in their swarm, not the 5 added first. The others show queued, with their progress kept.
  • A torrent you stopped by hand does not count and is not started.
  • With the slow rule, more than 5 can be running: the slow ones keep going without a slot. Leave it off for a hard ceiling of 5.
  • queueing is not needed: it only governs active_seeds and active_limit.
  • Over the shim, app/setPreferences with {"max_active_downloads": 5} does the same, but writes active_downloads on every local engine, hoard included. app/preferences shows race's values (max_active_downloads -1 when race has none, the slow-torrent keys at race's or their defaults), and setPreferences writes max_active_downloads and each slow-torrent key only when it differs from that: a client that posts back the page it read leaves your [hoard] values alone, but one that sends a new value overwrites them on every engine.
  • An extra engine takes active_downloads from its own session, or from its role's section if it does not set one.

Share limits

qBittorrent's share limits: when a seed reaches any of its limits (each tested with >=), the engine's action is applied.

Key Meaning Template race and hoard Absent from the file
max_ratio Ratio limit (float). -1.0 off
max_seeding_time Minutes of seeding. -1 off
max_inactive_seeding_time Minutes with nothing uploaded. -1 off
share_limit_action "stop", "remove" (files stay) or "remove_with_files". "stop" stop

A negative value switches a limit off. "pause" and "" read as stop, and so does a value the daemon cannot read. The ratio is uploaded ÷ downloaded (lifetime counters); when downloaded is under 1 % of the data held (a seed-mode add or a cross-seed), it is uploaded ÷ data held, as in qBittorrent.

The tracker's minimum comes first

A limit never acts before the seed has served its tracker's Minimum seed (announce_min_seed_hours, declared per tracker host on the Trackers tab), the same test the race drain uses (Race Drain and Graduation). This holds for every action, stop included.

  • A tracker with nothing declared holds the limit: the seed keeps seeding, whatever the limit says. To let limits act on a tracker's torrents, declare its hours, 0 included.
  • A torrent with no tracker owes nothing: the action applies.
  • The tracker tested is the torrent's first live tracker.
  • A held seed is logged once, not every pass: share limit reached, held: the tracker declares no minimum seeding time … or … the tracker's minimum seeding time is not served yet.

What the worker does

flowchart TD
    A["Share-limit pass: 90 s after start,<br/>then every 120 s, per local engine"] --> B{"Seeding, or stopped by the queue<br/>with all its data?"}
    B -->|"no: downloading, checking,<br/>error, stopped by you"| X["Not touched"]
    B -->|yes| C{"Ratio, seeding time or inactive time<br/>at or over its limit (torrent's own, else engine's)?"}
    C -->|no| X
    C -->|yes| D{"Tracker's Minimum seed declared<br/>and served? (no tracker: yes)"}
    D -->|no| H["Held: keeps seeding, logged once"]
    D -->|yes| M{"500 actions already this pass?"}
    M -->|yes| NX["Left for the next pass"]
    M -->|no| ACT{"share_limit_action of the engine"}
    ACT -->|stop| ST["Stopped as by Stop:<br/>shows stopped, the queue never restarts it"]
    ACT -->|remove| RM["Removed from this engine,<br/>trackers hear stopped, files stay"]
    ACT -->|remove_with_files| RF["Removed; files deleted<br/>except those another torrent reads"]
Loading
  • Each local engine has its own worker; at most 500 stops or removals per pass, so a ratio limit turned on over a large library spreads its work: at most 15,000 actions an hour per engine, so 100,000 seeds already over the limit take about 7 hours to be acted on.
  • Removal goes through the normal removal path: trackers receive stopped, and the files are deleted except those another loaded torrent reads at the same path (see Jobs, Bulk Actions and Moving Data).
  • Seeding time is the torrent's counted seeding time, kept across restarts.

Not implemented in 4.4: the inactivity clock lives in memory — it starts when the worker first sees the seed, so every daemon start grants a full max_inactive_seeding_time again (the editor says "Counted from the daemon's start") — there is no per-torrent action (a torrent's limits always use its engine's share_limit_action) and no super seeding.

Per torrent

A torrent can carry its own limits: -2 = follow its engine, -1 = no limit, ≥ 0 = its own limit (times in minutes). They belong to the torrent, so every copy has them; each copy still uses its own engine's action.

  • Web UI: right-click › Share limits… opens Share limits: {n} torrent(s) with Ratio limit, Seeding time limit (minutes) and Inactive seeding time limit (minutes), then Apply / Cancel. An empty field keeps the current value. For one torrent the fields start at its current values.
  • Detail panel (race and hoard, General): the Share limits line shows what is in force, e.g. ratio 2 · seeding 1440 min · inactive none → stop, with (engine) next to a value taken from the engine.
  • Native: GET|POST /api/torrents/:info_hash/share-limits, body {"ratio_limit": 2.0, "seeding_time_limit": 1440, "inactive_seeding_time_limit": -2} (each optional, at least one, else 400). The answer lists, per local copy, the torrent's own values, the effective ones and the engine's action.
  • Shim: torrents/setShareLimits with hashes (or all), ratioLimit and seedingTimeLimit required (400 without them), inactiveSeedingTimeLimit optional (absent = -2). The same three fields are read on torrents/add. torrents/info and properties carry ratio_limit, seeding_time_limit, inactive_seeding_time_limit (the torrent's own) and max_ratio, max_seeding_time, max_inactive_seeding_time (in force).

Per engine

  • Web UI: Session Race / Session Hoard, keys as above; share_limit_action is a drop-down (stop, remove, remove_with_files).
  • Native: GET|POST /api/engines/:id/share-limits, body any of max_ratio, max_seeding_time, max_inactive_seeding_time, share_limit_action. Unknown field or bad action → 400, unknown engine → 404. It writes the key into [race], [hoard] or the engine's block (creating it if missing).
  • Shim: app/preferences shows the limits of race (or the first local engine): max_ratio_enabled, max_ratio, max_seeding_time_enabled, max_seeding_time, max_inactive_seeding_time_enabled, max_inactive_seeding_time, max_ratio_act (0 stop, 1 remove, 3 remove with files). app/setPreferences takes a value only with its *_enabled flag and writes it on every local engine, and only when it differs from what the page shows (a client that posts back the whole page changes nothing). max_ratio_act = 2 (super seeding) is logged and leaves the action unchanged.

A share limit and a workflow can do the same job; a workflow is for rules a share limit cannot express (per category, per tracker, tags…).

Speed limits

Three levels, each optional. The client cap is shared by every engine, an engine's cap by all of that engine's torrents together (one cap on their sum, not a per-torrent default), a torrent's cap by that torrent alone. A block of data has to clear all three, so the narrowest one wins:

Level Set by Unit Kept across restarts
Client (qBittorrent's "global" limit, one for the whole process, above every engine) shim only: transfer/setUploadLimit, transfer/setDownloadLimit (limit, 400 without it), setPreferences up_limit / dl_limit bytes/s, 0 or negative = none yes (store setting client_rate_limits, restored at boot)
Engine: upload_rate_limit, download_rate_limit Session Race / Session Hoard; POST /api/engines/:id/rate-limits file and editor: bytes/s; native route: KiB/s (upload_kib, download_kib) yes (the file)
Torrent right-click › Limit rate…; POST /api/torrents/:info_hash/limits; shim torrents/setUploadLimit, setDownloadLimit; add fields upLimit, dlLimit UI and native: KiB/s (up_kib, down_kib); shim: bytes/s yes (the torrent's resume record)
  • Template, race and hoard: upload_rate_limit = 0, download_rate_limit = 0 (unlimited). 0 or negative = unlimited at every level.
  • The caps are token buckets on peer traffic, with a burst of a quarter of a second (at least 32 KiB). A change applies at once, without a restart.
  • Limit rate… opens Limit rate: {n} torrent(s) with Upload limit (KiB/s) and Download limit (KiB/s): "This torrent's own cap, in KiB/s. 0 = no cap of its own (the engine's still applies). Leave a field empty to keep its current value." The native route caps every local copy unless ?engine= names one; the shim caps every copy.
  • The detail panel's Speed limits line reads ↑ X · ↓ Y: per direction the torrent's own cap, or the engine's followed by (engine) when the torrent has none or the engine's is narrower, or unlimited. The client cap is not shown there.
  • GET /api/engines/:id/rate-limits answers the engine's caps from the file (upload_kib, download_kib), what the engine holds now (upload_limit, download_limit, bytes/s) and the client cap (client_upload_limit, client_download_limit).
  • Over the shim, transfer/info and sync/maindata report up_rate_limit / dl_rate_limit = the client cap; torrents/uploadLimit / downloadLimit answer 0 for none, torrents/info says -1.

Not implemented in 4.4: the client-wide cap can only be set over the qBittorrent API — no web UI control and no native route writes it — and there are no alternative or scheduled speed limits (toggleSpeedLimitsMode does not exist).

The choker

Off by default, and best left off. With choking = true, each seeding torrent serves only max_uploads_per_torrent interested peers at a time, re-ranked every 10 seconds by 0.7 × rarity + 0.3 × speed (rarity = share of the torrent the peer still lacks; speed = what it took in the last 10 s, relative to the fastest). Downloading torrents are not choked. Switched off while running, it unchokes every peer it had choked.

Key Template race Template hoard Absent from the file
choking false false false
max_uploads_per_torrent 100 -1 0 = 4 slots

max_uploads_per_torrent is read only while choking = true; negative = unlimited. Both are live. The template warns why it is off: on a large library it churns the peers and can cut total upload by an order of magnitude; turn it on only when your uplink itself is the bottleneck. The shim's app/preferences shows race's max_uploads_per_torrent; setPreferences does not write it.

[race.custom_choking] from 3.x is still ignored; the daemon names it in a startup warning that points to choking = true and max_uploads_per_torrent.

Startup pause (start_paused)

start_paused = true in an engine's section (absent = false; not in the template; read at start) holds that engine when it starts: it dials no peer and sends no announce (nor scrape) until you release it. A torrent already in the announce schedule checks again about once a minute (60 s plus a spread of up to 6 s), so it announces about a minute after the release. The schedule takes a catalogue in over up to half an hour after startup (at least 100 torrents per 10 s); a torrent not yet admitted when you release the engine announces when it is admitted.

  • A banner sits above the tabs on every tab and cannot be dismissed: Startup pause: {engines} not announcing, "No announces or peer connections are leaving Hydranos. Adjust your rate limits now if you need to, then start. Torrents you paused yourself stay paused.", button Start now (Starting… while it works). It checks again every 10 seconds.
  • API: GET /api/startup-pause → {"held": [...], "holding": true}; POST /api/startup-pause/release → {"status": "ok", "released": [...], "holding": false}. Releasing twice is harmless.
  • /health answers degraded (still 200) while an engine is held; /api/engines carries start_paused for each engine.
  • The hold is per process, not per torrent: releasing it does not start torrents you stopped.

Known bugs and limits in 4.4

Not implemented in 4.4: no queue positions — the priority calls answer 409, and a Start over the ceiling is stopped again within 30 s — use Force download (setForceStart over the shim), a larger active_downloads or dont_count_slow_torrents.

Known bug in 4.4: the Advanced settings editor shows wrong hints for hoard: max_connections default: 8000 (the template has 12000, absent is 0 = no cap), max_uploads_per_torrent default: 20 (the template has -1, absent is 0 = 4 slots) and inactivity_timeout default: 90 (the template has 20, and the key does nothing) — trust the tables on this page.

Accepted but ignored in 4.4: inactivity_timeout (in the template, 20 for both engines) is read by nothing and not named at startup; its editor hint "Seconds before an idle peer is considered inactive." describes nothing — the peer idle timeout is peer_timeout (absent or 0 = 300 s, under 120 raised to 120, live).

The qBittorrent importer brings neither speed limits nor share limits over (Migrating from qBittorrent).

Related pages

⚠️ **GitHub.com Fallback** ⚠️