39 Util::logInfo(
'ActionQuit constructor called - starting exit process');
43 $this->splash =
new Splash();
46 self::GAUGE_PROCESSES * count(
$bearsamppBins->getServices() ) + self::GAUGE_OTHERS,
53 $bearsamppWinbinder->setHandler( $this->splash->getWbWindow(), $this,
'processWindow', 2000 );
56 $bearsamppWinbinder->mainLoop();
59 $bearsamppWinbinder->reset();
125 $name .=
' (' . $service->getName() .
')';
143 Util::logInfo(
'Starting graceful shutdown process with optimized service order');
151 Util::logDebug(
'Service shutdown order: ' . implode(
' -> ', $shutdownOrder));
154 foreach ($shutdownOrder as $sName) {
156 if (!isset($allServices[$sName])) {
157 Util::logDebug(
'Service not found in available services: ' . $sName);
161 $service = $allServices[$sName];
166 $this->splash->incrProgressBar();
173 Util::logInfo(
'Successfully stopped and removed service: ' . $displayName);
175 Util::logWarning(
'Failed to stop/remove service: ' . $displayName .
' (may not be installed)');
180 foreach ($allServices as $sName => $service) {
181 if (!in_array($sName, $shutdownOrder)) {
185 $this->splash->incrProgressBar();
194 $this->splash->setTextLoading(
'Removing symlinks...');
198 $this->splash->incrProgressBar();
203 $this->splash->setTextLoading(
'Performing cleanup verification...');
208 $this->splash->setTextLoading(
'Completing shutdown...');
209 if (strtoupper(substr(PHP_OS, 0, 3)) ===
'WIN') {
220 $bearsamppWinbinder->destroyWindow($window);
238 $startTime = microtime(
true);
240 Util::logTrace(
'Starting PHP process termination (excluding PID: ' . $excludePid .
')');
243 $loadingPids = array();
246 foreach ($pids as $pid) {
247 $loadingPids[] = intval(trim($pid));
249 Util::logTrace(
'Loading PIDs to preserve: ' . implode(
', ', $loadingPids));
252 $targets = [
'php-win.exe',
'php.exe'];
255 if (microtime(
true) - $startTime > $timeout) {
256 Util::logTrace(
'Process termination timeout exceeded, continuing with remaining operations');
264 if (in_array($exe, $targets) && $pid != $excludePid && !in_array($pid, $loadingPids)) {
268 } elseif (in_array($pid, $loadingPids)) {
275 $splash->setTextLoading(
'Final cleanup...');
279 Util::logTrace(
'Initiating self-termination for PID: ' . $currentPid);
283 Util::logTrace(
'Self-termination via Vbs::killProc failed, using alternative method');
285 }
catch (\Exception $e) {
286 Util::logTrace(
'Exception during self-termination: ' . $e->getMessage());
291 if ($window && $bearsamppWinbinder) {
294 $bearsamppWinbinder->destroyWindow($window);
295 }
catch (\Exception $e) {
296 Util::logTrace(
'Exception during window destruction: ' . $e->getMessage());
301 if (microtime(
true) - $startTime > $timeout * 1.5) {
318 'all_stopped' =>
true,
320 'still_running' => [],
321 'verification_failed' => []
324 foreach ($services as $sName => $service) {
329 $isInstalled = $service->isInstalled();
330 $isRunning = $isInstalled ? $service->isRunning() :
false;
332 $results[
'services'][$sName] = [
333 'name' => $displayName,
334 'installed' => $isInstalled,
335 'running' => $isRunning
340 $results[
'still_running'][] = $displayName;
341 $results[
'all_stopped'] =
false;
349 if ($service->isRunning()) {
352 Util::logInfo(
'Successfully force stopped service: ' . $displayName);
354 } elseif ($isInstalled) {
355 Util::logDebug(
'Service stopped but still installed: ' . $displayName);
357 Util::logDebug(
'Service verified stopped and removed: ' . $displayName);
360 }
catch (\Exception $e) {
361 Util::logError(
'Failed to verify service status for ' . $displayName .
': ' . $e->getMessage());
362 $results[
'verification_failed'][] = $displayName;
363 $results[
'all_stopped'] =
false;
367 if ($results[
'all_stopped']) {
405 foreach ($symlinkPaths as $path) {
406 if (file_exists($path) || is_link($path)) {
408 $results[
'remaining'][] = basename($path);
409 $results[
'success'] =
false;
413 if (is_link($path)) {
415 } elseif (is_dir($path)) {
420 if (!file_exists($path)) {
421 Util::logInfo(
'Successfully removed remaining symlink: ' . $path);
422 $results[
'remaining'] = array_diff($results[
'remaining'], [basename($path)]);
423 if (empty($results[
'remaining'])) {
424 $results[
'success'] =
true;
427 }
catch (\Exception $e) {
428 Util::logError(
'Failed to remove symlink ' . $path .
': ' . $e->getMessage());
433 if ($results[
'success']) {
436 Util::logWarning(
'Some symlinks could not be removed: ' . implode(
', ', $results[
'remaining']));
462 if (!is_dir($tmpPath)) {
468 $files = glob($tmpPath .
'/*');
470 if ($files ===
false) {
475 foreach ($files as $file) {
477 $basename = basename($file);
478 if (in_array($basename, [
'.',
'..',
'.gitkeep',
'README.md'])) {
483 $size = is_file($file) ? filesize($file) : 0;
485 if (is_file($file)) {
486 if (@unlink($file)) {
487 $results[
'cleaned']++;
488 $results[
'size_freed'] += $size;
491 $results[
'failed'][] = $basename;
492 $results[
'success'] =
false;
495 } elseif (is_dir($file)) {
499 }
catch (\Exception $e) {
500 $results[
'failed'][] = $basename;
501 $results[
'success'] =
false;
502 Util::logError(
'Error removing temp file ' . $basename .
': ' . $e->getMessage());
506 $sizeMB = round($results[
'size_freed'] / 1024 / 1024, 2);
507 Util::logInfo(
'Cleaned up ' . $results[
'cleaned'] .
' temporary files (' . $sizeMB .
' MB freed)');
509 if (!empty($results[
'failed'])) {
510 Util::logWarning(
'Failed to clean up ' . count($results[
'failed']) .
' files');
513 }
catch (\Exception $e) {
514 Util::logError(
'Error during temp file cleanup: ' . $e->getMessage());
515 $results[
'success'] =
false;
542 foreach ($procs as
$proc) {
547 if ($pid == $currentPid) {
552 if (strpos($exePath, $bearsamppPath) === 0) {
553 $processName = basename($exePath);
556 if (strpos($exePath, $bearsamppPath .
'/www/') === 0) {
561 if (strtolower($processName) ===
'bearsampp.exe') {
562 Util::logDebug(
'Skipping main Bearsampp process: ' . $processName .
' (PID: ' . $pid .
')');
567 $orphaned[
'found'] =
true;
568 $orphaned[
'processes'][] = [
570 'name' => $processName,
574 Util::logWarning(
'Found orphaned process: ' . $processName .
' (PID: ' . $pid .
')');
579 Util::logInfo(
'Terminated orphaned process: ' . $processName .
' (PID: ' . $pid .
')');
580 }
catch (\Exception $e) {
581 Util::logError(
'Failed to terminate orphaned process ' . $processName .
': ' . $e->getMessage());
586 if (!$orphaned[
'found']) {
589 Util::logWarning(
'Found ' . count($orphaned[
'processes']) .
' orphaned process(es)');
592 }
catch (\Exception $e) {
593 Util::logError(
'Error checking for orphaned processes: ' . $e->getMessage());
608 private function generateCleanupReport($serviceVerification, $symlinkVerification, $tempCleanup, $orphanedProcesses)
618 if (!$serviceVerification[
'all_stopped']) {
619 $report[
'success'] =
false;
621 if (!empty($serviceVerification[
'still_running'])) {
622 $report[
'errors'][] =
'Services still running: ' . implode(
', ', $serviceVerification[
'still_running']);
625 if (!empty($serviceVerification[
'verification_failed'])) {
626 $report[
'warnings'][] =
'Could not verify status of: ' . implode(
', ', $serviceVerification[
'verification_failed']);
630 $report[
'summary'][] =
'Services checked: ' . count($serviceVerification[
'services']);
633 if (!$symlinkVerification[
'success']) {
634 $report[
'warnings'][] =
'Symlinks not fully removed: ' . implode(
', ', $symlinkVerification[
'remaining']);
638 if ($tempCleanup[
'cleaned'] > 0) {
639 $sizeMB = round($tempCleanup[
'size_freed'] / 1024 / 1024, 2);
640 $report[
'summary'][] =
'Temp files cleaned: ' . $tempCleanup[
'cleaned'] .
' (' . $sizeMB .
' MB)';
643 if (!empty($tempCleanup[
'failed'])) {
644 $report[
'warnings'][] =
'Failed to clean ' . count($tempCleanup[
'failed']) .
' temp file(s)';
648 if ($orphanedProcesses[
'found']) {
649 $report[
'warnings'][] =
'Found ' . count($orphanedProcesses[
'processes']) .
' orphaned process(es)';
650 foreach ($orphanedProcesses[
'processes'] as
$proc) {
651 $report[
'summary'][] =
'Orphaned: ' .
$proc[
'name'] .
' (PID: ' .
$proc[
'pid'] .
')';
669 $startTime = microtime(
true);
677 if (microtime(
true) - $startTime > $maxTime) {
678 Util::logDebug(
'Cleanup verification timeout reached, skipping remaining checks');
686 if ($tempCleanup[
'cleaned'] > 0) {
687 $sizeMB = round($tempCleanup[
'size_freed'] / 1024 / 1024, 2);
688 Util::logInfo(
'Quick cleanup: ' . $tempCleanup[
'cleaned'] .
' temp files removed (' . $sizeMB .
' MB freed)');
691 if ($orphanedProcesses[
'found']) {
692 Util::logInfo(
'Quick cleanup: ' . count($orphanedProcesses[
'processes']) .
' orphaned process(es) terminated');
695 $duration = round(microtime(
true) - $startTime, 2);
696 Util::logInfo(
'Quick cleanup verification completed in ' . $duration .
' seconds');
698 }
catch (\Exception $e) {
verifyServicesStoppedAndCleanup($services)
getServiceShutdownOrder()
performQuickCleanupVerification($services)
generateCleanupReport($serviceVerification, $symlinkVerification, $tempCleanup, $orphanedProcesses)
checkForOrphanedProcesses()
static terminatePhpProcesses($excludePid, $window=null, $splash=null, $timeout=10)
getServiceDisplayName($sName, $service)
processWindow($window, $id, $ctrl, $param1, $param2)
const EXIT_REMOVE_SERVICE_TEXT
const EXIT_STOP_OTHER_PROCESS_TEXT
static deleteCurrentSymlinks()
static logError($data, $file=null)
static logTrace($data, $file=null)
static logInfo($data, $file=null)
static logDebug($data, $file=null)
static formatUnixPath($path)
static logWarning($data, $file=null)
static killBins($refreshProcs=false)