newInstanceWithoutConstructor(); foreach ([ 'username' => $username, 'prefix' => $username . '_', 'userConf' => [], 'packageConf' => [], 'settings' => $settings, ] as $property => $value) { $prop = $class->getProperty($property); $prop->setAccessible(true); $prop->setValue($user, $value); } return $user; } if (!function_exists('pcntl_fork')) { fail('pcntl extension is required to safely test withQueueLock() without risking a hang'); } // Make the jobs/ directory read-only so mkdir(queue.lock) fails for a reason // OTHER than "already exists" (EACCES, not EEXIST) - this is the branch that // must never busy-loop forever. chmod($pluginTempRoot . '/data/jobs', 0500); $pid = pcntl_fork(); if ($pid === -1) { fail('could not fork to safely bound the withQueueLock() call'); } if ($pid === 0) { // Child: attempt the call. If it hangs, the parent will kill us after // the deadline below and the test fails. If it throws (correct fixed // behavior), exit 42. Any other outcome exits 1. $settings = load_settings(''); $daUser = make_da_user($settings, 'demo'); $queue = new BackupQueueService($settings, $daUser); $class = new ReflectionClass(BackupQueueService::class); $method = $class->getMethod('withQueueLock'); $method->setAccessible(true); try { $method->invoke($queue, static fn () => 'unreachable'); exit(1); } catch (RuntimeException $e) { exit(42); } catch (Throwable $e) { exit(1); } } $deadline = microtime(true) + 5.0; $status = 0; $exited = false; while (microtime(true) < $deadline) { $result = pcntl_waitpid($pid, $status, WNOHANG); if ($result === $pid) { $exited = true; break; } usleep(50000); } if (!$exited) { posix_kill($pid, SIGKILL); pcntl_waitpid($pid, $status); @chmod($pluginTempRoot . '/data/jobs', 0700); exec('rm -rf ' . escapeshellarg($pluginTempRoot)); fail('withQueueLock() busy-looped instead of giving up after a bounded number of retries (killed after 5s)'); } @chmod($pluginTempRoot . '/data/jobs', 0700); exec('rm -rf ' . escapeshellarg($pluginTempRoot)); $exitCode = pcntl_wexitstatus($status); if ($exitCode !== 42) { fail("withQueueLock() did not fail with the expected RuntimeException (child exit code: $exitCode)"); } echo "backup_queue_lock_test: OK\n";