httpswift / swifter

Tiny http server engine written in Swift programming language.
BSD 3-Clause "New" or "Revised" License
3.9k stars 537 forks source link

HttpServer stops working after locking the phone for ~1 minute and then unlocking it #96

Open lucatorella opened 8 years ago

lucatorella commented 8 years ago

If I lock the iPhone (the actual device and not the simulator), when I unlock it after at least ~1 minute (so that the app is suspended), the server stops. In particular the acceptClientSocket() function, called by the while loop running the server, throws an exception. Because of that the server stop working.

This issue is quite easy to reproduce and I included a simple snippet to reproduce it. Just create a new project (I used the Simple app template), and paste the following code in the ViewController.swift file:

import UIKit
import Swifter

class ViewController: UIViewController {
    private let server = HttpServer()

    override func viewDidLoad() {
        super.viewDidLoad()

        server["/magic"] = { .OK(.Html("You asked for " + $0.path)) }
        try! server.start(UInt16(1027))
    }
}

Note: it's important to use an actual device. Just lock the phone, wait, and unlock. Boom the SocketError.AcceptFailed exception will be raised and as a consequence the stopServer function is called.

SwimByteRun commented 8 years ago

It sounds like your app is being suspended, and I believe this is expected behavior. On an actual iOS device you will be subject to the application execution states, and you'll need to grant permission to allow the application to continue to run in the background.

From Apples "App Programming Guide for iOS"

For tasks that require more execution time to implement, you must request specific permissions to run them in the background without their being suspended. In iOS, only specific app types are allowed to run in the background:

  • Apps that play audible content to the user while in the background, such as a music player app
  • Apps that record audio content while in the background
  • Apps that keep users informed of their location at all times, such as a navigation app
  • Apps that support Voice over Internet Protocol (VoIP)
  • Apps that need to download and process new content regularly
  • Apps that receive regular updates from external accessories

Apps that implement these services must declare the services they support and use system frameworks to implement the relevant aspects of those services. Declaring the services lets the system know which services you use, but in some cases it is the system frameworks that actually prevent your application from being suspended.

An always running server on your phone isn't explicitly permitted, as it would have severe battery life consequences.

SwimByteRun commented 8 years ago

I think #42 is tracking this

lucatorella commented 8 years ago

I'm not expecting the server to run when the app is in background mode, I'm expecting the server to resume (and not to stop) once the app is back in the foreground.

SwimByteRun commented 8 years ago

Oh sorry, I misunderstood. Isn't this still up to the developer to manage the server within the UIApplicationDelegate? When the applicationWillResignActive delegate is called, server.stop() should be executed. Likewise the developer should execute server.start() on the applicationDidBecomeActive delegate.

lucatorella commented 8 years ago

Thanks @swimbyterun I'll try that. Anyway it would be great if the library could suspend and resume the server automatically as GCDWebServer does.

lucatorella commented 8 years ago

And yes it is related to https://github.com/glock45/swifter/issues/42

lucatorella commented 8 years ago

I tried the workaround and it's working, thanks. Unfortunately I'm getting, from time to time, and apparently only on iOS 8 the following crash:

EXC_GUARD 0x08fd4dbfade2dead

the crash happens at the line: try server.start(UInt16(port)) inside the method responsible for UIApplicationDidBecomeActiveNotification notification.

SwimByteRun commented 8 years ago

Did you mean try server.stop(UInt16(port)) instead? UIApplicationDidEnterBackgroundNotification is called when your application is about to be suspended

lucatorella commented 8 years ago

Sorry copied that wrong notification, and I corrected my previous comment. I meant it crashes at the line try server.start(UInt16(port)). Here is an extract of my code:

init(){
  NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("applicationDidBecomeActiveNotification"), name: UIApplicationDidBecomeActiveNotification, object: nil)
  NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("applicationDidEnterBackgroundNotification"), name: UIApplicationDidEnterBackgroundNotification, object: nil)
}

@objc func applicationDidBecomeActiveNotification() {
  do {
    try server?.start(UInt16(port))
  } catch let error as NSError {
    print("AssetsServer.applicationDidBecomeActiveNotification: \(error.localizedDescription)")
  }
}

@objc func applicationDidEnterBackgroundNotification() {
  server?.stop()
}
lucatorella commented 8 years ago

I'm seeing quite often these EXC_GUARD 0x08fd4dbfade2dead crashes.

Here is the trace:

Thread : Crashed: com.apple.root.background-qos
0  libsystem_kernel.dylib         0x1824d2830 close + 8
1  Swifter                        0x1012c1514 _TFC7Swifter12HttpServerIO4stopfS0_FT_T_ + 52
2  Swifter                        0x1012c3730 _TFFC7Swifter12HttpServerIO5startFS0_FzTVSs6UInt16_T_U_FT_T_ + 572
3  libdispatch.dylib              0x18239d630 _dispatch_call_block_and_release + 24
4  libdispatch.dylib              0x18239d5f0 _dispatch_client_callout + 16
5  libdispatch.dylib              0x1823aba88 _dispatch_root_queue_drain + 2140
6  libdispatch.dylib              0x1823ab224 _dispatch_worker_thread3 + 112
7  libsystem_pthread.dylib        0x1825b1470 _pthread_wqthread + 1092
8  libsystem_pthread.dylib        0x1825b1020 start_wqthread + 4
lucatorella commented 8 years ago

also, restarting the server when receiving the UIApplicationDidBecomeActiveNotification sometimes wasn't restarting the server. Temporarily I fix it by using a dispatch_after of half a second before trying to restart the server.

damian-kolakowski commented 8 years ago

@lucatorella this answer explains what's happening - http://stackoverflow.com/questions/32429431/exc-guard-exception. I am going to check this in the evening.