Why does my Timer get paused sometimes?

Hi, I am making a Launch Daemon app in Swift for MacOS. This app needs to make a request to an api once every couple of minutes. I do this using a Timer but I have experienced that on some users machines the timer does not get restarted once the system comes back from sleep. I also have other repeating timers that have the same behaviour so it is not related to just one. When this happens the app does not crash the timers just suddenly stop.

This is the Timer im using:

Code Block
class RepeatingTimer {
  public var timer: DispatchSourceTimer?
   
  init(interval: Int, queue: DispatchQueue, deadline: DispatchTime = .now(), eventHandler: @escaping ()-> Void) {
    timer = DispatchSource.makeTimerSource(flags: [], queue: queue)
    timer?.schedule(deadline: deadline, repeating: .seconds(interval))
    timer?.setEventHandler {
      eventHandler()
    }
  }
   
  func start() {
    timer?.resume()
  }
   
  func stop() {
    timer = nil
  }
   
}

I stop the Timers on applicationWillTerminate
Timers are not made to work in background, except some specific case. See:

https://stackoverflow.com/questions/42319172/swift-3-how-to-make-timer-work-in-background
and
h ttps://www.raywenderlich. com/5817-background-modes-tutorial-getting-started

See this for more in depth discussion:
https://developer.apple.com/forums/thread/127444
Thanks a lot for the quick response. Does this still apply for MacOS apps or is it only for IOS?

I don't believe applicationwillenterbackground is avaliable on MacOS. How could i stop the Timers when the system sleeps and restart them when it gets back up?
You're right, most of the thread is about iOS. But it is also mentioned that some solution worked for MacOS. Have a look.
Ok i have some questions now. Given that my app is a Launch Daemon with no UI at all does that mean it is always running in the background? If it isnt how can i detect when the app sleeps or gets back up, should I use this methods applicationDidBecomeActive , applicationWillResignActive or are they not appropriate for my use case?

Given that my app is a Launch Daemon with no UI at all does that mean
it is always running in the background?

You can’t have an app that is a launchd daemon. Those are mutually incompatible concepts. So, let’s start by clarifying some things:
  • How is this code launched? Using a launchd property list file? If so, where is that installed and what does that look like?

  • You keep mentioning concepts that are very iOS centric, so I have to ask: Are you building this code using Mac Catalyst? Or using the native macOS SDK?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

You can’t have an app that is a launchd daemon.

Oh i didn't know that. It works perfectly fine though because the app has no UI and is very simple. I found out that I can get notified about when the system sleeps/wakes with this code and this seems that have helped me solve this issue.
Code Block
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(recieveSleepNotification), name: NSWorkspace.willSleepNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(recieveWakeNotification), name: NSWorkspace.didWakeNotification, object: nil)



If so, where is that installed and what does that look like?

Code Block
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.company.myapp</string>
    <key>Program</key>
    <string>/Applications/myapp.app/Contents/MacOS/myapp</string>
    <key>KeepAlive</key>
    <true/>
    <key>RunAtLoad</key>
    <true/>
</dict>
</plist>


Im not using Mac Catalyst I just build the app in Xcode.

Code Block
<?xml version="1.0" encoding="UTF-8"?>


Thanks. But I need to confirm one more thing: Where did you install that? /Library/LaunchDaemons?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Yes it is installed in /Library/LaunchDaemons.

BTW, what I said earlier about NSWorkspace notifications does not seem to be a reliable solution to my problem.

Yes it is installed in /Library/LaunchDaemons.

OK, that confirms that you are actually creating a launchd daemon. This means that you can’t use app constructs like AppKit. And, notably, NSWorkspace is part of AppKit.

For more on this, see the discussion of daemon-safe frameworks in Technote 2083 Daemons and Agents (it’s an old un but a good un).

Can you explain more about what sort of product you’re creating? What parts does it contain? And how do those parts fit together? Because, based on what I’ve read so far in this thread, it sounds like you have architectural problems that extend far beyond your original Dispatch timer issue.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Can you explain more about what sort of product you’re creating? 

OK, the app im developing will only run in the macs of the company i work at. The idea is for this to be a daemon that runs all the time and periodically sends data about the system to an api (some of this data i can only access as root). This app also comes with a Network Extension that uses the Content Filter apis to serve as a firewall.

Like I said earlier, one of the Timers i have repeats every 3 min and sends data to an api, based on this data the api tells my daemon what to do, wether it should download an update or update the current firewall rules used by the Net Ext. I was talking to @meatton about this and he suggested that I should merge my daemon and extension and perform all this repeating tasks inside the extension. I am against this idea for now because extensions cant be restarted using a simple kill command so for troubleshooting it can be sort of dangerous.

I understand that using an app as a daemon is not ideal but I don't really think that has anything to do with the problems I am facing. Also, even if I absolutely had to separate the daemon from this App i would just end up with a blank helper application that has nothing in it, no UI, no code, etc.

Lastly, given that the app is an enterprise solution it is ditributed via MDM.

I was talking to @meatton about this and he suggested that I should merge my daemon and extension and perform all this repeating tasks inside the extension.

I suggested removing the launch daemon from the equation and moving to a container app instead of a launch daemon as a container app. Then I suggested removing the timer from the equation completely and performing a date/timeInterval based comparison in the Network Extension's execution to decide on whether it was time to trigger the action that was previously performed from the timer.

Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com
Why does my Timer get paused sometimes?
 
 
Q