pixelfederation / swoole-bundle

Symfony Swoole Bundle
MIT License
32 stars 12 forks source link

Use cooperative scheduling in request handling by enabling swoole coroutine hooks for IO functions #9

Open Rastusik opened 2 years ago

Rastusik commented 2 years ago

Is your feature request related to a problem? Please describe. Current architecture of the swoole bundle expects the swoole http server process to handle exactly one request at once. I would like to enable cooperative scheduling by enabling the swoole IO functions coroutine hooks to be able to handle multiple requests concurrently, as the usage if Swoole was intended by design.

Describe the solution you'd like The bundle should have a configuration option (e.g. cooperative_scheduling) which would enable concurrent request processing. I'm currently doing research and figuring out how to do it, I've identified these pin points:

Describe alternatives you've considered I don't see any alternatives here, except the current implementation, feel free to discuss

Additional context Nope

smoelker commented 2 years ago

Just a thought on the stateful services / concurrency challenge: Symfony's DI container does not support concurrency at this moment but wouldn't the DI container be the best place to solve this problem?

I think it'd only require two changes to the container:

  1. Introduce a third lifetime scope for services (Singleton).
  2. Provide an extendable abstraction for storing request-scoped services.

Introduce a third lifetime scope for services In order to support concurrency we need three lifetime scopes:

  1. Singleton Always the same instance each time the service is requested from the container (true singleton).
  2. Scoped Same instance each time the service is requested from the container within the same scope. In a web context the scope would be a HTTP request.
  3. Transient New instance each time the service is requested from the container.

For traditional hosting environments (like PHP-FPM) Singleton and the Scoped would behave the same way.

Provide an extendable abstraction for storing request-scoped services Since the "scope" of the Scoped lifetime can mean something different for every host (webserver), we need some kind of abstraction. The code snippet below illustrates the concept. The container would be configured to take a dependency on a implementation of ScopeStorageInterface.


interface ScopeStorageInterface {
    public function has(string $serviceId): bool;
    public function get(string $serviceId): mixed;
    public function set(string $serviceId, mixed $instance): void;

}

class SwoolScopeStorage impement ScopeStorageInterface 
{
    public function get(string $serviceId) : mixed {
        return Co::getContext()['scoped_services'][$serviceId] ?? null; // Simplified
    }

    // ... etc
}

I wonder what you think of this approach and if it's feasable to have such a change implementen in the Symfony project.

Rastusik commented 2 years ago

Hi @smoelker , thank you for your suggestions. I'm not sure, but have you seen #10 ? I'm already testing the implementation on a real world project to be able to find bugs, for now there are some Symfony related memory leaks, but I'm working on it. So basically what you are describing is already done in the coroutines branch, I just need to test it better and find bugs. Regarding the Transient scope, this needs to be done on Symfony DI level and I'm not sure if that would be possible to do easily, but there is a way to process selected services even without the scope definition. Check the code and feel free to give any suggestions.