diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ddd843376..51b148441 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -54,7 +54,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: mysql, mbstring, json, xml, opcache, readline, redis, gd + extensions: mysql, mbstring, json, xml, opcache, readline, redis, gd, apcu - name: Initialize variables run: | diff --git a/app/Config/core.default.php b/app/Config/core.default.php index eb226485f..97ae661de 100644 --- a/app/Config/core.default.php +++ b/app/Config/core.default.php @@ -255,8 +255,9 @@ Configure::write('Acl.database', 'default'); * and their setttings. */ $engine = 'File'; -if (extension_loaded('apc') && function_exists('apc_dec') && (php_sapi_name() !== 'cli' || ini_get('apc.enable_cli'))) { - $engine = 'Apc'; +if (function_exists('apcu_dec') && (PHP_SAPI !== 'cli' || ini_get('apc.enable_cli'))) { + require_once APP . 'Plugin/ApcuCache/Engine/ApcuEngine.php'; // it is not possible to use plugin + $engine = 'Apcu'; // faster version of ApcEngine } // In development mode, caches should expire quickly. diff --git a/app/Plugin/ApcuCache/Engine/ApcuEngine.php b/app/Plugin/ApcuCache/Engine/ApcuEngine.php new file mode 100644 index 000000000..6d400e6b6 --- /dev/null +++ b/app/Plugin/ApcuCache/Engine/ApcuEngine.php @@ -0,0 +1,167 @@ + 'Apc'); + parent::init($settings); + return function_exists('apcu_dec'); + } + + /** + * Write data for key into cache + * + * @param string $key Identifier for the data + * @param mixed $value Data to be cached + * @param int $duration How long to cache the data, in seconds + * @return bool True if the data was successfully cached, false on failure + */ + public function write($key, $value, $duration) { + return apcu_store($key, $value, $duration); + } + + /** + * Read a key from the cache + * + * @param string $key Identifier for the data + * @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it + */ + public function read($key) { + return apcu_fetch($key); + } + + /** + * Increments the value of an integer cached key + * + * @param string $key Identifier for the data + * @param int $offset How much to increment + * @return false|int New incremented value, false otherwise + */ + public function increment($key, $offset = 1) { + return apcu_inc($key, $offset); + } + + /** + * Decrements the value of an integer cached key + * + * @param string $key Identifier for the data + * @param int $offset How much to subtract + * @return false|int New decremented value, false otherwise + */ + public function decrement($key, $offset = 1) { + return apcu_dec($key, $offset); + } + + /** + * Delete a key from the cache + * + * @param string $key Identifier for the data + * @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed + */ + public function delete($key) { + return apcu_delete($key); + } + + /** + * Delete all keys from the cache. This will clear every cache config using APC. + * + * @param bool $check If true, nothing will be cleared, as entries are removed + * from APC as they expired. This flag is really only used by FileEngine. + * @return bool True Returns true. + */ + public function clear($check) { + if ($check) { + return true; + } + $iterator = new APCUIterator( + '/^' . preg_quote($this->settings['prefix'], '/') . '/', + APC_ITER_NONE + ); + apcu_delete($iterator); + return true; + } + + /** + * Returns the `group value` for each of the configured groups + * If the group initial value was not found, then it initializes + * the group accordingly. + * + * @return array + */ + public function groups() { + if (empty($this->_compiledGroupNames)) { + foreach ($this->settings['groups'] as $group) { + $this->_compiledGroupNames[] = $this->settings['prefix'] . $group; + } + } + + $groups = apcu_fetch($this->_compiledGroupNames); + if (count($groups) !== count($this->settings['groups'])) { + foreach ($this->_compiledGroupNames as $group) { + if (!isset($groups[$group])) { + apcu_store($group, 1); + $groups[$group] = 1; + } + } + ksort($groups); + } + + $result = array(); + $groups = array_values($groups); + foreach ($this->settings['groups'] as $i => $group) { + $result[] = $group . $groups[$i]; + } + return $result; + } + + /** + * Increments the group value to simulate deletion of all keys under a group + * old values will remain in storage until they expire. + * + * @param string $group The group to clear. + * @return bool success + */ + public function clearGroup($group) { + apcu_inc($this->settings['prefix'] . $group, 1, $success); + return $success; + } + + /** + * Write data for key into cache if it doesn't exist already. + * If it already exists, it fails and returns false. + * + * @param string $key Identifier for the data. + * @param mixed $value Data to be cached. + * @param int $duration How long to cache the data, in seconds. + * @return bool True if the data was successfully cached, false on failure. + * @link http://php.net/manual/en/function.apc-add.php + */ + public function add($key, $value, $duration) { + return apc_add($key, $value, $duration); + } +} diff --git a/app/composer.json b/app/composer.json index 43b9400e2..5183a3d79 100644 --- a/app/composer.json +++ b/app/composer.json @@ -30,6 +30,7 @@ "ext-ssdeep": "For ssdeep hashes correlation", "ext-bcmath": "For faster validating IBAN numbers", "ext-rdkafka": "Required for publishing events to Kafka broker", + "ext-apcu": "To cache data in memory instead of file system", "elasticsearch/elasticsearch": "For logging to elasticsearch", "aws/aws-sdk-php": "To upload samples to S3", "jumbojett/openid-connect-php": "For OIDC authentication", diff --git a/travis/core.php b/travis/core.php index a231e380a..c22dee853 100644 --- a/travis/core.php +++ b/travis/core.php @@ -244,8 +244,9 @@ * and their setttings. */ $engine = 'File'; -if (extension_loaded('apc') && function_exists('apc_dec') && (php_sapi_name() !== 'cli' || ini_get('apc.enable_cli'))) { - $engine = 'Apc'; +if (function_exists('apcu_dec') && (PHP_SAPI !== 'cli' || ini_get('apc.enable_cli'))) { + require_once APP . 'Plugin/ApcuCache/Engine/ApcuEngine.php'; // it is not possible to use plugin + $engine = 'Apcu'; } // In development mode, caches should expire quickly.