DEV Community

Getting Symfony app ready for Swoole, RoadRunner, and FrankenPHP (no AI involved)

Sergii Dolgushev on March 04, 2024

Greetings dear reader! It has been a while since I wrote last time, so it is very nice to see you! And I hope this post will be interesting, and yo...
Collapse
 
cviniciussdias profile image
Vinicius Dias

Very nice! Thank you for sharing.
In that example, even after implementing the interface, we might see a counter value > 1, right?

In a race condition where a request accesses the value before its reset...

Does having repositories in controllers constructors lead to pdo connections remaining forever open? Is there a workaround?

Once again, thanks for sharing. :-D

Collapse
 
sergiid profile image
Sergii Dolgushev

Thank you very much for your question. The memory is shared within the same worker, and each worker can handle one request at a time.

Let's assume we have only one worker available and two concurrent requests were sent. The following will happen:

  1. The worker will start processing the request #1. Request #2 will be put "on hold"
  2. During request #1 processing, the memory state might change, but in the end, it will be reset because of ResetInterface.
  3. Once request #1 is processed, the worker will start processing request #2.

In this case, we can't actually get the race condition, because a single worker processes requests one at a time. The race condition is possible only with multiple workers. But in this case, they don't share the memory. So it shouldn't be a concern.

I was using the following example (with 5 seconds delay):

<?php

declare(strict_types=1);

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Service\ResetInterface;

class TestController extends AbstractController implements ResetInterface
{
    private int $counter = 0;

    #[Route(path: '/test', name: 'test', methods: [Request::METHOD_GET])]
    public function testAction(): Response
    {
        $content = '['.\date('c').'] Counter: '.(++$this->counter).PHP_EOL;

        sleep(5);

        return new Response($content, Response::HTTP_OK, ['Content-Type' => 'text/html']);
    }

    public function reset()
    {
        $this->counter = 0;
    }
}
Enter fullscreen mode Exit fullscreen mode

And was sending 2 requests within 5 seconds, to emulate the race condition.

Tried single frankenphp worker:

APP_RUNTIME=Runtime\\FrankenPhpSymfony\\Runtime /usr/local/bin/frankenphp php-server -l 127.0.0.1:8000 -w ./index.php,1
Enter fullscreen mode Exit fullscreen mode

And got the expected 1 for both requests, their total execution time was around 10 seconds, as the second one was "on hold" while the first one was processing.

And also checked frankenphp with two workers:

APP_RUNTIME=Runtime\\FrankenPhpSymfony\\Runtime /usr/local/bin/frankenphp php-server -l 127.0.0.1:8000 -w ./index.php,2
Enter fullscreen mode Exit fullscreen mode

And again expected result was returned for both requests, and the total execution time was around 5 seconds, as the second request was not waiting for the first one to be processed. The expected result is returned for both requests even without ResetInterface. As requests are handled by different workers that don't share the memory.

Dear @cviniciussdias I hope it helps with your question. Otherwise can you please provide an example to reproduce the case you are referring to?

Collapse
 
aerendir profile image
Adamo Crespi • Edited

Phanalist looks like a really useful tool: I was searching for a tool that would fail if set cyclomatic complexity would have been over a threshold and it seems it does exactly this.

Maybe I don’t have yet a real use case to test the stateless rules, but knowing this tool exists is for sure a good starting point!

Going to read the other articles in this series: that I you!

Collapse
 
miroslav_mal_119abad494 profile image
Miroslav Maláč

Have you tried using ResetInterface with Swoole while coroutines are enabled? A coroutine can switch requests within the same context before the reset occurs, especially if there is I/O involved.