-
Notifications
You must be signed in to change notification settings - Fork 601
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
Fix OHHTTPStubsProtocol threading #96
Conversation
Per Apple docs https://developer.apple.com/library/prerelease/ios/samplecode/CustomHTTPProtocol/Listings/Read_Me_About_CustomHTTPProtocol_txt.html : ``` In addition, an NSURLProtocol subclass is expected to call the various methods of the NSURLProtocolClient protocol from the client thread ``` So, instead of having callbacks happen on a background queue, OHHTTPStubsProtocol need to capture the executing runloop and execute callbacks from that runloop. This is a requirement of NSURLProtocol.
s/)/]
{ | ||
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); | ||
dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block); | ||
dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ | ||
CFRunLoopPerformBlock(self.clientRunLoop, kCFRunLoopDefaultMode, ^{ |
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.
Just out of curiosity, why not use block
directly as the 3rd parameter (as block
is already a dispatch_block_t
) instead of doing ^{ block() }
?
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.
Yup, it was unnecessary. Fixed.
Thanks, this seems ace! 👍 Do you think you would be able to add a Unit Test for that (to ensure that every delegate method is indeed invoked on the expected thread)? Also, don't forget to add an entry in the (Note: no need to create a new PR, new commits to your branch will be added here automatically) |
Totally understand wanting the unit test to go alongside. I doubt I'll On Thu, Apr 2, 2015 at 1:47 PM, AliSoftware notifications@github.com
|
👍. We're being bitten by what I think is this same issue. |
This issue was being hit quite frequently in our unit tests and this fix completely eliminates it. It only would get triggered about 5% of the time, but when unit tests are run ~500 times per day, those 25 failures are blocking for dev. |
@NSProgrammer that sounds exactly like what we're seeing. (cc @Twigz) |
I was planning to wait until I got time to add unit tests for that PR before merging it but maybe I'll merge it sooner and add tests later to avoid waiting too long. Will keep you guys posted |
Hi guys, I can't find a way to create a failing Unit Test: I wrote a test that creates an Here is my unit test so far. Do any of you see anything wrong with that test? isn't it testing the right thing? Because it is passing, even before applying that fix so I'm wondering… Do you guys have an actual example of when this fails for you, so I can get inspiration to write a proper test that would properly avoid regression once this fix is merged? |
At my job we're seeing this hang only one in 50 or so times. Assuming the hanging is a direct result of this particular threading bug, it'll be hard to write a consistently-failing unit test for it 😞 That test looks promising. I wonder, though, if it's possible to more directly test this requirement somehow…
Is there any way for the test to get the That might be a silly question, but I am not sure exactly how the |
@cdzombak Mmmmh, good idea, but unfortunately I don't think we can access the One way to properly test that directly could be to use mocks, e.g. using frameworks like Maybe I'll try to swizzle the |
That's what I was afraid of…
This is what I was thinking initially…yes, it's heavy-handed, but OCMock does seem like a possible answer. But then I wondered, can OCMock actually mock a method from a protocol like that? That's not something I've done before. |
Yeah I agree, given that @NSProgrammer was talking about their tests using OCMock earlier, maybe he got some pointers on how they did it on their side? |
Yeah, I am pretty confident that doing this isn't possible with OCMock (though I would be happy to be proven wrong).
If you're going to tie the test to whatever private class implements this protocol, it may still be worth considering OCMock. I know it's a lot to add to the project, but swizzling methods yourself can be tedious. Obviously, up to you, but it might be a tradeoff worth considering. |
One option is to apply indirection. You create a custom NSURLProtocolClient implementation that redirects all messages to the NSURLProtocol's client property and instead swizzle/mock the NSURLProtocol's client property. You might even be able to get away with overriding the getter for the "client" property in your NSURLProtocol subclass and instead have the custom NSURLProtocolClient indirect to the super.client instead. |
Ah, I like that idea @NSProgrammer will try to implement it ASAP! Thanks for the pointers. |
@NSProgrammer I finally mocked the In fact, the I don't know if your code is doing the wrong thing by calling those methods on the Any insight on that? |
@NSProgrammer I just finally merged your fix into master and pushed version I still have my work on attempts of Unit Testing it on the The question that remains is:
|
Yes, it is confusing commentary by Apple. The "client thread" they are referring to is the thread that start loading is called from, which is com.apple.NSURLConnectionLoader. I agree it is ambiguous with being potentially the thread the NSURLConnection or NSURLSession is called from, or even the NSRunLoop the NSURLConnection is registered with or the NSOperationQueue the NSURLSession has set as a delegate queue. However, we have confirmed with Apple directly that it is the thread that startLoading is called from. It is an implementation detail that the thread happens to be com.apple.NSURLConnectionLoader, but it makes sense that networking is synchronized via being on a dedicated thread. Sorry for the delay in replying. |
Thanks so much for those details and feedback :) I'll file a feedback on
Thanks again |
Sounds good, thanks! And just for reference, most of our NSURLProtocol learnings came from building CocoaSPDY. |
Interesting project! Will take a look, I didn't know about SPDY. As you seem to have worked a lot with |
I haven't personally made these discoveries or any acquaintances at Apple; that's mostly comes from the core contributors to CocoaSPDY. However, I know from working closely with them (and with NSURLProtocol over the past 10 years) that NSURLProtocol is not a priority for Apple and so things will often be forgotten for compatibility making it unstable and only the most common use cases are maintained (though I argue that's purely coincidence). For example: I wouldn't be surprised if the place where You can see in the latest addition to CocoaSPDY that I created a patch (submitted vicariously by a coworker) such that the NSURLSession (and therefore the configuration, delegate and delegate queue) can be provided to the SPDYURLProtocol (see the SPDYURLSession in NSURLRequest+SPDYURLRequest on the "develop" branch). This might be an avenue to pursue, though it is admittedly a workaround. Best of luck, we appreciate your framework and your dedication to keeping it fresh! |
Thanks! Yeah I started a branch with a workaround too, that creates a subclass of There is a |
Per Apple docs https://developer.apple.com/library/prerelease/ios/samplecode/CustomHTTPProtocol/Listings/Read_Me_About_CustomHTTPProtocol_txt.html :
So, instead of having callbacks happen on a background queue, OHHTTPStubsProtocol need to capture the executing runloop and execute callbacks from that runloop. This is a requirement of NSURLProtocol.
-- Twitter uses OHHTTPStubs for certain unit tests and we have made this change on our local fork. Providing the fix here for the benefit of all.