fix: serve URL resource packs over RakNet, not client-side CDN #86

Merged
smell-of-curry merged 2 commits from fix/url-packs-raknet into main 2026-07-21 23:20:43 +00:00
smell-of-curry commented 2026-07-21 23:07:58 +00:00 (Migrated from github.com)

Problem

When the hub and a downstream server ship different versions of the same resource pack UUID (routine: bds-manager's ProxyManager.generateConfig fetches the latest pokebedrock-res release on every proxy start, while the hub only updates on hub restart), players transferring to GOLD/SILVER got stuck on "Loading resource packs" or joined with a broken half-applied pack. Updating the hub to the same version, or installing the pack locally, "fixed" it — because both eliminate the client-side download.

Root cause

resource.ReadURL records the source URL as the pack's downloadURL. The gophertunnel listener then advertises it as TexturePackInfo.DownloadURL in ResourcePacksInfo, telling clients to fetch the pack themselves over HTTPS instead of over RakNet chunks.

That CDN path only fires on a pack cache miss — i.e. exactly when the client has the hub's older uuid_version cached and encounters our newer one. And it fails on GitHub release asset URLs for two independent reasons:

  1. GitHub answers with a 302 redirect to objects.githubusercontent.com; several client platforms (notably consoles) don't follow it.
  2. Our .mcpack has manifest.json at the zip root; the client-side CDN loader expects the pack inside a subfolder (the same layout resource.ReadURL's own docs call out).

So: version match → cache hit → no download → looks fine. Version mismatch → cache miss → broken CDN download → stuck/corrupted.

Fix

readURLPack() downloads the archive proxy-side and compiles it via resource.Read, leaving DownloadURL empty. The proxy now serves URL-configured packs over RakNet chunks — the same delivery path the hub (dragonfly, resource.ReadPath) uses, which works for every client and tolerates hub/downstream version skew.

Test

TestReadURLPackHasNoDownloadURL serves a minimal .mcpack from httptest and asserts the loaded pack has no DownloadURL and correct content length — guards against regressing to resource.ReadURL.

Notes

  • Trades client CDN bandwidth for proxy RakNet bandwidth (~52 MB per cache-miss join). Acceptable: it's what the hub already does for every first-time player.
  • The separate "stuck at 0/N chunks" complaint is a gophertunnel chunk-burst issue — every other implementation paces chunks (Geyser 256 KiB + 200 ms, PocketMine ACK-throttled, PowerNukkitX, WaterdogPE) but gophertunnel's fix is still an open PR: Sandertv/gophertunnel#446. Worth adopting via a fork/replace if stalls persist after this lands.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed URL-based resource packs so clients are no longer directed to download them directly from the source URL.
    • Added validation to ensure packs are fetched successfully before loading.
    • Improved handling of resource-pack version differences between proxy and hub restarts.
  • Tests

    • Added coverage verifying URL-loaded packs do not expose a client download URL and preserve the correct package size.
## Problem When the hub and a downstream server ship **different versions of the same resource pack UUID** (routine: bds-manager's `ProxyManager.generateConfig` fetches the latest pokebedrock-res release on every proxy start, while the hub only updates on hub restart), players transferring to GOLD/SILVER got stuck on "Loading resource packs" or joined with a broken half-applied pack. Updating the hub to the same version, or installing the pack locally, "fixed" it — because both eliminate the client-side download. ## Root cause `resource.ReadURL` records the source URL as the pack's `downloadURL`. The gophertunnel listener then advertises it as `TexturePackInfo.DownloadURL` in `ResourcePacksInfo`, telling clients to fetch the pack **themselves over HTTPS** instead of over RakNet chunks. That CDN path only fires on a pack cache miss — i.e. exactly when the client has the hub's older `uuid_version` cached and encounters our newer one. And it fails on GitHub release asset URLs for two independent reasons: 1. GitHub answers with a **302 redirect** to `objects.githubusercontent.com`; several client platforms (notably consoles) don't follow it. 2. Our `.mcpack` has `manifest.json` at the **zip root**; the client-side CDN loader expects the pack inside a subfolder (the same layout `resource.ReadURL`'s own docs call out). So: version match → cache hit → no download → looks fine. Version mismatch → cache miss → broken CDN download → stuck/corrupted. ## Fix `readURLPack()` downloads the archive proxy-side and compiles it via `resource.Read`, leaving `DownloadURL` empty. The proxy now serves URL-configured packs over **RakNet chunks** — the same delivery path the hub (dragonfly, `resource.ReadPath`) uses, which works for every client and tolerates hub/downstream version skew. ## Test `TestReadURLPackHasNoDownloadURL` serves a minimal `.mcpack` from `httptest` and asserts the loaded pack has no `DownloadURL` and correct content length — guards against regressing to `resource.ReadURL`. ## Notes - Trades client CDN bandwidth for proxy RakNet bandwidth (~52 MB per cache-miss join). Acceptable: it's what the hub already does for every first-time player. - The separate "stuck at 0/N chunks" complaint is a gophertunnel chunk-burst issue — every other implementation paces chunks (Geyser 256 KiB + 200 ms, PocketMine ACK-throttled, PowerNukkitX, WaterdogPE) but gophertunnel's fix is still an open PR: Sandertv/gophertunnel#446. Worth adopting via a fork/replace if stalls persist after this lands. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Fixed URL-based resource packs so clients are no longer directed to download them directly from the source URL. * Added validation to ensure packs are fetched successfully before loading. * Improved handling of resource-pack version differences between proxy and hub restarts. * **Tests** * Added coverage verifying URL-loaded packs do not expose a client download URL and preserve the correct package size. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
coderabbitai[bot] commented 2026-07-21 23:08:23 +00:00 (Migrated from github.com)

Review Change Stack

Warning

Review limit reached

@smell-of-curry, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 49 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 209e43c7-1dd1-49ad-b280-bcbdec06bf91

📥 Commits

Reviewing files that changed from the base of the PR and between de0858e2cf and fe37e39a61.

📒 Files selected for processing (2)
  • AGENTS.md
  • gobds/user_config.go
📝 Walkthrough

Walkthrough

URL resource packs now download through a local HTTP helper, are parsed without a DownloadURL, and are covered by tests using a generated .mcpack served from an httptest server. Documentation records pack delivery and version-skew behavior.

Changes

Resource pack loading

Layer / File(s) Summary
HTTP-based URL pack loading and validation
gobds/user_config.go, gobds/user_config_test.go, AGENTS.md
UserConfig.packs uses readURLPack, which validates HTTP responses and parses response bodies without download URL metadata. Tests verify the empty DownloadURL and pack length, while AGENTS.md documents resource-pack delivery and version skew.
Estimated code review effort: 2 (Simple) ~10 minutes
🚥 Pre-merge checks | 5
Passed checks (5 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly matches the main change: URL resource packs are now served over RakNet instead of client-side CDN downloads.
Docstring Coverage Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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
  • Commit unit tests in branch fix/url-packs-raknet

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.

<!-- This is an auto-generated comment: summarize by coderabbit.ai --> <!-- review_stack_entry_start --> [![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/smell-of-curry/gobds/pull/86?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- This is an auto-generated comment: rate limited by coderabbit.ai --> > [!WARNING] > ## Review limit reached > > `@smell-of-curry`, you've reached your PR review limit, so we couldn't start this review. > > **Next review available in:** **49 minutes** > > Enable **[usage-based reviews](https://app.coderabbit.ai/settings/billing?tab=usage&orgId=622c78ce-9e11-4f9b-9f86-5799be0786e4)** in Billing to review now. Otherwise, wait until the next included review is available. > You're only billed for reviews past your plan's rate limits ($0.25/file). > > <details> > <summary>How can I continue?</summary> > > After more reviews become available, a review can be triggered using the `@coderabbitai review` command as a PR comment. Alternatively, push new commits to this PR. > > To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. > > </details> > > > <details> > <summary>How do review limits work?</summary> > > CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. > > For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. > > Please refer [docs](https://docs.coderabbit.ai/management/plans#rate-limits) for additional details. > > </details> > > <details> > <summary>Review details</summary> > > <details> > <summary>⚙️ Run configuration</summary> > > **Configuration used**: defaults > > **Review profile**: CHILL > > **Plan**: Pro > > **Run ID**: `209e43c7-1dd1-49ad-b280-bcbdec06bf91` > > </details> > > <details> > <summary>📥 Commits</summary> > > Reviewing files that changed from the base of the PR and between de0858e2cf3c6b9b4f48e39adafbd42da1bef719 and fe37e39a619de52cba69594f1c03e214e738a0fe. > > </details> > > <details> > <summary>📒 Files selected for processing (2)</summary> > > * `AGENTS.md` > * `gobds/user_config.go` > > </details> > > </details> <!-- end of auto-generated comment: rate limited by coderabbit.ai --> <!-- walkthrough_start --> <details> <summary>📝 Walkthrough</summary> ## Walkthrough URL resource packs now download through a local HTTP helper, are parsed without a `DownloadURL`, and are covered by tests using a generated `.mcpack` served from an `httptest` server. Documentation records pack delivery and version-skew behavior. ### Changes **Resource pack loading** |Layer / File(s)|Summary| |---|---| |**HTTP-based URL pack loading and validation** <br> `gobds/user_config.go`, `gobds/user_config_test.go`, `AGENTS.md`|`UserConfig.packs` uses `readURLPack`, which validates HTTP responses and parses response bodies without download URL metadata. Tests verify the empty `DownloadURL` and pack length, while `AGENTS.md` documents resource-pack delivery and version skew.| **Estimated code review effort:** 2 (Simple) | ~10 minutes </details> <!-- walkthrough_end --> <!-- pre_merge_checks_walkthrough_start --> <details> <summary>🚥 Pre-merge checks | ✅ 5</summary> <details> <summary>✅ Passed checks (5 passed)</summary> | Check name | Status | Explanation | | :------------------------: | :------- | :----------------------------------------------------------------------------------------------------------------------------- | | Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. | | Title check | ✅ Passed | The title clearly matches the main change: URL resource packs are now served over RakNet instead of client-side CDN downloads. | | Docstring Coverage | ✅ Passed | No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. | | 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. | </details> </details> <!-- pre_merge_checks_walkthrough_end --> <!-- finishing_touch_checkbox_start --> <details> <summary>✨ Finishing Touches</summary> <details> <summary>🧪 Generate unit tests (beta)</summary> - [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Create PR with unit tests - [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Commit unit tests in branch `fix/url-packs-raknet` </details> </details> <!-- finishing_touch_checkbox_end --> <!-- tips_start --> --- Thanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=smell-of-curry/gobds&utm_content=86)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. <details> <summary>❤️ Share</summary> - [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai) - [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai) - [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai) - [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code) </details> <sub>Comment `@coderabbitai help` to get the list of available commands.</sub> <!-- tips_end -->
coderabbitai[bot] (Migrated from github.com) reviewed 2026-07-21 23:11:57 +00:00
coderabbitai[bot] (Migrated from github.com) left a comment

Actionable comments posted: 2

🤖 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 `@gobds/user_config.go`:
- Around line 143-149: The resource-pack download in the pack-fetching function
must have a finite deadline for both connection and response-body reads. Replace
the unbounded http.Get call with a timeout-configured HTTP client or
context-bound request, preserving the existing error wrapping and response-body
cleanup.
- Around line 131-141: Update the pack-layout wording in gobds/user_config.go
around readURLPack and in AGENTS.md lines 13-14 to consistently state that the
zip must have manifest.json at the archive root; change both affected sites and
preserve the surrounding explanation.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: c2a01343-7914-4731-9b1b-72ba7c5cdd4b

📥 Commits

Reviewing files that changed from the base of the PR and between 4084560eb7 and de0858e2cf.

📒 Files selected for processing (3)
  • AGENTS.md
  • gobds/user_config.go
  • gobds/user_config_test.go
**Actionable comments posted: 2** <details> <summary>🤖 Prompt for all review comments with AI agents</summary> ``` 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 `@gobds/user_config.go`: - Around line 143-149: The resource-pack download in the pack-fetching function must have a finite deadline for both connection and response-body reads. Replace the unbounded http.Get call with a timeout-configured HTTP client or context-bound request, preserving the existing error wrapping and response-body cleanup. - Around line 131-141: Update the pack-layout wording in gobds/user_config.go around readURLPack and in AGENTS.md lines 13-14 to consistently state that the zip must have manifest.json at the archive root; change both affected sites and preserve the surrounding explanation. ``` </details> <details> <summary>🪄 Autofix (Beta)</summary> Fix all unresolved CodeRabbit comments on this PR: - [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended) - [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes </details> --- <details> <summary>ℹ️ Review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: defaults **Review profile**: CHILL **Plan**: Pro **Run ID**: `c2a01343-7914-4731-9b1b-72ba7c5cdd4b` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 4084560eb79d3c250b18d7a652ea2e596174d7f3 and de0858e2cf3c6b9b4f48e39adafbd42da1bef719. </details> <details> <summary>📒 Files selected for processing (3)</summary> * `AGENTS.md` * `gobds/user_config.go` * `gobds/user_config_test.go` </details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
@ -127,6 +128,40 @@ func (c UserConfig) packs(log *slog.Logger) []*resource.Pack {
return packs
coderabbitai[bot] (Migrated from github.com) commented 2026-07-21 23:11:56 +00:00

🩺 Stability & Availability | 🟠 Major | Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant file and nearby declarations first.
git ls-files gobds/user_config.go
wc -l gobds/user_config.go
ast-grep outline gobds/user_config.go --view expanded

# Inspect the relevant section with line numbers.
sed -n '1,260p' gobds/user_config.go | cat -n

# Find callers / startup path references.
rg -n "readURLPack|resource.ReadURL|resource.Read\(|http.Get\(" gobds -S

Repository: smell-of-curry/gobds

Length of output: 11001


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the relevant function and adjacent code.
sed -n '120,220p' gobds/user_config.go | cat -n

# Find where the pack loader is invoked.
rg -n "readURLPack|read.*Pack|resource\.ReadURL|resource\.Read\(" . -S

# Check for any shared HTTP client / timeout setup in the repo.
rg -n "http\.Client|Timeout:|context\.WithTimeout|WithDeadline" . -S

Repository: smell-of-curry/gobds

Length of output: 6006


🌐 Web query:

Go net/http http.Get default client timeout resource.Read io.ReadAll blocking body stall

💡 Result:

In Go's net/http package, the http.DefaultClient has a Timeout of zero, which means it waits indefinitely [1][2][3]. This applies to the entire request lifecycle, including connection establishment, any redirects, and reading the response body [1][4][3]. If you use http.Get (which uses the DefaultClient) or any custom http.Client without a configured Timeout, a request can stall indefinitely if the server stops sending data or if there is a network partition during the reading of the body [5][6][3]. Key points regarding the behavior: 1. Blocking during io.ReadAll: The io.ReadAll function is a blocking operation [5][6][7]. It will continue to wait for data from the provided io.Reader until it reaches EOF or encounters an error [6][8]. If the connection remains open but the server halts transmission, io.ReadAll has no native knowledge of a "timeout" and will block the executing goroutine forever [5][6][7]. 2. Role of Client.Timeout: If you have configured a non-zero Timeout on your http.Client, that timer remains active even after the initial request (Get, Post, etc.) returns [1][2]. If io.ReadAll is still reading the response body when that total timeout duration is reached, the client will interrupt the reading process by canceling the request context, which causes the Body.Read call to return an error [1][4][3]. 3. Best Practices to Prevent Stalls: - Use a custom http.Client with a defined Timeout [9][3]. - If you need to handle long-running or streaming requests where a single total timeout is inappropriate, use context-aware requests (e.g., http.NewRequestWithContext) [10][9]. - For applications requiring granular control (such as ensuring reads respect a specific context cancellation), do not rely solely on io.ReadAll [7]. Consider using a custom reader that checks for context cancellation or wraps the read operation to ensure it does not hang indefinitely [7]. - Always close the response body (resp.Body.Close) to prevent resource leaks, which can contribute to socket exhaustion and application instability [1][10][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find where UserConfig.packs is used during startup.
rg -n "\.packs\(|packs\(log\)|loadCommands\(|listenerFunc\(" gobds -S

# Inspect the startup path in gobds.go around the main setup sequence.
wc -l gobds/gobds.go
sed -n '130,230p' gobds/gobds.go | cat -n

# Inspect listenerFunc if it wires packs into listener construction.
sed -n '260,340p' gobds/user_config.go | cat -n

Repository: smell-of-curry/gobds

Length of output: 7241


Bound the resource-pack fetch. http.Get has no timeout, and resource.Read(resp.Body) blocks on the body; since listenerFunc calls c.packs(srv.Log) during listener setup, a stalled URL can hang proxy startup. Use a client or request context with a finite deadline.

🤖 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 `@gobds/user_config.go` around lines 143 - 149, The resource-pack download in
the pack-fetching function must have a finite deadline for both connection and
response-body reads. Replace the unbounded http.Get call with a
timeout-configured HTTP client or context-bound request, preserving the existing
error wrapping and response-body cleanup.

Confirmed as addressed by @smell-of-curry

_🩺 Stability & Availability_ | _🟠 Major_ | _⚡ Quick win_ <details> <summary>🧩 Analysis chain</summary> 🏁 Script executed: ```shell #!/bin/bash set -euo pipefail # Map the relevant file and nearby declarations first. git ls-files gobds/user_config.go wc -l gobds/user_config.go ast-grep outline gobds/user_config.go --view expanded # Inspect the relevant section with line numbers. sed -n '1,260p' gobds/user_config.go | cat -n # Find callers / startup path references. rg -n "readURLPack|resource.ReadURL|resource.Read\(|http.Get\(" gobds -S ``` Repository: smell-of-curry/gobds Length of output: 11001 --- 🏁 Script executed: ```shell #!/bin/bash set -euo pipefail # Read the relevant function and adjacent code. sed -n '120,220p' gobds/user_config.go | cat -n # Find where the pack loader is invoked. rg -n "readURLPack|read.*Pack|resource\.ReadURL|resource\.Read\(" . -S # Check for any shared HTTP client / timeout setup in the repo. rg -n "http\.Client|Timeout:|context\.WithTimeout|WithDeadline" . -S ``` Repository: smell-of-curry/gobds Length of output: 6006 --- 🌐 Web query: `Go net/http http.Get default client timeout resource.Read io.ReadAll blocking body stall` 💡 Result: In Go's net/http package, the http.DefaultClient has a Timeout of zero, which means it waits indefinitely [1][2][3]. This applies to the entire request lifecycle, including connection establishment, any redirects, and reading the response body [1][4][3]. If you use http.Get (which uses the DefaultClient) or any custom http.Client without a configured Timeout, a request can stall indefinitely if the server stops sending data or if there is a network partition during the reading of the body [5][6][3]. Key points regarding the behavior: 1. Blocking during io.ReadAll: The io.ReadAll function is a blocking operation [5][6][7]. It will continue to wait for data from the provided io.Reader until it reaches EOF or encounters an error [6][8]. If the connection remains open but the server halts transmission, io.ReadAll has no native knowledge of a "timeout" and will block the executing goroutine forever [5][6][7]. 2. Role of Client.Timeout: If you have configured a non-zero Timeout on your http.Client, that timer remains active even after the initial request (Get, Post, etc.) returns [1][2]. If io.ReadAll is still reading the response body when that total timeout duration is reached, the client will interrupt the reading process by canceling the request context, which causes the Body.Read call to return an error [1][4][3]. 3. Best Practices to Prevent Stalls: - Use a custom http.Client with a defined Timeout [9][3]. - If you need to handle long-running or streaming requests where a single total timeout is inappropriate, use context-aware requests (e.g., http.NewRequestWithContext) [10][9]. - For applications requiring granular control (such as ensuring reads respect a specific context cancellation), do not rely solely on io.ReadAll [7]. Consider using a custom reader that checks for context cancellation or wraps the read operation to ensure it does not hang indefinitely [7]. - Always close the response body (resp.Body.Close) to prevent resource leaks, which can contribute to socket exhaustion and application instability [1][10][5]. Citations: - 1: https://pkg.go.dev/net/http@go1.26.4 - 2: https://github.com/golang/go/blob/master/src/net/http/client.go - 3: https://oneuptime.com/blog/post/2026-02-01-go-http-client-timeouts/view - 4: https://go.dev/src/net/http/client.go?m=text - 5: https://stackoverflow.com/questions/23967638/ioutil-readallresponse-body-blocks-forever-golang - 6: https://groups.google.com/g/golang-nuts/c/sAwDldpkMGQ - 7: https://github.com/ooni/probe/issues/1417 - 8: https://github.com/golang/go/blob/99d4927f/src/net/http/response.go - 9: https://stackoverflow.com/questions/16895294/how-to-set-timeout-for-http-get-requests-in-golang - 10: https://github.com/golang/go/blob/master/src/net/http/doc.go --- 🏁 Script executed: ```shell #!/bin/bash set -euo pipefail # Find where UserConfig.packs is used during startup. rg -n "\.packs\(|packs\(log\)|loadCommands\(|listenerFunc\(" gobds -S # Inspect the startup path in gobds.go around the main setup sequence. wc -l gobds/gobds.go sed -n '130,230p' gobds/gobds.go | cat -n # Inspect listenerFunc if it wires packs into listener construction. sed -n '260,340p' gobds/user_config.go | cat -n ``` Repository: smell-of-curry/gobds Length of output: 7241 --- </details> **Bound the resource-pack fetch.** `http.Get` has no timeout, and `resource.Read(resp.Body)` blocks on the body; since `listenerFunc` calls `c.packs(srv.Log)` during listener setup, a stalled URL can hang proxy startup. Use a client or request context with a finite deadline. <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@gobds/user_config.go` around lines 143 - 149, The resource-pack download in the pack-fetching function must have a finite deadline for both connection and response-body reads. Replace the unbounded http.Get call with a timeout-configured HTTP client or context-bound request, preserving the existing error wrapping and response-body cleanup. ``` </details> <!-- fingerprinting:phantom:poseidon:luna --> <!-- cr-indicator-types:potential_issue --> <!-- cr-comment:v1:ce332d97ee5b9dc4bd0f4284 --> <!-- This is an auto-generated reply by CodeRabbit --> ✅ Confirmed as addressed by @smell-of-curry <!-- This is an auto-generated comment by CodeRabbit -->
@ -130,0 +155,4 @@
}
defer func() {
_ = resp.Body.Close()
}()
coderabbitai[bot] (Migrated from github.com) commented 2026-07-21 23:11:56 +00:00

📐 Maintainability & Code Quality | 🟡 Minor | Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- gobds/user_config.go (relevant slice) ---\n'
sed -n '110,170p' gobds/user_config.go | cat -n

printf '\n--- AGENTS.md (relevant slice) ---\n'
sed -n '1,40p' AGENTS.md | cat -n

printf '\n--- Search for resource.ReadURL / TexturePackInfo.DownloadURL usage ---\n'
rg -n "ReadURL|TexturePackInfo|DownloadURL|manifest\.json" -S .

Repository: smell-of-curry/gobds

Length of output: 249


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- gobds/user_config.go (relevant slice) ---'
sed -n '110,170p' gobds/user_config.go | cat -n

echo
echo '--- AGENTS.md (relevant slice) ---'
sed -n '1,40p' AGENTS.md | cat -n

echo
echo '--- Search for resource.ReadURL / TexturePackInfo.DownloadURL / manifest.json ---'
rg -n "ReadURL|TexturePackInfo|DownloadURL|manifest\.json" -S gobds AGENTS.md .

Repository: smell-of-curry/gobds

Length of output: 9034


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- gobds/user_config_test.go ---'
sed -n '1,140p' gobds/user_config_test.go | cat -n

Repository: smell-of-curry/gobds

Length of output: 2379


Make the pack-layout wording consistent. gobds/user_config.go should say the zip needs manifest.json at the archive root, and AGENTS.md should use the same wording; the current text contradicts the pack test and each other.

📍 Affects 2 files
  • gobds/user_config.go#L131-L141 (this comment)
  • AGENTS.md#L13-L14
🤖 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 `@gobds/user_config.go` around lines 131 - 141, Update the pack-layout wording
in gobds/user_config.go around readURLPack and in AGENTS.md lines 13-14 to
consistently state that the zip must have manifest.json at the archive root;
change both affected sites and preserve the surrounding explanation.

Confirmed as addressed by @smell-of-curry

_📐 Maintainability & Code Quality_ | _🟡 Minor_ | _⚡ Quick win_ <details> <summary>🧩 Analysis chain</summary> 🏁 Script executed: ```shell #!/bin/bash set -euo pipefail printf '--- gobds/user_config.go (relevant slice) ---\n' sed -n '110,170p' gobds/user_config.go | cat -n printf '\n--- AGENTS.md (relevant slice) ---\n' sed -n '1,40p' AGENTS.md | cat -n printf '\n--- Search for resource.ReadURL / TexturePackInfo.DownloadURL usage ---\n' rg -n "ReadURL|TexturePackInfo|DownloadURL|manifest\.json" -S . ``` Repository: smell-of-curry/gobds Length of output: 249 --- 🏁 Script executed: ```shell #!/bin/bash set -euo pipefail echo '--- gobds/user_config.go (relevant slice) ---' sed -n '110,170p' gobds/user_config.go | cat -n echo echo '--- AGENTS.md (relevant slice) ---' sed -n '1,40p' AGENTS.md | cat -n echo echo '--- Search for resource.ReadURL / TexturePackInfo.DownloadURL / manifest.json ---' rg -n "ReadURL|TexturePackInfo|DownloadURL|manifest\.json" -S gobds AGENTS.md . ``` Repository: smell-of-curry/gobds Length of output: 9034 --- 🏁 Script executed: ```shell #!/bin/bash set -euo pipefail echo '--- gobds/user_config_test.go ---' sed -n '1,140p' gobds/user_config_test.go | cat -n ``` Repository: smell-of-curry/gobds Length of output: 2379 --- </details> **Make the pack-layout wording consistent.** `gobds/user_config.go` should say the zip needs `manifest.json` at the archive root, and `AGENTS.md` should use the same wording; the current text contradicts the pack test and each other. <details> <summary>📍 Affects 2 files</summary> - `gobds/user_config.go#L131-L141` (this comment) - `AGENTS.md#L13-L14` </details> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@gobds/user_config.go` around lines 131 - 141, Update the pack-layout wording in gobds/user_config.go around readURLPack and in AGENTS.md lines 13-14 to consistently state that the zip must have manifest.json at the archive root; change both affected sites and preserve the surrounding explanation. ``` </details> <!-- consolidated_sites_start --> <!-- <consolidated_sites> <site> <role>anchor</role> <file>gobds/user_config.go</file> <line_range>131-141</line_range> </site> <site> <role>sibling</role> <file>AGENTS.md</file> <line_range>13-14</line_range> </site> </consolidated_sites> --> <!-- consolidated_sites_end --> <!-- fingerprinting:phantom:poseidon:luna --> <!-- cr-indicator-types:potential_issue --> <!-- cr-comment:v1:f992b1ccd26bc10742cd20de --> <!-- This is an auto-generated reply by CodeRabbit --> ✅ Confirmed as addressed by @smell-of-curry <!-- This is an auto-generated comment by CodeRabbit -->
smell-of-curry (Migrated from github.com) reviewed 2026-07-21 23:18:38 +00:00
@ -127,6 +128,40 @@ func (c UserConfig) packs(log *slog.Logger) []*resource.Pack {
return packs
smell-of-curry (Migrated from github.com) commented 2026-07-21 23:18:38 +00:00

Fixed in fe37e39 — readURLPack now uses an http.Client with a 5-minute Timeout, which bounds the connect and the body read inside resource.Read, so a stalled URL fails startup with an error instead of hanging it.

Fixed in fe37e39 — readURLPack now uses an http.Client with a 5-minute Timeout, which bounds the connect and the body read inside resource.Read, so a stalled URL fails startup with an error instead of hanging it.
smell-of-curry (Migrated from github.com) reviewed 2026-07-21 23:18:39 +00:00
@ -130,0 +155,4 @@
}
defer func() {
_ = resp.Body.Close()
}()
smell-of-curry (Migrated from github.com) commented 2026-07-21 23:18:39 +00:00

Reworded in fe37e39. Note the original text was intentional: the subfolder layout is a requirement of the CLIENT-side CDN download path (per resource.ReadURL's own docs), while server-side compilation (resource.Read here, ReadPath on the hub) accepts manifest.json at the archive root — which is what the test exercises. The comment and AGENTS.md now spell out that distinction explicitly so they no longer read as contradicting the test.

Reworded in fe37e39. Note the original text was intentional: the subfolder layout is a requirement of the CLIENT-side CDN download path (per resource.ReadURL's own docs), while server-side compilation (resource.Read here, ReadPath on the hub) accepts manifest.json at the archive root — which is what the test exercises. The comment and AGENTS.md now spell out that distinction explicitly so they no longer read as contradicting the test.
coderabbitai[bot] (Migrated from github.com) reviewed 2026-07-21 23:18:52 +00:00
@ -127,6 +128,40 @@ func (c UserConfig) packs(log *slog.Logger) []*resource.Pack {
return packs
coderabbitai[bot] (Migrated from github.com) commented 2026-07-21 23:18:52 +00:00

@smell-of-curry, confirmed—using a five-minute http.Client.Timeout bounds both the fetch and resource.Read body consumption, preventing a stalled resource URL from blocking listener startup. Thanks for the fix.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

`@smell-of-curry`, confirmed—using a five-minute `http.Client.Timeout` bounds both the fetch and `resource.Read` body consumption, preventing a stalled resource URL from blocking listener startup. Thanks for the fix. <!-- <review_comment_addressed> --> --- <!-- This is an auto-generated comment: tweet message by coderabbit.ai --> If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=Stalled%20resource-pack%20downloads%20can%20hang%20startup%20indefinitely.%20%40coderabbitai%20caught%20it%3B%20a%205-minute%20HTTP%20timeout%20now%20fails%20fast%20instead%20of%20freezing%20the%20proxy.%20Try%20it%20for%20smarter%20reviews!%20%F0%9F%9A%80%F0%9F%A9%BA&url=https%3A%2F%2Fgithub.com%2Fsmell-of-curry%2Fgobds%2Fpull%2F86%23discussion_r3626315132)? Thank you for using CodeRabbit! <!-- end of auto-generated comment: tweet message by coderabbit.ai --> <!-- This is an auto-generated reply by CodeRabbit -->
coderabbitai[bot] (Migrated from github.com) reviewed 2026-07-21 23:19:00 +00:00
@ -130,0 +155,4 @@
}
defer func() {
_ = resp.Body.Close()
}()
coderabbitai[bot] (Migrated from github.com) commented 2026-07-21 23:19:00 +00:00

@smell-of-curry, thanks for the detailed clarification. The distinction is correct: the subfolder layout applies only to the client-side CDN path, while proxy/hub-side compilation accepts manifest.json at the archive root. The rewording resolves the ambiguity with the test.


✏️ Learnings added
Learnt from: smell-of-curry
Repo: smell-of-curry/gobds PR: 86
File: gobds/user_config.go:131-141
Timestamp: 2026-07-21T23:18:51.359Z
Learning: In `gobds/user_config.go`, resource-pack archive layout requirements differ by delivery path: client-side CDN downloads advertised through `resource.ReadURL` require the documented subfolder layout, while proxy/hub server-side compilation through `resource.Read` and `resource.ReadPath` accepts `manifest.json` at the archive root. URL-configured packs should use `readURLPack` so their `DownloadURL` remains empty and they are served over RakNet.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

`@smell-of-curry`, thanks for the detailed clarification. The distinction is correct: the subfolder layout applies only to the client-side CDN path, while proxy/hub-side compilation accepts `manifest.json` at the archive root. The rewording resolves the ambiguity with the test. <!-- <review_comment_addressed> --> --- <details> <summary>✏️ Learnings added</summary> ``` Learnt from: smell-of-curry Repo: smell-of-curry/gobds PR: 86 File: gobds/user_config.go:131-141 Timestamp: 2026-07-21T23:18:51.359Z Learning: In `gobds/user_config.go`, resource-pack archive layout requirements differ by delivery path: client-side CDN downloads advertised through `resource.ReadURL` require the documented subfolder layout, while proxy/hub server-side compilation through `resource.Read` and `resource.ReadPath` accepts `manifest.json` at the archive root. URL-configured packs should use `readURLPack` so their `DownloadURL` remains empty and they are served over RakNet. ``` > Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings). </details> --- <!-- This is an auto-generated comment: tweet message by coderabbit.ai --> If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=%40coderabbitai%20caught%20a%20subtle%20pack-layout%20doc%20mismatch%3A%20CDN%20downloads%20need%20a%20subfolder%2C%20while%20server%20compilation%20accepts%20root-level%20manifest.json.%20Clearer%20docs%2C%20fewer%20surprises!%20%F0%9F%94%8D%20Try%20it.&url=https%3A%2F%2Fgithub.com%2Fsmell-of-curry%2Fgobds%2Fpull%2F86%23discussion_r3626315129)? Thank you for using CodeRabbit! <!-- end of auto-generated comment: tweet message by coderabbit.ai --> <!-- This is an auto-generated reply by CodeRabbit -->
Sign in to join this conversation.
No description provided.