Remove file caching in auto loader (#7752)
* Remove file cache due to race condition * Improves APCu cache consistency and error logging Enhances APCu handling by validating both cache keys before using cached data, checking data integrity, and logging cache validation failures. Ensures both class and interface arrays are stored successfully, logging and clearing cache on partial store failures to prevent inconsistent states. Aims to improve reliability and aid in diagnosing APCu issues.
This commit is contained in:
@@ -35,21 +35,7 @@
|
|||||||
class auto_loader {
|
class auto_loader {
|
||||||
|
|
||||||
const CLASSES_KEY = 'autoloader_classes';
|
const CLASSES_KEY = 'autoloader_classes';
|
||||||
const CLASSES_FILE = 'autoloader_cache.php';
|
|
||||||
const INTERFACES_KEY = "autoloader_interfaces";
|
const INTERFACES_KEY = "autoloader_interfaces";
|
||||||
const INTERFACES_FILE = "autoloader_interface_cache.php";
|
|
||||||
/**
|
|
||||||
* Cache path and file name for classes
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private static $classes_file = null;
|
|
||||||
/**
|
|
||||||
* Cache path and file name for interfaces
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private static $interfaces_file = null;
|
|
||||||
private $classes;
|
private $classes;
|
||||||
/**
|
/**
|
||||||
* Tracks the APCu extension for caching to RAM drive across requests
|
* Tracks the APCu extension for caching to RAM drive across requests
|
||||||
@@ -64,6 +50,7 @@ class auto_loader {
|
|||||||
*/
|
*/
|
||||||
private $interfaces;
|
private $interfaces;
|
||||||
/**
|
/**
|
||||||
|
* Stores trait definitions (currently unused but kept for future expansion)
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $traits;
|
private $traits;
|
||||||
@@ -78,16 +65,6 @@ class auto_loader {
|
|||||||
//set if we can use RAM cache
|
//set if we can use RAM cache
|
||||||
$this->apcu_enabled = function_exists('apcu_enabled') && apcu_enabled();
|
$this->apcu_enabled = function_exists('apcu_enabled') && apcu_enabled();
|
||||||
|
|
||||||
//set classes cache location
|
|
||||||
if (empty(self::$classes_file)) {
|
|
||||||
self::$classes_file = sys_get_temp_dir() . DIRECTORY_SEPARATOR . self::CLASSES_FILE;
|
|
||||||
}
|
|
||||||
|
|
||||||
//set interface cache location
|
|
||||||
if (empty(self::$interfaces_file)) {
|
|
||||||
self::$interfaces_file = sys_get_temp_dir() . DIRECTORY_SEPARATOR . self::INTERFACES_FILE;
|
|
||||||
}
|
|
||||||
|
|
||||||
//classes must be loaded before this object is registered
|
//classes must be loaded before this object is registered
|
||||||
if ($disable_cache || !$this->load_cache()) {
|
if ($disable_cache || !$this->load_cache()) {
|
||||||
//cache miss so load them
|
//cache miss so load them
|
||||||
@@ -100,42 +77,35 @@ class auto_loader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the class cache from various sources.
|
* Loads the class cache from APCu if available.
|
||||||
*
|
*
|
||||||
* @return bool True if the cache is loaded successfully, false otherwise.
|
* @return bool True if the cache is loaded successfully, false otherwise.
|
||||||
*/
|
*/
|
||||||
public function load_cache(): bool {
|
public function load_cache(): bool {
|
||||||
$this->classes = [];
|
$this->classes = [];
|
||||||
$this->interfaces = [];
|
$this->interfaces = [];
|
||||||
$this->traits = [];
|
$this->traits = []; // Reset traits array
|
||||||
|
|
||||||
//use apcu when available
|
//use apcu when available - validate BOTH keys exist
|
||||||
if ($this->apcu_enabled && apcu_exists(self::CLASSES_KEY)) {
|
if ($this->apcu_enabled && apcu_exists(self::CLASSES_KEY) && apcu_exists(self::INTERFACES_KEY)) {
|
||||||
$this->classes = apcu_fetch(self::CLASSES_KEY, $classes_cached);
|
$this->classes = apcu_fetch(self::CLASSES_KEY, $classes_cached);
|
||||||
$this->interfaces = apcu_fetch(self::INTERFACES_KEY, $interfaces_cached);
|
$this->interfaces = apcu_fetch(self::INTERFACES_KEY, $interfaces_cached);
|
||||||
//don't use files when we are using apcu caching
|
|
||||||
if ($classes_cached && $interfaces_cached)
|
//validate fetched data is arrays and not corrupted
|
||||||
|
if ($classes_cached && $interfaces_cached &&
|
||||||
|
is_array($this->classes) && is_array($this->interfaces) &&
|
||||||
|
!empty($this->classes)) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//log when cache validation fails
|
||||||
|
if ($classes_cached || $interfaces_cached) {
|
||||||
|
self::log(LOG_WARNING, "APCu cache validation failed - classes_cached: " . ($classes_cached ? 'true' : 'false') . ", interfaces_cached: " . ($interfaces_cached ? 'true' : 'false') . ", is_array(classes): " . (is_array($this->classes) ? 'true' : 'false') . ", is_array(interfaces): " . (is_array($this->interfaces) ? 'true' : 'false'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//use PHP engine to parse it
|
//return false when we don't have classes in memory
|
||||||
if (file_exists(self::$classes_file)) {
|
return false;
|
||||||
$this->classes = include self::$classes_file;
|
|
||||||
}
|
|
||||||
|
|
||||||
//do the same for interface to class mappings
|
|
||||||
if (file_exists(self::$interfaces_file)) {
|
|
||||||
$this->interfaces = include self::$interfaces_file;
|
|
||||||
}
|
|
||||||
|
|
||||||
//catch edge case of first time using apcu cache
|
|
||||||
if ($this->apcu_enabled) {
|
|
||||||
apcu_store(self::CLASSES_KEY, $this->classes);
|
|
||||||
apcu_store(self::INTERFACES_KEY, $this->interfaces);
|
|
||||||
}
|
|
||||||
|
|
||||||
//return true when we have classes and false if the array is still empty
|
|
||||||
return (!empty($this->classes) && !empty($this->interfaces));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -168,9 +138,6 @@ class auto_loader {
|
|||||||
$files = array_merge($files, glob($path));
|
$files = array_merge($files, glob($path));
|
||||||
}
|
}
|
||||||
|
|
||||||
//reset the current array
|
|
||||||
$class_list = [];
|
|
||||||
|
|
||||||
//store the class name (key) and the path (value)
|
//store the class name (key) and the path (value)
|
||||||
foreach ($files as $file) {
|
foreach ($files as $file) {
|
||||||
$file_content = file_get_contents($file);
|
$file_content = file_get_contents($file);
|
||||||
@@ -246,50 +213,46 @@ class auto_loader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the cache by writing the classes and interfaces to files on disk.
|
* Updates the cache by storing classes and interfaces in APCu if available.
|
||||||
*
|
*
|
||||||
* @return bool True if the update was successful, false otherwise
|
* @return bool True if the update was successful, false otherwise
|
||||||
*/
|
*/
|
||||||
public function update_cache(): bool {
|
public function update_cache(): bool {
|
||||||
//guard against writing an empty file
|
//guard against empty cache
|
||||||
if (empty($this->classes)) {
|
if (empty($this->classes)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//update RAM cache when available
|
//update APCu cache when available
|
||||||
if ($this->apcu_enabled) {
|
if ($this->apcu_enabled) {
|
||||||
$classes_cached = apcu_store(self::CLASSES_KEY, $this->classes);
|
$classes_stored = apcu_store(self::CLASSES_KEY, $this->classes, 0);
|
||||||
$interfaces_cached = apcu_store(self::INTERFACES_KEY, $this->interfaces);
|
$interfaces_stored = apcu_store(self::INTERFACES_KEY, $this->interfaces, 0);
|
||||||
//do not save to drive when we are using apcu
|
|
||||||
if ($classes_cached && $interfaces_cached)
|
//log failures to help diagnose APCu issues
|
||||||
|
if (!$classes_stored) {
|
||||||
|
self::log(LOG_WARNING, "Failed to store classes to APCu");
|
||||||
|
}
|
||||||
|
if (!$interfaces_stored) {
|
||||||
|
self::log(LOG_WARNING, "Failed to store interfaces to APCu");
|
||||||
|
}
|
||||||
|
|
||||||
|
//both must succeed for consistency
|
||||||
|
if ($classes_stored && $interfaces_stored) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//if one failed, clear APCu to prevent inconsistent state
|
||||||
|
if ($classes_stored || $interfaces_stored) {
|
||||||
|
apcu_delete(self::CLASSES_KEY);
|
||||||
|
apcu_delete(self::INTERFACES_KEY);
|
||||||
|
self::log(LOG_WARNING, "Cleared APCu cache due to partial store failure");
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//export the classes array using PHP engine
|
//APCu not available, cache remains in memory only
|
||||||
$classes_array = var_export($this->classes, true);
|
return true;
|
||||||
|
|
||||||
//put the array in a form that it can be loaded directly to an array
|
|
||||||
$class_result = file_put_contents(self::$classes_file, "<?php\n return " . $classes_array . ";\n");
|
|
||||||
if ($class_result === false) {
|
|
||||||
//file failed to save - send error to syslog when debugging
|
|
||||||
$error_array = error_get_last();
|
|
||||||
self::log(LOG_WARNING, $error_array['message'] ?? '');
|
|
||||||
}
|
|
||||||
|
|
||||||
//export the interfaces array using PHP engine
|
|
||||||
$interfaces_array = var_export($this->interfaces, true);
|
|
||||||
|
|
||||||
//put the array in a form that it can be loaded directly to an array
|
|
||||||
$interface_result = file_put_contents(self::$interfaces_file, "<?php\n return " . $interfaces_array . ";\n");
|
|
||||||
if ($interface_result === false) {
|
|
||||||
//file failed to save - send error to syslog when debugging
|
|
||||||
$error_array = error_get_last();
|
|
||||||
self::log(LOG_WARNING, $error_array['message'] ?? '');
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = ($class_result && $interface_result);
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -321,48 +284,17 @@ class auto_loader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the cache of stored classes and interfaces.
|
* Clears the cache of stored classes and interfaces from APCu.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public static function clear_cache() {
|
public static function clear_cache() {
|
||||||
|
|
||||||
//check for apcu cache
|
//check for apcu cache and clear it
|
||||||
if (function_exists('apcu_enabled') && apcu_enabled()) {
|
if (function_exists('apcu_enabled') && apcu_enabled()) {
|
||||||
apcu_delete(self::CLASSES_KEY);
|
apcu_delete(self::CLASSES_KEY);
|
||||||
apcu_delete(self::INTERFACES_KEY);
|
apcu_delete(self::INTERFACES_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
//set default file
|
|
||||||
if (empty(self::$classes_file)) {
|
|
||||||
self::$classes_file = sys_get_temp_dir() . DIRECTORY_SEPARATOR . self::CLASSES_FILE;
|
|
||||||
}
|
|
||||||
|
|
||||||
//set file to clear
|
|
||||||
$classes_file = self::$classes_file;
|
|
||||||
|
|
||||||
//remove the file when it exists
|
|
||||||
if (file_exists($classes_file)) {
|
|
||||||
@unlink($classes_file);
|
|
||||||
$error_array = error_get_last();
|
|
||||||
//send to syslog when debugging with either environment variable or debug in the url
|
|
||||||
self::log(LOG_WARNING, $error_array['message'] ?? '');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty(self::$interfaces_file)) {
|
|
||||||
self::$interfaces_file = sys_get_temp_dir() . DIRECTORY_SEPARATOR . self::INTERFACES_FILE;
|
|
||||||
}
|
|
||||||
|
|
||||||
//set interfaces file to clear
|
|
||||||
$interfaces_file = self::$interfaces_file;
|
|
||||||
|
|
||||||
//remove the file when it exists
|
|
||||||
if (file_exists($interfaces_file)) {
|
|
||||||
@unlink($interfaces_file);
|
|
||||||
$error_array = error_get_last();
|
|
||||||
//send to syslog when debugging with either environment variable or debug in the url
|
|
||||||
self::log(LOG_WARNING, $error_array['message'] ?? '');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -433,7 +365,7 @@ class auto_loader {
|
|||||||
private function loader($class_name): bool {
|
private function loader($class_name): bool {
|
||||||
|
|
||||||
//sanitize the class name
|
//sanitize the class name
|
||||||
$class_name = preg_replace('[^a-zA-Z0-9_]', '', $class_name);
|
$class_name = preg_replace('/[^a-zA-Z0-9_]/', '', $class_name);
|
||||||
|
|
||||||
//find the path using the class_name as the key in the classes array
|
//find the path using the class_name as the key in the classes array
|
||||||
if (isset($this->classes[$class_name])) {
|
if (isset($this->classes[$class_name])) {
|
||||||
@@ -471,6 +403,7 @@ class auto_loader {
|
|||||||
$project_path = dirname(__DIR__, 2);
|
$project_path = dirname(__DIR__, 2);
|
||||||
|
|
||||||
//build the search path array
|
//build the search path array
|
||||||
|
$search_path = [];
|
||||||
$search_path[] = glob($project_path . "/resources/interfaces/" . $class_name . ".php");
|
$search_path[] = glob($project_path . "/resources/interfaces/" . $class_name . ".php");
|
||||||
$search_path[] = glob($project_path . "/resources/traits/" . $class_name . ".php");
|
$search_path[] = glob($project_path . "/resources/traits/" . $class_name . ".php");
|
||||||
$search_path[] = glob($project_path . "/resources/classes/" . $class_name . ".php");
|
$search_path[] = glob($project_path . "/resources/classes/" . $class_name . ".php");
|
||||||
|
|||||||
Reference in New Issue
Block a user