Files
rspade_system/app/RSpade/Core/Bootstrap/RsxBootstrap.php
root 84ca3dfe42 Fix code quality violations and rename select input components
Move small tasks from wishlist to todo, update npm packages
Replace #[Auth] attributes with manual auth checks and code quality rule
Remove on_jqhtml_ready lifecycle method from framework
Complete ACL system with 100-based role indexing and /dev/acl tester
WIP: ACL system implementation with debug instrumentation
Convert rsx:check JS linting to RPC socket server
Clean up docs and fix $id→$sid in man pages, remove SSR/FPC feature
Reorganize wishlists: priority order, mark sublayouts complete, add email
Update model_fetch docs: mark MVP complete, fix enum docs, reorganize
Comprehensive documentation overhaul: clarity, compression, and critical rules
Convert Contacts/Projects CRUD to Model.fetch() and add fetch_or_null()
Add JS ORM relationship lazy-loading and fetch array handling
Add JS ORM relationship fetching and CRUD documentation
Fix ORM hydration and add IDE resolution for Base_* model stubs
Rename Json_Tree_Component to JS_Tree_Debug_Component and move to framework
Enhance JS ORM infrastructure and add Json_Tree class name badges

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-23 21:39:43 +00:00

238 lines
7.6 KiB
PHP
Executable File

<?php
namespace App\RSpade\Core\Bootstrap;
use Exception;
use RuntimeException;
use App\RSpade\Core\Locks\RsxLocks;
/**
* RsxBootstrap - Early initialization for the RSpade framework
*
* This class handles critical early initialization tasks that must occur
* before the manifest or other systems are loaded. Most importantly, it
* acquires the global application read lock that ensures we can coordinate
* with other processes for exclusive operations like manifest rebuilding.
*/
class RsxBootstrap
{
/**
* The global application lock token
* @var string|null
*/
private static ?string $application_lock_token = null;
/**
* Whether bootstrap has been initialized
* @var bool
*/
private static bool $initialized = false;
/**
* Initialize the RSpade framework
*
* This MUST be called as early as possible in the application lifecycle,
* ideally in the bootstrap/app.php file right after Laravel boots.
*
* Acquires a global READ lock for the application that can be upgraded
* to WRITE when exclusive operations like manifest rebuilding are needed.
*
* @return void
*/
public static function initialize(): void
{
if (self::$initialized) {
return;
}
// Acquire global application read lock
// This ensures we can coordinate with other processes
self::__acquire_application_lock();
// Register shutdown handler to release lock
register_shutdown_function([self::class, 'shutdown']);
self::$initialized = true;
}
/**
* Acquire the global application lock
*
* All PHP processes acquire a READ lock by default.
* Artisan commands and always_write_lock mode use WRITE lock.
* This can be upgraded to WRITE for exclusive operations.
*
* @return void
*/
private static function __acquire_application_lock(): void
{
$always_write = config('rsx.locking.always_write_lock', false);
// Detect artisan commands by checking if running from CLI and the script name contains 'artisan'
$is_artisan = php_sapi_name() === 'cli' &&
isset($_SERVER['argv'][0]) &&
strpos($_SERVER['argv'][0], 'artisan') !== false;
// Determine lock type
// Artisan commands always get write lock
// Or if always_write_lock is enabled
$lock_type = ($is_artisan || $always_write) ? RsxLocks::WRITE_LOCK : RsxLocks::READ_LOCK;
// Issue warning if always_write_lock is enabled in production CLI mode
if ($always_write && php_sapi_name() === 'cli' && app()->environment('production')) {
fwrite(STDERR, "\033[33mWARNING: RSX_ALWAYS_WRITE_LOCK is enabled in production. " .
'ALL requests are serialized with exclusive write lock. ' .
"This severely impacts performance and should only be used for critical operations.\033[0m\n");
}
console_debug('CONCURRENCY', "Acquiring global application '" . ($lock_type == RsxLocks::WRITE_LOCK ? 'WRITE' : 'READ') . "'lock");
try {
self::$application_lock_token = RsxLocks::get_lock(
RsxLocks::SERVER_LOCK,
RsxLocks::LOCK_APPLICATION,
$lock_type,
config('rsx.locking.timeout', 30)
);
} catch (Exception $e) {
// If we can't acquire the lock, the application cannot proceed
shouldnt_happen(
"Failed to acquire application {$lock_type} lock: " . $e->getMessage() . "\n" .
'This likely means another process has an exclusive lock.'
);
}
console_debug('CONCURRENCY', 'Global application lock acquired');
// Check if log rotation is needed (development mode only)
// TODO: cant log rotate before getting build key
// We logrotate somewhere else, dont we?
// if (env('APP_ENV') !== 'production') {
// $current_hash = Manifest::get_build_key();
// $version_file = storage_path('rsx-tmp/log_version');
// $should_rotate = false;
// // Ensure directory exists
// $dir = dirname($version_file);
// if (!is_dir($dir)) {
// @mkdir($dir, 0755, true);
// }
// // Check if version file exists and compare hash
// if (file_exists($version_file)) {
// $stored_hash = trim(file_get_contents($version_file));
// if ($stored_hash !== $current_hash) {
// $should_rotate = true;
// }
// } else {
// // File doesn't exist, first run
// $should_rotate = true;
// }
// // Rotate logs if needed
// if ($should_rotate) {
// Debugger::logrotate();
// // Update version file with new hash
// file_put_contents($version_file, $current_hash);
// }
// }
}
/**
* Upgrade the application lock to exclusive write mode
*
* Used when performing operations that require exclusive access,
* such as manifest rebuilding or bundle compilation.
*
* @return string New lock token after upgrade
* @throws RuntimeException if upgrade fails
*/
public static function upgrade_to_write_lock(): string
{
if (!self::$application_lock_token) {
shouldnt_happen('Cannot upgrade lock - no application lock held');
}
try {
$new_token = RsxLocks::upgrade_lock(
self::$application_lock_token,
config('rsx.locking.timeout', 30)
);
self::$application_lock_token = $new_token;
return $new_token;
} catch (Exception $e) {
throw new RuntimeException(
'Failed to upgrade application lock to write mode: ' . $e->getMessage()
);
}
}
/**
* Get the current application lock token
*
* @return string|null
*/
public static function get_application_lock_token(): ?string
{
return self::$application_lock_token;
}
/**
* Check if we hold the application lock
*
* @return bool
*/
public static function has_application_lock(): bool
{
return self::$application_lock_token !== null;
}
/**
* Shutdown handler - releases locks
*
* @return void
*/
public static function shutdown(): void
{
if (self::$application_lock_token) {
try {
RsxLocks::release_lock(self::$application_lock_token);
} catch (Exception $e) {
// Ignore errors during shutdown
} finally {
self::$application_lock_token = null;
}
}
}
/**
* Force release of application lock (emergency use only)
*
* @return void
*/
public static function force_release(): void
{
if (self::$application_lock_token) {
RsxLocks::release_lock(self::$application_lock_token);
self::$application_lock_token = null;
}
}
/**
* Temporarily release application lock for testing
* Used by rsx:debug to prevent lock contention with Playwright
*
* @return void
*/
public static function temporarily_release_lock(): void
{
if (self::$application_lock_token) {
RsxLocks::release_lock(self::$application_lock_token);
self::$application_lock_token = null;
}
}
}