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
Make pling thread safe by using a connection pool and thread local storage #2
Conversation
The connections to APN are the only connections in pling that are steadily held open (Apple requires this). Without a connection pool there might be race-conditions when using pling in a multi-threaded context. This adds a connection pool so that only one thread at a time can use an open connection to APN.
I like the connection pool for APN, that certainly makes sense. 👍 I'm not entirely sure about the changes to C2DM and GCM. Are you sure those are a problem as well? As far as I know, Faraday is thread-safe and the "connection" as it is now, is basically a factory for requests, which are encapsulated in their own instances. What problem am I missing here? :) |
I don't think you're missing anything. I was wondering the same thing: is this necessary? The reason for my change was this: I couldn't find anything that said that Faraday is thread-safe. And: My other attempt was to not cache the connection at all. That would create a new object every time a request is made, but as from my understanding of the Ruby GC that shouldn't be a problem, since it's basically a local object that can be swept on every run. I actually implemented that and then changed it to Or do you think using no cache is the better solution? |
Instead of caching the connections in the C2DM and GCM gateways in instance variables, we use the thread local storage provided by `Thread.current`. The reason for that is thread safety, since gateway objects are practically singleton objects that can be used by multiple threads at the same time. With this change each thread that calls `deliver` on a gateway gets a fresh connection object and doesn't interfer with the other threads' state.
Fixed the specs and did a force push. |
You're right, It's not that I'm totally opposed to Not using a cache might be an option too, yes. However it might be a problem when sending a lot of messages in a short amount of time. But then again there are probably other issues as well in this case… One more thing that came to my mind: What about wrapping the gateway in a pool instead of the connections? This way pling could - in a way - handle the "thread safety" for all gateways, making it much simpler to develop thread-safe gateways…? |
One more thing about |
Thinking about it some more, I agree with your concern over And I think the most reasonable approach, at least in my opinion would just be to not cache the Faraday connection. That's the most simple and easiest to understand solution. Using a connection pool for each gateway could either slow sending a lot of messages down (since threads had to wait for the gateway, even if it's just a stateless HTTP connection) or cause a higher memory usage. Even though the approach is still nice and does make things easier, I'd prefer to not cache the connections in GCM/C2DM. Opinions? |
Ok, here is what I'm thinking about: I share the concerns about The connection pool is a good idea, but I think in general you want a connection pool not to be inaccessible (wrapped inside a private method of a class), because this means you can't share the connection pool, which is kind of the point of the connection pool concept. The current implementation allows sharing the connections over multiple threads, but not over multiple instances in the same thread. I think the right way to implement this would be to separate configuration/connection from the rest of the class, so you can have one connection pool by configuration und reuse the pool in other instances - similar to the way ActiveRecord does it. But I'm not sure if this is beyond this PR and we should tackle the problem another time. Please note that the TravisCI builds fail, because REE and 1.8.7 are not supported by celluloid (anymore?). Also the RBX build fails, but for other reasons I guess. I think we can drop support for REE and 1.8.7. Not sure what to do about RBX as I don't use it. |
So I'd say:
|
Looks like we agree about I don't agree with implementing the connection pool as a public method. The idea is to have multiple connections available, so that the gateway can use different connections for different threads. There's no reason why anything else but the gateway should use those connections. About the REE and 1.8.7 support: I agree that it's safe to drop them. About the Rubinius issue: Simply change the |
Just for the record: I didn't mean to make the connection just a public method but to separate it from the rest of the class entirely. And this has a point, because at the moment, if you initialize 5 instances of an APN gateway, you will have 5 connection pools with the exact same configuration, which makes no sense. Connection pools normally have to serve both purposes: multiple instances and multiple threads. |
In pling a gateway is basically what an adapter is in Active Record. It takes the credentials and connects to the push service, while implementing a standard interface to send messages via this particular service. The One example use-case of having multiple gateways of the same type is to send push notifications to different iOS apps. (Think: Facebook App/Facebook Pages App or Foursquare / Swarm) |
Using Thread.current is basically using a hidden global variable. This is simpler and safer.
Alright, pushed the changes. I don't get the error |
Make pling thread safe by using a connection pool and thread local storage
This is an attempt to make the gem thread-safe by changing the way the gateways handle their connections.
Since every gateway is in practice a singleton to which every thread has access when calling
Pling.deliver
race-conditions and data corruptions might arise.Especially the long-lasting connection in the APN gateway poses a problem. But the caching of connections in C2DM and GCM gateways via instance variables is not totally thread-safe either.
This change uses a connection pool for APN, so that each thread gets its own connection (which is kept alive) as soon as it wants it.
In the C2DM and GCM gateways this implementation uses thread local storage to cache the connection object instead of an instance variable.
What do you think?