Coverage for gws-app/gws/server/monitor.py: 33%
115 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 12:46 +0200
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 12:46 +0200
1import os
3import gws
4import gws.lib.watcher
5import gws.server.uwsgi_module
7from . import control
9_LOCK_FILE = '/tmp/monitor.lock'
10_RELOAD_FILE = '/tmp/monitor.reload'
11_RECONFIGURE_FILE = '/tmp/monitor.reconfigure'
12_TICK_FREQUENCY = 3
14DEFAULT_FREQUENCY = 30
17class _Task(gws.Data):
18 obj: gws.Node
19 frequency: int
20 lastTime: int
23class Object(gws.ServerMonitor):
24 watchPaths: set[str]
25 enabled: bool
26 frequency: int
27 watcher: gws.lib.watcher.Watcher
28 dirs: list
29 files: list
30 tasks: list[_Task]
32 def configure(self):
33 self.frequency = self.cfg('frequency', default=DEFAULT_FREQUENCY)
34 self.dirs = []
35 self.files = []
36 self.tasks = []
38 def watch_directory(self, dirname, pattern, recursive=False):
39 self.dirs.append((dirname, pattern, recursive))
41 def watch_file(self, path):
42 self.files.append(path)
44 def register_periodic_task(self, obj, frequency=0):
45 if not hasattr(obj, 'periodic_task'):
46 raise gws.Error(f'MONITOR: {obj!r} has no periodic_task')
47 self.tasks.append(
48 _Task(
49 obj=obj,
50 frequency=frequency or self.frequency,
51 lastTime=0,
52 )
53 )
55 def schedule_reload(self, with_reconfigure=False):
56 gws.log.info(f'MONITOR: reload scheduled {with_reconfigure=}')
57 self._touch(_RECONFIGURE_FILE if with_reconfigure else _RELOAD_FILE)
59 def start(self):
60 self._check_unlink(_LOCK_FILE)
61 self._check_unlink(_RELOAD_FILE)
62 self._check_unlink(_RECONFIGURE_FILE)
64 for t in self.tasks:
65 t.lastTime = gws.u.stime()
67 if not self.cfg('disableWatch'):
69 def notify(evt, path):
70 self._touch(_RECONFIGURE_FILE)
72 self.watcher = gws.lib.watcher.new(notify)
74 for d in self.dirs:
75 self.watcher.add_directory(*d)
76 for f in self.files:
77 self.watcher.add_file(f)
79 self.watcher.start()
81 uwsgi = gws.server.uwsgi_module.load()
82 uwsgi.register_signal(42, 'worker2', self._tick)
83 uwsgi.add_timer(42, _TICK_FREQUENCY)
85 gws.log.info(f'MONITOR: started')
87 def _tick(self, signo):
88 do_reconfigure = self._check_unlink(_RECONFIGURE_FILE)
89 do_reload = self._check_unlink(_RELOAD_FILE)
91 tasks = [t for t in self.tasks if gws.u.stime() - t.lastTime >= t.frequency]
93 if not do_reconfigure and not do_reload and not tasks:
94 # gws.log.debug(f'MONITOR: tick skip')
95 return
97 gws.log.debug(f'MONITOR: tick {do_reconfigure=} {do_reload=} tasks={len(tasks)}')
99 try:
100 self._touch(_LOCK_FILE, excl=True)
101 except FileExistsError:
102 gws.log.debug(f'MONITOR: locked')
103 return
105 try:
106 if do_reconfigure:
107 self._reload(True)
108 elif do_reload:
109 self._reload(False)
110 elif tasks:
111 self._run_periodic_tasks(tasks)
112 finally:
113 self._check_unlink(_LOCK_FILE)
115 def _reload(self, with_reconfigure):
116 gws.log.info(f'MONITOR: reloading...')
118 if not self._reload2(with_reconfigure):
119 return
121 # ok, reload ourselves
122 gws.log.info(f'MONITOR: bye bye')
123 control.reload_app('spool')
125 def _reload2(self, with_reconfigure):
126 if with_reconfigure:
127 try:
128 control.configure_and_store()
129 except Exception as exc:
130 gws.log.exception(f'MONITOR: configuration error: {exc!r}')
131 return False
133 try:
134 control.reload_app('mapproxy')
135 control.reload_app('web')
136 return True
137 except Exception as exc:
138 gws.log.exception(f'MONITOR: reload error: {exc!r}')
139 return False
141 def _run_periodic_tasks(self, tasks):
142 for t in tasks:
143 try:
144 t.obj.periodic_task()
145 t.lastTime = gws.u.stime()
146 except Exception as exc:
147 gws.log.exception(f'MONITOR: periodic task failed {t.obj}: {exc!r}')
149 def _touch(self, path, excl=False):
150 flags = os.O_CREAT | os.O_WRONLY
151 if excl:
152 flags |= os.O_EXCL
153 os.close(os.open(path, flags))
155 def _check_unlink(self, path):
156 try:
157 os.unlink(path)
158 return True
159 except FileNotFoundError:
160 return False