I create an http request using PUT to get data from the server. I got this data from the server and transform it on a PDF file.
When I run it for the first time everything runs fine, but after some calls, I start to get timeout errors.
Sometimes, I need to restart the app to be able to receive HTTP requests again.
This is the code that I use.
func callGetPdfFromEndpointUsingNSMutableURLRequest() {
if codigoBarra == "" {
messageError = "Código não localizado"
showingAlert = true
redirectToPdfView = false
showingActivityIndicator = false
return
}
let serviceRepository = ServiceRepository()
// let codigo_barra = "d152d36914313fedfbf36842a7195b723"
let json: [String: Any] = ["codigoBarra":"\(codigoBarra)"]
let request: NSMutableURLRequest = serviceRepository.clientURLRequest(endpointPesquisa, typeAutho: .basic, parms: "", body: json as Dictionary<String, AnyObject>)
print("request: \(request)")
print("request.url: \(String(describing: request.url))")
serviceRepository.put(request, retryLogin: true, completion: {isOk,msgError,httpCode,needLogin, response in
if isOk {
tratarRequisicaoPdf(response)
} else {
print("erro no request - is not ok | - httpCode: \(httpCode)")
var stringResponse:String = ""
if response != nil {
stringResponse = String(data: response as! Data, encoding: .utf8)!
} else {
stringResponse = "Sem resposta do servidor, tempo limite da solicitação foi esgotado."
}
messageError = "\(stringResponse)"
print(messageError)
showingAlert = true
redirectToPdfView = false
semaphore.signal()
}
semaphore.wait()
showingActivityIndicator = false
})
}
This error is unstable, sometimes it shows, sometimes it don't appear.
The people working on backend was not able to detect any problems.
I got the following error:
2022-05-20 15:33:15.442419-0300 CDA[2016:38068] Task <147B6F7F-E46A-47D0-A258-D6F3E5417D7E>.<1> finished with error [-1001] Error Domain=NSURLErrorDomain Code=-1001 "Esgotou-se o tempo limite da solicitação." UserInfo={_kCFStreamErrorCodeKey=-2102, NSUnderlyingError=0x7fd0e5245520 {Error Domain=kCFErrorDomainCFNetwork Code=-1001 "(null)" UserInfo={_kCFStreamErrorCodeKey=-2102, _kCFStreamErrorDomainKey=4}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <147B6F7F-E46A-47D0-A258-D6F3E5417D7E>.<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalDataTask <147B6F7F-E46A-47D0-A258-D6F3E5417D7E>.<1>"
), NSLocalizedDescription=Esgotou-se o tempo limite da solicitação., NSErrorFailingURLStringKey=https://myurl.sp.gov.br/gedave/api/spservicos/v1/buscaRequisicaoExame, NSErrorFailingURLKey=https://myurl.sp.gov.br/gedave/api/spservicos/v1/buscaRequisicaoExame, _kCFStreamErrorDomainKey=4}
response::: nil
erro: response is nil
httpCode: 0
What can I do to try to detect what was causing the timeout errors?
Edit:
I added the source code that can be viewed here https://swiftfiddle.com/mz2dxw6z6bda7a6t44cryncrpi.
NEW EDIT:
Answers to comments created by #Rob
Good to know about the NSUrlMutableRequest
I will try to use the 'finishAndInvalidate' in my URLSession. I didn't know about that.
My problem is unpredictable. Now I start the app and the first call got an timeout, after the second call the app works. Sometimes it starts working, but after some requests, I got a timeout
In the absence of a MCVE, there is not enough here to diagnose or reproduce the problem.
That having been said, there are some interesting clues:
You are calling wait (on a semaphore). Eliminate the DispatchSemaphore and if you want to know when the requests are done, use DispatchGroup. But when you use the dispatch group, use notify when it is done, not wait. Avoid blocking threads unnecessarily. And never block the main thread.
Your network request is performed by ServiceRepository, which you have not shared with us. But in your fiddle link, you show us unrelated URLSession code that is specifying the main queue as its delegate queue. If ServiceRepository is doing something similar, that, combined with the wait, above, could easily deadlock your code.
So, eliminate the semaphore, avoid ever calling wait (whether semaphore or dispatch group), and the deadlock risk is eliminated.
That having been said, that is only one potential timeout risk. The other scenario might be that you are simply issuing too many requests for the URLSession to run them without timing out.
If that is the case, you have a few options:
Increase the timeout threshold (either of the request or the session).
Try bumping up the timeout values and see if that mitigates the issue.
Submit uploads in just-in-time manner.
E.g., you might have a process where, rather than initiating all the uploads at once, that you issue each upload upon the completion of the prior upload. Or you can wrap the upload in a custom, asynchronous, Operation subclass (which is complicated in its own right), and then tell the operation queue to only attempt 4 at a time, or whatever. There are many techniques to tackle this, but the idea is to prevent timeouts by not even submitting the upload until you are confident that the URLSession can perform them.
Use background URLSession with file-based uploads.
If you have uploads that are so slow that they risk timing out, it begs the question of whether you really want to force the user to keep the app running in order to let the uploads finish at all. If you use a background URLSession, the background daemon will not let them timeout, but rather will upload them when it can.
Refactoring this for background URLSession is a non-trivial exercise (the first time you do it, anyway). You have to abandon completion handlers, use delegate-methods API, use file-based uploads, etc. But it is an incredibly powerful way to let uploads proceed in the background, allowing the user to leave the app, etc.
I don't see full code but you should have a look into semaphore usage - probably wrong multithread logic leads to not complete you request and your completion is hovered for a long period and causes URLDataTask to produce the timeout error
Related
I am trying to achieve functionality similar to Javascript/C#'s async/await. I am trying out the use of semaphores, and found that it works with URLSession in my XCode Playground.
Thus, i am now trying to perform the same thing with Firebase Authentication using the following code:
var response:String? = "test"
let semaphore = DispatchSemaphore(value: 0)
Auth.auth().createUser(withEmail: email, password: password) { (result, err) in
if err != nil {
response = "\(String(describing: err))"
}else{
response = nil
}
semaphore.signal()
}
let _ = semaphore.wait()
if response == nil{
self.transitionToHome()
}
However, the simulator freezes forever, appearing as if the semaphore.signal() never got called. Placing print statements near the semaphore.signal() didn't appear as well. I've also placed the firestore code in a DispatchQueue.global(qos: .background).async and subsequently tried to retrieve the response value in DispatchQueue.main.async but the response did not get updated as well. The code below reflects what i did:
DispatchQueue.global(qos: .background).async {
Auth.auth().createUser(withEmail: email, password: password) { (result, err) in
if err != nil {
response = "\(String(describing: err))"
}else{
response = nil
}
semaphore.signal()
}
let _ = semaphore.wait()
}
if response == nil{
self.transitionToHome()
}
While this did not freeze up the UI, the response value was not picked up after the DispatchQueue was called. I've also called the if-else block within a DispatchQueue.main.async block but that had the same result too.
Further, after waiting for a period of time, i see this error popping up in my xcode terminal:
020-01-02 01:33:25.447842+0800 das-carddeckapp[78136:10508853] Connection 4: received failure notification
2020-01-02 01:33:25.448179+0800 das-carddeckapp[78136:10508853] Connection 4: failed to connect 1:50, reason -1
2020-01-02 01:33:25.448387+0800 das-carddeckapp[78136:10508853] Connection 4: encountered error(1:50)
2020-01-02 01:33:25.457587+0800 das-carddeckapp[78136:10508853] Task <3A3720D6-7549-4C31-96A2-C88B89294821>.<1> HTTP load failed, 0/0 bytes (error code: -1009 [1:50])
I know that using completion handlers will make this work, but I want to try to get this to work before resorting to completion handlers. Any help is greatly appreciated!
The first example (where you’re calling wait on the main thread) is “deadlocking” because it is blocking the main thread with wait, which is to be signaled from the createUser closure, which also wants to run on the main thread (which you’ve blocked).
I know that semaphores is considered a bad pattern but i just wanted to understand why is it not working.
Your second example, where you are dispatching the wait to the global queue resolves the deadlock. But because you’ve dispatched that asynchronously, that means that the main thread will carry on as the global queue waits for the response. As a result, you are almost guaranteed to not have a value ready to return. (And if you attempt to change this to call the global queue synchronously, you’d just re-introduce the deadlock.)
In short, you can’t easily have the main queue wait for an asynchronous method which calls its completion handler on the main queue. That is a programmatic “Catch-22”.
But setting that aside, if this is an iOS app, it just simply a serious problem to ever block the main thread (even if you could solve the deadlock). It’s a horrible UX when an app freezes. This is especially true for unfortunate mobile users who find themselves on poor cellular connection, where it might result in a non-trivial delay. Worse, if you’re unlucky enough to block the main thread at the wrong time, the iOS watchdog process may unceremoniously kill your app.
Hey, I get it. We’ve all been there. When we first encountered asynchronous programming patterns, like network requests, it feels it would be so much more logical and intuitive if we could just wait for the response. But it should be avoided at all costs. You’ll be much happier in the long run if you stick with well-established asynchronous programming patterns, rather than fighting it.
I opened a stream to a server I have. When I send the text "PING" it responds with "PONG". I have successfully connected, sent messages, and received responses. I have a pretty bare bones test below.
The Problem
I want to handle the messages from the server as they come in. Currently, I send three PINGs and later I get three PONGS. The server responds with PONG immediately after but my code is not handling the response until after the main thread completes. The expected result is to see the PONG messages immediately after the PING is sent because they are being handled concurrently.
What I tried
Pretty much, what you see below. I thought "I want to handle the stream responses at the same time as sending messages so I'll need to do that on another thread". So, I put the RunLoop in another thread via GCD. That did not help....Can't figure our how to make the StreamDelegate handle its delegate method stream in a separate thread...
Current Console Result
PING
PING
PING
PONG
PONG
PONG
Desired Console Result
PING
PONG
PING
PONG
PING
PONG
The Code
import Foundation
import XCTest
class StreamTests: XCTestCase, StreamDelegate {
var inputStream: InputStream?
var outputStream: OutputStream?
let url: URL = URL(string: "http://theserver.com:4222")!
func testAsyncStream() {
self.setupStream()
let ping = "PING".data(using: String.Encoding.utf8)!
print("PING")
self.outputStream?.writeStreamWhenReady(ping)
sleep(1)
print("PING")
self.outputStream?.writeStreamWhenReady(ping)
sleep(1)
print("PING")
self.outputStream?.writeStreamWhenReady(ping)
sleep(1)
}
private func setupStream() {
guard let host = url.host, let port = url.port else { print("Failed URL parse"); return }
var readStream: Unmanaged<CFReadStream>?
var writeStream: Unmanaged<CFWriteStream>?
CFStreamCreatePairWithSocketToHost(nil, host as CFString!, UInt32(port), &readStream, &writeStream)
self.inputStream = readStream!.takeRetainedValue() as InputStream
self.outputStream = writeStream!.takeRetainedValue() as OutputStream
guard let inStream = self.inputStream, let outStream = self.outputStream else { return }
inStream.open()
outStream.open()
DispatchQueue.global(qos: .utility).sync { [weak self] in
for stream in [inStream, outStream] {
stream.delegate = self
stream.schedule(in: .current, forMode: .defaultRunLoopMode)
}
RunLoop.current.run(mode: .defaultRunLoopMode, before: Date.distantFuture)
}
}
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
switch aStream {
case inputStream!:
switch eventCode {
case [.hasBytesAvailable]:
print("PONG")
break
default:
break
}
default:
break
}
}
}
Don’t try to write unit tests for multithreaded code. It will bite you in the ass later.
The reason unit testing code that runs on multiple threads is hard is the fact that you can’t control the order of execution of threads, nor the allocated time per thread – this is an OS decision.
Thus, in order to make sure the code submitted on the other thread executes and populates the expected data, you’ll need to block the main thread, where unit tests usually runs, for an amount of time large enough to be sure the other thread finishes the work.
Now, the tricky part is to find what that amount of time should be. Make it too short and you’ll see random failures of your unit tests, make it too long and you’ll increase the duration of your unit tests more and more. And theoretically there’s no upper limit for how long will need to wait for the other thread to finish, as this is out of our control (remember, the OS decides which thread to pick up next and how much time to allocate to it).
And worse, when a unit test like this starts failing on a CI machine but it doesn’t fail on your machine, who’s to blame: the CI machine for being too slow, or your code misbehaving in some conditions that happen only on the CI machine? This ambiguity can lead to lot of time wasted in trying to figure out what the hack happens with the tested code.
Conclusion: don’t try to write unit tests for code that executes parts of its work on a different thread. The reason is simple: robust unit tests need to have control over all inputs of the tested code, and the second thread is not something it can control (unless you mock the thread dispatching, but that’s another story).
Instead, push as much logic as you can into single-thread methods, and tests those methods instead. In the end, most of the bugs occur due to incorrect business logic.
I have created a Swift 3.0 server using the Perfect Framework. Everything works great as expected but i am trying to learn if there is a better way to do some things.
Coming from an iOS background I know to always call any blocking function in a different thread. Does this still hold when developing in the server?
For example, if I have a long blocking task (like making another request or performing a large database query), does it make a difference to call it synchronously:
routes.add(method: .get, uri: "/", handler: { request, response in
longSynchronousTask()
response.appendBody(string: "Finished")
response.completed()
})
or should I be doing this asynchronously?
routes.add(method: .get, uri: "/", handler: { request, response in
longAsynchronousTask(completion: {
response.appendBody(string: "Finished")
response.completed()
})
})
Depends on the framework. I wasn't able to find more information about perfect.org's architecture but since it claims to run on a "high-performance asynchronous networking engine", the expectation is that a thread handling a request is not supposed to block.
Most reactive frameworks (like Node.js, Vert.x) rely on one or multiple event threads that are handling requests.
If these threads block, no more requests can be handled!
Which means that longer running tasks should be run in their own threads.
There are frameworks that provide a mechanism for that (like worker-threads).
The question then becomes: What is a longer running task?
if your task does I/O in an asynchronous fashion and merely 'waits' for I/O, you can do that on the event thread.
If you do lengthy computation, you might be better off putting that into a separate thread.
yes, you can. I use the following code test, it's OK.
struct DBOperationQueue {
static let messageOperationQueue:OperationQueue = {
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 5
return operationQueue
}()
}
func handler(request: HTTPRequest, response: HTTPResponse) {
DBOperationQueue.messageOperationQueue.addOperation {
sleep(10)
print("sleep complete")
response.setHeader(.contentType, value: "text/html")
response.appendBody(string: "<html><title>Hello, world!</title><body>Hello, world!</body></html>")
response.completed()
}
}
I'm using a completion handler to preform a network request, Once the network request is completed I add the result to a dictionary i've constructed that contains other information pertaining to the result. Then I call a function to change the text of the labels in my view controller. The code I'm using is:
requestCalculateTotals(data["id"] as! String, pickupDate: data["pickup_date"] as! String, dropoffDate: data["dropoff_date"] as! String){
(calculateData: NSDictionary) in
self.data.addEntriesFromDictionary(calculateData as [NSObject : AnyObject])
self.refreshDetails()
}
This causes a SIGABRT error and I dont understand why? I've tested the completion handler and the network request and they both work, I can print the result data and it contains the data I expect. The refreshDetails function works outside of the completion handler but then it won't be waiting for the network request.
Is there something I've missed when constructing the completion handler or is there a there a better way I could wait for a network request?
EDIT I get the error: "This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes. This will cause an exception in a future release." How could I add my refreshDetails function in the main thread when the network request is complete?
below is the code when user uploads a video from mobile application to S3
def uploadVideo = Action(parse.multipartFormData) { implicit request =>
try {
var height = 0
var width = 0
request.body.files.map { mov =>
var videoName = System.currentTimeMillis() + ".mpeg"
amazonS3Client.putObject(bucketVideos, videoName, mov.ref.file)
}
val map = Map("result" -> "success")
Ok(write(map))
} catch {
case e: Exception =>
Ok(write(Map("result" -> "error")))
}
}
the above code work fine but in case user cancel while uploading of video then error occurs
[error] play - Exception caught in RequestBodyHandler
java.nio.channels.ClosedChannelException: null
at org.jboss.netty.channel.socket.nio.AbstractNioWorker.cleanUpWriteBuffer(AbstractNioWorker.java:434) ~[netty.jar:na]
at org.jboss.netty.channel.socket.nio.AbstractNioWorker.writeFromUserCode(AbstractNioWorker.java:129) ~[netty.jar:na]
at org.jboss.netty.channel.socket.nio.NioServerSocketPipelineSink.handleAcceptedSocket(NioServerSocketPipelineSink.java:99) ~[netty.jar:na]
at org.jboss.netty.channel.socket.nio.NioServerSocketPipelineSink.eventSunk(NioServerSocketPipelineSink.java:36) ~[netty.jar:na]
at org.jboss.netty.channel.Channels.write(Channels.java:725) ~[netty.jar:na]
at org.jboss.netty.handler.codec.oneone.OneToOneEncoder.doEncode(OneToOneEncoder.java:71) ~[netty.jar:na]
and this doesn't go to catch block!!
1.can this is harmfull to server or not?(because it is not needed any response if error occours)
2.if yes, how to handle?
This is all happening in Play's internals that are handling parsing the body of the Request. In fact, during the upload to your server, you haven't even reached the try block yet because the file hasn't finished uploading. Only once the upload is complete do you have the TemporaryFile available.
So no, you can't catch this error, and why would you want to? The user closed the connection. They're not even waiting for a response, so why send one? Let Play handle it.
This is also not a good way of handling an upload, though. For small files, it's passable, but if someone is proxying a huge video upload through your server to S3, it's going to:
Take almost twice is long to serve the response (which will cause the user to hang while you upload to S3).
Block one of Play's threads for handling requests for the entire time that file is uploading to S3, and given enough of these uploads (not many at all), you will no longer be able to process requests until an upload has completed.
Consider at least creating a separate ExecutionContext to use for handling uploads, or even better, look into having the user upload directly to S3 via a signed form, which would remove the need to proxy the upload at all.