FEAT 015
id: FEAT-015
title: Broadband upload slot allocation — fixed cap + weak-slot reclamation
status: Done
priority: Major
category: feature
labels: [upload, broadband, performance, upload-queue, throttler, preferences]
milestone: ~
created: 2026-04-08
source: main commit d731bbe (Stabilize broadband upload slot allocation); historical design in docs/history/features/FEATURE-BROADBAND.md
Historical reference only:
stale-v0.72a-experimental-cleanandanalysis\stale-v0.72a-experimental-cleanare retired reference sources, not active branch targets or current baselines. Use them only as provenance or idea-extraction sources; landed status is determined againstmain. See Historical References.
Summary¶
The broadband upload slot controller is merged to main.
Mainline commit:
d731bbe—Stabilize broadband upload slot allocation
The stock upload controller scaled slot count linearly with bandwidth using a hard-coded
25 KiB/s per-slot target (UPLOAD_CLIENT_MAXDATARATE). On a modern broadband link
(50+ Mbit/s) this drove slot count toward 100, filling the pipe by accumulation rather
than by keeping a small set of strong slots.
eMule-main now uses the budget-based controller documented below: a configurable
steady-state slot target, per-slot rate derived from one finite configured upload
budget, and proactive weak-slot reclamation.
Mainline status: landed on main. The detailed branch-design notes below are kept
as the implementation contract/history for the shipped behavior.
Scope split: Non-slot broadband extras still present on the branch, such as low-ratio queue scoring and ratio/cooldown UI columns, are no longer part of this story. Track them separately under FEAT-023.
The Problem (stock v0.72a behavior)¶
UploadQueue.cpp:401-406 — slot count formula:
// 4 slots or more - linear growth by 1 KiB/s steps, cap off at UPLOAD_CLIENT_MAXDATARATE
nResult = min(UPLOAD_CLIENT_MAXDATARATE, nOpenSlots * 1024);
Opcodes.h:109-111:
#define UPLOAD_CLIENT_MAXDATARATE (25*1024) // 25 KiB/s per slot target
#define MAX_UP_CLIENTS_ALLOWED 100
On a 50 Mbit/s (~6100 KiB/s) uplink: - target per slot: 25 KiB/s - implied slot count: 6100 / 25 = 244 → capped at 100 - result: 100 slots all transferring at ~60 KiB/s each, no weak-slot retirement
A modern link works better with 12 strong slots at ~500 KiB/s each.
Implemented Controller Design¶
Preferences¶
| Key | Type | Default | Meaning |
|---|---|---|---|
[UploadPolicy] MaxUploadClientsAllowed |
int | 8 | Steady-state slot target |
[UploadPolicy] SlowUploadThresholdFactor |
float | 0.33 | Slow-slot threshold as a fraction of per-slot target |
[UploadPolicy] SlowUploadGraceSeconds |
int | 30 | Slow-rate recycle grace after warm-up |
[UploadPolicy] SlowUploadWarmupSeconds |
int | 60 | Startup protection before slow/zero recycle can apply |
[UploadPolicy] ZeroUploadRateGraceSeconds |
int | 10 | Zero-rate recycle grace after warm-up |
[UploadPolicy] SlowUploadCooldownSeconds |
int | 120 | Score suppression after weak-slot recycle |
[UploadPolicy] SessionTransferLimitMode |
enum | Percent of file size | Transfer-rotation mode |
[UploadPolicy] SessionTransferLimitValue |
int | 55 | Value for the selected transfer-rotation mode |
[UploadPolicy] SessionTimeLimitSeconds |
int | 3600 | Time-based session rotation backstop |
Configured upload budget¶
uint32 configuredBudget = GetMaxUpload(); // KiB/s
On feature/broadband-stabilization, upload is always finite:
- the configured upload limit is the only slot-control budget source
- unlimited upload is not supported
- missing, zero, or legacy-unlimited upload values normalize to 6100 KiB/s
Per-slot target¶
uint32 targetPerSlot = max(3u, configuredBudget / MaxUploadClientsAllowed); // KiB/s floor 3
Existing 75% admission threshold applies to targetPerSlot.
Slot admission (current stabilization branch)¶
- Fill until
MaxUploadClientsAllowedis reached - Do not open temporary overflow slots above the configured cap
MAX_UP_CLIENTS_ALLOWED = 100kept as absolute ceiling only- Underfill is still evaluated, but only to justify reclaiming weak slots
- Slow-slot recycling only activates after underfill persists ≥ 2 seconds
Slow/stuck slot reclamation (UpdownClient.h + UploadClient.cpp)¶
New state per CUpDownClient tracks accumulated slow/zero-rate time and
post-recycle cooldown.
Slow threshold: smoothed upload rate < targetPerSlot / 3
Eviction triggers (only while queue non-empty AND at/above soft cap AND underfilled):
- fresh slots are protected for 60 seconds before recycle can apply
- recycle after 30 seconds below slow threshold
- recycle after 10 seconds at exactly 0 upload rate
Evicted clients are requeued immediately (protocol compat) but queue score held
at 0 for 120 seconds (Cooldown column).
Good samples reduce accumulated slow time. Neutral periods freeze timers.
Session rotation overrides¶
Replace SESSIONMAXTRANS/SESSIONMAXTIME checks in upload slot logic with:
- SessionTransferLimitMode + SessionTransferLimitValue
- SessionTimeLimitSeconds
Current default:
- percent-of-file transfer mode with value 55
- 3600 second time limit
Socket buffer / disk prefetch scaling¶
Replace fixed 75 KiB/s / 100 KiB/s thresholds in UploadDiskIOThread.cpp:
- Large socket send buffer: enable when slot rate ≥ targetPerSlot / 2
- Disk prefetch: 1 → 3 → 5 blocks based on rate relative to targetPerSlot
Files to Modify¶
| File | Change |
|---|---|
Preferences.h/cpp |
Slot-controller UploadPolicy preference keys with load/save/defaults |
Opcodes.h |
No change to existing constants |
UploadQueue.h/cpp |
Replace slot-count formula; add budget/target computation; add weak-slot recycle gates |
UploadBandwidthThrottler.h/cpp |
Replace UPLOAD_CLIENT_MAXDATARATE guard with targetPerSlot |
UpdownClient.h |
Add slow-tracking and cooldown members for weak-slot recycle |
UploadClient.cpp |
Slow/stuck detection + eviction logic; cooldown queue-score zero |
UploadDiskIOThread.cpp |
Replace fixed buffer/prefetch thresholds with targetPerSlot-based scaling |
PPgTweaks.cpp |
Broadband settings surfaced under Preferences > Tweaks > Broadband |
Implementation Order¶
Preferences.h/cpp— slot-controllerUploadPolicykeysUploadQueue.cpp— fixed-cap admission and weak-slot recycleUploadBandwidthThrottler.cpp— cap-aware slot pressureUpdownClient.h/UploadClient.cpp— slow-slot tracking and cooldownUploadDiskIOThread.cpp— per-slot-rate buffer/prefetch scaling- Session rotation overrides
PPgTweaksbroadband settings
Acceptance Criteria¶
- [ ] Default
MaxUploadClientsAllowed=8holds slot count near 8 on a 50 Mbit/s link (does not grow to 100) - [ ] Manually setting
MaxUploadClientsAllowed=12holds slot count at or below 12 on a 50 Mbit/s link - [ ] Fresh upload slots are protected from recycle for the first 60 s
- [ ] Slow uploaders (<
targetPerSlot / 3) are evicted after 30 s and enter 120 s cooldown - [ ] Zero-rate slots are evicted after 10 s once warm-up has completed
- [ ] Cooldown prevents immediate slot re-entry; column shows remaining time
- [ ]
SessionTransferLimitMode/SessionTransferLimitValue/SessionTimeLimitSecondsoverride stock session rotation - [ ]
Preferences > Tweaks > Broadbandpage loads, saves, applies without restart - [ ] Missing or legacy-unlimited upload config normalizes to
6100 KiB/s - [ ] Friend slots remain the only deliberate scheduling exception
- [ ] LowID reconnects do not bypass the normal waiting/admission path
- [ ] Collection handling is correctness-only and does not use a separate scheduler path
Reference¶
Full design and rationale: docs/history/features/FEATURE-BROADBAND.md
Stale branch reference: archive/v0.72a-experimental-clean-provisional-20260404
eMuleAI Ratio columns: analysis/emuleai/srchybrid/KnownFile.h:124-133, UploadListCtrl.cpp:355-362