ccplugin.py
changeset 11638 12de153c0d0e
parent 11637 a9cde6a3394c
child 11639 f38ec5e29de3
equal deleted inserted replaced
11637:a9cde6a3394c 11638:12de153c0d0e
     7 import atexit
     7 import atexit
     8 import errno
     8 import errno
     9 import os
     9 import os
    10 import signal
    10 import signal
    11 import sys
    11 import sys
       
    12 import tempfile
    12 import time
    13 import time
    13 import threading
    14 import threading
    14 import subprocess
    15 import subprocess
    15 
    16 
    16 from cubicweb import BadCommandUsage, ExecutionError
    17 from cubicweb import BadCommandUsage, ExecutionError
    49           'help': 'debug if -D is set, error otherwise',
    50           'help': 'debug if -D is set, error otherwise',
    50           }),
    51           }),
    51     )
    52     )
    52 
    53 
    53     _reloader_environ_key = 'CW_RELOADER_SHOULD_RUN'
    54     _reloader_environ_key = 'CW_RELOADER_SHOULD_RUN'
       
    55     _reloader_filelist_environ_key = 'CW_RELOADER_FILELIST'
    54 
    56 
    55     def debug(self, msg):
    57     def debug(self, msg):
    56         print('DEBUG - %s' % msg)
    58         print('DEBUG - %s' % msg)
    57 
    59 
    58     def info(self, msg):
    60     def info(self, msg):
   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