Skip to content

Commit 37d9f64

Browse files
task: Preload singletons in idle state of job::execute
The worker object starst the JobCommandController without having a message to execute and idles while waiting for th emessage. This change loads all available singletons during the idle phase so that they are present once a message is ready for execution.
1 parent 731f712 commit 37d9f64

4 files changed

Lines changed: 162 additions & 0 deletions

File tree

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
namespace Netlogix\JobQueue\FastRabbit\SingletonPreloading;
4+
5+
use Neos\Flow\Core\Bootstrap;
6+
use Neos\Flow\ObjectManagement\Configuration\Configuration;
7+
use Neos\Flow\ObjectManagement\ObjectManager;
8+
use Neos\Flow\ObjectManagement\ObjectManagerInterface;
9+
use Neos\Flow\Reflection\ReflectionService;
10+
use Neos\Flow\Annotations as Flow;
11+
use Throwable;
12+
13+
use Traversable;
14+
15+
use function array_filter;
16+
use function is_a;
17+
18+
/**
19+
* Fetch all known singleton classes from the ObjectManager to have
20+
* all of them in the memory and ready to use.
21+
*
22+
* This should not be used "as is" because there's a high risk of
23+
* creating singletons with expiring connections, like, for example,
24+
* database connections.
25+
*
26+
* It's not enough to just exclude those database connections here
27+
* because there might be other singletons depending on those connections
28+
* through constructor injection, which triggers loading them anyway.
29+
*/
30+
class AllSingletonsPreloader implements SingletonsPreloader
31+
{
32+
#[Flow\InjectConfiguration(path: 'AllSingletonsPreloader.ignoreClassNames', package: 'Netlogix.JobQueue.FastRabbit')]
33+
protected array $ignoreClassNames = [];
34+
35+
public function __construct(
36+
protected readonly ObjectManager $objectManager,
37+
protected readonly ReflectionService $reflectionService
38+
) {
39+
}
40+
41+
public function collect(): void
42+
{
43+
$objectManager = Bootstrap::$staticObjectManager;
44+
foreach ($this->getSingletonClassNames($objectManager) as $className) {
45+
try {
46+
$objectManager->get($className);
47+
} catch (Throwable $e) {
48+
// ignore
49+
}
50+
}
51+
}
52+
53+
/**
54+
* @return Traversable<string>
55+
*/
56+
public function getSingletonClassNames(ObjectManagerInterface $objectManager): Traversable
57+
{
58+
foreach (self::getSingletonClassNamesFromReflection($objectManager) as $className) {
59+
foreach ($this->ignoreClassNames as $ignoredClassName) {
60+
if (is_a($className, $ignoredClassName, true)) {
61+
continue;
62+
}
63+
}
64+
yield $className;
65+
}
66+
}
67+
68+
#[Flow\CompileStatic]
69+
public static function getSingletonClassNamesFromReflection(ObjectManagerInterface $objectManager): array
70+
{
71+
return array_filter(
72+
array: $objectManager->get(ReflectionService::class)->getAllClassNames(),
73+
callback: static function ($className) use ($objectManager): bool {
74+
try {
75+
return $objectManager->getScope($className) === Configuration::SCOPE_SINGLETON;
76+
} catch (\Exception $e) {
77+
return false;
78+
}
79+
}
80+
);
81+
}
82+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
namespace Netlogix\JobQueue\FastRabbit\SingletonPreloading;
4+
5+
use Flowpack\JobQueue\Common\Command\JobCommandController;
6+
use Neos\Flow\Annotations as Flow;
7+
use Neos\Flow\Aop\JoinPointInterface;
8+
use Neos\Flow\Cli\Request;
9+
use Neos\Flow\Mvc\Controller;
10+
use Neos\Flow\ObjectManagement\ObjectManagerInterface;
11+
use Neos\Flow\Reflection\ClassReflection;
12+
13+
#[Flow\Aspect]
14+
class JobCommandInitializationAspect
15+
{
16+
public function __construct(
17+
protected readonly ObjectManagerInterface $objectManager
18+
) {
19+
}
20+
21+
#[Flow\AfterReturning('within(' . JobCommandController::class . ') && method(.*->initializeCommandMethodArguments())')]
22+
public function preloadSingletonsWhenJobCommandControllerGetsInitialized(JoinPointInterface $joinPoint): void
23+
{
24+
$jobCommandController = $joinPoint->getProxy();
25+
assert($jobCommandController instanceof JobCommandController);
26+
27+
$reflection = new ClassReflection($jobCommandController);
28+
29+
$commandMethodName = $reflection
30+
->getProperty('commandMethodName')
31+
->getValue($jobCommandController);
32+
33+
if ($commandMethodName !== 'executeCommand') {
34+
return;
35+
}
36+
37+
$request = $reflection
38+
->getProperty('request')
39+
->getValue($jobCommandController);
40+
assert($request instanceof Request);
41+
42+
$arguments = $reflection
43+
->getProperty('arguments')
44+
->getValue($jobCommandController);
45+
assert($arguments instanceof Controller\Arguments);
46+
47+
foreach ($arguments as $argument) {
48+
assert($argument instanceof Controller\Argument);
49+
if ($argument->isRequired() && !$request->hasArgument($argument->getName())) {
50+
// only preload if the request is blocked by fetching an argument via stdin
51+
$this->objectManager
52+
->get(SingletonsPreloader::class)
53+
->collect();
54+
return;
55+
}
56+
}
57+
}
58+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Netlogix\JobQueue\FastRabbit\SingletonPreloading;
4+
5+
interface SingletonsPreloader
6+
{
7+
public function collect(): void;
8+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Netlogix:
2+
JobQueue:
3+
FastRabbit:
4+
5+
AllSingletonsPreloader:
6+
ignoreClassNames:
7+
# cannot be preloaded due to missing constructor arguments
8+
Neos\Flow\ObjectManagement\CompileTimeObjectManager: Neos\Flow\ObjectManagement\CompileTimeObjectManager
9+
Neos\Flow\Property\TypeConverter\AbstractTypeConverter: Neos\Flow\Property\TypeConverter\AbstractTypeConverter
10+
Neos\Flow\Security\Authentication\Controller\AbstractAuthenticationController: Neos\Flow\Security\Authentication\Controller\AbstractAuthenticationController
11+
Neos\Flow\Session\Aspect\SessionObjectMethodsPointcutFilter: Neos\Flow\Session\Aspect\SessionObjectMethodsPointcutFilter
12+
# must not be preloaded because they hold connections that can expire
13+
Doctrine\DBAL\Connection: Doctrine\DBAL\Connection
14+
Doctrine\ORM\EntityManagerInterface: Doctrine\ORM\EntityManagerInterface

0 commit comments

Comments
 (0)