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 {
|
||||
|
||||
const CLASSES_KEY = 'autoloader_classes';
|
||||
const CLASSES_FILE = 'autoloader_cache.php';
|
||||
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;
|
||||
/**
|
||||
* Tracks the APCu extension for caching to RAM drive across requests
|
||||
@@ -64,6 +50,7 @@ class auto_loader {
|
||||
*/
|
||||
private $interfaces;
|
||||
/**
|
||||
* Stores trait definitions (currently unused but kept for future expansion)
|
||||
* @var array
|
||||
*/
|
||||
private $traits;
|
||||
@@ -78,16 +65,6 @@ class auto_loader {
|
||||
//set if we can use RAM cache
|
||||
$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
|
||||
if ($disable_cache || !$this->load_cache()) {
|
||||
//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.
|
||||
*/
|
||||
public function load_cache(): bool {
|
||||
$this->classes = [];
|
||||
$this->interfaces = [];
|
||||
$this->traits = [];
|
||||
$this->traits = []; // Reset traits array
|
||||
|
||||
//use apcu when available
|
||||
if ($this->apcu_enabled && apcu_exists(self::CLASSES_KEY)) {
|
||||
//use apcu when available - validate BOTH keys exist
|
||||
if ($this->apcu_enabled && apcu_exists(self::CLASSES_KEY) && apcu_exists(self::INTERFACES_KEY)) {
|
||||
$this->classes = apcu_fetch(self::CLASSES_KEY, $classes_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;
|
||||
}
|
||||
|
||||
//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
|
||||
if (file_exists(self::$classes_file)) {
|
||||
$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));
|
||||
//return false when we don't have classes in memory
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,9 +138,6 @@ class auto_loader {
|
||||
$files = array_merge($files, glob($path));
|
||||
}
|
||||
|
||||
//reset the current array
|
||||
$class_list = [];
|
||||
|
||||
//store the class name (key) and the path (value)
|
||||
foreach ($files as $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
|
||||
*/
|
||||
public function update_cache(): bool {
|
||||
//guard against writing an empty file
|
||||
//guard against empty cache
|
||||
if (empty($this->classes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//update RAM cache when available
|
||||
//update APCu cache when available
|
||||
if ($this->apcu_enabled) {
|
||||
$classes_cached = apcu_store(self::CLASSES_KEY, $this->classes);
|
||||
$interfaces_cached = apcu_store(self::INTERFACES_KEY, $this->interfaces);
|
||||
//do not save to drive when we are using apcu
|
||||
if ($classes_cached && $interfaces_cached)
|
||||
$classes_stored = apcu_store(self::CLASSES_KEY, $this->classes, 0);
|
||||
$interfaces_stored = apcu_store(self::INTERFACES_KEY, $this->interfaces, 0);
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
//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
|
||||
$classes_array = var_export($this->classes, 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;
|
||||
//APCu not available, cache remains in memory only
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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
|
||||
*/
|
||||
public static function clear_cache() {
|
||||
|
||||
//check for apcu cache
|
||||
//check for apcu cache and clear it
|
||||
if (function_exists('apcu_enabled') && apcu_enabled()) {
|
||||
apcu_delete(self::CLASSES_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 {
|
||||
|
||||
//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
|
||||
if (isset($this->classes[$class_name])) {
|
||||
@@ -471,6 +403,7 @@ class auto_loader {
|
||||
$project_path = dirname(__DIR__, 2);
|
||||
|
||||
//build the search path array
|
||||
$search_path = [];
|
||||
$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/classes/" . $class_name . ".php");
|
||||
|
||||
Reference in New Issue
Block a user