lauchdaemon does not (always) work automatically?

Hi!

I wrote a backup console application. It does use ssh/sshfs to access a web server via public key. Also it does send an SMTP-mail when finished. If the process is started via

launchctl load DAEMON

it works always properly (RunAtLoad is true). But when the daemon is called automatically at night it does not always work. In my log I get these errors:

ssh: connect to host SERVERURL port 22: Undefined error: 0

and

Failed sending: NIOConnectionError(host: "mail.mydomain", port: 25, dnsAError: Optional(NIOCore.SocketAddressError.unknown(host: "mail.mydomain", port: 25)), dnsAAAAError: Optional(NIOCore.SocketAddressError.unknown(host: "mail.mydomain", port: 25)), connectionErrors: [])

it does run on a MacMini M1 with macOS 12.2.

Any clue what's wrong or how to find the reason of this issue?

PS. On another MacMini (Intel) with macOS 11.6 the backup works since a year but there is always an admin user logged in.

Answered by GreatOm in 704944022

Now I got it working. I use a check at start of my application with following code:

if !NetworkCheck().waitForNetwork() {
    print("Error: No network available!")
    exit(exitCodes.NoNetwork.rawValue)
}

and this is my NetworkCheck class:

import Foundation
import Network

class NetworkCheck {
   private let networkMonitor : NWPathMonitor

   init() {
      self.networkMonitor = NWPathMonitor(requiredInterfaceType: .wiredEthernet)
      self.networkMonitor.start(queue: .global())
   }

   func waitForNetwork(networkTimeout : Double = 30.0) -> Bool {
       let networkLookupStart = Date.now
       var isConected         = false

       while abs(networkLookupStart.timeIntervalSinceNow) <= networkTimeout {
           networkMonitor.pathUpdateHandler = { path in
               if path.status == .satisfied {
                   isConected = true
               }
           }

           if isConected {
               break
           }
           sleep(1)
       }

       return isConected
   }

   deinit {
       networkMonitor.cancel()
   }
}

But when the daemon is called automatically at night it does not always work.

How is it starting? Via the StartInterval property? Or perhaps StartCalendarInterval?

Share and Enjoy

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

StartCalendarInterval like this (for the other days the same):

	<key>StartCalendarInterval</key>
	<array>
		<dict>
			<key>Weekday</key>
			<integer>0</integer>
			<key>Hour</key>
			<integer>5</integer>
			<key>Minute</key>
			<integer>0</integer>
		</dict>

Edit: The job is started properly but the log shows the errors above. I assume there is a networking issue in case no user is logged in (GUI or via ssh in Terminal).

StartCalendarInterval

Thanks for clarifying that.

I assume there is a networking issue in case no user is logged in

The networking stack on the Mac should work independently of whether a user is logged in.

It looks like you’re using SwiftNIO for your networking. Are you use Network framework under the covers? That is, are you using NIO Transport Services?

Share and Enjoy

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

The networking stack on the Mac should work independently of whether a user is logged in.

I think so, too but I have no clue why it works always when called manually vs. automatically.

It looks like you’re using SwiftNIO for your networking. Are you use Network framework under the covers? That is, are you using NIO Transport Services?

Yes. I use SwiftNIO 2.35.0 for my Mailer class which is based on SwiftSMTP.

But even when there is a SwiftNIO issue the shell call using sshfs to mount a remote volume should work still work…

I wonder if there is a security issue. The deamon process is called as admin user

	<key>UserName</key>
	<string>admin</string>
	<key>GroupName</key>
	<string>admin</string>

and this works as expected. I checked this via ProcessInfo().userName debug output.

Edit: AFAIK I don't use NIOTransportServices although the package is included in projects package list. A source search for NIOTransportServices gave no hit.

I wonder if there is a security issue.

That seems unlikely.

My working theory here is that the Mac has woken up to run your daemon and the networking stack is not fully up at the time that your daemon runs. Apple’s networking APIs tend to deal with this well — they’re designed around a model where your make a request and they wait for connectivity — but the low-level BSD Sockets API is not. If something goes wrong, they tend to fail immediately.

If this theory is correct then you may be able to avoid this problem by layering your SwiftNIO code on top of NIO Transport Services (which uses Network framework under the covers). However, I’m not an SwiftNIO expert, so I can’t give you specific instructions on how to do that.

Share and Enjoy

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

Many thanks for your help. Just a few minutes ago I thought the same because Remote Desktop and it showed me sleeping for the server…

As a quick solution I added a launch daemon plist which calls caffeinate to prevent sleeping. I'm eager so see if the backup does run tonight and I will post the result tomorrow.

Today I got a working backup. So it is in fact the missing network after sleeping. I did now add a check to wait for a network connection. I will post an update next week with some sample code when I got it working.

Accepted Answer

Now I got it working. I use a check at start of my application with following code:

if !NetworkCheck().waitForNetwork() {
    print("Error: No network available!")
    exit(exitCodes.NoNetwork.rawValue)
}

and this is my NetworkCheck class:

import Foundation
import Network

class NetworkCheck {
   private let networkMonitor : NWPathMonitor

   init() {
      self.networkMonitor = NWPathMonitor(requiredInterfaceType: .wiredEthernet)
      self.networkMonitor.start(queue: .global())
   }

   func waitForNetwork(networkTimeout : Double = 30.0) -> Bool {
       let networkLookupStart = Date.now
       var isConected         = false

       while abs(networkLookupStart.timeIntervalSinceNow) <= networkTimeout {
           networkMonitor.pathUpdateHandler = { path in
               if path.status == .satisfied {
                   isConected = true
               }
           }

           if isConected {
               break
           }
           sleep(1)
       }

       return isConected
   }

   deinit {
       networkMonitor.cancel()
   }
}
lauchdaemon does not (always) work automatically?
 
 
Q