172 os.dup2(0, 2) # standard error (2) |
174 os.dup2(0, 2) # standard error (2) |
173 |
175 |
174 def restart_with_reloader(self): |
176 def restart_with_reloader(self): |
175 self.debug('Starting subprocess with file monitor') |
177 self.debug('Starting subprocess with file monitor') |
176 |
178 |
|
179 with tempfile.NamedTemporaryFile(delete=False) as f: |
|
180 filelist_path = f.name |
|
181 |
177 while True: |
182 while True: |
178 args = [self.quote_first_command_arg(sys.executable)] + sys.argv |
183 args = [self.quote_first_command_arg(sys.executable)] + sys.argv |
179 new_environ = os.environ.copy() |
184 new_environ = os.environ.copy() |
180 new_environ[self._reloader_environ_key] = 'true' |
185 new_environ[self._reloader_environ_key] = 'true' |
|
186 new_environ[self._reloader_filelist_environ_key] = filelist_path |
181 proc = None |
187 proc = None |
182 try: |
188 try: |
183 try: |
189 try: |
184 proc = subprocess.Popen(args, env=new_environ) |
190 proc = subprocess.Popen(args, env=new_environ) |
185 exit_code = proc.wait() |
191 exit_code = proc.wait() |
194 self.info( |
200 self.info( |
195 'Waiting for the server to stop. Hit CTRL-C to exit') |
201 'Waiting for the server to stop. Hit CTRL-C to exit') |
196 exit_code = proc.wait() |
202 exit_code = proc.wait() |
197 |
203 |
198 if exit_code != 3: |
204 if exit_code != 3: |
199 return exit_code |
205 with open(filelist_path) as f: |
|
206 filelist = [line.strip() for line in f] |
|
207 if filelist: |
|
208 self.info("Reloading failed. Waiting for a file to change") |
|
209 mon = Monitor(extra_files=filelist, nomodules=True) |
|
210 while mon.check_reload(): |
|
211 time.sleep(1) |
|
212 else: |
|
213 return exit_code |
200 |
214 |
201 self.info('%s %s %s' % ('-' * 20, 'Restarting', '-' * 20)) |
215 self.info('%s %s %s' % ('-' * 20, 'Restarting', '-' * 20)) |
202 |
216 |
203 def set_needreload(self): |
217 def set_needreload(self): |
204 self._needreload = True |
218 self._needreload = True |
205 |
219 |
206 def install_reloader(self, poll_interval, extra_files): |
220 def install_reloader(self, poll_interval, extra_files, filelist_path): |
207 mon = Monitor( |
221 mon = Monitor( |
208 poll_interval=poll_interval, extra_files=extra_files, |
222 poll_interval=poll_interval, extra_files=extra_files, |
209 atexit=self.set_needreload) |
223 atexit=self.set_needreload, filelist_path=filelist_path) |
210 mon_thread = threading.Thread(target=mon.periodic_reload) |
224 mon_thread = threading.Thread(target=mon.periodic_reload) |
211 mon_thread.daemon = True |
225 mon_thread.daemon = True |
212 mon_thread.start() |
226 mon_thread.start() |
213 |
227 |
214 def pyramid_instance(self, appid): |
228 def pyramid_instance(self, appid): |
221 |
235 |
222 if self['reload']: |
236 if self['reload']: |
223 _turn_sigterm_into_systemexit() |
237 _turn_sigterm_into_systemexit() |
224 self.debug('Running reloading file monitor') |
238 self.debug('Running reloading file monitor') |
225 extra_files = [sys.argv[0], cwconfig.main_config_file()] |
239 extra_files = [sys.argv[0], cwconfig.main_config_file()] |
226 self.install_reloader(self['reload-interval'], extra_files) |
240 self.install_reloader( |
|
241 self['reload-interval'], extra_files, |
|
242 filelist_path=os.environ.get( |
|
243 self._reloader_filelist_environ_key)) |
227 |
244 |
228 if not self['reload'] and not self['debug']: |
245 if not self['reload'] and not self['debug']: |
229 self.daemonize(cwconfig['pid-file']) |
246 self.daemonize(cwconfig['pid-file']) |
230 self.record_pid(cwconfig['pid-file']) |
247 self.record_pid(cwconfig['pid-file']) |
231 |
248 |
297 """ |
314 """ |
298 A file monitor and server stopper. |
315 A file monitor and server stopper. |
299 |
316 |
300 It is a simplified version of pyramid pserve.Monitor, with little changes: |
317 It is a simplified version of pyramid pserve.Monitor, with little changes: |
301 |
318 |
302 - The constructor takes extra_files and atexit |
319 - The constructor takes extra_files, atexit, nomodules and filelist_path |
303 - The process is stopped by auto-kill with signal SIGTERM |
320 - The process is stopped by auto-kill with signal SIGTERM |
304 """ |
321 """ |
305 def __init__(self, poll_interval, extra_files=[], atexit=None): |
322 def __init__(self, poll_interval=1, extra_files=[], atexit=None, |
|
323 nomodules=False, filelist_path=None): |
306 self.module_mtimes = {} |
324 self.module_mtimes = {} |
307 self.keep_running = True |
325 self.keep_running = True |
308 self.poll_interval = poll_interval |
326 self.poll_interval = poll_interval |
309 self.extra_files = extra_files |
327 self.extra_files = extra_files |
310 self.atexit = atexit |
328 self.atexit = atexit |
|
329 self.nomodules = nomodules |
|
330 self.filelist_path = filelist_path |
311 |
331 |
312 def _exit(self): |
332 def _exit(self): |
313 if self.atexit: |
333 if self.atexit: |
314 self.atexit() |
334 self.atexit() |
315 os.kill(os.getpid(), signal.SIGTERM) |
335 os.kill(os.getpid(), signal.SIGTERM) |
322 time.sleep(self.poll_interval) |
342 time.sleep(self.poll_interval) |
323 |
343 |
324 def check_reload(self): |
344 def check_reload(self): |
325 filenames = list(self.extra_files) |
345 filenames = list(self.extra_files) |
326 |
346 |
327 for module in list(sys.modules.values()): |
347 if not self.nomodules: |
328 try: |
348 for module in list(sys.modules.values()): |
329 filename = module.__file__ |
349 try: |
330 except (AttributeError, ImportError): |
350 filename = module.__file__ |
331 continue |
351 except (AttributeError, ImportError): |
332 if filename is not None: |
352 continue |
333 filenames.append(filename) |
353 if filename is not None: |
|
354 filenames.append(filename) |
334 |
355 |
335 for filename in filenames: |
356 for filename in filenames: |
336 try: |
357 try: |
337 stat = os.stat(filename) |
358 stat = os.stat(filename) |
338 if stat: |
359 if stat: |
347 self.module_mtimes[filename] = mtime |
368 self.module_mtimes[filename] = mtime |
348 elif self.module_mtimes[filename] < mtime: |
369 elif self.module_mtimes[filename] < mtime: |
349 print('%s changed; reloading...' % filename) |
370 print('%s changed; reloading...' % filename) |
350 return False |
371 return False |
351 |
372 |
|
373 if self.filelist_path: |
|
374 with open(self.filelist_path) as f: |
|
375 filelist = set((line.strip() for line in f)) |
|
376 |
|
377 filelist.update(filenames) |
|
378 |
|
379 with open(self.filelist_path, 'w') as f: |
|
380 for filename in filelist: |
|
381 f.write('%s\n' % filename) |
|
382 |
352 return True |
383 return True |