Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions src/Phaseolies/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -1170,11 +1170,26 @@ public function terminate(
$this->endDeferScope($deferScope, $terminationFailure ?? $exception);
}
} finally {
$cleanupFailure = null;

try {
$this->cleanupRequestScopedServices();
} finally {
} catch (\Throwable $e) {
$cleanupFailure = $e;
}

try {
if ($context !== null) {
$this->endContext($context);
$this->endContext($context, $terminationFailure ?? $cleanupFailure);
}
} finally {
if ($cleanupFailure !== null) {
// Never replace the exception that is already ending the request.
if ($terminationFailure !== null) {
$this->reportTeardownFailure('Request cleanup failed', $cleanupFailure);
} else {
throw $cleanupFailure;
}
}
}
}
Expand Down Expand Up @@ -1543,7 +1558,7 @@ public function dispatch($request): DispatchResult
Response::dispatchHttpException($e);
} catch (\Throwable $renderFailure) {
$this->endDeferScope($deferScope, $renderFailure);
$this->endContext($context);
$this->endContext($context, $renderFailure);

throw $renderFailure;
}
Expand All @@ -1554,7 +1569,7 @@ public function dispatch($request): DispatchResult
return new DispatchResult($this, $request, null, $e, $deferScope, $context);
} catch (\Throwable $e) {
$this->endDeferScope($deferScope, $e);
$this->endContext($context);
$this->endContext($context, $e);

throw $e;
}
Expand Down
91 changes: 80 additions & 11 deletions src/Phaseolies/DI/Concerns/InteractsWithExecutionContexts.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public function beginContext(string $type, array $metadata = []): ExecutionConte
try {
$this->fireContextListeners('begin', $context);
} catch (\Throwable $e) {
$this->endContext($context);
$this->endContext($context, $e);

throw $e;
}
Expand All @@ -63,11 +63,12 @@ public function beginContext(string $type, array $metadata = []): ExecutionConte
* End a context: end any contexts nested inside it, run the end
*
* @param ExecutionContext $context
* @param \Throwable|null $original
* @return void
*
* @throws \Throwable
*/
public function endContext(ExecutionContext $context): void
public function endContext(ExecutionContext $context, ?\Throwable $original = null): void
{
$index = $this->contextIndex($context);

Expand All @@ -82,7 +83,7 @@ public function endContext(ExecutionContext $context): void
$index = count($this->contextStack) - 1;
}

$failure = null;
$failures = [];

$endingIds = [$context->id() => true];

Expand All @@ -96,9 +97,9 @@ public function endContext(ExecutionContext $context): void
}

try {
$this->endContext($descendant);
$this->endContext($descendant, $original);
} catch (\Throwable $e) {
$failure ??= $e;
$failures[] = $e;
}
}

Expand All @@ -108,15 +109,77 @@ public function endContext(ExecutionContext $context): void
try {
$this->fireContextListeners('end', $ending);
} catch (\Throwable $e) {
$failure ??= $e;
$failures[] = $e;
}

unset($this->scopedInstances[$ending->id()], $this->liveContexts[$ending->id()]);
array_pop($this->contextStack);
}

if ($failure !== null) {
throw $failure;
if ($failures === []) {
return;
}

// Throw the first failure only when nothing else is failing. All other
// failures are reported, so none is lost and none replaces the original.
$thrown = $original === null ? array_shift($failures) : null;

foreach ($failures as $failure) {
$this->reportContextFailure($failure, $original);
}

if ($thrown !== null) {
throw $thrown;
}
}

/**
* Report a failure of a context-ending listener that is not being thrown
*
* @param \Throwable $failure
* @param \Throwable|null $original
* @return void
*/
protected function reportContextFailure(\Throwable $failure, ?\Throwable $original): void
{
$this->reportTeardownFailure('Execution context ending listener failed', $failure);
}

/**
* Log a failure that happened while tearing down and must not replace another exception
*
* @param string $label
* @param \Throwable $failure
* @return void
*/
protected function reportTeardownFailure(string $label, \Throwable $failure): void
{
$reported = false;

try {
if ($this->has('log')) {
\Phaseolies\Support\Facades\Log::error($label . ': ' . $failure->getMessage(), [
'exception' => $failure,
]);
$reported = true;
}
} catch (\Throwable) {
// Reporting must never break teardown; fall through to the last resort.
}

if (!$reported) {
try {
error_log(sprintf(
'%s: %s: %s in %s:%d',
$label,
get_class($failure),
$failure->getMessage(),
$failure->getFile(),
$failure->getLine()
));
} catch (\Throwable) {
// Nothing left to try.
}
}
}

Expand All @@ -133,10 +196,16 @@ public function runInContext(string $type, array $metadata, callable $callback):
$context = $this->beginContext($type, $metadata);

try {
return $callback($context);
} finally {
$this->endContext($context);
$result = $callback($context);
} catch (\Throwable $e) {
$this->endContext($context, $e);

throw $e;
}

$this->endContext($context);

return $result;
}

/**
Expand Down
Loading
Loading