Skip to content

fix(http): harden FileCache against short read and stat failure - #880

Merged
ithewei merged 2 commits into
masterfrom
fix-filecache-shortread-stat
Sep 15, 2026
Merged

ithewei merged 2 commits into
masterfrom
fix-filecache-shortread-stat

Conversation

@ithewei

@ithewei ithewei commented Sep 15, 2026

Copy link
Copy Markdown
Owner

What

Fixes two correctness bugs in the static-file FileCache (http/server/FileCache.{h,cpp}).

Bug 1: short read poisons the cache

In FileCache::Open, the entry is put() into the LRU before the file is read, and resize_buf() sets filebuf.len to the full st_size. On a short read() the function reported ERR_READ_FILE and returned NULL, but left a cached entry whose filebuf.len == st_size with an unfilled tail.

A later request would hit that entry, is_complete() (which only compares filebuf.len == st_size) would return true, and the server would send the partially-filled buffer — leaking uninitialized memory / serving corrupt content until the mtime changed or the entry was evicted.

Fix: remove the entry from the cache (Close(filepath)) on read failure.

Bug 2: is_modified() corrupts st on stat failure

is_modified() wrote the stat() result directly into st without checking the return value. When stat() fails (file removed, permission change, …) POSIX leaves the buffer undefined, corrupting st_size / st_mtime, which then poisons is_complete(), the ETag and the Last-Modified header.

Fix: stat() into a temporary; on failure keep the previous st and report "not modified" so the cached content is served until it is re-validated or evicted.

Testing

  • make libhv (with --with-http) ✅

- FileCache::Open: on a short read the entry was already put() into the
  LRU with filebuf.len == st_size, so is_complete() would later report it
  as complete and serve a partially-filled (garbage-tail) buffer. Remove
  the entry from the cache on read failure.
- file_cache_s::is_modified: stat() result was written directly into st
  without checking the return value; on failure POSIX leaves the buffer
  undefined, corrupting st_size/st_mtime (and thus is_complete/etag/
  Last-Modified). Stat into a temporary and keep the old st on failure.

Co-authored-by: TRAE CLI <traecli@bytedance.com>
Copilot AI lite review requested due to automatic review settings September 15, 2026 05:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unresolved concurrency and Windows metadata-validation issues block approval.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

This pull request hardens static-file FileCache handling for short reads and failed metadata checks.

Changes:

  • Evicts entries after failed reads.
  • Preserves cached metadata when POSIX stat() fails.
  • Unresolved concurrency and Windows _wstat() handling issues remain.
File summaries
File Summary
http/server/FileCache.h Adds guarded POSIX metadata refresh.
http/server/FileCache.cpp Evicts failed reads, but concurrent publication/removal remains unsafe.
Review details

Suppressed comments (1)

http/server/FileCache.cpp:111

  • FileCache is shared by the server's worker loops, but Get/put and Close are separate operations. If two requests read the same path concurrently and one fails after another request has inserted a replacement entry, this key-based Close(filepath) can remove the newer entry rather than the entry whose read failed. Make removal conditional on the cached value still being this fc (atomically), or serialize the load/remove sequence.
                    Close(filepath);
  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread http/server/FileCache.cpp
if (nread != fc->filebuf.len) {
hloge("Failed to read file: %s", filepath);
param->error = ERR_READ_FILE;
Close(filepath);
Comment thread http/server/FileCache.h Outdated
Comment on lines +37 to +38
if (stat(filepath.c_str(), &new_st) != 0) {
return false;
Extract stat_and_open() to hide the OS_WIN vs POSIX branching that was
duplicated in FileCache::Open, and route is_modified() through the same
_wstat/stat split. is_modified() now returns true on stat failure so a
removed/failed file forces a reopen (which then reports the error
cleanly) instead of serving stale cached content.

Co-authored-by: TRAE CLI <traecli@bytedance.com>
Copilot AI review requested due to automatic review settings September 15, 2026 06:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Concurrency and failed-revalidation handling issues remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (1)

http/server/FileCache.cpp:98

  • Removing the entry after read() fails does not close the concurrent-read window: put() publishes this file_cache_t before resize_buf()/read() completes, and another worker can observe filebuf.len == st_size via is_complete() and send the uninitialized tail while this thread is still reading. HttpServer shares one cache across worker loops, so the entry must be published only after initialization or initialization/reloads must be synchronized with readers; cleanup after failure is not sufficient.
                    Close(filepath);
  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread http/server/FileCache.cpp
if (nread != fc->filebuf.len) {
hloge("Failed to read file: %s", filepath);
param->error = ERR_READ_FILE;
Close(filepath);
Comment thread http/server/FileCache.h
Comment on lines +38 to +42
if (_wstat(hv::utf8_to_wchar(filepath).c_str(), (struct _stat*)&new_st) != 0)
return true;
#else
if (stat(filepath.c_str(), &new_st) != 0)
return true;
@ithewei
ithewei merged commit 2a2788a into master Sep 15, 2026
13 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.

2 participants