swoole-src
swoole-src copied to clipboard
Event on coroutine context switch
Because of the nature of application I am writing I need to have a global variable that is unique to each request. To do so I will need to switch it every time swoole switches context between different coroutines.
Could you add an event that allows to hook into coroutine context switch, so it will be possible to save and load the custom variable, to ensure each request will have it own verison?
I expect that will help with a more wider support for swoole, especially with legacy applications that use some kind of global objects.
Support for something like this would be nice (A simplified example):
class Singleton {
public $currentRequest = null;
public $requests = [];
}
Coroutine\on('started', function($newCid) {
// Do nothing
});
Coroutine\on('switch', function($oldCid, $newCid) {
Singleton::$requests[$oldCid] = Singleton::$currentRequest;
Singleton::$currentRequest = Singleton::$requests[$newCid];
});
Coroutine\on('finished', function($oldCid) {
unset(Singleton::$requests[$oldCid]);
});
Following events should be possibly enough to handle all use cases:
# When a new coroutine starts:
- started
- switched
- entered
# When returning from previous coroutine
- exited
- switched
- entered
# When a coroutine ends
- exited
- switched
- finished
Your Singleton is wrong... if you have multiple workers, you can end up with a few copies of one, or a different one in each thread. And when it comes to coroutines, they use/overwrite the same global variable within that thread.
You need to parse the coroutine tree and use Co::getContext, something like this example:
<?php
use \Co;
use \InvalidArgumentException;
/**
* Class Context
* Swoole data is passed around between coroutines, so we can't key the data in static variables per PID.
* Instead we need to use the Swoole\Coroutine::getContext and index it per Coroutine ID.
*/
class Context
{
/**
* Set a variable at this context level, so any child in the tree can access it.
* @param string $key
* @param mixed $value
*/
public static function set(string $key, mixed $value)
{
Co::getContext()[$key] = $value;
}
/**
* Get a value from the context tree.
* @param string $key
* @param mixed|null $default
* @return mixed
*/
public static function get(string $key, mixed $default = null): mixed
{
$cid = Co::getcid();
do {
if (isset(Co::getContext($cid)[$key])) {
return Co::getContext($cid)[$key];
}
$cid = Co::getPcid($cid);
} while ($cid !== -1 && $cid !== false);
# Not found
return $default ?? throw new InvalidArgumentException("Could not find `{$key}` in context.", 404);
}
/**
* Replaces a value in the context tree.
* @param string $key
* @param mixed $value
* @return bool
*/
public static function replace(string $key, mixed $value): bool
{
$cid = Co::getcid();
do {
if (isset(Co::getContext($cid)[$key])) {
Co::getContext($cid)[$key] = $value;
return true;
}
$cid = Co::getPcid($cid);
} while ($cid !== -1 && $cid !== false);
throw new InvalidArgumentException("Could not find `{$key}` in context.");
}
/**
* Replaces a value in the context tree.
* @param string $key
* @return bool
*/
public static function delete(string $key): bool
{
return self::replace($key, null);
}
}
It would be helpful to have a way to "hook a context" once, and then all it's children to be able to use that one, without having these loops in PHP, but usually you have some $request object you pass around that you can link the "global" variable to.
That won't work. The example I showed was a simplified version to show the problem. The singleton is a third party class, that a lot of other third party classes are extending. I have no way of modifying the way it works.
But the base class contains a way to switch context using a single method. So All I need to do is somehow hook into the context switching logic and do that manually.
In a perfect world, yes you would use Swoole context to create per request singletons, but if you are using swoole together with non-swoole applications, then that might not be possible.
As I said this feature would allow swoole to be used with a wide variety of non-swoole applications, by giving them the ability to swap their singletons/global variables. I think opening the ability to use swoole with almost any application would greatly benefit swoole.
Here is the class that I need this feature for:
https://github.com/illuminate/support/blob/master/Facades/Facade.php
The key features here is the protected static $app that needs to be swapped using its
public static function setFacadeApplication($app) to change the service container used.
You need to check out laravel-swoole and see how they work with facades.
The reason those don't work, is because your static variable was meant to be self contained within a request, and in Swoole one request (thread) can accommodate N requests.
What you want, can be simply done by my example code above plus a custom go function where you would wrap the Swoole go function.
But doing that everywhere well be super slow, and you should really not be using static variable/globals/facades with Swoole. ( Once you get that working, you will hit a wall with the db connection and everything using sockets and whatnot, if they use a singleton like that)