-
-
Notifications
You must be signed in to change notification settings - Fork 146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
When serializing & later unserializing exceptions thrown by clashing function calls #180
Comments
Hey @brentkelly I'm a bit puzzled by your issue and your PR. Mainly because there is no technical reason to do any of this, but let me go through all of them and address them all, because I think we can solve you issue in a different way. First off don't pollute the global namespace, and I know you can't help if because the framework of your choice did. Mainly telling you this that if you ever think of doing it you hopefully don't. Because it will another maintainers job harder. Secondly, technically this is a bug in But, thirdly, why are you serialising a |
Can you explain why you're serializing a If you are trying to do so to process a job in the background (as transferring to another thread or process), you need to solve this problem differently. You need to create your own |
@gitneko Knowing Laravel a little bit, they absolutely love singletons for easy access. If the callables in |
Hi @WyriHaximus thanks for your reply. I'll reverse your questions two & three so I can address them in that order:
Partially correct. Yes I am transferring it to another thread or process. But no, the reference to Unserializng works fine too except - aside from the parts outlined above. Whatever the case - referencing the full namespaced function fixes the issue and it works perfectly. From there. This allows us to easily use a clean promise-based approach to define code & state that should have its execution deferred to a future thread. Why would you want to do this? There are a ton of use cases that come to mind. But a couple off the top of my head:
My situation is the latter, working with a chatbot framework to use API calls. All of this can be achieved directly with closures no problem already. So why do I care - well for all for the reasons that promises are superior to straight closures in the first place.
Agree - it is technically a bug |
Here is a (very) basic example to illustrate it in action: Thread 1: use Opis\Closure\SerializableClosure;
use React\Promise\Deferred;
// write serialized content to this file so we can execute in two parts /
// different threads
$filename = __DIR__ . '/serialized';
$outside = [1, 2, 3];
$deferred = new Deferred();
$deferred->promise()
->then(function ($value) use ($outside) {
echo "Received value to then is $value\n";
echo "Outside is" . print_r($outside, 1) . "\n";
return 'bar';
})
->done(function ($value) {
echo "Received value to done is $value\n";
});
// write to a file for later use
$serialized = \Opis\Closure\serialize($deferred);
file_put_contents($filename, $serialized); Thread 2: use Opis\Closure\SerializableClosure;
use React\Promise\Deferred;
$filename = __DIR__ . '/serialized';
$serialized = file_get_contents($filename);
Opis\Closure\unserialize($serialized)
->resolve('foo'); This results in: PHP Fatal error: Uncaught Error: Call to undefined function resolve() in closure://static function ($value = null) use (&$target) {
if ($target !== null) {
$target->settle(resolve($value));
$target = null;
}
}:4
Stack trace:
#0 /home/brent/code/php/vendor/react/promise/src/Deferred.php(36): React\Promise\Promise::{closure}()
#1 closure://function () use ($deferred) {
$deferred->resolve('foo');
}(3): React\Promise\Deferred->resolve()
#2 /home/brent/code/php/index.php(40): {closure}()
#3 {main}
thrown in closure://static function ($value = null) use (&$target) {
if ($target !== null) {
$target->settle(resolve($value));
$target = null;
}
} on line 4 Applying my pull request results in: Received value to then is foo
Outside isArray
(
[0] => 1
[1] => 2
[2] => 3
)
Received value to done is bar
Done |
Not using singletons.
They do however also say they can resolve function and constant names so I'll log an issue there too & see where that leads. |
I'm sure there are other ways to also tackle the problem. However I'm trying to implement a clean easy-to-use and read declarative interface to defining both code that will execute now, and code that will execute based on future state / events. A promise is perfectly suited to describing this - given that is exactly what a promise does. In PHP the natural assumption is that a promise has to be declared and resolve in the same thread - for the reasons you outline above. But I'm exploring if I'm sure they may be some limitations & potential side effects - for a start if you're not careful you could easily have to start serializing & storing massive related object instances / datasets that were never intended to be written to storage. But at this stage it appears to work perfectly for my use case and provides a very clean & tidy interface for my team to use. |
FYI here is a more complex proof of concept showing use of |
And that is why we expect it to be solved there and not here. Not because we don't want to help you out, but because we have been spending a lot of time to improve our code quality for the upcoming v3 release (the master branch). And this would be a regression for that, to solve a problem that doesn't exist within the repository. Now looking at the example you posted later on this issue I'm pretty sure we can make what you are looking for work. So to take you example, it looks like adding Thread 1: use Opis\Closure\SerializableClosure;
// write serialized content to this file so we can execute in two parts /
// different threads
$filename = __DIR__ . '/serialized';
$outside = [1, 2, 3];
// write to a file for later use
$serialized = \Opis\Closure\serialize(function ($value) use ($outside) {
echo "Received value to then is $value\n";
echo "Outside is" . print_r($outside, 1) . "\n";
return 'bar';
});
file_put_contents($filename, $serialized); Thread 2: use Opis\Closure\SerializableClosure;
use React\Promise\Deferred;
$filename = __DIR__ . '/serialized';
$serialized = file_get_contents($filename);
echo "Received value to done is ", (Opis\Closure\unserialize($serialized))('foo'), "\n"; |
Not even opis can get around this limitation. Serializing There's no synchronization, no notification, nothing that makes both If you need that, you need as I've described before a message passing approach and keep two distinct deferred objects on each side and create these reactive when a "job arrives". I've gone down that road before. Since it's a bug in |
Yes the example is not attempting to show a use case for where promises are needed. As above the example was designed to be as simple as possible for the sole purpose of demonstrating the problem. |
It can, and does. See below.
Yes I understand how serializing works.
Agree they are not references to the same instance (as in memory location), but they are duplicates (as much as serialization can achieve) - in this manner they are essentially the same & they do behave the same - a few exceptions aside (discussed below).
Actually you're wrong here.
Yes when they unserialize they are essentially clones of the original instances. And yes they won't be synchronised to anything that occured after they were serialized - which is kind of the point given you don't want them destroyed along with the original instance when the thread terminates. Also obviously if referenced objects contain active connections (e.g. database connections) that usually get broken during serialization then those classes will need to declare Look at my example above: https://github.com/brentkelly/serialized-promise-demo. This is a working demonstration of it doing precisely what you say can't happen:
|
Appreciate your time & thoughts here guys. Thanks. I have an implementation of this working perfectly now aside from the issue my PR addresses. Obviously there are the usual side effects of serializing a programmer needs to be aware of (as illustrated in the post above) but aside from that it seems to be working just as well as any serialized closure solution. While the bug exists in While I appreciate wanting to keep a code base clean with a future version 3, I was hoping you would consider a Obviously it's your opinion that counts here though & it sounds like that decision has been made - so I'll close this & have to run on a fork or switch to a more compatible project unless Thanks. |
If you serialize a
Deferred
instance, and later restore / unserialize it (using Opis/Closure) - attempting to resolve from there will potentially throw errors if there are other global functions namedresolve
orreject
.As is the case with Laravel (where there is a global
resolve
function).Deferred
instancethen
ordone
.Deferred
instance$unserializedDeferred->resolve('foo');
This throws an error in Laravel as the namespace seems to be lost when unserializing. Meaning the call to the
resolve()
function (targeting\React\Promise\resolve
) actually calls Laravel's globalresolve
function - throwing an error.By being more specific on the function call and including the namespace, this avoids this confusion & calls the correct method.
For example: on line 232 of
\React\Promise\Promise
Instead of:
Call:
IMO this makes the code easier to read also - removing any ambiguity as to where these functions are defined.
I've submitted a PR to address the issue: #179
The text was updated successfully, but these errors were encountered: