Coverage for gws-app/gws/server/monitor.py: 35%

107 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-16 23:09 +0200

1import os 

2 

3import gws 

4import gws.config 

5import gws.lib.lock 

6import gws.lib.osx 

7import gws.lib.watcher 

8import gws.server.uwsgi_module 

9 

10from . import control 

11 

12_LOCK_FILE = '/tmp/monitor.lock' 

13_RELOAD_FILE = '/tmp/monitor.reload' 

14_RECONFIGURE_FILE = '/tmp/monitor.reconfigure' 

15_TICK_FREQUENCY = 3 

16 

17 

18class Object(gws.ServerMonitor): 

19 watchPaths: set[str] 

20 enabled: bool 

21 frequency: int 

22 watcher: gws.lib.watcher.Watcher 

23 dirs: list 

24 files: list 

25 tasks: list[gws.Node] 

26 taskTime: int 

27 

28 def configure(self): 

29 self.frequency = self.cfg('frequency', default=30) 

30 self.dirs = [] 

31 self.files = [] 

32 self.tasks = [] 

33 self.taskTime = 0 

34 

35 def watch_directory(self, dirname, pattern, recursive=False): 

36 self.dirs.append((dirname, pattern, recursive)) 

37 

38 def watch_file(self, path): 

39 self.files.append(path) 

40 

41 def register_periodic_task(self, obj): 

42 self.tasks.append(obj) 

43 

44 def schedule_reload(self, with_reconfigure=False): 

45 gws.log.info(f'MONITOR: reload scheduled {with_reconfigure=}') 

46 os.open(_RECONFIGURE_FILE if with_reconfigure else _RELOAD_FILE, os.O_CREAT | os.O_WRONLY) 

47 

48 def start(self): 

49 

50 self._check_unlink(_LOCK_FILE) 

51 self._check_unlink(_RELOAD_FILE) 

52 self._check_unlink(_RECONFIGURE_FILE) 

53 

54 self.taskTime = gws.u.stime() 

55 

56 if not self.cfg('disableWatch'): 

57 def notify(evt, path): 

58 os.open(_RECONFIGURE_FILE, os.O_CREAT | os.O_WRONLY) 

59 

60 self.watcher = gws.lib.watcher.new(notify) 

61 

62 for d in self.dirs: 

63 self.watcher.add_directory(*d) 

64 for f in self.files: 

65 self.watcher.add_file(f) 

66 

67 self.watcher.start() 

68 

69 uwsgi = gws.server.uwsgi_module.load() 

70 uwsgi.register_signal(42, 'worker2', self._tick) 

71 uwsgi.add_timer(42, _TICK_FREQUENCY) 

72 

73 gws.log.info(f'MONITOR: started') 

74 

75 def _tick(self, signo): 

76 

77 do_reconfigure = self._check_unlink(_RECONFIGURE_FILE) 

78 do_reload = self._check_unlink(_RELOAD_FILE) 

79 do_tasks = (gws.u.stime() - self.taskTime >= self.frequency) and self.tasks 

80 

81 if not do_reconfigure and not do_reload and not do_tasks: 

82 return 

83 

84 gws.log.debug(f'MONITOR: tick {do_reconfigure=} {do_reload=} {do_tasks=}') 

85 

86 try: 

87 os.open(_LOCK_FILE, os.O_CREAT | os.O_EXCL | os.O_WRONLY) 

88 except FileExistsError: 

89 gws.log.debug(f'MONITOR: locked') 

90 return 

91 

92 try: 

93 if do_reconfigure: 

94 self._reload(True) 

95 elif do_reload: 

96 self._reload(False) 

97 elif do_tasks: 

98 self._run_periodic_tasks() 

99 self.taskTime = gws.u.stime() 

100 finally: 

101 self._check_unlink(_LOCK_FILE) 

102 

103 def _reload(self, with_reconfigure): 

104 gws.log.info(f'MONITOR: reloading...') 

105 

106 if not self._reload2(with_reconfigure): 

107 return 

108 

109 # ok, reload ourselves 

110 gws.log.info(f'MONITOR: bye bye') 

111 control.reload_app('spool') 

112 

113 def _reload2(self, with_reconfigure): 

114 if with_reconfigure: 

115 try: 

116 control.configure_and_store() 

117 except Exception as exc: 

118 gws.log.exception(f'MONITOR: configuration error {exc!r}') 

119 return False 

120 

121 try: 

122 control.reload_app('mapproxy') 

123 control.reload_app('web') 

124 return True 

125 except Exception as exc: 

126 gws.log.exception(f'MONITOR: reload error {exc!r}') 

127 return False 

128 

129 def _run_periodic_tasks(self): 

130 for obj in self.tasks: 

131 try: 

132 obj.periodic_task() 

133 except: 

134 gws.log.exception(f'MONITOR: periodic task failed {obj}') 

135 

136 def _check_unlink(self, path): 

137 try: 

138 os.unlink(path) 

139 return True 

140 except FileNotFoundError: 

141 return False