Closed ValiDrv closed 4 years ago
The ZendVM has limitations, cannot suspend coroutine in the get/set magic methods.
When using __get for the first time, it will mark ZEND_ACC_USE_GUARDS on the class entity, The second time it will directly return null.
There is no solution, this needs to modify zendvm
Duplicate with https://github.com/swoole/swoole-src/issues/2625
@matyhtf thank you, that makes sense.
I do suggest tho, to make Swoole throw a [Swoole]RuntimeException if ever a coroutine tries to be suspended inside a get/set, if possible.
@ValiDrv Swoole cannot determine whether it is in the get/set method. When switching the coroutine, the scheduler needs to analyze all call stacks, and the performance loss is large
@matyhtf I totally understand that, but I don't think you need to track anything actually, and the performance hit would only happen when an exception is thrown, where the code logic would break anyway.
I'm not familiar with the Swoole C code, but I assume there is some kind of global error handler wrapping the entire code.
The PHP equivalent would be something like this:
try {
# Swoole code
}catch (Throwable $e) {
# Global error handler
# Some smarter future proof way to
# - match only "Notice: Undefined property: DTO1::$record in /test1.php on line 41" (Maybe by PHP internal error code)
# - match a list of php calls which use ZEND_ACC_USE_GUARDS, in this case ->__get() & ->__set()
# - The performance hit (loop getTraceAsString) should only happen when an exception is thrown (code could not work either way here)
if (preg_match('~Undefined property:~i', $e->getMessage()) && preg_match('~->(?:__get|__set)\(\)$~m', $e->getTraceAsString())) {
# Reword the exception
throw new RuntimeException("Cannot suspend coroutine in the __get/__set magic methods.");
}
# Send original
throw $e;
}
@ValiDrv
Yes, we can do that, but not good, you even use regex...
This is actually the same as multi-threaded access to the same resource, the OS will not give you any warning/exception. Of course, they are not the same, I think you can implement the lock mechanism through channels, but it is not very convenient to write.
The best way is not to use magic methods
Thanks. The regex was just a pseudo code example.
We wrapped all coroutine calls in a custom function, which in dev can check the call stack and throw an error of its called from get/set. (it's not ideal).
Doing so, we found a few places coroutines were called from __get
, which made logical sense in the grand scheme of things.
1. What did you do? If possible, provide a simple script for reproducing the error.
We work with allot of data transfer objects which can be chained like this:
$obj->child->child->property;
which are lazy loaded from the database on access.The problem is, if we use a
coroutine
to wait for the database results when we access->child
, the second request on the same object will fail with aNotice: Undefined property:
It seems to work using
__call
magic method,$obj->child()->child()->property();
Is there a way to make
__get
/__set
work/is there a workaround to make$obj->child->child->property;
work property when dealing withcoroutines
?Test code
Run the code:
2. What did you expect to see?
The
__get
function to be called5
times.3. What did you see instead?
The
__get
function got called only once, and 4 times it seems the__get
does not exist.**4. What version of Swoole are you using (show your
php --ri swoole
)?uname -a
&php -v
&gcc -v
) ?**phpswoole/swoole
Ps: Google translate for: https://wiki.swoole.com/#/coroutine/notice?id=%e5%9c%a8-__get-__set-%e9%ad%94%e6%9c%af%e6%96%b9%e6%b3%95%e9%87%8c%e4%b8%8d%e5%be%97%e4%ba%a7%e7%94%9f%e5%8d%8f%e7%a8%8b%e5%88%87%e6%8d%a2 Suggests to implement get/ set call method explicitly.
How can that be done without:
$obj->_get('child')->_get('child')->_get('property');
?