Skip to content

Fix inverted HEAD response and fd leak in index server - #338

Merged
folbricht merged 1 commit into
masterfrom
fix/index-server-head-inversion-fd-leak
May 17, 2026
Merged

folbricht merged 1 commit into
masterfrom
fix/index-server-head-inversion-fd-leak

Conversation

@folbricht

Copy link
Copy Markdown
Owner

Problem

HTTPIndexHandler.head (httpindexhandler.go) had two defects:

  1. Inverted status logic. It returned 200 OK when GetIndexReader failed and 404 Not Found when it succeeded. A client that does HEAD-before-PUT to avoid clobbering an index would see 404 for an index that exists and overwrite it.
  2. File-descriptor leak. The io.ReadCloser returned by GetIndexReader was discarded into _ and never closed. For LocalIndexStore this reader is the *os.File from os.Open, so every HEAD to an existing index leaked one fd until GC finalization. A sustained HEAD burst against a known index name could exhaust the process fd limit (EMFILE), breaking Accept() and legitimate GET/PUT traffic.

Fix

  • Correct the status mapping to mirror the sibling get handler in the same file: os.IsNotExist(err) → 404, other error → stderr log + 400, success → 200. This keeps HEAD and GET consistent for the same name and correctly detects the missing-file case for LocalIndexStore (the index-server default backing).
  • Close the returned reader deterministically with defer rdr.Close(), matching the defer obj.Close() pattern used by every GetIndex implementation, eliminating the fd leak.
  • Add TestIndexServerHead covering the existing-index (→ 200) and missing-index (→ 404) paths, which the existing suite never exercised.

Verification

  • go build ./cmd/desync — OK
  • go test ./cmd/desync -run TestIndexServer -v — all pass, including the new TestIndexServerHead
  • go test ./... — full suite green, no regressions
  • gofmt -l — clean

HTTPIndexHandler.head had inverted status logic: it returned 200 OK
when GetIndexReader failed and 404 Not Found when it succeeded. A
client doing HEAD-before-PUT to avoid clobbering would see 404 for an
existing index and overwrite it.

It also discarded the io.ReadCloser returned by GetIndexReader without
closing it. For LocalIndexStore that reader is the *os.File from
os.Open, so every HEAD to an existing index leaked a file descriptor
until GC finalization, allowing a sustained HEAD burst to exhaust the
process fd limit.

Correct the status mapping to mirror the sibling GET handler
(os.IsNotExist -> 404, other error -> stderr log + 400, success ->
200) and close the reader deterministically with defer. Add a test
covering the existing-index and missing-index HEAD paths.
@folbricht
folbricht merged commit 72c165d into master May 17, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

⌂ 换网址