Mercurial > templog
annotate py/tempserver.py @ 232:a01b7bccccd3
improve exception handling
author | Matt Johnston <matt@ucc.asn.au> |
---|---|
date | Sat, 11 Apr 2015 21:09:13 +0800 |
parents | e39ed85d87a5 |
children | 19569cb5ed46 |
rev | line source |
---|---|
148 | 1 #!/home/matt/templog/venv/bin/python |
141 | 2 |
3 import sys | |
4 import os | |
149
d686b111dab4
working better. logging works properly, cleanup fridge.off() happens.
Matt Johnston <matt@ucc.asn.au>
parents:
148
diff
changeset
|
5 import logging |
198 | 6 import time |
7 import signal | |
228 | 8 import asyncio |
149
d686b111dab4
working better. logging works properly, cleanup fridge.off() happens.
Matt Johnston <matt@ucc.asn.au>
parents:
148
diff
changeset
|
9 |
172 | 10 import lockfile.pidlockfile |
162
d73077e8cd67
Add daemon mode with locking, add "disabled" parameter
Matt Johnston <matt@ucc.asn.au>
parents:
160
diff
changeset
|
11 import daemon |
141 | 12 |
13 import utils | |
149
d686b111dab4
working better. logging works properly, cleanup fridge.off() happens.
Matt Johnston <matt@ucc.asn.au>
parents:
148
diff
changeset
|
14 from utils import L,D,EX,W |
145 | 15 import fridge |
16 import config | |
231 | 17 import sensor |
145 | 18 import params |
160 | 19 import uploader |
145 | 20 |
141 | 21 |
22 class Tempserver(object): | |
23 def __init__(self): | |
24 self.readings = [] | |
145 | 25 self.current = (None, None) |
149
d686b111dab4
working better. logging works properly, cleanup fridge.off() happens.
Matt Johnston <matt@ucc.asn.au>
parents:
148
diff
changeset
|
26 self.fridge = None |
232 | 27 self._wakeup = asyncio.Condition() |
141 | 28 |
149
d686b111dab4
working better. logging works properly, cleanup fridge.off() happens.
Matt Johnston <matt@ucc.asn.au>
parents:
148
diff
changeset
|
29 def __enter__(self): |
145 | 30 self.params = params.Params() |
149
d686b111dab4
working better. logging works properly, cleanup fridge.off() happens.
Matt Johnston <matt@ucc.asn.au>
parents:
148
diff
changeset
|
31 self.fridge = fridge.Fridge(self) |
160 | 32 self.uploader = uploader.Uploader(self) |
145 | 33 self.params.load() |
229 | 34 self.set_sensors(sensor.make_sensor(self)) |
228 | 35 asyncio.get_event_loop().add_signal_handler(signal.SIGHUP, self._reload_signal) |
149
d686b111dab4
working better. logging works properly, cleanup fridge.off() happens.
Matt Johnston <matt@ucc.asn.au>
parents:
148
diff
changeset
|
36 return self |
141 | 37 |
149
d686b111dab4
working better. logging works properly, cleanup fridge.off() happens.
Matt Johnston <matt@ucc.asn.au>
parents:
148
diff
changeset
|
38 def __exit__(self, exc_type, exc_value, traceback): |
d686b111dab4
working better. logging works properly, cleanup fridge.off() happens.
Matt Johnston <matt@ucc.asn.au>
parents:
148
diff
changeset
|
39 L("Exiting, cleanup handler"); |
d686b111dab4
working better. logging works properly, cleanup fridge.off() happens.
Matt Johnston <matt@ucc.asn.au>
parents:
148
diff
changeset
|
40 self.fridge.off() |
141 | 41 |
145 | 42 def run(self): |
149
d686b111dab4
working better. logging works properly, cleanup fridge.off() happens.
Matt Johnston <matt@ucc.asn.au>
parents:
148
diff
changeset
|
43 |
d686b111dab4
working better. logging works properly, cleanup fridge.off() happens.
Matt Johnston <matt@ucc.asn.au>
parents:
148
diff
changeset
|
44 if self.fridge is None: |
d686b111dab4
working better. logging works properly, cleanup fridge.off() happens.
Matt Johnston <matt@ucc.asn.au>
parents:
148
diff
changeset
|
45 raise Exception("Tempserver.run() must be within 'with Tempserver() as server'") |
d686b111dab4
working better. logging works properly, cleanup fridge.off() happens.
Matt Johnston <matt@ucc.asn.au>
parents:
148
diff
changeset
|
46 |
d686b111dab4
working better. logging works properly, cleanup fridge.off() happens.
Matt Johnston <matt@ucc.asn.au>
parents:
148
diff
changeset
|
47 # XXX do these go here or in __enter_() ? |
d686b111dab4
working better. logging works properly, cleanup fridge.off() happens.
Matt Johnston <matt@ucc.asn.au>
parents:
148
diff
changeset
|
48 self.start_time = self.now() |
228 | 49 tasks = ( |
50 self.fridge.run(), | |
51 self.sensors.run(), | |
52 self.uploader.run(), | |
53 ) | |
232 | 54 |
228 | 55 loop = asyncio.get_event_loop() |
232 | 56 result_tasks = loop.run_until_complete(asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)) |
57 # use the results so that exceptions get thrown | |
58 [t.result() for x in result_tasks for t in x] | |
145 | 59 |
60 def now(self): | |
61 return utils.monotonic_time() | |
62 | |
144 | 63 def set_sensors(self, sensors): |
148 | 64 if hasattr(self, 'sensors'): |
144 | 65 self.sensors.kill() |
66 self.sensors = sensors | |
67 self.wort_name = sensors.wort_name() | |
68 self.fridge_name = sensors.fridge_name() | |
141 | 69 |
70 def take_readings(self): | |
71 ret = self.readings | |
72 self.readings = [] | |
73 return ret | |
74 | |
75 def pushfront(self, readings): | |
76 """ used if a caller of take_readings() fails """ | |
160 | 77 self.readings = readings + self.readings |
141 | 78 |
144 | 79 # a reading is a map of {sensorname: value}. temperatures |
80 # are float degrees | |
141 | 81 def add_reading(self, reading): |
82 """ adds a reading at the current time """ | |
155 | 83 D("add_reading(%s)" % str(reading)) |
145 | 84 self.readings.append( (reading, self.now())) |
144 | 85 self.current = (reading.get(self.wort_name, None), |
86 reading.get(self.fridge_name, None)) | |
160 | 87 if len(self.readings) > config.MAX_READINGS: |
88 self.readings = self.readings[-config.MAX_READINGS:] | |
141 | 89 |
90 def current_temps(self): | |
144 | 91 """ returns (wort_temp, fridge_temp) tuple """ |
232 | 92 D("current: %s" % str(self.current)) |
148 | 93 return self.current |
145 | 94 |
232 | 95 @asyncio.coroutine |
219
16a83e2c97a0
sleep on a semaphore so it can start/stop immediately when there's a SIGHUP
Matt Johnston <matt@ucc.asn.au>
parents:
217
diff
changeset
|
96 def sleep(self, timeout): |
16a83e2c97a0
sleep on a semaphore so it can start/stop immediately when there's a SIGHUP
Matt Johnston <matt@ucc.asn.au>
parents:
217
diff
changeset
|
97 """ sleeps for timeout seconds, though wakes if the server's config is updated """ |
232 | 98 # XXX fixme - we should wake on _wakeup but asyncio Condition with wait_for is a bit broken? |
99 # https://groups.google.com/forum/#!topic/python-tulip/eSm7rZAe9LM | |
100 # For now we just sleep, ignore the _wakeup | |
101 yield from asyncio.sleep(timeout) | |
219
16a83e2c97a0
sleep on a semaphore so it can start/stop immediately when there's a SIGHUP
Matt Johnston <matt@ucc.asn.au>
parents:
217
diff
changeset
|
102 |
232 | 103 @asyncio.coroutine |
219
16a83e2c97a0
sleep on a semaphore so it can start/stop immediately when there's a SIGHUP
Matt Johnston <matt@ucc.asn.au>
parents:
217
diff
changeset
|
104 def _reload_signal(self): |
16a83e2c97a0
sleep on a semaphore so it can start/stop immediately when there's a SIGHUP
Matt Johnston <matt@ucc.asn.au>
parents:
217
diff
changeset
|
105 try: |
16a83e2c97a0
sleep on a semaphore so it can start/stop immediately when there's a SIGHUP
Matt Johnston <matt@ucc.asn.au>
parents:
217
diff
changeset
|
106 self.params.load() |
16a83e2c97a0
sleep on a semaphore so it can start/stop immediately when there's a SIGHUP
Matt Johnston <matt@ucc.asn.au>
parents:
217
diff
changeset
|
107 L("Reloaded.") |
232 | 108 yield from self._wakeup.acquire() |
109 self._wakeup.notify_all() | |
110 self._wakeup.release() | |
229 | 111 except Error as e: |
219
16a83e2c97a0
sleep on a semaphore so it can start/stop immediately when there's a SIGHUP
Matt Johnston <matt@ucc.asn.au>
parents:
217
diff
changeset
|
112 W("Problem reloading: %s" % str(e)) |
16a83e2c97a0
sleep on a semaphore so it can start/stop immediately when there's a SIGHUP
Matt Johnston <matt@ucc.asn.au>
parents:
217
diff
changeset
|
113 |
145 | 114 def setup_logging(): |
115 logging.basicConfig(format='%(asctime)s %(message)s', | |
116 datefmt='%m/%d/%Y %I:%M:%S %p', | |
232 | 117 level=logging.DEBUG) |
118 logging.getLogger("asyncio").setLevel(logging.DEBUG) | |
145 | 119 |
162
d73077e8cd67
Add daemon mode with locking, add "disabled" parameter
Matt Johnston <matt@ucc.asn.au>
parents:
160
diff
changeset
|
120 def start(): |
d73077e8cd67
Add daemon mode with locking, add "disabled" parameter
Matt Johnston <matt@ucc.asn.au>
parents:
160
diff
changeset
|
121 with Tempserver() as server: |
d73077e8cd67
Add daemon mode with locking, add "disabled" parameter
Matt Johnston <matt@ucc.asn.au>
parents:
160
diff
changeset
|
122 server.run() |
d73077e8cd67
Add daemon mode with locking, add "disabled" parameter
Matt Johnston <matt@ucc.asn.au>
parents:
160
diff
changeset
|
123 |
145 | 124 def main(): |
149
d686b111dab4
working better. logging works properly, cleanup fridge.off() happens.
Matt Johnston <matt@ucc.asn.au>
parents:
148
diff
changeset
|
125 setup_logging() |
145 | 126 |
174
c49d87bb81b9
fix to absolute path for lockfile, --daemon does chdir("/")
Matt Johnston <matt@ucc.asn.au>
parents:
172
diff
changeset
|
127 heredir = os.path.abspath(os.path.dirname(__file__)) |
c49d87bb81b9
fix to absolute path for lockfile, --daemon does chdir("/")
Matt Johnston <matt@ucc.asn.au>
parents:
172
diff
changeset
|
128 pidpath = os.path.join(heredir, 'tempserver.pid') |
172 | 129 pidf = lockfile.pidlockfile.PIDLockFile(pidpath, threaded=False) |
199 | 130 do_hup = '--hup' in sys.argv |
172 | 131 try: |
217 | 132 pidf.acquire(1) |
172 | 133 pidf.release() |
229 | 134 except (lockfile.AlreadyLocked, lockfile.LockTimeout) as e: |
172 | 135 pid = pidf.read_pid() |
199 | 136 if do_hup: |
137 try: | |
138 os.kill(pid, signal.SIGHUP) | |
230 | 139 print("Sent SIGHUP to process %d" % pid, file=sys.stderr) |
199 | 140 sys.exit(0) |
141 except OSError: | |
230 | 142 print("Process %d isn't running?" % pid, file=sys.stderr) |
199 | 143 sys.exit(1) |
144 | |
230 | 145 print("Locked by PID %d" % pid, file=sys.stderr) |
199 | 146 |
198 | 147 stale = False |
172 | 148 if pid > 0: |
198 | 149 if '--new' in sys.argv: |
150 try: | |
151 os.kill(pid, 0) | |
152 except OSError: | |
153 stale = True | |
154 | |
155 if not stale: | |
230 | 156 print("Stopping old tempserver pid %d" % pid, file=sys.stderr) |
198 | 157 os.kill(pid, signal.SIGTERM) |
158 time.sleep(2) | |
159 pidf.acquire(0) | |
160 pidf.release() | |
161 else: | |
162 try: | |
163 os.kill(pid, 0) | |
164 # must still be running PID | |
165 raise e | |
166 except OSError: | |
167 stale = True | |
168 | |
169 if stale: | |
170 # isn't still running, steal the lock | |
230 | 171 print("Unlinking stale lockfile %s for pid %d" % (pidpath, pid), file=sys.stderr) |
198 | 172 pidf.break_lock() |
162
d73077e8cd67
Add daemon mode with locking, add "disabled" parameter
Matt Johnston <matt@ucc.asn.au>
parents:
160
diff
changeset
|
173 |
199 | 174 if do_hup: |
230 | 175 print("Doesn't seem to be running", file=sys.stderr) |
199 | 176 sys.exit(1) |
177 | |
145 | 178 if '--daemon' in sys.argv: |
162
d73077e8cd67
Add daemon mode with locking, add "disabled" parameter
Matt Johnston <matt@ucc.asn.au>
parents:
160
diff
changeset
|
179 logpath = os.path.join(os.path.dirname(__file__), 'tempserver.log') |
d73077e8cd67
Add daemon mode with locking, add "disabled" parameter
Matt Johnston <matt@ucc.asn.au>
parents:
160
diff
changeset
|
180 logf = open(logpath, 'a+') |
d73077e8cd67
Add daemon mode with locking, add "disabled" parameter
Matt Johnston <matt@ucc.asn.au>
parents:
160
diff
changeset
|
181 with daemon.DaemonContext(pidfile=pidf, stdout=logf, stderr = logf): |
d73077e8cd67
Add daemon mode with locking, add "disabled" parameter
Matt Johnston <matt@ucc.asn.au>
parents:
160
diff
changeset
|
182 start() |
d73077e8cd67
Add daemon mode with locking, add "disabled" parameter
Matt Johnston <matt@ucc.asn.au>
parents:
160
diff
changeset
|
183 else: |
d73077e8cd67
Add daemon mode with locking, add "disabled" parameter
Matt Johnston <matt@ucc.asn.au>
parents:
160
diff
changeset
|
184 with pidf: |
d73077e8cd67
Add daemon mode with locking, add "disabled" parameter
Matt Johnston <matt@ucc.asn.au>
parents:
160
diff
changeset
|
185 start() |
145 | 186 |
187 if __name__ == '__main__': | |
188 main() |