-
Notifications
You must be signed in to change notification settings - Fork 1.4k
feat: Enhance filecache #823
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
a90085a
bc30edc
f14db65
f9bae97
f5e9a67
7c7d63e
1f83fce
a9790f6
16c24e7
4fd86b4
f1850b9
8cd32ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -1,90 +1,158 @@ | ||||||||
| #ifndef HV_FILE_CACHE_H_ | ||||||||
| #define HV_FILE_CACHE_H_ | ||||||||
|
|
||||||||
| /* | ||||||||
| * FileCache — Enhanced File Cache for libhv HTTP server | ||||||||
| * | ||||||||
| * Features: | ||||||||
| * 1. Configurable max_header_length (default 4096, tunable per-instance) | ||||||||
| * 2. prepend_header() returns bool to report success/failure | ||||||||
| * 3. Exposes header/buffer metrics via accessors | ||||||||
| * 4. Fixes stat() name collision in is_modified() | ||||||||
| * 5. max_cache_num / max_file_size configurable at runtime | ||||||||
| * 6. Reserved header space can be tuned per-instance | ||||||||
| * 7. Source-level API compatible; struct layout differs from original (no ABI/layout compatibility) | ||||||||
| */ | ||||||||
|
|
||||||||
| #include <memory> | ||||||||
| #include <map> | ||||||||
| #include <string> | ||||||||
| #include <mutex> | ||||||||
|
|
||||||||
| #include "hexport.h" | ||||||||
| #include "hbuf.h" | ||||||||
| #include "hstring.h" | ||||||||
| #include "LRUCache.h" | ||||||||
|
|
||||||||
| #define HTTP_HEADER_MAX_LENGTH 1024 // 1K | ||||||||
| #define FILE_CACHE_MAX_NUM 100 | ||||||||
| #define FILE_CACHE_MAX_SIZE (1 << 22) // 4M | ||||||||
| // Default values — may be overridden at runtime via FileCache setters | ||||||||
| #define FILE_CACHE_DEFAULT_HEADER_LENGTH 4096 // 4K | ||||||||
| #define FILE_CACHE_DEFAULT_MAX_NUM 100 | ||||||||
| #define FILE_CACHE_DEFAULT_MAX_FILE_SIZE (1 << 22) // 4M | ||||||||
|
|
||||||||
| typedef struct file_cache_s { | ||||||||
| mutable std::mutex mutex; // protects all mutable state below | ||||||||
| std::string filepath; | ||||||||
| struct stat st; | ||||||||
| time_t open_time; | ||||||||
| time_t stat_time; | ||||||||
| uint32_t stat_cnt; | ||||||||
| HBuf buf; // http_header + file_content | ||||||||
| hbuf_t filebuf; | ||||||||
| hbuf_t httpbuf; | ||||||||
| HBuf buf; // header_reserve + file_content | ||||||||
| hbuf_t filebuf; // points into buf: file content region | ||||||||
| hbuf_t httpbuf; // points into buf: header + file content after prepend | ||||||||
| char last_modified[64]; | ||||||||
| char etag[64]; | ||||||||
| std::string content_type; | ||||||||
|
|
||||||||
| // --- new: expose header metrics --- | ||||||||
| int header_reserve; // reserved bytes before file content | ||||||||
| int header_used; // actual bytes used by prepend_header | ||||||||
|
|
||||||||
| file_cache_s() { | ||||||||
| stat_cnt = 0; | ||||||||
| header_reserve = FILE_CACHE_DEFAULT_HEADER_LENGTH; | ||||||||
| header_used = 0; | ||||||||
| memset(last_modified, 0, sizeof(last_modified)); | ||||||||
| memset(etag, 0, sizeof(etag)); | ||||||||
| } | ||||||||
|
|
||||||||
| // NOTE: caller must hold mutex. | ||||||||
| // On Windows, Open() uses _wstat() directly instead of calling this. | ||||||||
| bool is_modified() { | ||||||||
| time_t mtime = st.st_mtime; | ||||||||
| stat(filepath.c_str(), &st); | ||||||||
| ::stat(filepath.c_str(), &st); | ||||||||
| return mtime != st.st_mtime; | ||||||||
| } | ||||||||
|
|
||||||||
| // NOTE: caller must hold mutex | ||||||||
| bool is_complete() { | ||||||||
| if(S_ISDIR(st.st_mode)) return filebuf.len > 0; | ||||||||
| return filebuf.len == st.st_size; | ||||||||
| if (S_ISDIR(st.st_mode)) return filebuf.len > 0; | ||||||||
| return filebuf.len == (size_t)st.st_size; | ||||||||
| } | ||||||||
|
|
||||||||
| void resize_buf(int filesize) { | ||||||||
| buf.resize(HTTP_HEADER_MAX_LENGTH + filesize); | ||||||||
| filebuf.base = buf.base + HTTP_HEADER_MAX_LENGTH; | ||||||||
| // NOTE: caller must hold mutex — invalidates filebuf/httpbuf pointers | ||||||||
| void resize_buf(size_t filesize, int reserved) { | ||||||||
| if (reserved < 0) reserved = 0; | ||||||||
| header_reserve = reserved; | ||||||||
| buf.resize((size_t)reserved + filesize); | ||||||||
| filebuf.base = buf.base + reserved; | ||||||||
| filebuf.len = filesize; | ||||||||
| // Invalidate httpbuf since buffer may have been reallocated | ||||||||
| httpbuf.base = NULL; | ||||||||
| httpbuf.len = 0; | ||||||||
| header_used = 0; | ||||||||
| } | ||||||||
|
|
||||||||
| void prepend_header(const char* header, int len) { | ||||||||
| if (len > HTTP_HEADER_MAX_LENGTH) return; | ||||||||
| void resize_buf(size_t filesize) { | ||||||||
| resize_buf(filesize, header_reserve); | ||||||||
| } | ||||||||
|
|
||||||||
| // Thread-safe: prepend header into reserved space. | ||||||||
| // Returns true on success, false if header exceeds reserved space. | ||||||||
| // On failure, httpbuf falls back to filebuf (body only, no header). | ||||||||
| bool prepend_header(const char* header, int len) { | ||||||||
| std::lock_guard<std::mutex> lock(mutex); | ||||||||
| if (len <= 0 || len > header_reserve) { | ||||||||
| // Safe fallback: point httpbuf at filebuf so callers always get valid data | ||||||||
| httpbuf = filebuf; | ||||||||
|
||||||||
| httpbuf = filebuf; | |
| httpbuf = filebuf; | |
| header_used = 0; |
Copilot
AI
Apr 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prepend_header() writes into the shared reserved space of the cached entry and updates httpbuf to point at that region. Even with the mutex, callers read/use fc->httpbuf after the lock is released, so concurrent requests can observe httpbuf changing underneath them or have their header bytes overwritten. Consider redesigning to keep the cache entry immutable for serving (store only the body) and construct headers per request without modifying the shared entry.
Copilot
AI
Apr 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
max_file_size / OpenParam::max_read are declared as int, but they represent a byte size and are compared against st.st_size (typically off_t, potentially >2GB). Using int can overflow/truncate on large files and makes it harder to configure sizes beyond INT_MAX. Consider switching these fields and related APIs to size_t (or uint64_t) so large-file thresholds work correctly on 64-bit platforms.
Copilot
AI
Apr 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OpenParam::max_read defaults to FILE_CACHE_DEFAULT_MAX_FILE_SIZE, while FileCache now has a runtime-configurable max_file_size (and HttpServer sets it from service->max_file_cache_size). Callers that don't explicitly set param.max_read will silently ignore the instance’s configured max_file_size, which can lead to inconsistent caching behavior across call sites. Consider defaulting OpenParam::max_read from the owning FileCache instance (e.g., in Open() when param->max_read is unset/0), or remove one of these knobs to keep a single source of truth.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
read(2) returns ssize_t, but the loop stores it in an int. If max_read is configured above INT_MAX (or on platforms where ssize_t is wider), this can truncate and break the loop logic. Use ssize_t for nread (and pass a size_t count that is capped to SSIZE_MAX if needed).