view py/tempserver.py @ 514:0c29d1a16c8c

fix pid locking
author Matt Johnston <matt@ucc.asn.au>
date Fri, 19 Dec 2014 21:39:01 +0800
parents 4a8a1e886a8c
children 16a83e2c97a0
line wrap: on
line source

#!/home/matt/templog/venv/bin/python

import sys
import os
import logging
import time
import signal

import gevent
import gevent.monkey
import lockfile.pidlockfile
import daemon

import utils
from utils import L,D,EX,W
import fridge
import config
import sensor_ds18b20
import params
import uploader


class Tempserver(object):
    def __init__(self):
        self.readings = []
        self.current = (None, None)
        self.fridge = None

        # don't patch os, fork() is used by daemonize
        gevent.monkey.patch_all(os=False, thread=False)

    def __enter__(self):
        self.params = params.Params()
        self.fridge = fridge.Fridge(self)
        self.uploader = uploader.Uploader(self)
        self.params.load()
        self.set_sensors(sensor_ds18b20.DS18B20s(self))
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        L("Exiting, cleanup handler");
        self.fridge.off()

    def run(self):

        if self.fridge is None:
            raise Exception("Tempserver.run() must be within 'with Tempserver() as server'")

        # XXX do these go here or in __enter_() ?
        self.start_time = self.now()
        self.fridge.start()
        self.sensors.start()
        self.uploader.start()

        # won't return.
        while True:
            try:
                gevent.sleep(60)
            except KeyboardInterrupt:
                break

    def now(self):
        return utils.monotonic_time()

    def set_sensors(self, sensors):
        if hasattr(self, 'sensors'):
            self.sensors.kill()
        self.sensors = sensors
        self.wort_name = sensors.wort_name()
        self.fridge_name = sensors.fridge_name()

    def take_readings(self):
        ret = self.readings
        self.readings = []
        return ret

    def pushfront(self, readings):
        """ used if a caller of take_readings() fails """
        self.readings = readings + self.readings

    # a reading is a map of {sensorname: value}. temperatures
    # are float degrees
    def add_reading(self, reading):
        """ adds a reading at the current time """
        D("add_reading(%s)" % str(reading))
        self.readings.append( (reading, self.now()))
        self.current = (reading.get(self.wort_name, None),
                    reading.get(self.fridge_name, None))
        if len(self.readings) > config.MAX_READINGS:
            self.readings = self.readings[-config.MAX_READINGS:]

    def current_temps(self):
        """ returns (wort_temp, fridge_temp) tuple """
        return self.current

def setup_logging():
    logging.basicConfig(format='%(asctime)s %(message)s', 
            datefmt='%m/%d/%Y %I:%M:%S %p',
            level=logging.INFO)

def start():
    with Tempserver() as server:
        server.run()

def main():
    setup_logging()

    heredir = os.path.abspath(os.path.dirname(__file__))
    pidpath = os.path.join(heredir, 'tempserver.pid')
    pidf = lockfile.pidlockfile.PIDLockFile(pidpath, threaded=False)
    do_hup = '--hup' in sys.argv
    try:
        pidf.acquire(1)
        pidf.release()
    except (lockfile.AlreadyLocked, lockfile.LockTimeout), e:
        pid = pidf.read_pid()
        if do_hup:
            try:
                os.kill(pid, signal.SIGHUP)
                print>>sys.stderr, "Sent SIGHUP to process %d" % pid
                sys.exit(0)
            except OSError:
                print>>sys.stderr, "Process %d isn't running?" % pid
                sys.exit(1)

        print>>sys.stderr, "Locked by PID %d" % pid
    
        stale = False
        if pid > 0:
            if '--new' in sys.argv:
                try:
                    os.kill(pid, 0)
                except OSError:
                    stale = True

                if not stale:
                    print>>sys.stderr, "Stopping old tempserver pid %d" % pid
                    os.kill(pid, signal.SIGTERM)
                    time.sleep(2)
                    pidf.acquire(0)
                    pidf.release()
            else:
                try:
                    os.kill(pid, 0)
                    # must still be running PID
                    raise e
                except OSError:
                    stale = True

        if stale:
            # isn't still running, steal the lock
            print>>sys.stderr, "Unlinking stale lockfile %s for pid %d" % (pidpath, pid)
            pidf.break_lock()

    if do_hup:
        print>>sys.stderr, "Doesn't seem to be running"
        sys.exit(1)

    if '--daemon' in sys.argv:
        logpath = os.path.join(os.path.dirname(__file__), 'tempserver.log')
        logf = open(logpath, 'a+')
        with daemon.DaemonContext(pidfile=pidf, stdout=logf, stderr = logf):
            start()
    else:
        with pidf:
            start()

if __name__ == '__main__':
    main()