diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b18f6d..f2a6f8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - php: ['8.4'] + php: ['8.4', '8.5'] steps: - uses: actions/checkout@v4 @@ -33,14 +33,18 @@ jobs: run: vendor/bin/phpunit soak: - name: Flat-memory soak (5k request cycles) + name: Flat-memory soak (5k request cycles, PHP ${{ matrix.php }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ['8.4', '8.5'] steps: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: '8.4' + php-version: ${{ matrix.php }} extensions: ffi ini-values: ffi.enable=1, opcache.jit=off coverage: none @@ -52,14 +56,18 @@ jobs: run: php -d ffi.enable=1 tools/soak.php 5000 soak-drop: - name: Reclaiming soak (5k persist/drop cycles) + name: Reclaiming soak (5k persist/drop cycles, PHP ${{ matrix.php }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ['8.4', '8.5'] steps: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: '8.4' + php-version: ${{ matrix.php }} extensions: ffi ini-values: ffi.enable=1, opcache.jit=off coverage: none @@ -71,14 +79,18 @@ jobs: run: php -d ffi.enable=1 tools/soak-drop.php 5000 request-boundary: - name: True multi-request gate (php-cgi/FastCGI) + name: True multi-request gate (php-cgi/FastCGI, PHP ${{ matrix.php }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ['8.4', '8.5'] steps: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: '8.4' + php-version: ${{ matrix.php }} extensions: ffi ini-values: ffi.enable=1, opcache.jit=off coverage: none diff --git a/README.md b/README.md index 92e0f6f..fb69c8e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![CI](https://github.com/lisachenko/php-shared-data-extension/actions/workflows/ci.yml/badge.svg)](https://github.com/lisachenko/php-shared-data-extension/actions/workflows/ci.yml) [![Latest Version](https://img.shields.io/packagist/v/lisachenko/php-shared-data-extension?include_prereleases)](https://packagist.org/packages/lisachenko/php-shared-data-extension) -[![PHP 8.4](https://img.shields.io/badge/php-8.4-777BB3.svg?logo=php&logoColor=white)](composer.json) +[![PHP 8.4 | 8.5](https://img.shields.io/badge/php-8.4%20%7C%208.5-777BB3.svg?logo=php&logoColor=white)](composer.json) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) **PHP objects that survive the request boundary. In pure PHP.** @@ -263,9 +263,7 @@ CI runs all of the above on every push and pull request. ## Requirements -- PHP ~8.4 (NTS) with `ext-ffi` -- `lisachenko/z-engine` — temporarily pinned to the - `claude/shared-objects-dag-memory-nq462w` branch, which adds the persistent - free primitives (`Core::persistentFree()`, `PersistentHashTable::destroy()`, - `HashTable::deleteIndex()`) this release needs; back to `dev-master` (or the - `8.4` release line once tagged) as soon as that lands +- PHP 8.4 or 8.5 (NTS) with `ext-ffi` +- `lisachenko/z-engine` — required as `8.4.x-dev || 8.5.x-dev`; z-engine tracks + one PHP minor per line, and Composer resolves the line matching the running + PHP (the `8.4` branch on PHP 8.4, `master` — aliased `8.5.x-dev` — on PHP 8.5) diff --git a/composer.json b/composer.json index be862c7..32674a9 100644 --- a/composer.json +++ b/composer.json @@ -3,8 +3,8 @@ "description": "Shared data extension for PHP: persistent memory and request-surviving objects", "type": "library", "require": { - "lisachenko/z-engine": "dev-master || ^8.4", - "php": "~8.4.0", + "lisachenko/z-engine": "8.4.x-dev || 8.5.x-dev", + "php": "^8.4", "ext-ffi": "*" }, "require-dev": { diff --git a/src/PersistentStore.php b/src/PersistentStore.php index 4fa1e32..c54cb7f 100644 --- a/src/PersistentStore.php +++ b/src/PersistentStore.php @@ -16,7 +16,6 @@ use FFI\CData; use ZEngine\Core; use ZEngine\Reflection\ReflectionValue; -use ZEngine\Type\HashTable; use ZEngine\Type\ObjectEntry; use ZEngine\Type\PersistentObjectFactory; @@ -332,10 +331,16 @@ public function detach(): void $objectEntry = ObjectEntry::fromCData($object->object); // Release the request-allocated properties hashtable rebuilt by - // get_object_vars()/var_dump()/casts, it would dangle next request + // get_object_vars()/var_dump()/casts, it would dangle next request. + // Mirrors zend_array_release(): drop our reference, and let the + // engine dismantle the table through its own allocator at zero $dynamicProperties = $objectEntry->getDynamicPropertiesPointer(); if ($dynamicProperties !== null) { - (new HashTable($dynamicProperties))->releaseReference(); + $gcHeader = $dynamicProperties->gc; + $gcHeader->refcount = $gcHeader->refcount - 1; + if ($gcHeader->refcount === 0) { + Core::call('rc_dtor_func', Core::cast('zend_refcounted *', $dynamicProperties)); + } $objectEntry->setDynamicPropertiesPointer(null); } } diff --git a/src/Persister.php b/src/Persister.php index fe40b06..444582a 100644 --- a/src/Persister.php +++ b/src/Persister.php @@ -410,7 +410,7 @@ private function persistStringSlot(CData $slot, string $path): void */ private function persistArray(CData $sourceArray, string $path): PersistentHashTable { - $target = PersistentHashTable::create(); + $target = new PersistentHashTable(); // Ownership is recorded up front: elements converted below may mint nested tables, // and they all belong to the same object - the one whose slot started this array diff --git a/src/Registry.php b/src/Registry.php index d487b46..b8290da 100644 --- a/src/Registry.php +++ b/src/Registry.php @@ -97,9 +97,9 @@ public static function fromAddress(int $address): self */ public static function create(): array { - $root = PersistentHashTable::create(); - self::addPointer($root, 'entries', PersistentHashTable::create()->getRawValue()); - self::addPointer($root, 'objects', PersistentHashTable::create()->getRawValue()); + $root = new PersistentHashTable(); + self::addPointer($root, 'entries', new PersistentHashTable()->getRawValue()); + self::addPointer($root, 'objects', new PersistentHashTable()->getRawValue()); return [new self($root), Core::addressOf($root->getRawValue())]; } @@ -121,12 +121,12 @@ public function store(string $name, PersistedEntry $entry): void $this->adjustShares($address, +1); } - $members = PersistentHashTable::create(); + $members = new PersistentHashTable(); foreach ($entry->members as $index => $address) { self::addLong($members, $index, $address); } - $meta = PersistentHashTable::create(); + $meta = new PersistentHashTable(); self::addLong($meta, 'count', $entry->count()); self::addPointer($meta, 'members', $members->getRawValue()); @@ -272,12 +272,12 @@ public function objectCount(): int */ private function addObject(PersistedObject $object): void { - $arrays = PersistentHashTable::create(); + $arrays = new PersistentHashTable(); foreach ($object->arrays as $index => $array) { self::addPointer($arrays, $index, $array); } - $meta = PersistentHashTable::create(); + $meta = new PersistentHashTable(); self::addPointer($meta, 'object', $object->object); self::addPointer($meta, 'snapshot', $object->snapshot); self::addInternedString($meta, 'class', $object->className);