Skip to content

Commit a667f79

Browse files
Improve mod_wsgi-express behaviour on Windows.
- start-server now launches httpd via Popen and waits in a loop that absorbs the launcher's own Ctrl-C so httpd can shut down gracefully, with a second Ctrl-C escalating to terminate to avoid hangs. Also pass the constructed environment to the child rather than only mutating os.environ. - --log-to-terminal now routes the access log to the CON device on Windows to match the error log, instead of falling back to a piped tee that does not exist there. - find_program splits PATH on os.pathsep so it parses correctly on Windows where PATH is semicolon separated and entries contain colons. - Source code reloading is disabled on Windows, where os.kill cannot deliver SIGINT to the process and would hard kill the server.
1 parent 154fe13 commit a667f79

4 files changed

Lines changed: 49 additions & 13 deletions

File tree

src/express/cli.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,33 @@ def cmd_start_server(params):
6767
httpd_arguments.extend(['-f', config['httpd_conf']])
6868
httpd_arguments.extend(['-DONE_PROCESS'])
6969

70-
os.environ['MOD_WSGI_MODULES_DIRECTORY'] = config['modules_directory']
71-
72-
subprocess.call([executable]+httpd_arguments)
73-
74-
sys.exit(0)
70+
# On Windows httpd shares our console, so a Ctrl-C delivers a
71+
# CTRL_C_EVENT to the whole console process group and httpd runs
72+
# its own graceful shutdown. We must not let the same Ctrl-C kill
73+
# this launcher first, or it returns to the shell while httpd is
74+
# still tearing down (and dumps a KeyboardInterrupt traceback). So
75+
# absorb our own interrupt and keep waiting until httpd exits.
76+
#
77+
# A first Ctrl-C is treated as "you should have received the
78+
# console event, shutting down gracefully, I will wait". If httpd
79+
# did not actually receive the event (for example a git-bash pty
80+
# bridge that does not forward it to the child), a second Ctrl-C
81+
# escalates to terminating it so we cannot hang indefinitely.
82+
83+
process = subprocess.Popen([executable]+httpd_arguments, env=environ)
84+
85+
interrupts = 0
86+
87+
while True:
88+
try:
89+
process.wait()
90+
break
91+
except KeyboardInterrupt:
92+
interrupts += 1
93+
if interrupts >= 2:
94+
process.terminate()
95+
96+
sys.exit(process.returncode)
7597

7698
else:
7799
executable = posixpath.join(config['server_root'], 'apachectl')

src/express/platform.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def default_run_group():
8282

8383
def find_program(names, default=None, paths=[]):
8484
for name in names:
85-
for path in os.environ['PATH'].split(':') + paths:
85+
for path in os.environ['PATH'].split(os.pathsep) + paths:
8686
program = posixpath.join(path, name)
8787
if os.path.exists(program):
8888
return program

src/express/reloader.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,17 @@ def track_changes(path):
9797
_files.append(path)
9898

9999
def start_reloader(interval=1.0):
100+
# The reloader triggers a restart by sending SIGINT to this process. On
101+
# Windows os.kill() cannot deliver SIGINT to ourselves: any signal other
102+
# than CTRL_C_EVENT/CTRL_BREAK_EVENT is implemented as an unconditional
103+
# TerminateProcess, so the reloader would hard kill the server rather than
104+
# trigger a graceful restart. Disable it on Windows.
105+
if os.name == 'nt':
106+
prefix = 'monitor (pid=%d):' % os.getpid()
107+
print('%s Source code reloading is not supported on Windows.' % prefix,
108+
file=sys.stderr)
109+
return
110+
100111
global _interval
101112
if interval < _interval:
102113
_interval = interval

src/express/server.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -255,14 +255,17 @@ def setup_server(command, args, options):
255255
options['access_log_file'] = posixpath.join(
256256
options['log_directory'], options['access_log_name'])
257257
else:
258-
try:
259-
with open('/dev/stdout', 'w'):
260-
pass
261-
except IOError:
262-
options['access_log_file'] = '|%s' % find_program(
263-
['tee'], default='tee')
258+
if os.name == 'nt':
259+
options['access_log_file'] = 'CON'
264260
else:
265-
options['access_log_file'] = '/dev/stdout'
261+
try:
262+
with open('/dev/stdout', 'w'):
263+
pass
264+
except IOError:
265+
options['access_log_file'] = '|%s' % find_program(
266+
['tee'], default='tee')
267+
else:
268+
options['access_log_file'] = '/dev/stdout'
266269

267270
if options['access_log_format']:
268271
if options['access_log_format'] in ('common', 'combined'):

0 commit comments

Comments
 (0)