Skip to content

Instantly share code, notes, and snippets.

@mathieuk
Last active January 5, 2019 11:54
Show Gist options
  • Save mathieuk/9495b76da54ead09297deb62915d4f46 to your computer and use it in GitHub Desktop.
Save mathieuk/9495b76da54ead09297deb62915d4f46 to your computer and use it in GitHub Desktop.
<?php
// This exitcode means the alarm handler of the parent process killed the child
define ('EXITCODE_PARENT_INTERVENED', 0); // MUST be 0
// This exitcode means the child achieved the second lock. That'd be weird.
define ('EXITCODE_LOCK_ACHIEVED', 1);
// This exitcode means the second lock got interrupted (and returned false).
define ('EXITCODE_LOCK_INTERRUPTED', 2);
// This exitcode means the alarm handler in the child got handled. We probably don't expect that to happen in the fixed case.
define ('EXITCODE_ALARM_HANDLED', 3);
function handleHungChildprocess($pid) {
return function() use ($pid) {
posix_kill($pid, SIGSTOP);
//echo "[parent] Killing pid $pid\n";
};
}
function runPcntlTest(bool $restartSyscalls, bool $wantAlarm) {
$pid = pcntl_fork();
if (!$pid) {
// child process that will run the test
pcntl_signal(
SIGALRM,
function() use ($wantAlarm) {
if ($wantAlarm)
exit(EXITCODE_ALARM_HANDLED);
},
$restartSyscalls
);
pcntl_alarm(1);
//echo "[child] I am child #", getmypid(), "\n";
touch('./lock.txt');
$fp = fopen('lock.txt', 'r');
flock($fp, LOCK_EX);
$fp2 = fopen('lock.txt', 'r');
$locked = flock($fp2, LOCK_EX);
if ($locked === false) {
exit(EXITCODE_LOCK_INTERRUPTED);
}
// we'll never actually get here
exit(EXITCODE_LOCK_ACHIEVED);
} else {
//echo "[parent] running as #", getmypid(), "\n";
// parent process;
pcntl_signal(SIGALRM, handleHungChildprocess($pid), false);
pcntl_alarm(2);
pcntl_waitpid($pid, $status, WUNTRACED );
$exitCode = pcntl_wexitstatus($status);
//echo "[parent] Child returned code $exitCode\n";
return $exitCode;
}
throw new Exception("Couldnt fork");
}
pcntl_async_signals(true);
$exitCodeMapping = [
EXITCODE_LOCK_INTERRUPTED => 'EXITCODE_LOCK_INTERRUPTED',
EXITCODE_ALARM_HANDLED => 'EXITCODE_ALARM_HANDLED',
EXITCODE_PARENT_INTERVENED => 'EXITCODE_PARENT_INTERVENED'
];
$scenarios = [
'Without SA_RESTART, without alarm, interrupted flock fails' => [
'restartSyscalls' => false,
'wantAlarm' => false,
'expectedExitCode' => EXITCODE_LOCK_INTERRUPTED
],
'Without SA_RESTART, with alarm, signal handler gets called' => [
'restartSyscalls' => false,
'wantAlarm' => true,
'expectedExitCode' => EXITCODE_ALARM_HANDLED
],
'With SA_RESTART: interrupted flock fails to call signal handler and gets terminated' => [
'restartSyscalls' => true,
'wantAlarm' => true,
'expectedExitCode' => EXITCODE_PARENT_INTERVENED
]
];
foreach ($scenarios as $description => $arguments) {
echo "Runnning '$description' .... ";
$exitCode = runPcntlTest($arguments['restartSyscalls'], $arguments['wantAlarm']);
echo $exitCode === $arguments['expectedExitCode'] ?
"SUCCESS\n" :
sprintf(
"FAILED! Got exitcode %d (%s) instead of %d (%s)\n",
$exitCode,
$exitCodeMapping[$exitCode],
$arguments['expectedExitCode'],
$exitCodeMapping[$arguments['expectedExitCode']]
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment