Skip to content

[Cache][EventDispatcher][HttpClient][HttpKernel][Messenger][Validator][Workflow] Don't enable tracing unless the profiler is enabled #60243

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

Merged
merged 1 commit into from
Apr 27, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\HttpKernel\Log\DebugLoggerConfigurator;
use Symfony\Component\HttpKernel\Profiler\ProfilerStateChecker;
use Symfony\Component\JsonStreamer\Attribute\JsonStreamable;
use Symfony\Component\JsonStreamer\JsonStreamWriter;
use Symfony\Component\JsonStreamer\StreamReaderInterface;
Expand Down Expand Up @@ -963,6 +964,11 @@ private function registerProfilerConfiguration(array $config, ContainerBuilder $
$loader->load('collectors.php');
$loader->load('cache_debug.php');

if (!class_exists(ProfilerStateChecker::class)) {
$container->removeDefinition('profiler.state_checker');
$container->removeDefinition('profiler.is_disabled_state_checker');
}

if ($this->isInitializedConfigEnabled('form')) {
$loader->load('form_debug.php');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
service('debug.stopwatch'),
service('logger')->nullOnInvalid(),
service('.virtual_request_stack')->nullOnInvalid(),
service('profiler.is_disabled_state_checker')->nullOnInvalid(),
])
->tag('monolog.logger', ['channel' => 'event'])
->tag('kernel.reset', ['method' => 'reset'])
Expand Down
11 changes: 11 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Symfony\Component\HttpKernel\EventListener\ProfilerListener;
use Symfony\Component\HttpKernel\Profiler\FileProfilerStorage;
use Symfony\Component\HttpKernel\Profiler\Profiler;
use Symfony\Component\HttpKernel\Profiler\ProfilerStateChecker;

return static function (ContainerConfigurator $container) {
$container->services()
Expand Down Expand Up @@ -56,5 +57,15 @@
->set('.virtual_request_stack', VirtualRequestStack::class)
->args([service('request_stack')])
->public()

->set('profiler.state_checker', ProfilerStateChecker::class)
->args([
service_locator(['profiler' => service('profiler')->ignoreOnUninitialized()]),
param('kernel.runtime_mode.web'),
])

->set('profiler.is_disabled_state_checker', 'Closure')
->factory(['Closure', 'fromCallable'])
->args([[service('profiler.state_checker'), 'isProfilerDisabled']])
;
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
->decorate('validator', null, 255)
->args([
service('debug.validator.inner'),
service('profiler.is_disabled_state_checker')->nullOnInvalid(),
])
->tag('kernel.reset', [
'method' => 'reset',
Expand Down
37 changes: 37 additions & 0 deletions src/Symfony/Component/Cache/Adapter/TraceableAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, NamespacedPo

public function __construct(
protected AdapterInterface $pool,
protected readonly ?\Closure $disabled = null,
) {
}

Expand All @@ -45,6 +46,9 @@ public function get(string $key, callable $callback, ?float $beta = null, ?array
if (!$this->pool instanceof CacheInterface) {
throw new BadMethodCallException(\sprintf('Cannot call "%s::get()": this class doesn\'t implement "%s".', get_debug_type($this->pool), CacheInterface::class));
}
if ($this->disabled?->__invoke()) {
return $this->pool->get($key, $callback, $beta, $metadata);
}

$isHit = true;
$callback = function (CacheItem $item, bool &$save) use ($callback, &$isHit) {
Expand All @@ -71,6 +75,9 @@ public function get(string $key, callable $callback, ?float $beta = null, ?array

public function getItem(mixed $key): CacheItem
{
if ($this->disabled?->__invoke()) {
return $this->pool->getItem($key);
}
$event = $this->start(__FUNCTION__);
try {
$item = $this->pool->getItem($key);
Expand All @@ -88,6 +95,9 @@ public function getItem(mixed $key): CacheItem

public function hasItem(mixed $key): bool
{
if ($this->disabled?->__invoke()) {
return $this->pool->hasItem($key);
}
$event = $this->start(__FUNCTION__);
try {
return $event->result[$key] = $this->pool->hasItem($key);
Expand All @@ -98,6 +108,9 @@ public function hasItem(mixed $key): bool

public function deleteItem(mixed $key): bool
{
if ($this->disabled?->__invoke()) {
return $this->pool->deleteItem($key);
}
$event = $this->start(__FUNCTION__);
try {
return $event->result[$key] = $this->pool->deleteItem($key);
Expand All @@ -108,6 +121,9 @@ public function deleteItem(mixed $key): bool

public function save(CacheItemInterface $item): bool
{
if ($this->disabled?->__invoke()) {
return $this->pool->save($item);
}
$event = $this->start(__FUNCTION__);
try {
return $event->result[$item->getKey()] = $this->pool->save($item);
Expand All @@ -118,6 +134,9 @@ public function save(CacheItemInterface $item): bool

public function saveDeferred(CacheItemInterface $item): bool
{
if ($this->disabled?->__invoke()) {
return $this->pool->saveDeferred($item);
}
$event = $this->start(__FUNCTION__);
try {
return $event->result[$item->getKey()] = $this->pool->saveDeferred($item);
Expand All @@ -128,6 +147,9 @@ public function saveDeferred(CacheItemInterface $item): bool

public function getItems(array $keys = []): iterable
{
if ($this->disabled?->__invoke()) {
return $this->pool->getItems($keys);
}
$event = $this->start(__FUNCTION__);
try {
$result = $this->pool->getItems($keys);
Expand All @@ -151,6 +173,9 @@ public function getItems(array $keys = []): iterable

public function clear(string $prefix = ''): bool
{
if ($this->disabled?->__invoke()) {
return $this->pool->clear($prefix);
}
$event = $this->start(__FUNCTION__);
try {
if ($this->pool instanceof AdapterInterface) {
Expand All @@ -165,6 +190,9 @@ public function clear(string $prefix = ''): bool

public function deleteItems(array $keys): bool
{
if ($this->disabled?->__invoke()) {
return $this->pool->deleteItems($keys);
}
$event = $this->start(__FUNCTION__);
$event->result['keys'] = $keys;
try {
Expand All @@ -176,6 +204,9 @@ public function deleteItems(array $keys): bool

public function commit(): bool
{
if ($this->disabled?->__invoke()) {
return $this->pool->commit();
}
$event = $this->start(__FUNCTION__);
try {
return $event->result = $this->pool->commit();
Expand All @@ -189,6 +220,9 @@ public function prune(): bool
if (!$this->pool instanceof PruneableInterface) {
return false;
}
if ($this->disabled?->__invoke()) {
return $this->pool->prune();
}
$event = $this->start(__FUNCTION__);
try {
return $event->result = $this->pool->prune();
Expand All @@ -208,6 +242,9 @@ public function reset(): void

public function delete(string $key): bool
{
if ($this->disabled?->__invoke()) {
return $this->pool->deleteItem($key);
}
$event = $this->start(__FUNCTION__);
try {
return $event->result[$key] = $this->pool->deleteItem($key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,19 @@
*/
class TraceableTagAwareAdapter extends TraceableAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface
{
public function __construct(TagAwareAdapterInterface $pool)
public function __construct(TagAwareAdapterInterface $pool, ?\Closure $disabled = null)
{
parent::__construct($pool);
parent::__construct($pool, $disabled);
}

public function invalidateTags(array $tags): bool
{
if ($this->disabled?->__invoke()) {
return $this->pool->invalidateTags($tags);
}
$event = $this->start(__FUNCTION__);
try {
return $event->result = $this->pool->invalidateTags($tags);

Check failure on line 33 in src/Symfony/Component/Cache/Adapter/TraceableTagAwareAdapter.php

View workflow job for this annotation

GitHub Actions / Psalm

UndefinedInterfaceMethod

src/Symfony/Component/Cache/Adapter/TraceableTagAwareAdapter.php:33:50: UndefinedInterfaceMethod: Method Symfony\Component\Cache\Adapter\AdapterInterface::invalidateTags does not exist (see https://psalm.dev/181)

Check failure on line 33 in src/Symfony/Component/Cache/Adapter/TraceableTagAwareAdapter.php

View workflow job for this annotation

GitHub Actions / Psalm

UndefinedInterfaceMethod

src/Symfony/Component/Cache/Adapter/TraceableTagAwareAdapter.php:33:50: UndefinedInterfaceMethod: Method Symfony\Component\Cache\Adapter\AdapterInterface::invalidateTags does not exist (see https://psalm.dev/181)
} finally {
$event->end = microtime(true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ private function addToCollector(string $id, string $name, ContainerBuilder $cont
if (!$definition->isPublic() || !$definition->isPrivate()) {
$recorder->setPublic($definition->isPublic());
}
$recorder->setArguments([new Reference($innerId = $id.'.recorder_inner')]);
$recorder->setArguments([new Reference($innerId = $id.'.recorder_inner'), new Reference('profiler.is_disabled_state_checker', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE)]);

foreach ($definition->getMethodCalls() as [$method, $args]) {
if ('setCallbackWrapper' !== $method || !$args[0] instanceof Definition || !($args[0]->getArguments()[2] ?? null) instanceof Definition) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public function __construct(
protected Stopwatch $stopwatch,
protected ?LoggerInterface $logger = null,
private ?RequestStack $requestStack = null,
protected readonly ?\Closure $disabled = null,
) {
}

Expand Down Expand Up @@ -103,6 +104,9 @@ public function hasListeners(?string $eventName = null): bool

public function dispatch(object $event, ?string $eventName = null): object
{
if ($this->disabled?->__invoke()) {
return $this->dispatcher->dispatch($event, $eventName);
}
$eventName ??= $event::class;

$this->callStack ??= new \SplObjectStorage();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public function process(ContainerBuilder $container): void

foreach ($container->findTaggedServiceIds('http_client.client') as $id => $tags) {
$container->register('.debug.'.$id, TraceableHttpClient::class)
->setArguments([new Reference('.debug.'.$id.'.inner'), new Reference('debug.stopwatch', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)])
->setArguments([new Reference('.debug.'.$id.'.inner'), new Reference('debug.stopwatch', ContainerInterface::IGNORE_ON_INVALID_REFERENCE), new Reference('profiler.is_disabled_state_checker', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)])
->addTag('kernel.reset', ['method' => 'reset'])
->setDecoratedService($id, null, 5);
$container->getDefinition('data_collector.http_client')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class TraceableResponse implements ResponseInterface, StreamableInterface
public function __construct(
private HttpClientInterface $client,
private ResponseInterface $response,
private mixed &$content,
private mixed &$content = false,
private ?StopwatchEvent $event = null,
) {
}
Expand Down
5 changes: 5 additions & 0 deletions src/Symfony/Component/HttpClient/TraceableHttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,17 @@ final class TraceableHttpClient implements HttpClientInterface, ResetInterface,
public function __construct(
private HttpClientInterface $client,
private ?Stopwatch $stopwatch = null,
private ?\Closure $disabled = null,
) {
$this->tracedRequests = new \ArrayObject();
}

public function request(string $method, string $url, array $options = []): ResponseInterface
{
if ($this->disabled?->__invoke()) {
return new TraceableResponse($this->client, $this->client->request($method, $url, $options));
}

$content = null;
$traceInfo = [];
$this->tracedRequests[] = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ class TraceableEventDispatcher extends BaseTraceableEventDispatcher
{
protected function beforeDispatch(string $eventName, object $event): void
{
if ($this->disabled?->__invoke()) {
return;
}
switch ($eventName) {
case KernelEvents::REQUEST:
$event->getRequest()->attributes->set('_stopwatch_token', bin2hex(random_bytes(3)));
Expand Down Expand Up @@ -57,6 +60,9 @@ protected function beforeDispatch(string $eventName, object $event): void

protected function afterDispatch(string $eventName, object $event): void
{
if ($this->disabled?->__invoke()) {
return;
}
switch ($eventName) {
case KernelEvents::CONTROLLER_ARGUMENTS:
$this->stopwatch->start('controller', 'section');
Expand Down
33 changes: 33 additions & 0 deletions src/Symfony/Component/HttpKernel/Profiler/ProfilerStateChecker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\HttpKernel\Profiler;

use Psr\Container\ContainerInterface;

class ProfilerStateChecker
{
public function __construct(
private ContainerInterface $container,
private bool $defaultEnabled,
) {
}

public function isProfilerEnabled(): bool
{
return $this->container->get('profiler')?->isEnabled() ?? $this->defaultEnabled;
}

public function isProfilerDisabled(): bool
{
return !$this->isProfilerEnabled();
}
}
2 changes: 1 addition & 1 deletion src/Symfony/Component/HttpKernel/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/error-handler": "^6.4|^7.0",
"symfony/event-dispatcher": "^6.4|^7.0",
"symfony/event-dispatcher": "^7.3",
"symfony/http-foundation": "^7.3",
"symfony/polyfill-ctype": "^1.8",
"psr/log": "^1|^2|^3"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ private function registerBusToCollector(ContainerBuilder $container, string $bus
{
$container->setDefinition(
$tracedBusId = 'debug.traced.'.$busId,
(new Definition(TraceableMessageBus::class, [new Reference($tracedBusId.'.inner')]))->setDecoratedService($busId)
(new Definition(TraceableMessageBus::class, [new Reference($tracedBusId.'.inner'), new Reference('profiler.is_disabled_state_checker', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE)]))->setDecoratedService($busId)
);

$container->getDefinition('data_collector.messenger')->addMethodCall('registerBus', [$busId, new Reference($tracedBusId)]);
Expand Down
5 changes: 5 additions & 0 deletions src/Symfony/Component/Messenger/TraceableMessageBus.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,16 @@ class TraceableMessageBus implements MessageBusInterface

public function __construct(
private MessageBusInterface $decoratedBus,
protected readonly ?\Closure $disabled = null,
) {
}

public function dispatch(object $message, array $stamps = []): Envelope
{
if ($this->disabled?->__invoke()) {
return $this->decoratedBus->dispatch($message, $stamps);
}

$envelope = Envelope::wrap($message, $stamps);
$context = [
'stamps' => array_merge([], ...array_values($envelope->all())),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class TraceableValidator implements ValidatorInterface, ResetInterface

public function __construct(
private ValidatorInterface $validator,
protected readonly ?\Closure $disabled = null,
) {
}

Expand Down Expand Up @@ -56,6 +57,10 @@ public function validate(mixed $value, Constraint|array|null $constraints = null
{
$violations = $this->validator->validate($value, $constraints, $groups);

if ($this->disabled?->__invoke()) {
return $violations;
}

$trace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 7);

$file = $trace[0]['file'];
Expand Down
4 changes: 4 additions & 0 deletions src/Symfony/Component/Workflow/Debug/TraceableWorkflow.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class TraceableWorkflow implements WorkflowInterface
public function __construct(
private readonly WorkflowInterface $workflow,
private readonly Stopwatch $stopwatch,
protected readonly ?\Closure $disabled = null,
) {
}

Expand Down Expand Up @@ -90,6 +91,9 @@ public function getCalls(): array

private function callInner(string $method, array $args): mixed
{
if ($this->disabled?->__invoke()) {
return $this->workflow->{$method}(...$args);
}
$sMethod = $this->workflow::class.'::'.$method;
$this->stopwatch->start($sMethod, 'workflow');

Expand Down
Loading
Loading