Edited at

launchdが言うことを聞かない場合に見直すべきこと

More than 1 year has passed since last update.


困った…

LaunchDaemons(LaunchAgents)が起動しない。

2017/02/04 11:11:47.894 com.apple.xpc.launchd[1]: (org.jenkins-ci[1437]) Service exited with abnormal code: 1

2017/02/04 11:11:47.894 com.apple.xpc.launchd[1]: (org.jenkins-ci) Service only ran for 0 seconds. Pushing respawn out by 10 seconds.

10秒おきに再起動しようとする(失敗する)。

2017/02/04 11:11:57.283 com.apple.xpc.launchd[1]: (org.jenkins-ci[1437]) Service exited with abnormal code: 1

2017/02/04 11:11:57.283 com.apple.xpc.launchd[1]: (org.jenkins-ci) Service only ran for 0 seconds. Pushing respawn out by 10 seconds.
2017/02/04 11:12:07.376 com.apple.xpc.launchd[1]: (org.jenkins-ci[1437]) Service exited with abnormal code: 1
2017/02/04 11:12:07.376 com.apple.xpc.launchd[1]: (org.jenkins-ci) Service only ran for 0 seconds. Pushing respawn out by 10 seconds.
2017/02/04 11:12:17.245 com.apple.xpc.launchd[1]: (org.jenkins-ci[1437]) Service exited with abnormal code: 1
2017/02/04 11:12:17.245 com.apple.xpc.launchd[1]: (org.jenkins-ci) Service only ran for 0 seconds. Pushing respawn out by 10 seconds.
...

止めようとしても止まらない。

sudo launchctl stop org.jenkins-ci

アンロードしても止まらない。

sudo launchctl unload /Library/LaunchDaemons/org.jenkins-ci.plist

システム再起動したら復活してる!!

$ sudo launchctl list | grep jenkins

328 0 org.jenkins-ci

しかも同じサービスが二つある!!

$ sudo launchctl list | grep jenkins

62 0 org.jenkins-ci
$ launchctl list | grep jenkins
1079 0 org.jenkins-ci

そんな場合に見直すべきことをここに記しておきます。


loadとunload

今までずーっと勘違いしていたんですが、launchctlのloadunloadはあくまで「今実行中の」launchdの状態を変更するものであって、ログアウトしたりシステム終了すれば消えて無くなります。launchd自体に起動すべきサービスの情報を保持しておく機能はないのです。

では、再起動時にどうやってサービスが実行されるのかというと、こんなスクリプトがシステム起動時と各ユーザーログイン時に行われていると思ってください。(もちろん模擬コードね)


システム起動時

sudo launchctl load /System/Library/LaunchDaemons/*.plist

sudo launchctl load /Library/LaunchDaemons/*.plist


各ユーザーログイン時

launchctl load /System/Library/LaunchAgents/*.plist

launchctl load /Library/LaunchAgents/*.plist
launchctl load ~/Library/LaunchAgents/*.plist

なので、いくらstopunloadをしても.plistがOS指定の場所に残っている限り、再起動後に再び動き始めてしまいます。必要のないものはunloadだけではダメで、.plist自体を消してください。

サービスを設定した後は、一度再起動して動作を確認しましょう。


DaemonとAgent

さらにややこしい話。launchdで扱うサービスには二つあって、違いは以下のような感じ

Daemon


  • システム起動時にloadされる。(全体で一つのインスタンス)

  • 実行するユーザーを指定できる。(.plistのUserNameで指定)

  • GUIを表示できない。(表示してはいけない)

Agent


  • ユーザーログイン時にloadされる。(ユーザーごとに起動)

  • ログインユーザーで実行される。(UserNameは無視される)

  • GUIを表示できる。

と、されていますが、実は

launchd(launchctl)自体はDaemonとAgentを区別していません。

Mac OS X(mach?)の運用上のルールでしかないのです。

本質的な違いは

Daemon


  • システム側のlaunchdから実行したもの



    • sudo launchctl load hoge.plistで登録


    • sudo launchctl listで確認できる



Agent


  • ユーザー側のlaunchdから実行したもの



    • launchctl load hoge.plistで登録


    • launchctl listで確認できる



だけです。つまり「2つあるlaunchdのどちらで動いているのか?」という話なんですね。

なので、システムデーモンとして設計されたhogeサービスを

launchctl load /Library/LaunchDaemons/hoge.plist

でユーザーエージェントとして普通に起動できてしまいます(正しく動くかどうかは別として)。シェルから手動でloadする限りにおいては、.plistはどこにあっても構わないのです。もちろん、再起動後はMacOSXのルールに従い、システムデーモンとして起動します。

ここが混乱していると、さっきまでDaemonとして動いていたはずなのに、再起動後はAgentになっていたり、違うユーザーで動いていたり、収拾がつかなくなります(なりました…)。


Agentでないとダメな場合

自動実行という観点からだと、システムを起動するだけで実行できるDaemonの方が便利かとは思いますが、前述の通りGUIにアクセスするアプリだとそうはいきません。

Unity、お前のことだ!!

JenkinsからのコマンドラインビルドであってもEnlightenがGPUを使ってレンダリングするかららしいんですが、そういったアプリをDaemonとして実行すると、以下のようなエラーが延々と出続けることになります。前述の通り、GUIが使えないというだけで、使っているアプリでもDaemonとして実行はできてしまうので。

2016-12-03 12:44:41.895 Unity[713:17181] CFPasteboardRef CFPasteboardCreate(CFAllocatorRef, CFStringRef) : failed to create global data

2016-12-03 12:44:41.895 Unity[713:17181] CFPasteboardRef CFPasteboardCreate(CFAllocatorRef, CFStringRef) : failed to create global data
2016-12-03 12:44:41.895 Unity[713:17181] CFPasteboardRef CFPasteboardCreate(CFAllocatorRef, CFStringRef) : failed to create global data
2016-12-03 12:44:41.895 Unity[713:17181] CFPasteboardRef CFPasteboardCreate(CFAllocatorRef, CFStringRef) : failed to create global data
...

あきらめてAgentにして、ログインしたままにしておきましょう。

実は.plistのUserNameをログインユーザーにしてSessionCreateを消せばDaemonとしてでもGUIアプリを実行できるんですが、ログインしていないとダメなので(当たり前か)、あまり意味ないかも。また、キーチェーン関連で問題が発生してしまうので(ログインキーチェーンにアクセスできない?)、やっぱりAgentとして起動すべきのようです。


ログファイルのパーミッション

ここからはlaunchdとは直接的には関係ないのですが、よくある話として。

サービスを起動するための.plistにはUserNameというパラメータがあって、Daemonの場合はそのユーザーで起動されます。Agentではそれが無視されて各ログインユーザーで起動します。なんやかんやでいろいろ試していると、いつの間にか意図しないユーザーでログファイルが記録されてしまっていることがあるわけです。

例えばJenkinsのログファイルは/var/log/jenkins/フォルダに記録されますが、ここが違うユーザーになっていると、アクセス権の問題でログには何も記録されず、それ自体がエラー扱いとなってアプリは終了し、システムログにサービスの起動に失敗したことだけが延々と記録されてしまいます。

こういう場合はひとまずchmodなどでログファイルの所有権を変更しましょう。ログファイルやフォルダ自体を消すという手もありますが、ログファイルが再生成されるとは限りませんし、また、そのフォルダに消してはいけないファイルがある可能性もなくはないのです。少なくとも、$(JENKINS_HOME)/tmpは消してはいけません(tmpという名前に騙された…)。

つまり、マルチユーザーを考慮していないアプリの場合は

/Library/LaunchAgents

に置くと各ユーザーで実行されてアクセス権の問題が発生してしまうので、特定のユーザーの

~/Library/LaunchAgents

に置く方が安全、ということですね。


終わりに

ちなみに自分の環境でJenkinsサービスが二つ起動してしまったのは、DaemonからAgentに変更するときにmvではなくcpしてしまっていて、両方に.plistが残っていたからでした。手動で片方をunloadした時点では正しく動いて満足したのに、数日後に再起動するとまたおかしくなるという。最終的にどうにかなりましたが、その話はまた今度。

楽しい週末だった…。


ご参考

A launchd Tutorial

http://www.launchd.info

launchdctl(1) Mac OS X Manual Page

https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man1/launchctl.1.html