feat: add global and per download rate limiting to surge#465
feat: add global and per download rate limiting to surge#465junaid2005p wants to merge 35 commits into
Conversation
…crrent downloaders
…mit to not reflect since RLock was being used before rate limit
…rs to ensure it is populated properly before being written into db
…N is waiting or blocking for tokens
Binary Size Analysis
|
…ecking if the id existed. fixed that
…was never cleaned up
…n tui to go through
|
Let me pull this, tell it out and get back to you by EOW. I'm having a really hectic week rn :| |
…returning so hot path resume bug is also fixed
… released before settings were saved in config
|
@greptileai |
|
It's in the func (m *RootModel) clearDownloadRateLimit(downloadID string) error {
if m.Service == nil {
return nil
}
clearer, ok := m.Service.(interface{ ClearRateLimit(string) error }) // <-- redundant assertion
if !ok {
return fmt.Errorf("service does not support clearing download rate limits") // <-- unreachable
}
return clearer.ClearRateLimit(downloadID)
}Since |
|
@junaid2005p gonna make a couple of modifications to the TUI which I feel will look better! |
|
Go ahead @SuperCoolPencil |
|
I also think we do not need a new settings section for this, we could just have a modal |
|
Interesting I will consider and let you know A modal is generally fine but a settings tab makes it easier to persist the speed limits throughout sessions |
|
can you set limit for a specific download? |
|
Yes — this PR adds exactly that. You can set a per-download rate limit with: You can also reset it back to the default with: The full set of options:
Human-readable units (KB/s, MB/s, Mbps, etc.) are parsed client-side and sent as raw bytes/s to the server. |


Greptile Summary
This PR adds a complete rate-limiting system to Surge: a custom token-bucket
RateLimiterandMultiLimiter, per-download and global/default limit controls wired through the worker pool, HTTP API endpoints, a newsurge limitCLI command, and a "Speed Limits" tab in the TUI settings.WorkerPoolgainsSetGlobalRateLimit,SetDefaultDownloadRateLimit,SetDownloadRateLimit, andClearDownloadRateLimit; the concurrent downloader reads fromd.State.GetRateLimit()at pause time so snapshots are no longer stale.inheritshortcut routing toClearRateLimit;parseRateLimitQueryaccepts raw byte integers (the CLI converts human-readable units client-side).validateSettingnow handlesdl:<id>keys, fixing a previously-flagged silent validation failure.Confidence Score: 3/5
Large new feature with lingering concurrency edge cases from prior review rounds that have only been partially addressed.
Many previously-flagged issues (stale limiter on resume, stale rate in pause snapshot, double-unlock, silent TUI validation for dl: keys) are addressed. However, silent success for non-existent IDs and the TOCTOU window in ClearDownloadRateLimit remain open. SetDefaultDownloadRateLimit also nests both pool mutexes while all other functions keep them sequential.
internal/download/pool.go and internal/core/local_service.go
Important Files Changed
Comments Outside Diff (3)
internal/download/pool.go, line 366-414 (link)downloadLimitersentries are never removedensureLimiterForConfiginserts a*RateLimiterintodownloadLimitersfor every new download ID, butCancel, the completion branch inworker, andGracefulShutdownnever calldelete(p.downloadLimiters, id). For a long-running Surge daemon processing many downloads, this map grows without bound.Prompt To Fix With AI
internal/config/settings.go, line 322 (link)initializeCategoriesList, butCategoryOrderappends it after "Extension" (the last tab). Users navigating the settings TUI will find Network-related rate limit options under the very last tab rather than next to the other Network settings.Prompt To Fix With AI
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
internal/download/pool.go, line 566-574 (link)cfg.Limitermakes rate-limit changes invisible after hot-path resumeExtractPausedConfigdeletes the download's entry fromdownloadLimiters, but the returnedcfgstill carriescfg.Limiter— the old*MultiLimiterwhose inner per-download*RateLimiteris now orphaned (no longer tracked in the map). WhenAddcallsensureLimiterForConfig, the guardif cfg.Limiter == nilprevents the new limiter from being installed, so the resumed download executes against the orphaned limiter. Any subsequentsurge limit <id> XupdatesdownloadLimiters[id](the new limiter) but has zero effect on the running download. The fix is to nil outcfg.Limiterbefore returning, soensureLimiterForConfigalways wires the fresh limiter that it installs intodownloadLimiters.Prompt To Fix With AI
Prompt To Fix All With AI
Reviews (13): Last reviewed commit: "refactor: removed dead code branch" | Re-trigger Greptile