-
Notifications
You must be signed in to change notification settings - Fork 193
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add debug scope to warn on incorrect scope usage (#823)
* Add debug scope to warn on incorrect scope usage * Enable assertions in unit tests
- Loading branch information
Showing
6 changed files
with
198 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace OpenTelemetry\Context; | ||
|
||
use function basename; | ||
use function count; | ||
use function debug_backtrace; | ||
use const DEBUG_BACKTRACE_IGNORE_ARGS; | ||
use function sprintf; | ||
use function trigger_error; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
final class DebugScope implements ScopeInterface | ||
{ | ||
private const DEBUG_TRACE_CREATE = '__debug_trace_create'; | ||
private const DEBUG_TRACE_DETACH = '__debug_trace_detach'; | ||
|
||
private ContextStorageScopeInterface $scope; | ||
|
||
public function __construct(ContextStorageScopeInterface $node) | ||
{ | ||
$this->scope = $node; | ||
$this->scope[self::DEBUG_TRACE_CREATE] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); | ||
} | ||
|
||
public function detach(): int | ||
{ | ||
$this->scope[self::DEBUG_TRACE_DETACH] ??= 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]), | ||
)); | ||
} elseif (($flags & ScopeInterface::MISMATCH) !== 0) { | ||
trigger_error(sprintf( | ||
'Scope: unexpected call to Scope::detach() for scope #%d, scope successfully detached but another scope should have been detached first', | ||
spl_object_id($this), | ||
)); | ||
} elseif (($flags & ScopeInterface::INACTIVE) !== 0) { | ||
trigger_error(sprintf( | ||
'Scope: unexpected call to Scope::detach() for scope #%d, scope successfully detached from different execution context', | ||
spl_object_id($this), | ||
)); | ||
} | ||
|
||
return $flags; | ||
} | ||
|
||
public function __destruct() | ||
{ | ||
if (!isset($this->scope[self::DEBUG_TRACE_DETACH])) { | ||
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]), | ||
)); | ||
} | ||
} | ||
|
||
private static function formatBacktrace(array $trace): string | ||
{ | ||
$s = ''; | ||
for ($i = 0, $n = count($trace) + 1; ++$i < $n;) { | ||
$s .= "\n\t"; | ||
$s .= 'at '; | ||
if (isset($trace[$i]['class'])) { | ||
$s .= strtr($trace[$i]['class'], ['\\' => '.']); | ||
$s .= '.'; | ||
} | ||
$s .= strtr($trace[$i]['function'] ?? '{main}', ['\\' => '.']); | ||
$s .= '('; | ||
if (isset($trace[$i - 1]['file'])) { | ||
$s .= basename($trace[$i - 1]['file']); | ||
if (isset($trace[$i - 1]['line'])) { | ||
$s .= ':'; | ||
$s .= $trace[$i - 1]['line']; | ||
} | ||
} else { | ||
$s .= 'Unknown Source'; | ||
} | ||
$s .= ')'; | ||
} | ||
|
||
return $s . "\n"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace OpenTelemetry\Tests\Unit\Context; | ||
|
||
use OpenTelemetry\Context\Context; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
/** | ||
* @covers \OpenTelemetry\Context\DebugScope | ||
*/ | ||
final class DebugScopeTest extends TestCase | ||
{ | ||
public function test_detached_scope_detach(): void | ||
{ | ||
$scope1 = Context::getCurrent()->activate(); | ||
|
||
$scope1->detach(); | ||
|
||
$this->expectNotice(); | ||
$this->expectNoticeMessage('already detached'); | ||
$scope1->detach(); | ||
} | ||
|
||
public function test_order_mismatch_scope_detach(): void | ||
{ | ||
$scope1 = Context::getCurrent()->activate(); | ||
$scope2 = Context::getCurrent()->activate(); | ||
|
||
try { | ||
$this->expectNotice(); | ||
$this->expectNoticeMessage('another scope'); | ||
$scope1->detach(); | ||
} finally { | ||
$scope2->detach(); | ||
} | ||
} | ||
|
||
public function test_inactive_scope_detach(): void | ||
{ | ||
$scope1 = Context::getCurrent()->activate(); | ||
|
||
Context::storage()->fork(1); | ||
Context::storage()->switch(1); | ||
|
||
try { | ||
$this->expectNotice(); | ||
$this->expectNoticeMessage('different execution context'); | ||
$scope1->detach(); | ||
} finally { | ||
Context::storage()->switch(0); | ||
Context::storage()->destroy(1); | ||
} | ||
} | ||
|
||
public function test_missing_scope_detach(): void | ||
{ | ||
try { | ||
$this->expectNotice(); | ||
$this->expectNoticeMessage('missing call'); | ||
Context::getCurrent()->activate(); | ||
} finally { | ||
/** @psalm-suppress PossiblyNullReference */ | ||
Context::storage()->scope()->detach(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters