Issue #1891980 by Berdir, amateescu: Add Service termination API to reliable terminate/shut down services in the container.
parent
b0666783f5
commit
a92b264dce
|
@ -14,6 +14,7 @@ use Drupal\Core\DependencyInjection\Compiler\RegisterPathProcessorsPass;
|
|||
use Drupal\Core\DependencyInjection\Compiler\RegisterRouteFiltersPass;
|
||||
use Drupal\Core\DependencyInjection\Compiler\RegisterRouteEnhancersPass;
|
||||
use Drupal\Core\DependencyInjection\Compiler\RegisterParamConvertersPass;
|
||||
use Drupal\Core\DependencyInjection\Compiler\RegisterServicesForDestructionPass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
@ -302,6 +303,10 @@ class CoreBundle extends Bundle {
|
|||
|
||||
$container->register('plugin.manager.condition', 'Drupal\Core\Condition\ConditionManager');
|
||||
|
||||
$container->register('kernel_destruct_subscriber', 'Drupal\Core\EventSubscriber\KernelDestructionSubscriber')
|
||||
->addMethodCall('setContainer', array(new Reference('service_container')))
|
||||
->addTag('event_subscriber');
|
||||
|
||||
$container->addCompilerPass(new RegisterMatchersPass());
|
||||
$container->addCompilerPass(new RegisterRouteFiltersPass());
|
||||
// Add a compiler pass for registering event subscribers.
|
||||
|
@ -312,6 +317,8 @@ class CoreBundle extends Bundle {
|
|||
// Add a compiler pass for upcasting of entity route parameters.
|
||||
$container->addCompilerPass(new RegisterParamConvertersPass());
|
||||
$container->addCompilerPass(new RegisterRouteEnhancersPass());
|
||||
// Add a compiler pass for registering services needing destruction.
|
||||
$container->addCompilerPass(new RegisterServicesForDestructionPass());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\DependencyInjection\Compiler\RegisterServicesForDestructionPass.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
|
||||
/**
|
||||
* Adds services tagged "needs_destruction" to the "kernel_destruct_subscriber"
|
||||
* service.
|
||||
*/
|
||||
class RegisterServicesForDestructionPass implements CompilerPassInterface {
|
||||
|
||||
/**
|
||||
* Implements \Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface::process().
|
||||
*/
|
||||
public function process(ContainerBuilder $container) {
|
||||
if (!$container->hasDefinition('kernel_destruct_subscriber')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$definition = $container->getDefinition('kernel_destruct_subscriber');
|
||||
|
||||
$services = $container->findTaggedServiceIds('needs_destruction');
|
||||
foreach ($services as $id => $attributes) {
|
||||
$definition->addMethodCall('registerService', array($id));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\DestructableInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core;
|
||||
|
||||
/**
|
||||
* The interface for services that need explicit destruction.
|
||||
*/
|
||||
interface DestructableInterface {
|
||||
|
||||
/**
|
||||
* Performs destruct operations.
|
||||
*/
|
||||
public function destruct();
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\EventSubscriber\KernelDestructionSubscriber.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\EventSubscriber;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerAware;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
/**
|
||||
* Destructs services that are initiated and tagged with "needs_destruction".
|
||||
*/
|
||||
class KernelDestructionSubscriber extends ContainerAware implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* Holds an array of service ID's that will require destruction.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $services = array();
|
||||
|
||||
/**
|
||||
* Registers a service for destruction.
|
||||
*
|
||||
* Calls to this method are set up in
|
||||
* RegisterServicesForDestructionPass::process().
|
||||
*
|
||||
* @param string $id
|
||||
* Name of the service.
|
||||
*/
|
||||
public function registerService($id) {
|
||||
$this->services[] = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked by the terminate kernel event.
|
||||
*
|
||||
* @param \Symfony\Component\HttpKernel\Event\PostResponseEvent $event
|
||||
* The event object.
|
||||
*/
|
||||
public function onKernelTerminate(PostResponseEvent $event) {
|
||||
foreach ($this->services as $id) {
|
||||
// Check if the service was initialized during this request, destruction
|
||||
// is not necessary if the service was not used.
|
||||
if ($this->container->initialized($id)) {
|
||||
$service = $this->container->get($id);
|
||||
$service->destruct();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the methods in this class that should be listeners.
|
||||
*
|
||||
* @return array
|
||||
* An array of event listener definitions.
|
||||
*/
|
||||
static function getSubscribedEvents() {
|
||||
$events[KernelEvents::TERMINATE][] = array('onKernelTerminate', 100);
|
||||
return $events;
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@ class AliasManager implements AliasManagerInterface {
|
|||
/**
|
||||
* The Key/Value Store to use for state
|
||||
*
|
||||
* @var \Drupal\Core\KeyValueStore\DatabaseStorage
|
||||
* @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\DrupalKernel\ServiceDestructionTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\DrupalKernel;
|
||||
|
||||
use Drupal\simpletest\DrupalUnitTestBase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
|
||||
|
||||
/**
|
||||
* Tests the service destruction functionality.
|
||||
*/
|
||||
class ServiceDestructionTest extends DrupalUnitTestBase {
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Service destruction',
|
||||
'description' => 'Tests that services are correctly destructed.',
|
||||
'group' => 'DrupalKernel',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that services are destructed when used.
|
||||
*/
|
||||
public function testDestructionUsed() {
|
||||
// Enable the test module to add it to the container.
|
||||
$this->enableModules(array('bundle_test'));
|
||||
|
||||
// The service has not been destructed yet.
|
||||
$this->assertNull(state()->get('bundle_test.destructed'));
|
||||
|
||||
// Get the service destructor.
|
||||
$service_destruction = $this->container->get('kernel_destruct_subscriber');
|
||||
|
||||
// Call the class and then invoke the kernel terminate event.
|
||||
$this->container->get('bundle_test_class');
|
||||
$response = new Response();
|
||||
$event = new PostResponseEvent($this->container->get('kernel'), $this->container->get('request'), $response);
|
||||
$service_destruction->onKernelTerminate($event);
|
||||
$this->assertTrue(state()->get('bundle_test.destructed'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that services are not unnecessarily destructed when not used.
|
||||
*/
|
||||
public function testDestructionUnused() {
|
||||
// Enable the test module to add it to the container.
|
||||
$this->enableModules(array('bundle_test'));
|
||||
|
||||
// The service has not been destructed yet.
|
||||
$this->assertNull(state()->get('bundle_test.destructed'));
|
||||
|
||||
// Get the service destructor.
|
||||
$service_destruction = $this->container->get('kernel_destruct_subscriber');
|
||||
|
||||
// Simulate a shutdown. The test class has not been called, so it should not
|
||||
// be destructed.
|
||||
$response = new Response();
|
||||
$event = new PostResponseEvent($this->container->get('kernel'), $this->container->get('request'), $response);
|
||||
$service_destruction->onKernelTerminate($event);
|
||||
$this->assertNull(state()->get('bundle_test.destructed'));
|
||||
}
|
||||
}
|
|
@ -19,7 +19,9 @@ class BundleTestBundle extends Bundle
|
|||
{
|
||||
public function build(ContainerBuilder $container) {
|
||||
$container->register('bundle_test_class', 'Drupal\bundle_test\TestClass')
|
||||
->addTag('event_subscriber');
|
||||
->addArgument(new Reference('state'))
|
||||
->addTag('event_subscriber')
|
||||
->addTag('needs_destruction');
|
||||
|
||||
// Override a default bundle used by core to a dummy class.
|
||||
$container->register('file.usage', 'Drupal\bundle_test\TestFileUsage');
|
||||
|
|
|
@ -7,11 +7,30 @@
|
|||
|
||||
namespace Drupal\bundle_test;
|
||||
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||
use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
|
||||
use Drupal\Core\DestructableInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
class TestClass implements EventSubscriberInterface {
|
||||
class TestClass implements EventSubscriberInterface, DestructableInterface {
|
||||
|
||||
/**
|
||||
* The state keyvalue collection.
|
||||
*
|
||||
* @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $state
|
||||
* The state key value store.
|
||||
*/
|
||||
public function __construct(KeyValueStoreInterface $state) {
|
||||
$this->state = $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple kernel listener method.
|
||||
|
@ -30,4 +49,11 @@ class TestClass implements EventSubscriberInterface {
|
|||
$events[KernelEvents::REQUEST][] = array('onKernelRequestTest', 100);
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\Core\DestructableInterface::destruct().
|
||||
*/
|
||||
public function destruct() {
|
||||
$this->state->set('bundle_test.destructed', TRUE);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,8 @@ class ViewsBundle extends Bundle {
|
|||
|
||||
$container->register('views.views_data', 'Drupal\views\ViewsDataCache')
|
||||
->addArgument(new Reference('cache.views_info'))
|
||||
->addArgument(new Reference('config.factory'));
|
||||
->addArgument(new Reference('config.factory'))
|
||||
->addTag('needs_destruction');
|
||||
|
||||
$container->register('views.executable', 'Drupal\views\ViewExecutableFactory');
|
||||
|
||||
|
|
|
@ -7,13 +7,14 @@
|
|||
|
||||
namespace Drupal\views;
|
||||
|
||||
use Drupal\Core\Config\ConfigFactory;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Config\ConfigFactory;
|
||||
use Drupal\Core\DestructableInterface;
|
||||
|
||||
/**
|
||||
* Class to manage and lazy load cached views data.
|
||||
*/
|
||||
class ViewsDataCache {
|
||||
class ViewsDataCache implements DestructableInterface {
|
||||
|
||||
/**
|
||||
* The base cache ID to use.
|
||||
|
@ -241,25 +242,18 @@ class ViewsDataCache {
|
|||
}
|
||||
|
||||
/**
|
||||
* Destructs the ViewDataCache object.
|
||||
* Implements \Drupal\Core\DestructableInterface::destruct().
|
||||
*/
|
||||
public function __destruct() {
|
||||
try {
|
||||
if ($this->rebuildCache && !empty($this->storage)) {
|
||||
// Keep a record with all data.
|
||||
$this->set($this->baseCid, $this->storage);
|
||||
// Save data in seperate cache entries.
|
||||
foreach ($this->storage as $table => $data) {
|
||||
$cid = $this->baseCid . ':' . $table;
|
||||
$this->set($cid, $data);
|
||||
}
|
||||
public function destruct() {
|
||||
if ($this->rebuildCache && !empty($this->storage)) {
|
||||
// Keep a record with all data.
|
||||
$this->set($this->baseCid, $this->storage);
|
||||
// Save data in seperate cache entries.
|
||||
foreach ($this->storage as $table => $data) {
|
||||
$cid = $this->baseCid . ':' . $table;
|
||||
$this->set($cid, $data);
|
||||
}
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// During testing the table is gone before this fires.
|
||||
// @todo Use terminate() instead of __destruct(), see
|
||||
// http://drupal.org/node/512026.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue