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:
frytimo
2026-02-24 16:21:44 -04:00
committed by GitHub
parent 6a134eaf61
commit 1ee116c3ee
+50 -117
View File
@@ -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");