-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[2.3] [WIP] Synchronized services... #7007
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
Conversation
will review the PR in more detail when I get home but I think proxy objects might be a better solution. we could support automatically "unloading" of predict loaded proxies referencing request scoped services when we enter/leave the given sub request. |
This PR is transparent as far as the services are concerned, which is one of the primary characteristics of the service container, the objects must not know that they are managed by the service container. How would proxy objects work with listeners? |
As the DIC is the one that creates the proxies, it would it could track them and therefore ensure that the state is managed. So when I access the request proxy for the first time in a service it would fetch the request service from the DIC instance inside the proxy and so the proxy would become initialized. Then when I leave the scope/enter a new scope the DIC can automatically revert the back to the uninitialized state, ensuring that the service would be initialized again based on the now current state of the DIC. |
@fabpot By the way, the example you put about the locales, is currently an issue in the 2.2 branch, but not in the 2.1. |
On Twitter, someone suggested volatile instead of contagious? |
@lsmith77: Have a look at the PR, my solution is much simpler than that, no moving parts. |
Well I dont think my proposal is anymore complicated, especially of we add support for proxied services anyway. My concern is mostly about "propagating" or "recalling" the setters, because in theory the service could have assigned a request scoped service to another dependency and it may not be practical to have to write logic in the setter to then propagate this as well. Now in my proposal instead of recalling the setters, you simply iterate over the proxies and reset their state. this will then also update any dependency that are not managed by the DIC. from a users perspective its more reliable and i think from the DIC point of view its not any more complex either. again assuming we want to add proxy support anyway. |
Is using array_key_exists() really needed here? I know isset() is (was??) faster then array_key_exists(). About the naming +1 for volatile. |
volatile indeed sounds better .. does Spring not have similar needs? |
I think that the volatile name does not make justice, yes the service will be volatile changing with each request, but it will be fixed to the request itself, so it will not be available outside the scope of the request which will kind of confusing?. |
On Twitter still, someone suggests "synchronized" |
Looks excellent. May I suggest |
@merk: |
You're right. It sounds weird. The correct way of saying it is Another suggestion: isInfectious() 😏 |
Synchronized sounds good! 👍 |
how about "scopeSafe"? |
I suggested synchronized on twitter, because for some reason I couldn't comment here with my phone... The reason I think it is a good name is because it implies that the container does some work for you. (edit:) And volatile has an "unstable" ring to it, but that's of course a matter of taste. |
What about "Scoped Services" for the new concept? On 2/8/13, Gerard van Helden notifications@github.com wrote:
|
I've just updated the code to use synchronized instead of contagious. |
@@ -557,7 +557,7 @@ protected function get{$name}Service() | |||
*/ | |||
private function addServices() | |||
{ | |||
$publicServices = $privateServices = $aliasServices = ''; | |||
$publicServices = $privateServices = $aliasServices = $propagaters = ''; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
propagators? s/e/o
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've renamed it to synchronizers as this is more accurate now.
@@ -206,6 +206,10 @@ public function set($id, $service, $scope = self::SCOPE_CONTAINER) | |||
} | |||
|
|||
$this->services[$id] = $service; | |||
|
|||
if (method_exists($this, $method = 'synchronize'.strtr($id, array('_' => '', '.' => '_')).'Service')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mixing the assignment into the method_exists() call like that makes the code quite hard to read. Can we break it out to a separate $method = line and then the if() statement? That would improve readability considerably.
Overall I think this looks like a solid approach, and would resolve the issues we ran into in Drupal. It should be fairly easy for us (and anyone else using the DI component) to adapt to it. Nice work, Fabien! "synchronized" sounds like a fine name to me. I think this works better than proxy objects per @lsmith's suggestion, since it avoids new object instantiation entirely. It looks like existing constructor-based dependencies would still reinitialize, though, which gives developers a choice of whether or not it makes sense to reinitialize their services. |
@igorw wrote a very interesting article about the problems with syncronized services and scopes here: https://igor.io/2013/03/31/stateless-services.html I think it's worth reading as he presents some interesting points and demonstrates the problems very well. |
A little off topic, but if we abolish scopes completely. How would we handle scope="prototype" then? Really like to idea of @beberlei ! And it should be possible to introduce this without BC breaks no? |
@sstok my idea would only be about removing scope="request" (in the long run, i.e. 3.0) |
i would like to propose that we hold an IRC meeting to discuss this .. ideally tomorrow evening since we do not have much time until the end of the dev phase for 2.3 |
I will attend if it happens. |
I am afraid i am not into the topic deep enough to be able to responsibly say something about this. In the beta phase we maintained the rule that only people with insight into the code are allowed to vote on changes. I am not sure that except @fabpot anyone (maybe @stof ) can reasonable say that he can make an informed decision about this topic. My impression was that synchronized services is only necessary, because the request is wrongly a service, although its a data/value object. I proposed an idea about how to possibly solve this, and slowly deprecate request as a service. This was done without thinking about consequences like:
"request" as a service is a very fundamental concept in the MVC stack of Symfony2, it may well be that this pushes us to necessary solutions that we could have avoided much earlier, but not anymore in the 2.* lifecycle. I know Fabien tried to work with the request stack prior before merging this PR, so his decision to merge this took all the three points into account, which i cannot say from my experiments. I don't think it makes sense to discuss this topic further without having a concrete alternative on the table in form of a pull request. Since Fabien tried several different approaches here, I am not sure that any option is not something he has considered already. So without his definitive input on the topic with regard to the possibility of an alternative solutions, it doesn't make sense to discuss it. |
I agree with @igorw's blog post that this PR is problematic. More than being a blurry concept, "synchronized" has a very different connotation for any developer that used Java before (in Java, "synchronized" marks exclusively accessible code/services in multi-threaded environments). I also agree with @beberlei that the request should not be a service. It is no "service" by definition, as it does not process any business logic. It's just a data container. To me, a "requestContext" service with a |
It might seem obvious today that the Request is not a service, but back in the days, I fought hard to avoid the introduction of the Response as a service as well. So, we only have half of a problem today ;) The Request object is indeed a very special "service", or put another way, this is the only data object that is/should be managed by the service container. Why? to have the ability to inject the Request into other services. But just to be able to get the Request from the DIC, we had to introduce a lot of different notions: the request scope, synchronized services, strict arguments, synthetic services, and perhaps more. Even if all those concepts might be of interest by themselves, the truth is that they are not that often useful when managing "regular" services (and Silex/Pimple is probably the best demonstration of that). But as of this PR, I think we now have a working model for managing the Request and its life-cycle in the context of the DIC. I say "working model" because an average developer can now depend on the Request without knowing anything about the internals. That being said, I'm the first to recognize that things are complex behind the scene just for this one object (even if this is probably the most important one). I also agree that having so many features just to be able to easily work with the Request is a strong smell that something is probably wrong. So, what about @beberlei proposal? Here are my first thoughts:
As many of you probably know now, I'm not that good at thinking/talking about concepts without real/working code. So, I will play with @beberlei code in the next couple of days and get back here with my findings and thoughts (and I highly encourage others to do the same to get familiar with it). Then, we will list the pros and the cons of both approaches and that will allow us to discuss what to do for 2.3 and/or 3.0. Enough for now, let's go back to the code now! |
What do you think about reverting the PR until a final decision is made? |
A note about BC in relation to @beberlei's proposal: It might be possible to create a |
@bschussek Whatever the decision we take, we won't revert this PR as it fixes bugs with our current approach. And as we would have to keep the current way for BC reasons, this bug fix should stay in anyway. That's one of the reasons I did not consider using a registry/proxy in the first place when I was working on a solution for the problems we had. @igorw We discussed that already (it was @lsmith77 proposal IIRC) and that's out of question: the request is used quite frequently, so we won't introduce a proxy for such an important object in Symfony. Another drawback for @beberlei proposal I've just thought about:
|
When passing the request via action method signatures, we could pass in the plain, unproxied, request instance. There is no reason to ever put a controller into the request scope, same applies to listeners, that I guess also are guaranteed to get the proper request instance via the event object? |
@lsmith77 You are right for controllers, but for listeners, the problem is a bit different: they get the right request when the scope is entered, but before this PR, they were no aware that a request scope finished. And this is the only problem that this PR tried to address. |
Just FYI .. we already have one place where we use a registry which is the ManagerRegistry for doctrine. |
@lsmith77 which is also a pain point when using the bridges outside of the full stack framework as everything in components relies on a manager and the only implementation of it i have seen is the one in symfony. |
@lsmith77 the goal of the ManagerRegistry is to allow multiple managers in the app (and retrieved lazily), not to stack managers on top of each other. So we don't have the issue of poping the old one. |
What actually is the status of this issue? The fix discussed here is marked as merged, but #6932 is still open/broken. It's a little hard to comprehend this long discussion without background knowledge! |
…definitions (ogizanagi) This PR was merged into the 3.1 branch. Discussion ---------- [DependencyInjection] ContainerBuilder: Remove obsolete definitions | Q | A | ------------- | --- | Branch? | 3.1 | Bug fix? | yes | New feature? |no | BC breaks? | may be considered | Deprecations? | may be considered | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - IIUC, [the obsolete definitions thing was tied to scoped & sync services](#7007). Thus, this code [is not meant to be used since 3.0 and can be removed](#13289). However, it may be considered as a BC break and would require introducing a new deprecation instead, because the current code allows to set a service on a frozen container if it has an obsolete synthetic definition...which is weird, isn't it ? (I doubt this feature was explicitly intended to allow setting a synthetic service more than once, and it wasn't before sync services introduction) I suggest this as a patch for 3.1, under the pretext of forgotten code tied to a previous deprecation & feature removal, but let me know if it should be considered differently. Commits ------- daa7d00 [DependencyInjection] ContainerBuilder: Remove obsolete definitions
Todo:
refs #6932, refs #5012
This PR is a proof of concept that tries to find a solution for some problems we have with scopes and services depending on scoped services (mostly the request service in Symfony).
Basically, whenever you want to inject the Request into a service, you have two possibilities:
This PR addresses this issue by allowing to use the second option but you service still always has the right Request service (see below for a longer explanation on how it works).
There is another issue that this PR fixes: edge cases and weird behaviors. There are several bug reports about some weird behaviors, and most of the time, this is related to the sub-requests. That's because the Request is injected into several Symfony objects without being updated correctly when leaving the request scope. Let me explain that: when a listener for instance needs the Request object, it can listen to the
kernel.request
event and store the request somewhere. So, whenever you enter a sub-request, the listener will get the new one. But when the sub-request ends, the listener has no way to know that it needs to reset the request to the master one. In practice, that's not really an issue, but let me show you an example of this issue in practice:To fix these issues, this PR introduces a new notion in the DIC: synchronized services. When a service is marked as synchronized, all method calls involving this service will be called each time this service is set. When in a scope, methods are also called to restore the previous version of the service when the scope leaves.
If you have a look at the router or the locale listener, you will see that there is now a
setRequest
method that will called whenever the request service changes (because theContainer::set()
method is called or because the service is changed by a scope change).