Your Location is: Home > Symfony

Maintenance mode using LockComponent

From: Yugoslavia View: 2090 olidem 

Question

I have implemented a maintenance mode in my symfony 5.1.8 app. It uses the Lock Component.

The core is a request listener and a helper service:

My issue: Some subrequests get caught in my listener.

My suspicion: There is a race condition when just testing for the lock.

class MaintenanceModeRequestListener
{
    /** @var MaintenanceModeHelper */
    protected $lockHelper;

    /** @var UrlGeneratorInterface */
    protected $router;

    public function __construct(
        MaintenanceModeHelper $lockHelper,
        UrlGeneratorInterface $router
    )
    {
        $this->lockHelper = $lockHelper;
        $this->router = $router;
    }

    public function onKernelRequest(RequestEvent $event)
    {
        $inMMode = $this->lockHelper->isInMaintenanceMode();
        $requestIsMaintenance = $event->getRequest()->getRequestUri() === '/maintenance/';

        if($inMMode && !$requestIsMaintenance)

            $event->setResponse(new RedirectResponse($this->router->generate('show-maintenance-mode')));

        else if(!$inMMode && $requestIsMaintenance)

            $event->setResponse(new RedirectResponse($this->router->generate('index')));
    }
}

so any incoming request will check if the Lock is locked and proceed accordingly.

The helper is important:

class MaintenanceModeHelper
{
    protected $lock;

    public function __construct(LockFactory $lockFactory)
    {
        $this->lock = $lockFactory->createLock('maintenance', 5);
    }

    /**
     * @return bool true iff in maintenance mode
     */
    public function isInMaintenanceMode() { /* race condition here ? Shouldn't this be an atomic operation? */
        $obtained = $this->lock->acquire(false);
        if ($obtained) {
            $this->lock->release();
        }
        return !$obtained;
    }

    /**
     * @return bool true iff "I" am in the warmup command process
     */
    public function isAcquired() {
        return $this->lock->isAcquired();
    }

    public function acquire() {
        $acquireResult = $this->lock->acquire(false);
        return $acquireResult;
    }

    public function release() {
        $result =  $this->lock->release();
        return $result;
    }
}

Now, here's my problem: If the page has sub-requests, e.g. an iframe or image, such that the browser will issue a second request to the server, it does happen on some machines, that the second request gets caught in the request listener and is redirected to the maintenance page. I am no sure if "sub-request" is the correct term for the second request that loads an image resource or an iframe.

The following screenshot illustrates this issue: enter image description here

I am even able to track this into symfony's toolbar: Here you see the log messages for the subrequest: the lock has been acquired by someone else. enter image description here

Best answer