supervisorのプロセス再起動の仕組み

プロセス管理ツールの supervisor では、設定したプログラムをデーモン起動させ、意図せずにプロセスが終了した場合は、自動再起動する仕組みがある。

プログラムを確認すると、基本的には supervisord が各プログラムを fork & exec し、あとは異常終了により zombie になったプロセスがないか wait(non-blocking)  し、終了したプロセスを再び fork & exec しているだけ。supervisord デーモンを strace  すると select(2) と wait4(2) のシステムコールの嵐になっている。

簡単なサンプルコード

supervisor のソースコードをもとに、fork & exec => wait & reap zombies => fork & exec の一連のフローを実装したプログラムが以下。fork したプログラム(ここでは 30秒の sleep)が終了すると、自動起動するようにしている。

import os
import signal
import sys
import time

class Supervisor(object):
    def __init__(self):
        self.pidhistory = {}

    def run(self):
        p = Process()
        pid = p.spawn()
        self.pidhistory[pid] = 'dummy'
        self.runforever()

    def runforever(self):
        while True:
            time.sleep(1)
            pid, sts = self.reap()
            if (pid, sts) <> (0, 0):
                sys.stderr.write(decode_wait_status(sts) + '\n')
                self.pidhistory.pop(pid, None)
                p = Process()
                self.pidhistory[p.spawn()] = 'dummy'
            sys.stderr.write('.')

    def reap(self):
        # reap daemon process
        try:
            pid, sts = os.waitpid(-1, os.WNOHANG)
        except OSError, why:
            sys.stderr.write(str(why) + '\n')
            pid, sts = 0, 0
        return pid, sts

class Process(object):
    def __init__(self, path = '/bin/sleep', args = ['sleep', '30']):
        self.path = path
        self.args = args
        self.env = os.environ.copy()

    def _spawn_as_child(self):
        env = os.environ.copy()
        os.execve(self.path, self.args, self.env)

    def _spawn_as_parent(self, pid):
        sys.stderr.write('spawned: %r with pid %s\n' % ('sleep', pid))

    def spawn(self):
        pid = os.fork()
        if pid == 0:
            self._spawn_as_child()
        elif  pid > 0:
            self._spawn_as_parent(pid)
            return pid

def decode_wait_status(sts):
    if os.WIFEXITED(sts):
        es = os.WEXITSTATUS(sts) & 0xffff
        msg = "exit status %s" % es
    elif os.WIFSIGNALED(sts):
        sig = os.WTERMSIG(sts)
        msg = "terminated by signal %s" % sig
    else:
        msg = "unknown termination cause 0x%04x" % sts
    return msg

def main():
    s = Supervisor()
    s.run()

if __name__ == '__main__':
    main()

プログラムを起動し、シグナルを送ってみる

$ python mini-visor.py
spawned: 'sleep' with pid 1371
.............................exit status 0
spawned: 'sleep' with pid 1533
..........terminated by signal 9
spawned: 'sleep' with pid 1534
..........terminated by signal 15
spawned: 'sleep' with pid 1535
.........terminated by signal 2
spawned: 'sleep' with pid 1536
..............................exit status 0

雑感

  • 今年の正月前後にやったシステム・プログラミングのよい復習になった。
  • supervisord のソースコードは各ファイルやクラスやメソッドが長く、入り組んでいて、自分には少し読みにくく感じた。
Advertisements
Tagged with: , , ,
Posted in linux, middleware

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Archives
  • RT @__apf__: How to write a research paper: a guide for software engineers & practitioners. docs.google.com/presentation/d… /cc @inwyrd 4 months ago
  • RT @HayatoChiba: 昔、自然と対話しながら数学に打ち込んだら何かを悟れるのではと思いたち、専門書1つだけ持ってパワースポットで名高い奈良の山奥に1週間籠ったことがある。しかし泊まった民宿にドカベンが全巻揃っていたため、水島新司と対話しただけで1週間過ぎた。 それ… 5 months ago
  • RT @googlecloud: Ever wonder what underwater fiber optic internet cables look like? Look no further than this deep dive w/ @NatAndLo: https… 5 months ago
  • @ijin UTC+01:00 な時間帯で生活しています、、、 10 months ago
  • RT @mattcutts: Google's world-class Site Reliability Engineering team wrote a new book: amazon.com/Site-Reliabili… It's about managing produc… 1 year ago
%d bloggers like this: