Skip to content

Commit 122aff0

Browse files
authored
Prevent leaking TracerProvider references in register_shutdown_function (#716)
1 parent ac95256 commit 122aff0

File tree

2 files changed

+53
-1
lines changed

2 files changed

+53
-1
lines changed

src/SDK/Trace/TracerProvider.php

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,15 @@
1414
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler;
1515
use OpenTelemetry\SDK\Trace\Sampler\ParentBased;
1616
use function register_shutdown_function;
17+
use function spl_object_id;
18+
use WeakReference;
1719

1820
final class TracerProvider implements API\TracerProviderInterface
1921
{
2022
public const DEFAULT_TRACER_NAME = 'io.opentelemetry.contrib.php';
2123

24+
/** @var array<int, WeakReference<self>>|null */
25+
private static ?array $tracerProviders = null;
2226
private static ?API\TracerInterface $defaultTracer = null;
2327

2428
/** @var array<string, API\TracerInterface> */
@@ -53,7 +57,7 @@ public function __construct(
5357
$spanProcessors
5458
);
5559

56-
register_shutdown_function([$this, 'shutdown']);
60+
self::registerShutdownFunction($this);
5761
}
5862

5963
public function forceFlush(): ?bool
@@ -116,6 +120,41 @@ public function shutdown(): bool
116120
return true;
117121
}
118122

123+
self::unregisterShutdownFunction($this);
124+
119125
return $this->tracerSharedState->shutdown();
120126
}
127+
128+
public function __destruct()
129+
{
130+
$this->shutdown();
131+
}
132+
133+
private static function registerShutdownFunction(TracerProvider $tracerProvider): void
134+
{
135+
if (self::$tracerProviders === null) {
136+
register_shutdown_function(static function (): void {
137+
$tracerProviders = self::$tracerProviders;
138+
self::$tracerProviders = null;
139+
140+
// Push tracer provider shutdown to end of queue
141+
// @phan-suppress-next-line PhanTypeMismatchArgumentInternal
142+
register_shutdown_function(static function (array $tracerProviders): void {
143+
foreach ($tracerProviders as $reference) {
144+
if ($tracerProvider = $reference->get()) {
145+
$tracerProvider->shutdown();
146+
}
147+
}
148+
}, $tracerProviders);
149+
});
150+
}
151+
152+
self::$tracerProviders[spl_object_id($tracerProvider)] = WeakReference::create($tracerProvider);
153+
}
154+
155+
private static function unregisterShutdownFunction(TracerProvider $tracerProvider): void
156+
{
157+
/** @psalm-suppress PossiblyNullArrayAccess */
158+
unset(self::$tracerProviders[spl_object_id($tracerProvider)]);
159+
}
121160
}

tests/Unit/SDK/Trace/TracerProviderTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use OpenTelemetry\SDK\Trace\Tracer;
1111
use OpenTelemetry\SDK\Trace\TracerProvider;
1212
use PHPUnit\Framework\TestCase;
13+
use WeakReference;
1314

1415
/**
1516
* @coversDefaultClass \OpenTelemetry\SDK\Trace\TracerProvider
@@ -174,4 +175,16 @@ public function test_get_tracer_returns_noop_tracer_after_shutdown(): void
174175
$provider->getTracer('foo')
175176
);
176177
}
178+
179+
/**
180+
* @coversNothing
181+
*/
182+
public function test_tracer_register_shutdown_function_does_not_leak_reference(): void
183+
{
184+
$provider = new TracerProvider();
185+
$reference = WeakReference::create($provider);
186+
187+
$provider = null;
188+
$this->assertTrue($reference->get() === null);
189+
}
177190
}

0 commit comments

Comments
 (0)