Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle out-of-order destruction of Fibers/DebugScopes during final shutdown #1209

Merged
merged 3 commits into from
Jan 13, 2024
Merged
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
57 changes: 48 additions & 9 deletions src/Context/DebugScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@

namespace OpenTelemetry\Context;

use function assert;
use function basename;
use function class_exists;
use function count;
use function debug_backtrace;
use const DEBUG_BACKTRACE_IGNORE_ARGS;
use Fiber;
use const PHP_VERSION_ID;
use function register_shutdown_function;
use function spl_object_id;
use function sprintf;
use function trigger_error;

Expand All @@ -16,28 +22,37 @@
*/
final class DebugScope implements ScopeInterface
{
private const DEBUG_TRACE_CREATE = '__debug_trace_create';
private const DEBUG_TRACE_DETACH = '__debug_trace_detach';
private static bool $shutdownHandlerInitialized = false;
private static bool $finalShutdownPhase = false;

private ContextStorageScopeInterface $scope;
private ?int $fiberId;
private array $createdAt;
private ?array $detachedAt = null;

public function __construct(ContextStorageScopeInterface $node)
public function __construct(ContextStorageScopeInterface $scope)
{
$this->scope = $node;
$this->scope[self::DEBUG_TRACE_CREATE] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$this->scope = $scope;
$this->fiberId = self::currentFiberId();
$this->createdAt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);

if (!self::$shutdownHandlerInitialized) {
self::$shutdownHandlerInitialized = true;
register_shutdown_function('register_shutdown_function', static fn () => self::$finalShutdownPhase = true);
}
}

public function detach(): int
{
$this->scope[self::DEBUG_TRACE_DETACH] ??= debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$this->detachedAt ??= debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);

$flags = $this->scope->detach();

if (($flags & ScopeInterface::DETACHED) !== 0) {
trigger_error(sprintf(
'Scope: unexpected call to Scope::detach() for scope #%d, scope was already detached %s',
spl_object_id($this),
self::formatBacktrace($this->scope[self::DEBUG_TRACE_DETACH]),
self::formatBacktrace($this->detachedAt),
));
} elseif (($flags & ScopeInterface::MISMATCH) !== 0) {
trigger_error(sprintf(
Expand All @@ -56,15 +71,39 @@ public function detach(): int

public function __destruct()
{
if (!isset($this->scope[self::DEBUG_TRACE_DETACH])) {
if (!$this->detachedAt) {
// Handle destructors invoked during final shutdown
// DebugScope::__destruct() might be called before fiber finally blocks run
if (self::$finalShutdownPhase && $this->fiberId !== self::currentFiberId()) {
return;
}

trigger_error(sprintf(
'Scope: missing call to Scope::detach() for scope #%d, created %s',
spl_object_id($this->scope),
self::formatBacktrace($this->scope[self::DEBUG_TRACE_CREATE]),
self::formatBacktrace($this->createdAt),
));
}
}

/**
* @phan-suppress PhanUndeclaredClassReference
* @phan-suppress PhanUndeclaredClassMethod
*/
private static function currentFiberId(): ?int
{
if (PHP_VERSION_ID < 80100) {
return null;
}

assert(class_exists(Fiber::class, false));
if (!$fiber = Fiber::getCurrent()) {
return null;
}

return spl_object_id($fiber);
}

private static function formatBacktrace(array $trace): string
{
$s = '';
Expand Down
Loading