Skip to content

Commit 0b21f6c

Browse files
committed
fix: fix LRU impl and more
1 parent a5e3122 commit 0b21f6c

8 files changed

Lines changed: 131 additions & 39 deletions

ext/user_cache/tests/user_cache_entries_hint.phpt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@ echo shell_exec("$php $args -d user_cache.entries_hint=-1 -r " . escapeshellarg(
2424
$out = shell_exec("$php $args -d user_cache.entries_hint=16777213 -r " . escapeshellarg($code) . " 2>&1");
2525
echo $out, "\n";
2626

27-
/* The clamped table plus its per-key lock region (1024 records * 40 bytes
28-
* at this scale) must still leave half the segment for value data. */
27+
/* The clamped table plus its per-key lock region (1024 records at this
28+
* scale) must still leave half the segment for value data. Entry records
29+
* are 48 bytes and lock records 40 on 64-bit layouts, 44 and 36 on 32-bit;
30+
* each entry also carries a 4-byte access stamp. */
31+
[$entry_bytes, $lock_bytes] = PHP_INT_SIZE >= 8 ? [48, 40] : [44, 36];
2932
preg_match('/(\d+)\s*$/', $out, $m);
3033
$cap = (int) $m[1];
31-
var_dump($cap >= 100000, $cap * (48 + 4) + 1024 * 40 <= 8 * 1024 * 1024);
34+
var_dump($cap >= 100000, $cap * ($entry_bytes + 4) + 1024 * $lock_bytes <= 8 * 1024 * 1024);
3235
?>
3336
--EXPECTF--
3437
int(1361)

ext/user_cache/tests/user_cache_eviction_policies.phpt

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ user_cache.shm_size=4M
99
<?php
1010
$php = escapeshellarg(getenv('TEST_PHP_EXECUTABLE') ?: PHP_BINARY);
1111
$args = '-n -d user_cache.enable=1 -d user_cache.enable_cli=1 -d user_cache.shm_size=4M';
12-
$code = <<<'CODE'
12+
/* Run the child from a file: escapeshellarg() on Windows strips the double
13+
* quotes and percent signs the inline code would need. */
14+
$child = __DIR__ . '/user_cache_eviction_policies_child.php';
15+
file_put_contents($child, <<<'CODE'
16+
<?php
1317
$cache = UserCache\Cache::getPool('p');
1418
$blob = str_repeat('x', 8192);
1519
$ok = 0;
@@ -27,16 +31,19 @@ printf(
2731
(int) ($status->getExpungeCount() > 0),
2832
(int) ($status->getStoreFailureCount() > 0)
2933
);
30-
CODE;
34+
CODE);
3135

3236
foreach (['lru', 'clear', 'none'] as $policy) {
3337
echo $policy, ': ', shell_exec(
34-
"$php $args -d user_cache.eviction_policy=$policy -r " . escapeshellarg($code)
38+
"$php $args -d user_cache.eviction_policy=$policy " . escapeshellarg($child)
3539
);
3640
}
3741

3842
/* Invalid values are rejected at INI time and fall back to the lru default. */
39-
echo shell_exec("$php $args -d user_cache.eviction_policy=bogus -r " . escapeshellarg('echo "rejected\n";') . " 2>&1");
43+
echo shell_exec("$php $args -d user_cache.eviction_policy=bogus -r " . escapeshellarg("echo 'rejected';") . " 2>&1"), "\n";
44+
45+
/* Unlink here instead of --CLEAN-- so --repeat runs keep this test. */
46+
unlink($child);
4047
?>
4148
--EXPECTF--
4249
lru: stored=600 full=1 evicted=1 wiped=0 failed=0
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
--TEST--
2+
UserCache\Cache: LRU eviction absorbs a block size-class step without wiping
3+
--INI--
4+
user_cache.enable=1
5+
user_cache.enable_cli=1
6+
opcache.file_cache_only=0
7+
user_cache.shm_size=4M
8+
--FILE--
9+
<?php
10+
$cache = UserCache\Cache::getPool('sizeclass');
11+
12+
/* Fill the segment with one value size class. */
13+
$small = str_repeat('a', 8192);
14+
for ($i = 0; $i < 600; $i++) {
15+
$cache->store('s' . $i, $small);
16+
}
17+
18+
/* Churn with values two alignment steps larger: every LRU victim frees a
19+
* block strictly smaller than the incoming store needs, so eviction must
20+
* keep going until a fitting region exists instead of falling back to a
21+
* full wipe. */
22+
$large = str_repeat('b', 8192 + 16);
23+
$ok = 0;
24+
for ($i = 0; $i < 300; $i++) {
25+
if ($cache->store('l' . $i, $large)) {
26+
$ok++;
27+
}
28+
}
29+
30+
$status = UserCache\Cache::getStatus();
31+
var_dump($ok === 300);
32+
var_dump($status->getEvictionCount() > 0);
33+
var_dump($status->getExpungeCount() === 0);
34+
var_dump($cache->fetch('l299') === $large);
35+
?>
36+
--EXPECT--
37+
bool(true)
38+
bool(true)
39+
bool(true)
40+
bool(true)

ext/user_cache/tests/user_cache_remember_by_ref_callback.phpt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ user_cache.shm_size=16M
99
<?php
1010
$cache = UserCache\Cache::getPool('remember-by-ref');
1111

12+
/* Drop leftovers so a --repeat re-run in the same process misses again. */
13+
$cache->delete('by-ref');
14+
$cache->delete('plain');
15+
1216
$g = 42;
1317
$byRef = function &(string $key) use (&$g) {
1418
return $g;

ext/user_cache/user_cache_alloc_posix.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ static int user_cache_alloc_posix_create_segments(size_t requested_size, php_use
4141
* result as a signed int: its -1 error return in a size_t would pass
4242
* the > 0 guard and index far outside the array (see the equivalent
4343
* upstream fix in ext/opcache/shared_alloc_posix.c, GH-22429). */
44+
size_t shared_segment_lg_index = 0, shared_segment_sindexes[3] = {0};
4445
const size_t entries = sizeof(shared_segment_sindexes) / sizeof(shared_segment_sindexes[0]);
45-
size_t shared_segment_lg_index = 0, shared_segment_sindexes[3] = {0};
4646
int i, shared_segment_sizes;
4747

4848
shared_segment_sizes = getpagesizes(shared_segment_sindexes, entries);
@@ -52,6 +52,7 @@ static int user_cache_alloc_posix_create_segments(size_t requested_size, php_use
5252
if (shared_segment_sindexes[i] != 0 &&
5353
!(requested_size % shared_segment_sindexes[i])) {
5454
shared_segment_lg_index = i;
55+
5556
break;
5657
}
5758
}
@@ -62,6 +63,7 @@ static int user_cache_alloc_posix_create_segments(size_t requested_size, php_use
6263
*shared_segments_p = (php_user_cache_shm_segment_posix **) calloc(1, sizeof(php_user_cache_shm_segment_posix) + sizeof(void *));
6364
if (!*shared_segments_p) {
6465
*error_in = "calloc";
66+
6567
return PHP_USER_CACHE_ALLOC_FAILURE;
6668
}
6769
shared_segment = (php_user_cache_shm_segment_posix *)((char *)(*shared_segments_p) + sizeof(void *));

ext/user_cache/user_cache_entries.c

Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2388,32 +2388,14 @@ static bool user_cache_publish_prepared_shared_graph_locked(
23882388
);
23892389
}
23902390

2391-
static size_t user_cache_entry_storage_footprint_locked(const php_user_cache_entry *entry)
2392-
{
2393-
size_t footprint = 0;
2394-
2395-
if (entry->value_offset != 0 && user_cache_value_uses_offset(entry->value_type)) {
2396-
footprint += php_user_cache_block_payload_capacity(entry->value_offset);
2397-
}
2398-
2399-
if (entry->key_offset != 0 &&
2400-
(entry->reserved & PHP_USER_CACHE_ENTRY_RESERVED_COMBINED_VALUE_KEY) == 0
2401-
) {
2402-
footprint += php_user_cache_block_payload_capacity(entry->key_offset);
2403-
}
2404-
2405-
return footprint;
2406-
}
2407-
2408-
static bool user_cache_evict_lru_locked(size_t needed_size)
2391+
static bool user_cache_evict_lru_locked(size_t needed_size, size_t needed_key_size)
24092392
{
24102393
php_user_cache_header *header = php_user_cache_header_ptr();
24112394
php_user_cache_entry *entries, *entry;
24122395
uint64_t lock_now, lock_now_rel;
24132396
uint32_t hand, slot, scanned, collected, stamp, best_slot,
24142397
best_stamp = 0, victims = 0, *stamps
24152398
;
2416-
size_t freed = 0;
24172399
int8_t graph_quiescent = -1;
24182400
bool evicted_any = false;
24192401

@@ -2425,7 +2407,6 @@ static bool user_cache_evict_lru_locked(size_t needed_size)
24252407
stamps = php_user_cache_access_stamps_ptr(header);
24262408
hand = header->eviction_hand % header->capacity;
24272409
lock_now = (uint64_t) time(NULL);
2428-
/* The lock probe compares time_base-relative lease deadlines. */
24292410
lock_now_rel = php_user_cache_time_rel(header, lock_now);
24302411

24312412
user_cache_access_note_time(lock_now);
@@ -2455,9 +2436,6 @@ static bool user_cache_evict_lru_locked(size_t needed_size)
24552436
continue;
24562437
}
24572438

2458-
/* An undrained reader set would orphan the payload instead
2459-
* of freeing it, so its bytes must not count toward freed;
2460-
* probe lazily so graph-free scans never pay the drain wait. */
24612439
if (graph_quiescent < 0) {
24622440
graph_quiescent = php_user_cache_quiesce_graph_payloads_locked() ? 1 : 0;
24632441
}
@@ -2467,10 +2445,6 @@ static bool user_cache_evict_lru_locked(size_t needed_size)
24672445
}
24682446
}
24692447

2470-
/* Both skips are load-bearing: evicting a per-key-locked entry
2471-
* would break the writer serialization contract, and counting a
2472-
* pinned graph payload as freed makes the retry fail into the
2473-
* full-clear fallback. */
24742448
if (php_user_cache_entry_key_lock_active_locked(
24752449
header,
24762450
entry->hash,
@@ -2496,15 +2470,16 @@ static bool user_cache_evict_lru_locked(size_t needed_size)
24962470
}
24972471

24982472
entry = &entries[best_slot];
2499-
freed += user_cache_entry_storage_footprint_locked(entry);
25002473

25012474
user_cache_delete_entry_locked(header, entry);
25022475

25032476
header->eviction_count++;
25042477
victims++;
25052478
evicted_any = true;
25062479

2507-
if (needed_size == 0 || freed >= needed_size) {
2480+
if (needed_size == 0 ||
2481+
php_user_cache_alloc_can_satisfy_locked(needed_size, needed_key_size)
2482+
) {
25082483
break;
25092484
}
25102485
}
@@ -2517,6 +2492,7 @@ static bool user_cache_evict_lru_locked(size_t needed_size)
25172492
static bool user_cache_reclaim_space_for_store_locked(
25182493
bool can_reclaim,
25192494
size_t needed_size,
2495+
size_t needed_key_size,
25202496
bool *expired_retry_used,
25212497
bool *evict_retry_used,
25222498
bool *clear_retry_used)
@@ -2545,7 +2521,7 @@ static bool user_cache_reclaim_space_for_store_locked(
25452521
) {
25462522
*evict_retry_used = true;
25472523

2548-
if (user_cache_evict_lru_locked(needed_size)) {
2524+
if (user_cache_evict_lru_locked(needed_size, needed_key_size)) {
25492525
return true;
25502526
}
25512527
}
@@ -2622,6 +2598,7 @@ static php_user_cache_store_attempt_result user_cache_store_attempt_locked(
26222598
user_cache_reclaim_space_for_store_locked(
26232599
true,
26242600
0,
2601+
0,
26252602
expired_retry_used,
26262603
evict_retry_used,
26272604
clear_retry_used
@@ -2833,6 +2810,7 @@ static php_user_cache_store_attempt_result user_cache_store_attempt_locked(
28332810
user_cache_reclaim_space_for_store_locked(
28342811
true,
28352812
prepared->payload_size + key_size,
2813+
0,
28362814
expired_retry_used,
28372815
evict_retry_used,
28382816
clear_retry_used
@@ -2878,6 +2856,7 @@ static php_user_cache_store_attempt_result user_cache_store_attempt_locked(
28782856
user_cache_reclaim_space_for_store_locked(
28792857
true,
28802858
prepared->payload_size,
2859+
0,
28812860
expired_retry_used,
28822861
evict_retry_used,
28832862
clear_retry_used
@@ -2969,7 +2948,14 @@ static php_user_cache_store_attempt_result user_cache_store_attempt_locked(
29692948
if (options->retry_after_memory_pressure &&
29702949
user_cache_reclaim_space_for_store_locked(
29712950
can_reclaim,
2972-
prepared->payload_size + key_size,
2951+
use_combined_publish
2952+
? prepared->payload_size + key_size
2953+
: prepared->payload_size
2954+
,
2955+
use_combined_publish
2956+
? 0
2957+
: key_size
2958+
,
29732959
expired_retry_used,
29742960
evict_retry_used,
29752961
clear_retry_used

ext/user_cache/user_cache_internal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,7 @@ bool php_user_cache_header_init_locked(void);
764764
bool php_user_cache_header_adoptable_locked(void);
765765
void php_user_cache_free_locked(uint32_t payload_offset);
766766
uint32_t php_user_cache_alloc_locked(size_t size, const void *src);
767+
bool php_user_cache_alloc_can_satisfy_locked(size_t size, size_t key_size);
767768
bool php_user_cache_startup_storage_before_request(void);
768769
void php_user_cache_shutdown_storage(void);
769770
void php_user_cache_ensure_ready_impl(void);

ext/user_cache/user_cache_storage.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4193,6 +4193,55 @@ uint32_t php_user_cache_alloc_locked(size_t size, const void *src)
41934193
return user_cache_alloc_from_tail_locked(header, size, total_size, src);
41944194
}
41954195

4196+
bool php_user_cache_alloc_can_satisfy_locked(size_t size, size_t key_size)
4197+
{
4198+
php_user_cache_header *header = php_user_cache_header_ptr();
4199+
uint32_t free_offset;
4200+
size_t value_total, key_total, region, largest, second = 0;
4201+
4202+
if (!header || size == 0 || size > UINT32_MAX - sizeof(php_user_cache_block)) {
4203+
return false;
4204+
}
4205+
4206+
value_total = PHP_USER_CACHE_ALIGNED_SIZE(sizeof(php_user_cache_block) + size);
4207+
key_total = key_size != 0
4208+
? PHP_USER_CACHE_ALIGNED_SIZE(sizeof(php_user_cache_block) + key_size)
4209+
: 0
4210+
;
4211+
if (value_total > UINT32_MAX || key_total > UINT32_MAX - value_total) {
4212+
return false;
4213+
}
4214+
4215+
region = header->next_free <= header->data_size
4216+
? header->data_size - header->next_free
4217+
: 0
4218+
;
4219+
largest = region;
4220+
4221+
for (free_offset = header->free_list;
4222+
free_offset != 0;
4223+
free_offset = php_user_cache_block_ptr(free_offset)->next_free
4224+
) {
4225+
region = php_user_cache_block_ptr(free_offset)->size;
4226+
4227+
if (region >= value_total + key_total) {
4228+
return true;
4229+
}
4230+
4231+
if (region > largest) {
4232+
second = largest;
4233+
largest = region;
4234+
} else if (region > second) {
4235+
second = region;
4236+
}
4237+
}
4238+
4239+
4240+
return largest >= value_total + key_total ||
4241+
(largest >= value_total && (key_total == 0 || second >= key_total))
4242+
;
4243+
}
4244+
41964245
bool php_user_cache_startup_storage_before_request(void)
41974246
{
41984247
php_user_cache_storage *storage = &php_user_cache_active_context()->storage;

0 commit comments

Comments
 (0)