Bearsampp 2026.7.11
Loading...
Searching...
No Matches
class.servicehelper.php
Go to the documentation of this file.
1<?php
2/*
3 * Copyright (c) 2021-2024 Bearsampp
4 * License: GNU General Public License version 3 or later; see LICENSE.txt
5 * Author: Bear
6 * Website: https://bearsampp.com
7 * Github: https://github.com/Bearsampp
8 */
9
18{
23 private static $serviceMap = null;
24
29 private static $syntaxCheckMap = null;
30
37 private static function initializeMappings($bearsamppBins)
38 {
39 if (self::$serviceMap === null) {
40 self::$serviceMap = [
48 ];
49 }
50
51 if (self::$syntaxCheckMap === null) {
52 self::$syntaxCheckMap = [
56 ];
57 }
58 }
59
67 public static function getBinFromServiceName($serviceName, $bearsamppBins)
68 {
70 return isset(self::$serviceMap[$serviceName]) ? self::$serviceMap[$serviceName] : null;
71 }
72
80 public static function getSyntaxCheckCmd($serviceName, $bearsamppBins = null)
81 {
82 if ($bearsamppBins !== null) {
84 }
85 return isset(self::$syntaxCheckMap[$serviceName]) ? self::$syntaxCheckMap[$serviceName] : null;
86 }
87
95 public static function getServiceDisplayName($bin, $service)
96 {
97 return $bin->getName() . ' ' . $bin->getVersion() . ' (' . $service->getName() . ')';
98 }
99
107 public static function processServices($bearsamppBins, callable $callback)
108 {
110
111 foreach ($bearsamppBins->getServices() as $serviceName => $service) {
112 $bin = self::getBinFromServiceName($serviceName, $bearsamppBins);
113 $syntaxCheckCmd = self::getSyntaxCheckCmd($serviceName);
114
115 if ($bin !== null) {
116 $callback($serviceName, $service, $bin, $syntaxCheckCmd);
117 }
118 }
119 }
120
129 public static function startService($bin, $syntaxCheckCmd = null, $showErrors = true)
130 {
131 return Util::startService($bin, $syntaxCheckCmd, $showErrors);
132 }
133
140 public static function stopService($service)
141 {
142 return $service->stop();
143 }
144
151 public static function restartService($service)
152 {
153 return $service->restart();
154 }
155
173
180 public static function hasSyntaxCheck($serviceName)
181 {
182 if (self::$syntaxCheckMap === null) {
183 // Initialize with dummy bins object if needed
184 self::$syntaxCheckMap = [
188 ];
189 }
190 return isset(self::$syntaxCheckMap[$serviceName]);
191 }
192
200 public static function getServicePort($serviceName, $bearsamppBins)
201 {
202 $bin = self::getBinFromServiceName($serviceName, $bearsamppBins);
203 if ($bin === null) {
204 return 0;
205 }
206
207 // Different services have different methods to get port
208 if (method_exists($bin, 'getPort')) {
209 return $bin->getPort();
210 } elseif (method_exists($bin, 'getSmtpPort')) {
211 return $bin->getSmtpPort();
212 }
213
214 return 0;
215 }
216
230 public static function stopAllServicesParallel($bearsamppBins, ?callable $progressCallback = null, $shutdownTimeout = 15)
231 {
232 Log::trace('ServiceHelper::stopAllServicesParallel() starting');
233 $startTime = microtime(true);
234
235 $services = $bearsamppBins->getServices();
236 if (empty($services)) {
237 Log::trace('No services to shut down');
238 return true;
239 }
240
241 Log::trace('Starting parallel shutdown of ' . count($services) . ' services');
242
243 // Try parallel shutdown
244 $parallelSuccess = self::shutdownServicesParallel($services, $progressCallback, $shutdownTimeout);
245
246 if ($parallelSuccess) {
247 $duration = round(microtime(true) - $startTime, 3);
248 Log::info('Parallel shutdown completed successfully in ' . $duration . 's');
249 return true;
250 }
251
252 // Fallback to sequential
253 Log::info('Parallel shutdown timed out, falling back to sequential shutdown');
254 $sequentialSuccess = self::shutdownServicesSequential($services, $progressCallback);
255
256 $totalDuration = round(microtime(true) - $startTime, 3);
257 Log::info('Sequential shutdown completed in ' . $totalDuration . 's');
258
259 return $sequentialSuccess;
260 }
261
274 private static function shutdownServicesParallel($services, ?callable $progressCallback = null, $shutdownTimeout = 15)
275 {
276 Log::trace('Phase 1: Sending stop commands to all services');
277 $totalServices = count($services);
278 $currentIndex = 0;
279
280 // Phase 1: Send stop commands (non-blocking)
281 foreach ($services as $serviceName => $service) {
282 $currentIndex++;
283 if ($progressCallback) {
284 $progressCallback($currentIndex, $totalServices, $serviceName);
285 }
286
287 Log::trace('Sending stop to: ' . $serviceName);
288 $service->stop();
289 }
290
291 // Phase 2: Monitor status
292 Log::trace('Phase 2: Monitoring service status');
293 $monitorStartTime = microtime(true);
294 $monitorTimeout = $shutdownTimeout;
295 $checkInterval = 0.5;
296 $allStopped = false;
297
298 while ((microtime(true) - $monitorStartTime) < $monitorTimeout) {
299 $allStopped = true;
300
301 foreach ($services as $serviceName => $service) {
302 if (!$service->isStopped()) {
303 $allStopped = false;
304 break;
305 }
306 }
307
308 if ($allStopped) {
309 Log::trace('All services stopped in parallel phase');
310 return true;
311 }
312
313 usleep($checkInterval * 1000000);
314 }
315
316 // Phase 3: Force kill remaining services
317 Log::trace('Phase 3: Force killing remaining services');
318 foreach ($services as $serviceName => $service) {
319 if (!$service->isStopped()) {
320 Log::trace('Force killing: ' . $serviceName);
321 self::forceKillService($serviceName);
322 }
323 }
324
325 // Final check
326 return self::allServicesStopped($services);
327 }
328
339 private static function shutdownServicesSequential($services, ?callable $progressCallback = null)
340 {
341 Log::trace('Starting sequential shutdown phase');
342 $totalServices = count($services);
343 $currentIndex = 0;
344
345 foreach ($services as $serviceName => $service) {
346 $currentIndex++;
347 if ($progressCallback) {
348 $progressCallback($currentIndex, $totalServices, $serviceName);
349 }
350
351 Log::trace('Sequential stop: ' . $serviceName);
352 $service->stop();
353
354 if (!$service->isStopped()) {
355 Log::trace('Force killing: ' . $serviceName);
356 self::forceKillService($serviceName);
357 }
358 }
359
360 return self::allServicesStopped($services);
361 }
362
369 private static function forceKillService($serviceName)
370 {
371 $processMap = [
372 BinApache::SERVICE_NAME => 'httpd.exe',
373 BinMysql::SERVICE_NAME => 'mysqld.exe',
374 BinMariadb::SERVICE_NAME => 'mysqld.exe',
375 BinMailpit::SERVICE_NAME => 'mailpit.exe',
376 BinMemcached::SERVICE_NAME => 'memcached.exe',
377 BinPostgresql::SERVICE_NAME => 'postgres.exe',
378 BinXlight::SERVICE_NAME => 'xlightftpd.exe',
379 'nodejs' => 'node.exe', // NodeJS - not a Windows service but needs to be killed on exit
380 ];
381
382 if (isset($processMap[$serviceName])) {
383 Log::trace('Killing process: ' . $processMap[$serviceName]);
384 Win32Ps::killBins([$processMap[$serviceName]]);
385 return true;
386 }
387
388 return false;
389 }
390
397 private static function allServicesStopped($services)
398 {
399 foreach ($services as $serviceName => $service) {
400 if (!$service->isStopped()) {
401 return false;
402 }
403 }
404 return true;
405 }
406
420 public static function startAllServicesParallel($serviceInfos, ?callable $progressCallback = null, $startupTimeout = 30)
421 {
422 Log::trace('ServiceHelper::startAllServicesParallel() starting with ' . count($serviceInfos) . ' services');
423 $startTime = microtime(true);
424
425 if (empty($serviceInfos)) {
426 Log::trace('No services to start');
427 return true;
428 }
429
430 Log::trace('Starting parallel startup of ' . count($serviceInfos) . ' services');
431
432 // Try parallel startup
433 $parallelSuccess = self::startServicesParallel($serviceInfos, $progressCallback, $startupTimeout);
434
435 if ($parallelSuccess) {
436 $duration = round(microtime(true) - $startTime, 3);
437 Log::info('Parallel startup completed successfully in ' . $duration . 's');
438 return true;
439 }
440
441 // Fallback to sequential
442 Log::info('Parallel startup timed out, falling back to sequential startup');
443 $sequentialSuccess = self::startServicesSequential($serviceInfos, $progressCallback);
444
445 $totalDuration = round(microtime(true) - $startTime, 3);
446 Log::info('Sequential startup completed in ' . $totalDuration . 's');
447
448 return $sequentialSuccess;
449 }
450
463 private static function startServicesParallel($serviceInfos, ?callable $progressCallback = null, $startupTimeout = 30)
464 {
465 Log::trace('Phase 1: Sending start commands to all services');
466 $totalServices = count($serviceInfos);
467 $currentIndex = 0;
468
469 // Phase 1: Send start commands (non-blocking)
470 foreach ($serviceInfos as $serviceName => $serviceInfo) {
471 $currentIndex++;
472 if ($progressCallback) {
473 $progressCallback($currentIndex, $totalServices, $serviceInfo['name']);
474 }
475
476 Log::trace('Sending start to: ' . $serviceName);
477 $service = $serviceInfo['service'];
478
479 // Start the service
480 $service->start();
481 }
482
483 // Phase 2: Monitor status
484 Log::trace('Phase 2: Monitoring service status');
485 $monitorStartTime = microtime(true);
486 $monitorTimeout = $startupTimeout;
487 $checkInterval = 0.5;
488 $allRunning = false;
489 $failedServices = [];
490
491 while ((microtime(true) - $monitorStartTime) < $monitorTimeout) {
492 $allRunning = true;
493
494 foreach ($serviceInfos as $serviceName => $serviceInfo) {
495 $service = $serviceInfo['service'];
496
497 if (!$service->isRunning()) {
498 $allRunning = false;
499 $failedServices[$serviceName] = $serviceInfo;
500 }
501 }
502
503 if ($allRunning) {
504 Log::trace('All services running in parallel phase');
505 return true;
506 }
507
508 usleep($checkInterval * 1000000);
509 }
510
511 // Phase 3: Retry failed services
512 if (!empty($failedServices)) {
513 Log::trace('Phase 3: Retrying ' . count($failedServices) . ' failed services');
514
515 foreach ($failedServices as $serviceName => $serviceInfo) {
516 $service = $serviceInfo['service'];
517
518 if (!$service->isRunning()) {
519 Log::trace('Retrying start for: ' . $serviceName);
520 $service->start();
521 usleep(500000); // 500ms delay between retries
522 }
523 }
524
525 // Final check
526 return self::allServicesRunning($serviceInfos);
527 }
528
529 return true;
530 }
531
542 private static function startServicesSequential($serviceInfos, ?callable $progressCallback = null)
543 {
544 Log::trace('Starting sequential startup phase');
545 $totalServices = count($serviceInfos);
546 $currentIndex = 0;
547
548 foreach ($serviceInfos as $serviceName => $serviceInfo) {
549 $currentIndex++;
550 if ($progressCallback) {
551 $progressCallback($currentIndex, $totalServices, $serviceInfo['name']);
552 }
553
554 Log::trace('Sequential start: ' . $serviceName);
555 $service = $serviceInfo['service'];
556
557 $service->start();
558
559 // Small delay between service starts
560 usleep(200000);
561
562 if (!$service->isRunning()) {
563 Log::trace('Service failed to start: ' . $serviceName);
564 // Retry once
565 $service->start();
566 usleep(500000);
567 }
568 }
569
570 return self::allServicesRunning($serviceInfos);
571 }
572
579 private static function allServicesRunning($serviceInfos)
580 {
581 foreach ($serviceInfos as $serviceName => $serviceInfo) {
582 $service = $serviceInfo['service'];
583 if (!$service->isRunning()) {
584 return false;
585 }
586 }
587 return true;
588 }
589}
global $bearsamppBins
const CMD_SYNTAX_CHECK
const SERVICE_NAME
const CMD_SYNTAX_CHECK
static info($data, $file=null)
static trace($data, $file=null)
static startAllServicesParallel($serviceInfos, ?callable $progressCallback=null, $startupTimeout=30)
static getServicePort($serviceName, $bearsamppBins)
static shutdownServicesSequential($services, ?callable $progressCallback=null)
static forceKillService($serviceName)
static allServicesStopped($services)
static allServicesRunning($serviceInfos)
static startServicesParallel($serviceInfos, ?callable $progressCallback=null, $startupTimeout=30)
static getServiceDisplayName($bin, $service)
static hasSyntaxCheck($serviceName)
static stopAllServicesParallel($bearsamppBins, ?callable $progressCallback=null, $shutdownTimeout=15)
static shutdownServicesParallel($services, ?callable $progressCallback=null, $shutdownTimeout=15)
static stopService($service)
static restartService($service)
static getSyntaxCheckCmd($serviceName, $bearsamppBins=null)
static startService($bin, $syntaxCheckCmd=null, $showErrors=true)
static startServicesSequential($serviceInfos, ?callable $progressCallback=null)
static processServices($bearsamppBins, callable $callback)
static getBinFromServiceName($serviceName, $bearsamppBins)
static initializeMappings($bearsamppBins)
static startService($bin, $syntaxCheckCmd, $showWindow=false)
static killBins($refreshProcs=false)