Skip to content

Commit 3931824

Browse files
authored
Fix #122: Do exit(1) after all shutdown functions, even postponed ones (#123)
1 parent ee21ea1 commit 3931824

File tree

2 files changed

+39
-6
lines changed

2 files changed

+39
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- Enh #114: Show full argument by click (@xepozz)
99
- Enh #113: Simplify error log (@xepozz)
1010
- Enh #112: Add copy cURL button, sort request headers, fix UI (@xepozz)
11+
- Bug #122: Do `exit(1)` after all shutdown functions, even postponed ones (@samdark)
1112

1213
## 3.2.1 March 07, 2024
1314

src/ErrorHandler.php

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,17 @@ final class ErrorHandler
4141
private bool $enabled = false;
4242
private bool $initialized = false;
4343

44+
/**
45+
* @param LoggerInterface $logger Logger to write errors to.
46+
* @param ThrowableRendererInterface $defaultRenderer Default throwable renderer.
47+
* @param EventDispatcherInterface|null $eventDispatcher Event dispatcher for error events.
48+
* @param int $exitShutdownHandlerDepth Depth of the exit() shutdown handler to ensure it's executed last.
49+
*/
4450
public function __construct(
4551
private LoggerInterface $logger,
4652
private ThrowableRendererInterface $defaultRenderer,
4753
private ?EventDispatcherInterface $eventDispatcher = null,
54+
private int $exitShutdownHandlerDepth = 2
4855
) {
4956
}
5057

@@ -108,7 +115,7 @@ public function register(): void
108115

109116
$this->initializeOnce();
110117

111-
// Handles throwable, echo output and exit.
118+
// Handles throwable that isn't caught otherwise, echo output and exit.
112119
set_exception_handler(function (Throwable $t): void {
113120
if (!$this->enabled) {
114121
return;
@@ -199,16 +206,41 @@ private function renderThrowableAndTerminate(Throwable $t): void
199206
if (!empty($this->workingDirectory)) {
200207
chdir($this->workingDirectory);
201208
}
202-
// disable error capturing to avoid recursive errors while handling exceptions
209+
// Disable error capturing to avoid recursive errors while handling exceptions.
203210
$this->unregister();
204-
// set preventive HTTP status code to 500 in case error handling somehow fails and headers are sent
211+
// Set preventive HTTP status code to 500 in case error handling somehow fails and headers are sent.
205212
http_response_code(Status::INTERNAL_SERVER_ERROR);
206213

207214
echo $this->handle($t);
208215
$this->eventDispatcher?->dispatch(new ApplicationError($t));
209216

210-
register_shutdown_function(static function (): void {
211-
exit(1);
212-
});
217+
$handler = $this->wrapShutdownHandler(
218+
static function (): void {
219+
exit(1);
220+
},
221+
$this->exitShutdownHandlerDepth
222+
);
223+
224+
register_shutdown_function($handler);
225+
}
226+
227+
/**
228+
* Wraps shutdown handler into another shutdown handler to ensure it is called last after all other shutdown
229+
* functions, even those added to the end.
230+
*
231+
* @param callable $handler Shutdown handler to wrap.
232+
* @param int $depth Wrapping depth.
233+
* @return callable Wrapped handler.
234+
*/
235+
private function wrapShutdownHandler(callable $handler, int $depth): callable
236+
{
237+
$currentDepth = 0;
238+
while ($currentDepth < $depth) {
239+
$handler = static function() use ($handler): void {
240+
register_shutdown_function($handler);
241+
};
242+
$currentDepth++;
243+
}
244+
return $handler;
213245
}
214246
}

0 commit comments

Comments
 (0)