Skip to content

Commit

Permalink
Rotate PHP runtime after runtime crash (#1628)
Browse files Browse the repository at this point in the history
## Motivation for the change, related issues

We want Playground to be able be more resilient in the face of PHP
crashes. This PR rotates the PHP runtime when a PHP runtime crash is
detected.

Fixes #1453

## Testing Instructions (or ideally a Blueprint)

- Added new unit test
- CI
  • Loading branch information
brandonpayton authored Jul 23, 2024
1 parent e9105c2 commit 093e082
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 9 deletions.
68 changes: 67 additions & 1 deletion packages/php-wasm/node/src/test/rotate-php-runtime.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe('rotatePHPRuntime()', () => {
expect(recreateRuntimeSpy).toHaveBeenCalledTimes(2);
}, 30_000);

it('Should stop rotating after the cleanup handler is called', async () => {
it('Should not rotate after the cleanup handler is called, even if max requests is reached', async () => {
const recreateRuntimeSpy = vitest.fn(recreateRuntime);
const php = new PHP(await recreateRuntimeSpy());
const cleanup = rotatePHPRuntime({
Expand All @@ -88,6 +88,72 @@ describe('rotatePHPRuntime()', () => {
expect(recreateRuntimeSpy).toHaveBeenCalledTimes(2);
}, 30_000);

it('Should recreate the PHP runtime after a PHP runtime crash', async () => {
const recreateRuntimeSpy = vitest.fn(recreateRuntime);
const php = new PHP(await recreateRuntimeSpy());
rotatePHPRuntime({
php,
cwd: '/test-root',
recreateRuntime: recreateRuntimeSpy,
maxRequests: 1234,
});
// Cause a PHP runtime rotation due to error
await php.dispatchEvent({
type: 'request.error',
error: new Error('mock error'),
source: 'php-wasm',
});
expect(recreateRuntimeSpy).toHaveBeenCalledTimes(2);
}, 30_000);

it('Should not recreate the PHP runtime after a PHP fatal', async () => {
const recreateRuntimeSpy = vitest.fn(recreateRuntime);
const php = new PHP(await recreateRuntimeSpy());
rotatePHPRuntime({
php,
cwd: '/test-root',
recreateRuntime: recreateRuntimeSpy,
maxRequests: 1234,
});
// Trigger error with no `source`
await php.dispatchEvent({
type: 'request.error',
error: new Error('mock error'),
});
// Trigger error with request `source`
await php.dispatchEvent({
type: 'request.error',
error: new Error('mock error'),
source: 'request',
});
expect(recreateRuntimeSpy).toHaveBeenCalledTimes(1);
}, 30_000);

it('Should not rotate after the cleanup handler is called, even if there is a PHP runtime error', async () => {
const recreateRuntimeSpy = vitest.fn(recreateRuntime);
const php = new PHP(await recreateRuntimeSpy());
const cleanup = rotatePHPRuntime({
php,
cwd: '/test-root',
recreateRuntime: recreateRuntimeSpy,
maxRequests: 1,
});
// Rotate the PHP runtime
await php.run({ code: `` });
expect(recreateRuntimeSpy).toHaveBeenCalledTimes(2);

cleanup();

// No further rotation should happen
php.dispatchEvent({
type: 'request.error',
error: new Error('mock error'),
source: 'php-wasm',
});

expect(recreateRuntimeSpy).toHaveBeenCalledTimes(2);
}, 30_000);

it('Should hotswap the PHP runtime from 8.2 to 8.3', async () => {
let nbCalls = 0;
const recreateRuntimeSpy = vitest.fn(() => {
Expand Down
32 changes: 24 additions & 8 deletions packages/php-wasm/universal/src/lib/rotate-php-runtime.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { PHP } from './php';
import { PHPEvent } from './universal-php';

export interface RotateOptions {
php: PHP;
Expand Down Expand Up @@ -37,22 +38,37 @@ export function rotatePHPRuntime({
*/
maxRequests = 400,
}: RotateOptions) {
let handledCalls = 0;
let runtimeRequestCount = 0;
async function rotateRuntime() {
if (++handledCalls < maxRequests) {
return;
}
handledCalls = 0;

const release = await php.semaphore.acquire();
try {
php.hotSwapPHPRuntime(await recreateRuntime(), cwd);

// A new runtime has handled zero requests.
runtimeRequestCount = 0;
} finally {
release();
}
}
php.addEventListener('request.end', rotateRuntime);

async function rotateRuntimeAfterMaxRequests() {
if (++runtimeRequestCount < maxRequests) {
return;
}
await rotateRuntime();
}

async function rotateRuntimeForPhpWasmError(event: PHPEvent) {
if (event.type === 'request.error' && event.source === 'php-wasm') {
await rotateRuntime();
}
}

php.addEventListener('request.error', rotateRuntimeForPhpWasmError);
php.addEventListener('request.end', rotateRuntimeAfterMaxRequests);

return function () {
php.removeEventListener('request.end', rotateRuntime);
php.removeEventListener('request.error', rotateRuntimeForPhpWasmError);
php.removeEventListener('request.end', rotateRuntimeAfterMaxRequests);
};
}

0 comments on commit 093e082

Please sign in to comment.