SBTProxyURLProtocol calls completionHandler in the main thread
fermoya opened this issue · comments
Hi, I've noticed that SBTProxyURLProtocol.startLoading
returns the response for the subbed request in the main thread:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(stubbingResponseTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
....
}
Can we use dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
or any other queue that's not the main thread instead (or at least allow customization)? There are scenarios in my suite where I mock HW by using mocks within the main target whose values are customized from a UI-Test. For this, I use dummy http requests. The mocked functions can be async or sync. In order for the sync methods to work I need to block the main thread. However the test hangs indefinitely because the main thread is blocked and SBTProxyURLProtocol
tries to return the data into the main
queue.
I don't think the change I suggest breaks anything and I think is more accurate as you'd expect the response to come from a background thread instead of the main thread.
Modifying that queue would likely lead to thread-safety issues, so I would recommend against such changes. Instead of blocking the main thread with a semaphore, consider using the run loop (specifically, [NSRunLoop.mainRunLoop runUntilDate:]). While this approach isn't ideal, it could serve as a viable workaround.
Modifying that queue would likely lead to thread-safety issues
startLoading
is already called in a different thread than it's then dispatching the response (in the main queue now) so I don't understand why changing to a different queue would introduce any issue. At the very least I think it should usedispatch_get_current_queue
.
consider using the run loop
how would you do that? I do something like this:
func foo<Output>(
for call: @escaping (@escaping (Output) -> Void) -> Any
) -> Output {
let completionQueue = DispatchQueue(label: "com.test.UITest", qos: .background)
var output: Output?
completionQueue.async {
call { opResult in
output = opResult
}
}
RunLoop.main.run(until: Date(timeIntervalSinceNow: 10))
guard let output else {
fatalError("Shouldn't time out")
}
return output
}
But same difference, UI hangs for 10 seconds until it fails. Main thread is blocked so SBTProxyURLProtocol
doesn't get called
You need to invoke the RunLoop.main.run repeatedly until you detect that your operation is completed. There are a few examples in the tunnel codebase to take a look at.
@tcamin from what I see, it's really easy to enter a deadlock (see here), which I think it's my case and it's not something very difficult to achieve.
I still don't understand why SBTProxyURLProtocol.startLoading
has to be dispatched in the main queue. I've tried replacing the source code myself and it works just fine if a different queue is used instead