[pyramid/ctl] pyramid command will always run in foreground from now on

Daemonization used to make sens in the past, but today "pyramid" command is only used for dev and should never be used for deployement (you should use a wsgi server for that instead) so daemonization doesn't fit any use case anymore.

Closes #17232923

authorLaurent Peuch <cortex@worlddomination.be>
changeset6c48a49cd3c2
branchdefault
phasepublic
hiddenno
parent revision#c8ca784fdd77 [clean] remove pyramid warning about looping task
child revision#0ac07f55772c [html] Add tags ol, ul, li, i, q, #8e5f2c449e60 Merge 3.26
files modified by this revision
cubicweb/pyramid/pyramidctl.py
doc/changes/3.27.rst
# HG changeset patch
# User Laurent Peuch <cortex@worlddomination.be>
# Date 1558480615 -7200
# Wed May 22 01:16:55 2019 +0200
# Node ID 6c48a49cd3c2ddb82897c4e805d29a77baa243a4
# Parent c8ca784fdd77cf41fc336c7f286a5fbead560867
[pyramid/ctl] pyramid command will always run in foreground from now on

Daemonization used to make sens in the past, but today "pyramid" command is
only used for dev and should never be used for deployement (you should use a
wsgi server for that instead) so daemonization doesn't fit any use case
anymore.

Closes #17232923

diff --git a/cubicweb/pyramid/pyramidctl.py b/cubicweb/pyramid/pyramidctl.py
@@ -23,20 +23,17 @@
1 
2  The reloading strategy is heavily inspired by (and partially copied from)
3  the pyramid script 'pserve'.
4  """
5 
6 -import atexit
7 -import errno
8  import os
9  import signal
10  import sys
11  import time
12  import threading
13  import subprocess
14 
15 -from cubicweb import ExecutionError
16  from cubicweb.cwconfig import CubicWebConfiguration as cwcfg
17  from cubicweb.cwctl import CWCTL, InstanceCommand, init_cmdline_log_threshold
18  from cubicweb.pyramid import wsgi_application_from_cwconfig
19  from cubicweb.pyramid.config import get_random_secret_key
20  from cubicweb.server import serverctl, set_debug
@@ -95,21 +92,17 @@
21      """
22      name = 'pyramid'
23      actionverb = 'started'
24 
25      options = (
26 -        ('no-daemon',
27 -         {'action': 'store_true',
28 -          'help': 'Run the server in the foreground.'}),
29          ('debug-mode',
30           {'action': 'store_true',
31            'help': 'Activate the repository debug mode ('
32 -                  'logs in the console and the debug toolbar).'
33 -                  ' Implies --no-daemon'}),
34 +                  'logs in the console and the debug toolbar).'}),
35          ('debug',
36           {'short': 'D', 'action': 'store_true',
37 -          'help': 'Equals to "--debug-mode --no-daemon --reload"'}),
38 +          'help': 'Equals to "--debug-mode --reload"'}),
39          ('reload',
40           {'action': 'store_true',
41            'help': 'Restart the server if any source file is changed'}),
42          ('reload-interval',
43           {'type': 'int', 'default': 1,
@@ -175,96 +168,10 @@
44                  "handle this issue you must have the win32api module "
45                  "installed" % arg)
46          arg = win32api.GetShortPathName(arg)
47          return arg
48 
49 -    def _remove_pid_file(self, written_pid, filename):
50 -        current_pid = os.getpid()
51 -        if written_pid != current_pid:
52 -            # A forked process must be exiting, not the process that
53 -            # wrote the PID file
54 -            return
55 -        if not os.path.exists(filename):
56 -            return
57 -        with open(filename) as f:
58 -            content = f.read().strip()
59 -        try:
60 -            pid_in_file = int(content)
61 -        except ValueError:
62 -            pass
63 -        else:
64 -            if pid_in_file != current_pid:
65 -                msg = "PID file %s contains %s, not expected PID %s"
66 -                self.out(msg % (filename, pid_in_file, current_pid))
67 -                return
68 -        self.info("Removing PID file %s" % filename)
69 -        try:
70 -            os.unlink(filename)
71 -            return
72 -        except OSError as e:
73 -            # Record, but don't give traceback
74 -            self.out("Cannot remove PID file: (%s)" % e)
75 -        # well, at least lets not leave the invalid PID around...
76 -        try:
77 -            with open(filename, 'w') as f:
78 -                f.write('')
79 -        except OSError as e:
80 -            self.out('Stale PID left in file: %s (%s)' % (filename, e))
81 -        else:
82 -            self.out('Stale PID removed')
83 -
84 -    def record_pid(self, pid_file):
85 -        pid = os.getpid()
86 -        self.debug('Writing PID %s to %s' % (pid, pid_file))
87 -        with open(pid_file, 'w') as f:
88 -            f.write(str(pid))
89 -        atexit.register(
90 -            self._remove_pid_file, pid, pid_file)
91 -
92 -    def daemonize(self, pid_file):
93 -        pid = live_pidfile(pid_file)
94 -        if pid:
95 -            raise ExecutionError(
96 -                "Daemon is already running (PID: %s from PID file %s)"
97 -                % (pid, pid_file))
98 -
99 -        self.debug('Entering daemon mode')
100 -        pid = os.fork()
101 -        if pid:
102 -            # The forked process also has a handle on resources, so we
103 -            # *don't* want proper termination of the process, we just
104 -            # want to exit quick (which os._exit() does)
105 -            os._exit(0)
106 -        # Make this the session leader
107 -        os.setsid()
108 -        # Fork again for good measure!
109 -        pid = os.fork()
110 -        if pid:
111 -            os._exit(0)
112 -
113 -        # @@: Should we set the umask and cwd now?
114 -
115 -        import resource  # Resource usage information.
116 -        maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
117 -        if (maxfd == resource.RLIM_INFINITY):
118 -            maxfd = MAXFD
119 -        # Iterate through and close all file descriptors.
120 -        for fd in range(0, maxfd):
121 -            try:
122 -                os.close(fd)
123 -            except OSError:  # ERROR, fd wasn't open to begin with (ignored)
124 -                pass
125 -
126 -        if (hasattr(os, "devnull")):
127 -            REDIRECT_TO = os.devnull
128 -        else:
129 -            REDIRECT_TO = "/dev/null"
130 -        os.open(REDIRECT_TO, os.O_RDWR)  # standard input (0)
131 -        # Duplicate standard input to standard output and standard error.
132 -        os.dup2(0, 1)  # standard output (1)
133 -        os.dup2(0, 2)  # standard error (2)
134 -
135      def restart_with_reloader(self, filelist_path):
136          self.debug('Starting subprocess with file monitor')
137 
138          # Create or clear monitored files list file.
139          with open(filelist_path, 'w') as f:
@@ -334,15 +241,15 @@
140                      yield f
141 
142      def pyramid_instance(self, appid):
143          self._needreload = False
144 
145 -        debugmode = self['debug-mode'] or self['debug']
146          autoreload = self['reload'] or self['debug']
147 -        daemonize = not (self['no-daemon'] or debugmode or autoreload)
148 
149 -        cwconfig = cwcfg.config_for(appid, debugmode=debugmode)
150 +        # debugmode=True is to force to have a StreamHandler used instead of
151 +        # writting the logs into a file in /tmp
152 +        cwconfig = cwcfg.config_for(appid, debugmode=True)
153          filelist_path = os.path.join(cwconfig.apphome,
154                                       '.pyramid-reload-files.list')
155 
156          pyramid_ini_path = os.path.join(cwconfig.apphome, "pyramid.ini")
157          if not os.path.exists(pyramid_ini_path):
@@ -359,14 +266,10 @@
158              extra_files.extend(self.i18nfiles(cwconfig))
159              self.install_reloader(
160                  self['reload-interval'], extra_files,
161                  filelist_path=filelist_path)
162 
163 -        if daemonize:
164 -            self.daemonize(cwconfig['pid-file'])
165 -            self.record_pid(cwconfig['pid-file'])
166 -
167          if self['dbglevel']:
168              self['loglevel'] = 'debug'
169              set_debug('|'.join('DBG_' + x.upper() for x in self['dbglevel']))
170          init_cmdline_log_threshold(cwconfig, self['loglevel'])
171 
@@ -392,39 +295,10 @@
172 
173 
174  CWCTL.register(PyramidStartHandler)
175 
176 
177 -def live_pidfile(pidfile):  # pragma: no cover
178 -    """(pidfile:str) -> int | None
179 -    Returns an int found in the named file, if there is one,
180 -    and if there is a running process with that process id.
181 -    Return None if no such process exists.
182 -    """
183 -    pid = read_pidfile(pidfile)
184 -    if pid:
185 -        try:
186 -            os.kill(int(pid), 0)
187 -            return pid
188 -        except OSError as e:
189 -            if e.errno == errno.EPERM:
190 -                return pid
191 -    return None
192 -
193 -
194 -def read_pidfile(filename):
195 -    if os.path.exists(filename):
196 -        try:
197 -            with open(filename) as f:
198 -                content = f.read()
199 -            return int(content.strip())
200 -        except (ValueError, IOError):
201 -            return None
202 -    else:
203 -        return None
204 -
205 -
206  def _turn_sigterm_into_systemexit():
207      """Attempts to turn a SIGTERM exception into a SystemExit exception."""
208      try:
209          import signal
210      except ImportError:
diff --git a/doc/changes/3.27.rst b/doc/changes/3.27.rst
@@ -44,10 +44,13 @@
211    to fail now.
212 
213  * All "cubicweb-ctl" command only accept one instance argument from now one
214    (instead of 0 to n)
215 
216 +* 'pyramid' command will always run in the foreground now, by consequence the
217 +  option ``--no-daemon`` has been removed.
218 +
219  * DBG_MS flag has been removed since it is not used anymore
220 
221  Deprecated code drops
222  ---------------------
223