Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/uvhttp_lru_cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -689,21 +689,43 @@ void uvhttp_lru_cache_set_max_file_size(cache_manager_t* cache,
/**
* set maximum memory usage for cache
*/
/* Range is enforced at the write point, not only at create time.
* uvhttp_lru_cache_create rejects max_memory_usage == 0 ("cannot be zero"),
* and the eviction path reads the limit as "enabled only when > 0". A setter
* that accepted 0 therefore silently disabled the memory ceiling on a live
* cache — a value create refuses to build in the first place. */
void uvhttp_lru_cache_set_max_memory_usage(cache_manager_t* cache,
size_t max_memory_usage) {
if (!cache)
return;

if (max_memory_usage == 0) {
UVHTTP_LOG_WARN(
"Ignoring max_memory_usage=0 (create rejects it; 0 would disable "
"the memory ceiling)");
return;
}

cache->max_memory_usage = max_memory_usage;
}

/**
* set maximum entries for cache
*/
/* Same rule as create: max_entries must be positive. The eviction path
* treats "> 0" as "limit enabled", so accepting 0 here would turn a bounded
* cache into an unbounded one behind the caller's back. */
void uvhttp_lru_cache_set_max_entries(cache_manager_t* cache, int max_entries) {
if (!cache)
return;

if (max_entries <= 0) {
UVHTTP_LOG_WARN(
"Ignoring max_entries=%d (must be positive, same rule as create)",
max_entries);
return;
}

cache->max_entries = max_entries;
}

Expand Down
23 changes: 14 additions & 9 deletions src/uvhttp_response.c
Original file line number Diff line number Diff line change
Expand Up @@ -463,18 +463,23 @@ uvhttp_error_t uvhttp_response_set_body(uvhttp_response_t* response,
return UVHTTP_ERROR_INVALID_PARAM;
}

if (response->body) {
uvhttp_free(response->body);
response->body = NULL;
}

response->body = uvhttp_alloc(length);
if (!response->body) {
response->body_length = 0;
/* Atomic replace: allocate the new body FIRST, then drop the old one.
*
* The previous order (free old -> alloc new) lost the old body whenever
* the allocation failed: the caller got UVHTTP_ERROR_OUT_OF_MEMORY and
* a response with body=NULL, so a retry had nothing to fall back on.
* set_header already had this property for free because uvhttp_realloc
* keeps the old block when it fails; set_body allocates a fresh block,
* so it has to buy the atomicity explicitly. */
char* new_body = uvhttp_alloc(length);
if (!new_body) {
return UVHTTP_ERROR_OUT_OF_MEMORY;
}
memcpy(new_body, body, length);

memcpy(response->body, body, length);
/* Only now is the old body expendable. */
uvhttp_free(response->body);
response->body = new_body;
response->body_length = length;

return UVHTTP_OK;
Expand Down
76 changes: 75 additions & 1 deletion test/unit/test_lru_cache_capacity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,78 @@ TEST_F(LruCacheCapacity, EntryJustUnderThresholdIsAccepted) {
EXPECT_EQ(cache_->entry_count, 1);
}

#endif /* UVHTTP_FEATURE_STATIC_FILES */
/*
* 背景:uvhttp_lru_cache_create 拒绝 max_memory_usage == 0("cannot be zero")
* 且要求 max_entries > 0,而淘汰路径把 "> 0" 读作"限额生效"。setter 此前
* 直接赋值,于是两条写路径对同一个不变量给出相反答案:create 拒绝的值,
* setter 照单全收,还顺手把运行中缓存的限额关掉了(0 → 视为无限额)。
*
* 同一个库内的正确范式是 uvhttp_lru_cache_set_batch_eviction_size:越界时
* 告警并保持原值,而不是静默接受。
*/

TEST_F(LruCacheCapacity, SetMaxMemoryUsageRejectsZero) {
Create(kBudget);

uvhttp_lru_cache_set_max_memory_usage(cache_, 0);

EXPECT_EQ(cache_->max_memory_usage, kBudget)
<< "create 拒绝 0(cannot be zero),setter 不得把它写进去";
}

/* 越界值不生效后,限额仍是原来那个 —— 不是被静默放宽 */
TEST_F(LruCacheCapacity, ZeroMaxMemoryUsageDoesNotWidenTheBudget) {
Create(kBudget);

uvhttp_lru_cache_set_max_memory_usage(cache_, 0);

/* 预算仍是 16KB:8KB 内容条目(+约 10KB 开销)依旧放不进 */
EXPECT_EQ(Put("/big.bin", 8 * 1024, 'x'), UVHTTP_ERROR_OUT_OF_MEMORY)
<< "写入 0 之后内存上限必须仍然生效";
}

TEST_F(LruCacheCapacity, SetMaxMemoryUsageAcceptsPositiveValue) {
Create(kBudget);

uvhttp_lru_cache_set_max_memory_usage(cache_, 32 * 1024);

EXPECT_EQ(cache_->max_memory_usage, 32 * 1024);
EXPECT_EQ(Put("/big.bin", 8 * 1024, 'x'), UVHTTP_OK)
<< "合法的更大预算应立即生效";
}

TEST_F(LruCacheCapacity, SetMaxEntriesRejectsZeroAndNegative) {
Create(kBudget);

uvhttp_lru_cache_set_max_entries(cache_, 0);
EXPECT_EQ(cache_->max_entries, 10) << "create 要求 max_entries > 0";

uvhttp_lru_cache_set_max_entries(cache_, -5);
EXPECT_EQ(cache_->max_entries, 10) << "负值同样必须被拒";
}

/* 关闭条目限额会让有界缓存退化成无界缓存 —— 这正是校验要阻止的后果 */
TEST_F(LruCacheCapacity, ZeroMaxEntriesWouldDisableTheEntryCeiling) {
Create(kBudget);

uvhttp_lru_cache_set_max_entries(cache_, 0);

/* 限额仍是 10:放入第 11 条时应触发淘汰,entry_count 不越过上限 */
for (int i = 0; i < 11; i++) {
char path[32];
snprintf(path, sizeof(path), "/f%d.bin", i);
Put(path, 64, 's');
}
EXPECT_LE(cache_->entry_count, cache_->max_entries)
<< "条目上限必须仍然生效";
}

TEST_F(LruCacheCapacity, SetMaxEntriesAcceptsPositiveValue) {
Create(kBudget);

uvhttp_lru_cache_set_max_entries(cache_, 3);

EXPECT_EQ(cache_->max_entries, 3);
}

#endif /* UVHTTP_FEATURE_STATIC_FILES */
Loading
Loading