Skip to content

Commit

Permalink
feat(coroutines): added support for coroutines into taks workers, con…
Browse files Browse the repository at this point in the history
…figuration reordering and refactoring
  • Loading branch information
Rastusik committed Feb 6, 2023
1 parent 19eb714 commit b5c4a09
Show file tree
Hide file tree
Showing 39 changed files with 1,133 additions and 549 deletions.
6 changes: 0 additions & 6 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,6 @@ insert_final_newline = false
[*.{yml,yaml,json}]
indent_size = 2

[src/Bridge/Symfony/**/*.{yml,yaml}]
indent_size = 4

[tests/Fixtures/Symfony/app/config/**/*.{yml,yaml}]
indent_size = 4

[composer.json]
indent_size = 4

Expand Down
236 changes: 123 additions & 113 deletions docs/configuration-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,119 +11,129 @@ Documentation of available configuration parameters. See also symfony [bundle co

```yaml
swoole:
http_server:
port: 9501
host: 0.0.0.0
running_mode: process
socket_type: tcp
ssl_enabled: false
trusted_hosts: localhost,127.0.0.1
trusted_proxies:
- '*'
- 127.0.0.1/8
- 192.168.2./16

# enables static file serving
static: advanced
# equals to:
# ---
# static:
# public_dir: '%kernel.project_dir%/public'
# strategy: advanced
# mime_types: ~
# ---
# strategy can be one of: (default) auto, off, advanced, default
# - off: turn off feature
# - auto: use 'advanced' when debug enabled or not production environment
# - advanced: use request handler class \K911\Swoole\Server\RequestHandler\AdvancedStaticFilesServer
# - default: use default swoole static serving (faster than advanced, but supports less content types)
# ---
# mime types registration by file extension for static files serving in format: 'file extension': 'mime type'
# this only works when 'static' strategy is set to 'advanced'
#
# mime_types:
# '*': 'text/plain' # fallback override
# customFileExtension: 'custom-mime/type-name'
# sqlite: 'application/x-sqlite3'

# enables hot module reload using inotify
coroutines_support:
enabled: false
# default false. when enabled, swoole coroutine hooks for IO apis get activated
# (https://www.swoole.co.uk/docs/modules/swoole-coroutine-enableCoroutine) and all stateful services
# are being used in contextual way with multiple instances for each service
# (https://www.swoole.co.uk/article/isolating-variables-with-coroutine-context)
stateful_services:
- SomeStatefulServiceId
# add stateful service ids which need to be proxified and the bundle cannot detect them alone
# check the section below about coroutines usage
compile_processors:
- class: ProcessorClass1
priority: 10
- ProcessorClass2 # default priority
# register classes implementing the CompileProcessor interface
# check the section below about coroutines usage
hmr: auto
# hmr can be one of: off, (default) auto, inotify
# - off: turn off feature
# - auto: use inotify if installed in the system
# - inotify: use inotify

# enables api server on specific port
# by default it is disabled (can be also enabled using --api flag via cli)
api: true
# equals to:
# ---
# api:
# enabled: true
# host: 0.0.0.0
# port: 9200

# additional swoole symfony bundle services
services:

# see: \K911\Swoole\Bridge\Symfony\HttpFoundation\TrustAllProxiesRequestHandler
trust_all_proxies_handler: true

# see: \K911\Swoole\Bridge\Symfony\HttpFoundation\CloudFrontRequestFactory
cloudfront_proto_header_handler: true

# see: \K911\Swoole\Bridge\Upscale\Blackfire\WithProfiler
blackfire_profiler: false

# see: \K911\Swoole\Bridge\Tideways\Apm\WithApm
tideways_apm:
enabled: true
service_name: 'app_name' # service name for Tideways APM UI

# swoole http server settings
# see https://www.swoole.co.uk/docs/modules/swoole-server/configuration
settings:
reactor_count: 2
worker_count: 4
# when not set, swoole sets these are automatically set based on count of host CPU cores
task_worker_count: 2 # one of: positive number, "auto", or null to disable creation of task worker processes (default: null)

log_level: auto
# can be one of: (default) auto, debug, trace, info, notice, warning, error
# - auto: when debug set to debug, when not set to notice
# - {debug,trace,info,notice,warning,error}: see swoole configuration

log_file: '%kernel.logs_dir%/swoole_%kernel.environment%.log'
pid_file: /var/run/swoole_http_server.pid

buffer_output_size: 2097152
# in bytes, 2097152b = 2 MiB

package_max_length: 8388608
# in bytes, 8388608b = 8 MiB

worker_max_request: 0
# integer >= 0, indicates the number of requests after which a worker reloads automatically
# This can be useful to limit memory leaks
worker_max_request_grace: ~
# 'grace period' for worker reloading. If not set, default is worker_max_request / 2. Worker reloads
# after 'worker_max_request + rand(0,worker_max_request_grace)' requests
http_server:
port: 9501
host: 0.0.0.0
running_mode: process
socket_type: tcp
ssl_enabled: false
trusted_hosts: localhost,127.0.0.1
trusted_proxies:
- '*'
- 127.0.0.1/8
- 192.168.2./16

# enables static file serving
static: advanced
# equals to:
# ---
# static:
# public_dir: '%kernel.project_dir%/public'
# strategy: advanced
# mime_types: ~
# ---
# strategy can be one of: (default) auto, off, advanced, default
# - off: turn off feature
# - auto: use 'advanced' when debug enabled or not production environment
# - advanced: use request handler class \K911\Swoole\Server\RequestHandler\AdvancedStaticFilesServer
# - default: use default swoole static serving (faster than advanced, but supports less content types)
# ---
# mime types registration by file extension for static files serving in format: 'file extension': 'mime type'
# this only works when 'static' strategy is set to 'advanced'
#
# mime_types:
# '*': 'text/plain' # fallback override
# customFileExtension: 'custom-mime/type-name'
# sqlite: 'application/x-sqlite3'

# enables hot module reload using inotify
hmr: auto
# hmr can be one of: off, (default) auto, inotify
# - off: turn off feature
# - auto: use inotify if installed in the system
# - inotify: use inotify

# enables api server on specific port
# by default it is disabled (can be also enabled using --api flag via cli)
api: true
# equals to:
# ---
# api:
# enabled: true
# host: 0.0.0.0
# port: 9200

# additional swoole symfony bundle services
services:

# see: \K911\Swoole\Bridge\Symfony\HttpFoundation\TrustAllProxiesRequestHandler
trust_all_proxies_handler: true

# see: \K911\Swoole\Bridge\Symfony\HttpFoundation\CloudFrontRequestFactory
cloudfront_proto_header_handler: true

# see: \K911\Swoole\Bridge\Upscale\Blackfire\WithProfiler
blackfire_profiler: false

# see: \K911\Swoole\Bridge\Tideways\Apm\WithApm
tideways_apm:
enabled: true
service_name: 'app_name' # service name for Tideways APM UI

# swoole http server settings
# see https://openswoole.com/docs/modules/swoole-server/configuration
settings:
reactor_count: 2
worker_count: 4
# when not set, swoole sets these are automatically set based on count of host CPU cores

log_level: auto
# can be one of: (default) auto, debug, trace, info, notice, warning, error
# - auto: when debug set to debug, when not set to notice
# - {debug,trace,info,notice,warning,error}: see swoole configuration

log_file: '%kernel.logs_dir%/swoole_%kernel.environment%.log'
pid_file: /var/run/swoole_http_server.pid

buffer_output_size: 2097152
# in bytes, 2097152b = 2 MiB

package_max_length: 8388608
# in bytes, 8388608b = 8 MiB

worker_max_request: 0
# integer >= 0, indicates the number of requests after which a worker reloads automatically
# This can be useful to limit memory leaks
worker_max_request_grace: ~
# 'grace period' for worker reloading. If not set, default is worker_max_request / 2. Worker reloads
# after 'worker_max_request + rand(0,worker_max_request_grace)' requests

task_worker: # task workers' specific settings
services:
reset_handler: true # default true, set to false to disable services resetter on task processing end
settings:
worker_count: 2 # one of: positive number, "auto", or null to disable creation of task worker processes (default: null)
platform:
coroutines:
enabled: false
# default false. when enabled, swoole coroutine hooks for IO apis get activated
# (https://openswoole.com/docs/modules/swoole-coroutine-enableCoroutine) and all stateful services
# are being used in contextual way with multiple instances for each service
# (https://openswoole.com/article/isolating-variables-with-coroutine-context)
max_coroutines: 100
# default value is 100000, if not set
# it helps limitting this number, so you can be sure, that there is a limited amount of proxified service instances
# (https://openswoole.com/docs/modules/swoole-server/configuration#max_coroutine)
stateful_services:
- SomeStatefulServiceId
# add stateful service ids which need to be proxified and the bundle cannot detect them alone
# check the section below about coroutines usage
compile_processors:
- class: ProcessorClass1
priority: 10
- ProcessorClass2 # default priority
# register classes implementing the CompileProcessor interface
# check the section below about coroutines usage
```

## Additional info for coroutines usage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,12 @@
use Doctrine\Bundle\DoctrineBundle\ManagerConfigurator;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use ReflectionClass;
use ReflectionProperty;
use UnexpectedValueException;

final class BlockingProxyFactoryOverridingManagerConfigurator
{
private ManagerConfigurator $wrapped;

private static ?ReflectionProperty $emProxyFactoryPropRefl = null;
private static ?\ReflectionProperty $emProxyFactoryPropRefl = null;

public function __construct(ManagerConfigurator $wrapped)
{
Expand All @@ -25,7 +22,7 @@ public function __construct(ManagerConfigurator $wrapped)
public function configure(EntityManagerInterface $entityManager): void
{
if (!$entityManager instanceof EntityManager) {
throw new UnexpectedValueException(sprintf('%s needed, got %s.', EntityManager::class, get_class($entityManager)));
throw new \UnexpectedValueException(sprintf('%s needed, got %s.', EntityManager::class, get_class($entityManager)));
}

$this->replaceProxyFactory($entityManager);
Expand All @@ -39,10 +36,10 @@ private function replaceProxyFactory(EntityManager $entityManager): void
$proxyFactoryProp->setValue($entityManager, $proxyFactory);
}

private function getEmProxyFactoryReflectionProperty(): ReflectionProperty
private function getEmProxyFactoryReflectionProperty(): \ReflectionProperty
{
if (null === self::$emProxyFactoryPropRefl) {
$emReflClass = new ReflectionClass(EntityManager::class);
$emReflClass = new \ReflectionClass(EntityManager::class);
self::$emProxyFactoryPropRefl = $emReflClass->getProperty('proxyFactory');
self::$emProxyFactoryPropRefl->setAccessible(true);
}
Expand Down
3 changes: 1 addition & 2 deletions src/Bridge/Doctrine/DoctrineProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use UnexpectedValueException;

final class DoctrineProcessor implements CompileProcessor
{
Expand All @@ -26,7 +25,7 @@ public function process(ContainerBuilder $container, Proxifier $proxifier): void
$entityManagers = $container->getParameter('doctrine.entity_managers');

if (!\is_array($entityManagers)) {
throw new UnexpectedValueException('Cannot obtain array of entity managers.');
throw new \UnexpectedValueException('Cannot obtain array of entity managers.');
}

$connectionSvcIds = [];
Expand Down
3 changes: 1 addition & 2 deletions src/Bridge/Doctrine/ORM/EntityManagerStabilityChecker.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@

use Doctrine\ORM\EntityManager;
use K911\Swoole\Bridge\Symfony\Container\StabilityChecker;
use UnexpectedValueException;

final class EntityManagerStabilityChecker implements StabilityChecker
{
public function isStable(object $service): bool
{
if (!$service instanceof EntityManager) {
throw new UnexpectedValueException(\sprintf('Invalid service - expected %s, got %s', EntityManager::class, \get_class($service)));
throw new \UnexpectedValueException(\sprintf('Invalid service - expected %s, got %s', EntityManager::class, \get_class($service)));
}

return $service->isOpen();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

namespace K911\Swoole\Bridge\Symfony\Bundle\DependencyInjection\CompilerPass;

use K911\Swoole\Bridge\Symfony\Bundle\DependencyInjection\ContainerConstants;
use K911\Swoole\Bridge\Symfony\Container\CoWrapper;
use K911\Swoole\Bridge\Symfony\Messenger\ContextReleasingTransportHandler;
use K911\Swoole\Bridge\Symfony\Messenger\SwooleServerTaskTransportFactory;
use K911\Swoole\Bridge\Symfony\Messenger\SwooleServerTaskTransportHandler;
use K911\Swoole\Server\HttpServer;
Expand Down Expand Up @@ -32,5 +35,16 @@ public function process(ContainerBuilder $container): void
$transportHandler->setArgument('$decorated', new Reference(SwooleServerTaskTransportHandler::class.'.inner'));
$transportHandler->setDecoratedService(TaskHandlerInterface::class, null, -10);
$container->setDefinition(SwooleServerTaskTransportHandler::class, $transportHandler);

if (false === (bool) $container->getParameter(ContainerConstants::PARAM_COROUTINES_ENABLED)) {
return;
}

$svcResettingHandler = new Definition(ContextReleasingTransportHandler::class);
$svcResettingHandler->setArgument('$decorated', new Reference(ContextReleasingTransportHandler::class.'.inner'));
$svcResettingHandler->setArgument('$coWrapper', new Reference(CoWrapper::class));
// this decorator has to be on top, so it can reset coroutine context after coroutine is finished
$svcResettingHandler->setDecoratedService(TaskHandlerInterface::class, null, -10000);
$container->setDefinition(ContextReleasingTransportHandler::class, $svcResettingHandler);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@
use K911\Swoole\Bridge\Symfony\Container\Proxy\Instantiator;
use K911\Swoole\Bridge\Symfony\Container\ServicePool\DiServicePool;
use K911\Swoole\Bridge\Symfony\Container\StabilityChecker;
use RuntimeException;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use UnexpectedValueException;

final class Proxifier
{
Expand Down Expand Up @@ -58,7 +56,7 @@ public function __construct(
public function proxifyService(string $serviceId): void
{
if (!$this->container->has($serviceId)) {
throw new RuntimeException(sprintf('Service missing: %s', $serviceId));
throw new \RuntimeException(sprintf('Service missing: %s', $serviceId));
}

$serviceDef = $this->container->findDefinition($serviceId);
Expand Down Expand Up @@ -95,7 +93,7 @@ public function getResetterRefs(): array
private function doProxifyService(string $serviceId, Definition $serviceDef): void
{
if (!$this->container->has($serviceId)) {
throw new RuntimeException(sprintf('Service missing: %s', $serviceId));
throw new \RuntimeException(sprintf('Service missing: %s', $serviceId));
}

$this->prepareProxifiedService($serviceDef);
Expand All @@ -115,7 +113,7 @@ private function doProxifyService(string $serviceId, Definition $serviceDef): vo
private function doProxifyDecoratedService(string $serviceId, Definition $serviceDef): void
{
if (null === $serviceDef->innerServiceId) {
throw new UnexpectedValueException(sprintf('Inner service id missing for service %s', $serviceId));
throw new \UnexpectedValueException(sprintf('Inner service id missing for service %s', $serviceId));
}

$decoratedServiceId = $serviceDef->innerServiceId;
Expand Down
Loading

0 comments on commit b5c4a09

Please sign in to comment.