What's the best way to keep a process running without a tight loop?

I'm working on a long-running command-line application which watches the file system and reacts to some events. What's the best way to keep it running until it gets sent the exit signal? I guess the naive way would be to have a while(true) loop after setup, bu that is undesirable for obvious reasons.

1 Like

Here's some info on how to use DispatchGroup and its .enter(), .leave(), wait().

May I ask what APIs you use for watching the file system?

1 Like

I’m not an expert on this, so someone else may correct me, but generally you want to be responding to events from the system where possible - e.g. existing within an NSRunLoop on macOS. If that’s not possible, a while loop with a sched_yield() or a sleep() call is usually a reasonable choice.

The answers to macos - How can I receive notifications of filesystem changes in OS X? - Stack Overflow may point you in a reasonable direction depending on what OS you’re targeting.

main.swift

import Cocoa
let app = NSApplication.shared
app.delegate = FSAppDelegate.init()
app.activate(ignoringOtherApps: true)
app.run()

FSAppDelegate.swift

import Cocoa

class FSAppDelegate: NSResponder, NSApplicationDelegate {
  func applicationDidFinishLaunching(_ notification: Notification) {
    // ... start your process or trigger it set timer intervals

    // timer example:
    let timer = Timer(fire: Date(), interval: 0.1, repeats: true) { _ in FSApp.nextFrame() }
    RunLoop.current.add(timer, forMode: RunLoop.Mode.default) 
  }
}

class FSApp {
  static func nextFrame() {
    // ... run your process
  }
}

If you need to listen for keystrokes; look at NSEvent.addMonitorForEvents

1 Like

Sure. Currently I'm using the File System Events API (wrapped by the Witness library).

At the moment this is just a small utility library for my workstation, so macOS-only is ok for now. I might consider swapping it out for something more cross-platform in the future.

Thanks, asking because I needed to watch files for modification in a project some time ago, and I ended up using using Witness too, but it seemed like there should be a simpler way.

Would that work in a pure command line application? let's say just using SPM and not starting a project through Xcode?

It should, no reason why not.

If you look at the Witness source code, you'll see that during initialization, it schedules itself in the default mode of CFRunLoopGetCurrent(). You need to ensure that this run loop runs in the default mode.

Using Foundation, you can run the current thread's run loop in the default mode indefinitely like this:

RunLoop.current.run()

For more information, read the “Run Loops” chapter of the Threading Programming Guide.

2 Likes

There’s two good general answers here:

  • If the async libraries you’re using depend on the run loop, use RunLoop.current.run() per mayoff’s post.

  • If not, use dispatchMain().

Things that you should not do:

  • Any sort of while loop — This essentially causes your tool to poll, which is a bad idea because you want any long-running tool to stay off the CPU as much as possible.

  • Anything involving sched_yield() or sleep() — This has the same drawbacks as the previous point, albeit with a reduced polling rate.

  • Anything involving AppKit, unless your code actually depends on AppKit — Starting up AppKit can be expensive but the real issue is that it limits the contexts in which your tool can run. There are good reasons to use AppKit from a tool — for example, you might want to use NSWorkspace — but if you do that then your tool can only run in a user login context.

  • Waiting on a dispatch group — If a dispatch group is sufficient, dispatchMain() is also sufficient, and dispatchMain() is better. Specifically, a dispatch group will park the main thread inside the wait() call, but dispatchMain() actually allows the main thread to terminate, creating a new thread to do work when work becomes available, and that reduces the impact of your tool even further.

ps There are also various special-purpose answers, like xpc_main, but it sounds like you’re looking for a general solution.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

13 Likes

pps I’m happy to chat about all of these details but it’d probably be best to move that discussion to DevForums (and specifically Core OS > Processes) because it’s very Apple specific.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like