SwooleTableApcuDriver.php

PHP

Path: src/Mini/ApcuDrivers/SwooleTableApcuDriver.php

<?php
namespace mini\Mini\ApcuDrivers;

use Swoole\Table;
use Swoole\Lock;

class SwooleTableApcuDriver implements ApcuDriverInterface
{
    use ApcuDriverTrait;

    /** @var Table */
    protected Table $table;

    /** @var Lock global mutex used only to implement SETNX semantics for _add() */
    protected Lock $addLock;

    /**
     * @param int $size       Number of rows in the table (must be power of two in Swoole <= 4.x).
     * @param int $valueSize  Max payload size in bytes (must fit your serialized values + TTL metadata).
     */
    public function __construct(int $size = 1024, int $valueSize = 4096)
    {
        $table = new Table($size);
        $table->column('payload', Table::TYPE_STRING, $valueSize);
        $table->create();

        $this->table   = $table;
        $this->addLock = new Lock(SWOOLE_MUTEX);
    }

    /* --------------------------------------------------------------------
     * LOW-LEVEL BACKEND PRIMITIVES FOR ApcuDriverTrait
     * ------------------------------------------------------------------ */

    /**
     * Fetch raw payload from Swoole\Table.
     */
    protected function _fetch(string $key, bool &$found = null): ?string
    {
        // Table::get() returns array or false; using field shortcut variant
        $payload = $this->table->get($key, 'payload');
        if ($payload === false || $payload === null) {
            $found = false;
            return null;
        }

        $found = true;
        return $payload;
    }

    /**
     * Atomic "add if not exists" (SETNX) using a coarse mutex.
     *
     * We deliberately do NOT lock in _store/_delete, because the trait
     * provides per-key locking for the operations that require it.
     */
    protected function _add(string $key, string $payload, int $ttl): bool
    {
        $this->addLock->lock();
        try {
            if ($this->table->exists($key)) {
                return false;
            }
            // Swoole\Table has no TTL; trait handles logical TTL and
            // backend TTL is effectively ignored for entries here.
            return $this->table->set($key, ['payload' => $payload]);
        } finally {
            $this->addLock->unlock();
        }
    }

    /**
     * Unconditional overwrite (SET).
     */
    protected function _store(string $key, string $payload, int $ttl): bool
    {
        return $this->table->set($key, ['payload' => $payload]);
    }

    /**
     * Delete a row.
     */
    protected function _delete(string $key): bool
    {
        return $this->table->del($key);
    }

    /* --------------------------------------------------------------------
     * GARBAGE COLLECTION
     * ------------------------------------------------------------------ */

    /**
     * Probabilistic GC: 1 in 10,000 chance to clean expired entries.
     *
     * Swoole\Table has no native TTL, so we scan for logically expired entries.
     */
    protected function maybeGarbageCollect(): void
    {
        if (mt_rand(0, 9999) !== 0) {
            return;
        }

        // Scan table and delete expired entries
        foreach ($this->table as $key => $row) {
            $expired = false;
            $expiresAt = null;
            $this->unpackValue($row['payload'], $expired, $expiresAt);

            if ($expired) {
                $this->table->del($key);
            }
        }
    }

    /* --------------------------------------------------------------------
     * REQUIRED ApcuDriverInterface METHODS NOT PROVIDED BY THE TRAIT
     * ------------------------------------------------------------------ */

    /**
     * apcu_cache_info(): here we just return something minimal and honest.
     */
    public function info(bool $limited = false): array|false
    {
        $entries = 0;
        foreach ($this->table as $k => $row) {
            $entries++;
        }

        return [
            'num_entries' => $entries,
            'limited'     => $limited,
            'driver'      => 'swoole_table',
        ];
    }

    /**
     * apcu_sma_info(): Swoole\Table doesn’t expose allocator details,
     * so we just return a minimal stub.
     */
    public function sma_info(bool $limited = false): array|false
    {
        return [
            'available_memory' => null,
            'used_memory'      => null,
            'num_seg'          => 1,
            'seg_size'         => null,
            'limited'          => $limited,
            'driver'           => 'swoole_table',
        ];
    }

    /**
     * apcu_clear_cache(): wipe the table.
     */
    public function clear_cache(): bool
    {
        // Swoole\Table is Traversable: we can iterate and delete.
        foreach ($this->table as $key => $row) {
            $this->table->del($key);
        }
        return true;
    }

    /**
     * apcu_enabled(): this driver is enabled if Swoole is available.
     */
    public function enabled(): bool
    {
        return extension_loaded('swoole');
    }
}