Skip to content

feat: add URL download speed test functionality - #240

Open
b1n23 wants to merge 4 commits into
autobrr:developfrom
b1n23:feature/url-download-speedtest
Open

feat: add URL download speed test functionality#240
b1n23 wants to merge 4 commits into
autobrr:developfrom
b1n23:feature/url-download-speedtest

Conversation

@b1n23

@b1n23 b1n23 commented Jul 22, 2026

Copy link
Copy Markdown

Summary

  • Add URL download speed test functionality with configurable threading and timeouts
  • Support both predefined download servers and custom URLs
  • Enable scheduling for URL download tests with full configuration options
  • Add 8 built-in servers from major providers

Why

  • Provide an alternative speed test method that doesn't depend on Ookla, LibreSpeed, or iperf3 servers
  • Allow users to test download speeds from specific URLs or CDN endpoints
  • Enable performance testing of custom file hosting or CDN infrastructure
  • Support scenarios where traditional speed test servers may be blocked or unavailable

Testing

  • pnpm -C web lint
  • pnpm -C web build
  • Other:

Screenshots (if UI)

netronome

Checklist

  • Diff is scoped to the issue (no unrelated changes)
  • No new lockfiles (pnpm only)
  • No generated/dist files committed
  • AI-assisted changes reviewed and edited by a human

Summary by CodeRabbit

  • New Features

    • Added a URL Download speed test mode with custom URLs or built-in download servers.
    • Exposed download threads (2/4/8) and download timeout controls.
    • Added live progress updates and full result/history support with URL Download labeling.
    • Added scheduling support for URL Download tests, including URL/threads/timeout display.
  • Bug Fixes

    • Added stricter input validation for URL, thread counts, and timeout ranges.
    • Improved URL Download timeout handling and result persistence to reduce premature failures.

b1n23 added 3 commits July 11, 2026 16:39
- Implement multi-threaded URL download testing with 2/4/8 thread options
- Add 9 preset global download servers (Cloudflare, OVH, Linode, Vultr, DataPacket)
- Support custom URL input with validation
- Add comprehensive HTTP headers for CDN compatibility
- Extend database schema for URL download scheduling
- Add TestTypeURLDownload constant and related types
- Fix switch statement duplicate cases in speedtest handlers
Frontend improvements:
- Add URL download test UI in ServerList component
- Custom URL input with thread selection (2/4/8 threads)
- Configurable timeout with default 30 seconds
- Display URL download results in dashboard with proper server names
- Add URL download scheduling support in ScheduleManager
- Show test configuration (threads, timeout) in schedule display
- Update SpeedHistoryChart to support URL download metrics
- Fix field name consistency (useUrlDownload)

UI enhancements:
- Improve schedule display format: "hostname (X threads, Ys timeout) - URL Download"
- Fix URL wrapping issues in schedule details
- Update columns to display URL download server information
- Add comprehensive HTTP client timeouts to prevent goroutine leaks
- Configure transport-level timeouts (dial, TLS handshake, response header)
- Set client timeout slightly longer than context for graceful cancellation
- Add connection pool management (MaxIdleConns, IdleConnTimeout)
- Ensure all network phases have independent timeout protection

This prevents goroutine leaks when HTTP connections hang or timeout,
ensuring workers exit within timeout+15s maximum.
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new url_download speed-test mode with configurable URLs, threads, and timeouts. The backend runs concurrent HTTP downloads and stores results, while the frontend supports execution, scheduling, progress, history, and result display.

Changes

URL Download Execution

Layer / File(s) Summary
Runner and download contracts
internal/types/types.go, internal/speedtest/url_download.go, internal/speedtest/url_download_servers.go
Adds URL-download options, built-in servers, URL validation, concurrent HTTP workers, progress callbacks, timeout handling, and result calculation.
Backend integration and persistence
internal/database/schedule.go, internal/server/handlers.go, internal/speedtest/speedtest.go, internal/speedtest/result_handler.go
Registers the runner, validates schedule options, selects URL-download timeouts, routes server retrieval, and derives saved result host identifiers.
Frontend controls and execution flow
web/src/components/Main.tsx, web/src/components/speedtest/ServerList.tsx, web/src/components/speedtest/SpeedTestTab.tsx, web/src/types/*
Adds URL Download controls, built-in server loading, custom URL handling, thread and timeout inputs, progress state, and mutation wiring.
URL download schedules
web/src/components/speedtest/ScheduleManager.tsx
Stores URL-download schedule options and displays the configured URL, host, threads, and timeout.
Result and history presentation
web/src/components/speedtest/columns.tsx, web/src/components/speedtest/SpeedHistoryChart.tsx
Labels URL downloads, redacts public hosts, and displays upload and jitter as unavailable.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant Main
  participant SpeedTestService
  participant UrlDownloadRunner
  participant HTTPDownloadServers
  participant ResultHandler
  Browser->>Main: Configure URL, threads, and timeout
  Main->>SpeedTestService: Start url_download test
  SpeedTestService->>UrlDownloadRunner: RunTest with options
  UrlDownloadRunner->>HTTPDownloadServers: Execute concurrent HTTP downloads
  HTTPDownloadServers-->>UrlDownloadRunner: Bytes, TTFB, and worker status
  UrlDownloadRunner-->>SpeedTestService: Progress and result
  SpeedTestService->>ResultHandler: SaveResult
Loading

Possibly related PRs

Poem

A rabbit hops where download streams flow,
With four quick threads in a tidy row.
URLs sparkle, progress bars gleam,
Bytes race onward like a carrot dream.
“URL Download!” the bunny cries—
While speedy results fill the skies.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly summarizes the main change: adding URL download speed test functionality.
Description check ✅ Passed The description covers Summary, Why, Testing, Screenshots, and Checklist, matching the required template well.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/src/components/speedtest/ScheduleManager.tsx (1)

284-330: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

"Create Schedule" isn't disabled/guarded for url_download without a server or custom URL.

requiresServerSelection only covers "iperf"/"librespeed", so for testType === "url_download" with no server selected and no customUrl, isMissingServer/isCreateDisabled stay false and handleCreateSchedule's early guard (lines 323-329) never fires. The request still goes out and gets rejected server-side per validateScheduleServerIDs's "url_download schedules require a download URL or server ID" check, so there's no data-integrity risk, but the button/UX won't reflect the missing-input state the way it does for iperf3/LibreSpeed, and the user gets a raw backend error instead of the friendly inline message.

🐛 Proposed fix
   const requiresServerSelection = testType === "iperf" || testType === "librespeed";
   const isMissingServer = requiresServerSelection && selectedServers.length === 0;
+  const isMissingUrlDownloadTarget =
+    testType === "url_download" && selectedServers.length === 0 && !customUrl?.trim();
   const isMissingTime = scheduleType === "exact" && exactTimes.length === 0;
-  const isCreateDisabled = isMissingServer || isMissingTime;
+  const isCreateDisabled = isMissingServer || isMissingUrlDownloadTarget || isMissingTime;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/speedtest/ScheduleManager.tsx` around lines 284 - 330,
Update the schedule validation around requiresServerSelection, isMissingServer,
renderButtonContent, and handleCreateSchedule so url_download requires either a
selected server or a non-empty customUrl. Preserve the existing
iperf3/LibreSpeed behavior, expose the same disabled state and friendly prompt
for missing URL/server input, and prevent submission before the backend request.
🧹 Nitpick comments (5)
internal/speedtest/url_download.go (2)

79-93: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Threads/timeout validation duplicated with internal/database/schedule.go.

The 2/4/8 threads and 1-300 timeout checks here mirror validateScheduleServerIDs in internal/database/schedule.go. See consolidated comment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/speedtest/url_download.go` around lines 79 - 93, The download
threads and timeout validation in the URL download setup duplicates the rules in
validateScheduleServerIDs. Consolidate these shared constraints into a reusable
validation symbol and update the validation flow here and in
validateScheduleServerIDs to call it, preserving the existing defaults, accepted
thread values, timeout range, and error behavior.

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated URL-download validation rules (threads 2/4/8, timeout 1-300s) across two files. Extracting a shared validator would prevent the two copies from silently drifting apart if the constraints ever change.

  • internal/speedtest/url_download.go#L79-93: extract the threads/timeout checks into a shared helper (e.g. types.ValidateURLDownloadOptions(threads, timeout int) error) and call it here.
  • internal/database/schedule.go#L28-42: call the same shared helper instead of re-implementing the 2/4/8 and 1-300 checks inline.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/speedtest/url_download.go` at line 1, Extract the duplicated
URL-download threads and timeout validation from the URL-download validation
flow and the schedule validation flow into one shared helper, such as
types.ValidateURLDownloadOptions(threads, timeout int) error. Replace both
inline checks with calls to this helper, preserving the allowed thread values
(2, 4, or 8) and timeout range (1–300 seconds).
internal/database/schedule.go (1)

28-42: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicates threads/timeout validation from internal/speedtest/url_download.go.

Same 2/4/8 and 1-300 constraints are re-implemented in RunTest. See consolidated comment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/database/schedule.go` around lines 28 - 42, Remove the duplicated
DownloadThreads and DownloadTimeout validation from the schedule validation
block, and reuse the existing validation from the URL-download implementation in
internal/speedtest/url_download.go. Keep the download URL/server ID validation
in place and ensure invalid thread or timeout values still produce the
established validation errors through the shared path.
web/src/types/types.ts (1)

32-43: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse the new TestType instead of a duplicated literal union.

testType here re-declares the exact same four-value literal union that TestType (lines 7-14) already expresses. Any future test type addition now needs to be kept in sync across two separate declarations.

♻️ Proposed refactor
-  testType: "speedtest" | "iperf3" | "librespeed" | "url_download";
+  testType: TestType;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/types/types.ts` around lines 32 - 43, Update the
SpeedTestResult.testType property to reference the existing TestType declaration
from this types module instead of repeating the four-value literal union,
preserving the same allowed values and keeping future additions centralized.
web/src/components/speedtest/ServerList.tsx (1)

299-329: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Use the shared server-fetch helper here. web/src/api/speedtest.ts#getServers already wraps this /servers?testType=... request, so reusing it would avoid duplicating the same fetch/error-handling logic in ServerList.tsx.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/speedtest/ServerList.tsx` around lines 299 - 329, Replace
the duplicated request logic in fetchUrlDownloadServers with the shared
getServers helper from web/src/api/speedtest.ts, passing the url_download test
type and preserving the existing state update and error propagation behavior.
Keep the useEffect’s toast handling unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/speedtest/url_download.go`:
- Around line 41-53: When resolving a builtin server in
UrlDownloadRunner.RunTest, capture server.Host in opts.ServerHost alongside
opts.ServerName. Update the builtin-IDC branch of SaveResult to assign
serverHost from opts.ServerHost instead of result.Server, preserving the real
hostname in stored results.
- Around line 104-331: The download worker coordination must not close errChan
until all workers have exited, and ttfb access must be synchronized. Update the
completion, timeout, and cancellation handling around done and errChan so
timeout paths stop or defer draining without closing the channel while workers
may still send; close it only after wg.Wait completes. Protect the ttfb write in
firstByteOnce and its later read with the existing mutex, or otherwise ensure
the read occurs after worker completion.

In `@web/src/components/Main.tsx`:
- Around line 309-323: Update the url_download progress initialization in
Main.tsx to set TestProgress.isUrlDownload to true alongside the existing type
flags, ensuring progress-display components can identify URL download tests.
- Around line 332-364: Remove the selected server host fallback from the
downloadUrl assignment in the speed test mutation. For url_download tests, use
only customUrl or selectedServers[0]?.url; when both are absent, leave
downloadUrl undefined so the backend can resolve the URL from server IDs.

---

Outside diff comments:
In `@web/src/components/speedtest/ScheduleManager.tsx`:
- Around line 284-330: Update the schedule validation around
requiresServerSelection, isMissingServer, renderButtonContent, and
handleCreateSchedule so url_download requires either a selected server or a
non-empty customUrl. Preserve the existing iperf3/LibreSpeed behavior, expose
the same disabled state and friendly prompt for missing URL/server input, and
prevent submission before the backend request.

---

Nitpick comments:
In `@internal/database/schedule.go`:
- Around line 28-42: Remove the duplicated DownloadThreads and DownloadTimeout
validation from the schedule validation block, and reuse the existing validation
from the URL-download implementation in internal/speedtest/url_download.go. Keep
the download URL/server ID validation in place and ensure invalid thread or
timeout values still produce the established validation errors through the
shared path.

In `@internal/speedtest/url_download.go`:
- Around line 79-93: The download threads and timeout validation in the URL
download setup duplicates the rules in validateScheduleServerIDs. Consolidate
these shared constraints into a reusable validation symbol and update the
validation flow here and in validateScheduleServerIDs to call it, preserving the
existing defaults, accepted thread values, timeout range, and error behavior.
- Line 1: Extract the duplicated URL-download threads and timeout validation
from the URL-download validation flow and the schedule validation flow into one
shared helper, such as types.ValidateURLDownloadOptions(threads, timeout int)
error. Replace both inline checks with calls to this helper, preserving the
allowed thread values (2, 4, or 8) and timeout range (1–300 seconds).

In `@web/src/components/speedtest/ServerList.tsx`:
- Around line 299-329: Replace the duplicated request logic in
fetchUrlDownloadServers with the shared getServers helper from
web/src/api/speedtest.ts, passing the url_download test type and preserving the
existing state update and error propagation behavior. Keep the useEffect’s toast
handling unchanged.

In `@web/src/types/types.ts`:
- Around line 32-43: Update the SpeedTestResult.testType property to reference
the existing TestType declaration from this types module instead of repeating
the four-value literal union, preserving the same allowed values and keeping
future additions centralized.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0a9e9b56-13bd-4911-a977-48788c0183c1

📥 Commits

Reviewing files that changed from the base of the PR and between ca7a971 and e4a08d7.

📒 Files selected for processing (15)
  • internal/database/schedule.go
  • internal/server/handlers.go
  • internal/speedtest/result_handler.go
  • internal/speedtest/speedtest.go
  • internal/speedtest/url_download.go
  • internal/speedtest/url_download_servers.go
  • internal/types/types.go
  • web/src/components/Main.tsx
  • web/src/components/speedtest/ScheduleManager.tsx
  • web/src/components/speedtest/ServerList.tsx
  • web/src/components/speedtest/SpeedHistoryChart.tsx
  • web/src/components/speedtest/SpeedTestTab.tsx
  • web/src/components/speedtest/columns.tsx
  • web/src/types/speedtest.ts
  • web/src/types/types.ts

Comment thread internal/speedtest/url_download.go Outdated
Comment on lines +104 to +331
startTime := time.Now()
var ttfb time.Duration
var totalBytes int64
var contentLength int64 // Total expected size from Content-Length header
var mu sync.Mutex
var wg sync.WaitGroup
var firstByteOnce sync.Once
errChan := make(chan error, threads)

// Progress reporting goroutine
progressDone := make(chan struct{})
go func() {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-progressDone:
return
case <-ticker.C:
mu.Lock()
elapsed := time.Since(startTime).Seconds()
currentSpeed := 0.0
if elapsed > 0 {
currentSpeed = float64(totalBytes) * 8 / elapsed / 1000000 // Mbps
}

// Calculate real progress
progress := 50.0 // Default fallback
if contentLength > 0 {
// Use actual download progress
progress = float64(totalBytes) / float64(contentLength) * 100.0
if progress > 100.0 {
progress = 100.0
}
} else {
// Estimate based on elapsed time vs timeout
timeProgress := (elapsed / float64(timeout)) * 100.0
if timeProgress > 95.0 {
progress = 95.0 // Cap at 95% for time-based estimation
} else {
progress = timeProgress
}
}
mu.Unlock()

if r.progressCallback != nil {
r.progressCallback(types.SpeedUpdate{
Type: "download",
ServerName: opts.ServerName,
Speed: currentSpeed,
Progress: progress,
IsComplete: false,
IsScheduled: opts.IsScheduled,
TestType: "url_download",
})
}
}
}
}()

// Launch download workers
for i := 0; i < threads; i++ {
wg.Add(1)
go func(workerID int) {
defer wg.Done()

req, err := http.NewRequestWithContext(testCtx, "GET", downloadURL, nil)
if err != nil {
errChan <- fmt.Errorf("worker %d: failed to create request: %w", workerID, err)
return
}

// Set User-Agent to avoid 403 errors from CDNs like Cloudflare
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36")
req.Header.Set("Accept", "*/*")
req.Header.Set("Accept-Encoding", "gzip, deflate")
req.Header.Set("Connection", "keep-alive")

// Set Referer header to the root URL (scheme + host)
if parsedURL, err := url.Parse(downloadURL); err == nil {
referer := fmt.Sprintf("%s://%s/", parsedURL.Scheme, parsedURL.Host)
req.Header.Set("Referer", referer)
}

// Configure HTTP client with timeouts to prevent goroutine leaks
// Client timeout is slightly longer than context to allow graceful cancellation
client := &http.Client{
Timeout: time.Duration(timeout+5) * time.Second,
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 10 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 10 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
},
}

workerStart := time.Now()
resp, err := client.Do(req)
if err != nil {
errChan <- fmt.Errorf("worker %d: request failed: %w", workerID, err)
return
}
defer resp.Body.Close()

// Record TTFB for the first worker
firstByteOnce.Do(func() {
ttfb = time.Since(workerStart)
log.Debug().
Int("worker", workerID).
Dur("ttfb", ttfb).
Msg("First byte received")

// Get Content-Length from the first successful response
if resp.ContentLength > 0 {
mu.Lock()
contentLength = resp.ContentLength * int64(threads) // Multiply by thread count
mu.Unlock()
log.Debug().
Int64("content_length", resp.ContentLength).
Int("threads", threads).
Int64("total_expected", contentLength).
Msg("Content-Length detected")
}
})

if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
errChan <- fmt.Errorf("worker %d: HTTP %d", workerID, resp.StatusCode)
return
}

// Read response body and discard to memory
buf := make([]byte, 32*1024)
for {
select {
case <-testCtx.Done():
return
default:
n, err := resp.Body.Read(buf)
if n > 0 {
mu.Lock()
totalBytes += int64(n)
mu.Unlock()
}
if err != nil {
if err == io.EOF {
return
}
log.Debug().
Err(err).
Int("worker", workerID).
Msg("Read error")
return
}
}
}
}(i)
}

// Don't wait indefinitely for goroutines - return before context deadline
// Use a separate goroutine to wait for cleanup
done := make(chan struct{})
go func() {
wg.Wait()
close(done)
}()

// Calculate time buffer: return 500ms before timeout to avoid handler 504
deadline, hasDeadline := testCtx.Deadline()
if !hasDeadline {
deadline = time.Now().Add(time.Duration(timeout) * time.Second)
}
timeBuffer := 500 * time.Millisecond
returnDeadline := deadline.Add(-timeBuffer)

// Create timer to return before actual timeout
returnTimer := time.NewTimer(time.Until(returnDeadline))
defer returnTimer.Stop()

// Wait for either completion or near-timeout
var errors []error
select {
case <-done:
// All workers completed normally
close(progressDone)
close(errChan)
for err := range errChan {
errors = append(errors, err)
}
case <-returnTimer.C:
// About to hit deadline - return immediately with partial results
// Don't wait for goroutines to finish to avoid handler timeout
close(progressDone)
close(errChan)
for err := range errChan {
errors = append(errors, err)
}
// Workers will cleanup in background
go func() {
<-done // Wait for cleanup to finish
}()
case <-testCtx.Done():
// Fallback: context cancelled/timeout - return immediately
close(progressDone)
close(errChan)
for err := range errChan {
errors = append(errors, err)
}
go func() {
<-done
}()
}

// Calculate results
elapsed := time.Since(startTime).Seconds()
if elapsed == 0 {
elapsed = 0.001
}

downloadSpeed := float64(totalBytes) * 8 / elapsed / 1000000 // Mbps
latency := fmt.Sprintf("%.2fms", float64(ttfb.Microseconds())/1000.0)

// Check timeout status
wasTimeout := ctx.Err() == context.DeadlineExceeded || testCtx.Err() == context.DeadlineExceeded

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- files of interest ---'
git ls-files 'internal/speedtest/*' 'internal/**/result_handler.go' 'internal/**/schedule.go' 'internal/**/handler.go' 'internal/**/server.go' | sed -n '1,200p'

echo
echo '--- search for recover() and panic handling ---'
rg -n "recover\(|panic\(" internal -g'*.go'

echo
echo '--- locate RunTest and callers ---'
rg -n "RunTest\(" internal -g'*.go'

echo
echo '--- outline speedtest files ---'
ast-grep outline internal/speedtest/url_download.go
ast-grep outline internal/speedtest/*.go 2>/dev/null || true

Repository: autobrr/netronome

Length of output: 910


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- RunTest and call sites ---'
rg -n "RunTest\(" internal -g'*.go'

echo
echo '--- recover/panic in speedtest and server paths ---'
rg -n "recover\(|panic\(" internal/server internal/speedtest -g'*.go'

echo
echo '--- relevant slices of url_download.go ---'
nl -ba internal/speedtest/url_download.go | sed -n '1,360p'

echo
echo '--- result_handler.go ---'
nl -ba internal/speedtest/result_handler.go | sed -n '1,240p'

Repository: autobrr/netronome

Length of output: 1655


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- recover() search across repo ---'
rg -n "recover\(" . -g'*.go' || true

echo
echo '--- url_download.go selected lines ---'
nl -ba internal/speedtest/url_download.go | sed -n '140,340p'

echo
echo '--- speedtest.go runner dispatch ---'
nl -ba internal/speedtest/speedtest.go | sed -n '100,190p'

Repository: autobrr/netronome

Length of output: 272


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- recover() / panic() across repo ---'
rg -n "recover\(|panic\(" . -g'*.go' || true

echo
echo '--- url_download.go around the worker/error paths ---'
sed -n '150,340p' internal/speedtest/url_download.go | cat -n

Repository: autobrr/netronome

Length of output: 7197


Don’t close errChan before the workers finish

  • The timeout/cancel branches close and drain errChan while workers can still send errors, which can panic on a send to a closed channel and take the process down.
  • ttfb is also written by the first worker and read later without synchronization; guard it with the existing mutex or read it only after wg.Wait().
🧰 Tools
🪛 golangci-lint (2.12.2)

[error] 131-131: ineffectual assignment to progress

(ineffassign)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/speedtest/url_download.go` around lines 104 - 331, The download
worker coordination must not close errChan until all workers have exited, and
ttfb access must be synchronized. Update the completion, timeout, and
cancellation handling around done and errChan so timeout paths stop or defer
draining without closing the channel while workers may still send; close it only
after wg.Wait completes. Protect the ttfb write in firstByteOnce and its later
read with the existing mutex, or otherwise ensure the read occurs after worker
completion.

Comment thread web/src/components/Main.tsx
Comment thread web/src/components/Main.tsx

@s0up4200 s0up4200 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi! Thanks for the PR. Was this implemented with an LLM? If so, which one, and in what harness?

Have you tested it yourself yet?

Please either address or mark the coderabbit feedbacks as resolved when you can 👍

@s0up4200 s0up4200 added enhancement New feature or request go Pull requests that update Go code web labels Jul 23, 2026
@b1n23

b1n23 commented Jul 24, 2026

Copy link
Copy Markdown
Author

Hi! Thanks for the PR. Was this implemented with an LLM? If so, which one, and in what harness?

Have you tested it yourself yet?

Please either address or mark the coderabbit feedbacks as resolved when you can 👍

Hi! Thanks for the review.

  • LLM? Yes, I used VS Code+Copilot+Claude Sonnet/Haiku 4.5 for most of the code, and ZCode+GLM 5.2 for a few tweaks.
  • I tested the URL download and scheduling features manually – they worked on my machine.
  • I'll go through the CodeRabbit feedback and try to fix/resolve them. I'm still a beginner, so please bear with me 😄

Let me know if anything else is needed.

- Store actual hostname instead of display name for built-in servers
- Add panic recovery to prevent crashes
- Fix URL validation and progress state flags
- Add schedule validation for URL downloads

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/speedtest/result_handler.go (1)

54-58: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Persist the hostname separately from the port.

u.Host includes an optional port, so custom URLs like https://example.com:8443/file currently store example.com:8443 in serverHost, while other tests use the hostname-only value. Use u.Hostname() for serverHost; keep u.Host in the serverID if the port needs to affect uniqueness.

Suggested fix
-				host := u.Host
+				host := u.Hostname()
 				serverHost = &host
 				serverID = fmt.Sprintf("url-custom-%s", u.Host)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/speedtest/result_handler.go` around lines 54 - 58, Update the custom
download URL handling around url.Parse in the result handler to assign
u.Hostname() to serverHost, keeping the hostname separate from any port.
Preserve u.Host in serverID so ports continue to distinguish custom server
identifiers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@internal/speedtest/result_handler.go`:
- Around line 54-58: Update the custom download URL handling around url.Parse in
the result handler to assign u.Hostname() to serverHost, keeping the hostname
separate from any port. Preserve u.Host in serverID so ports continue to
distinguish custom server identifiers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a7ec9ef4-c8a8-44e4-afba-b3960dc2b0aa

📥 Commits

Reviewing files that changed from the base of the PR and between e4a08d7 and a86e503.

📒 Files selected for processing (4)
  • internal/speedtest/result_handler.go
  • internal/speedtest/url_download.go
  • web/src/components/Main.tsx
  • web/src/components/speedtest/ScheduleManager.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • web/src/components/Main.tsx
  • web/src/components/speedtest/ScheduleManager.tsx
  • internal/speedtest/url_download.go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

database:migrations enhancement New feature or request go Pull requests that update Go code web

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants