##// END OF EJS Templates
server: add an error feedback mechanism for when the daemon fails to launch...
Matt Harbison -
r37229:f09a2eab default
parent child Browse files
Show More
@@ -1,174 +1,207 b''
1 # server.py - utility and factory of server
1 # server.py - utility and factory of server
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import os
10 import os
11 import tempfile
11 import tempfile
12
12
13 from .i18n import _
13 from .i18n import _
14
14
15 from . import (
15 from . import (
16 chgserver,
16 chgserver,
17 cmdutil,
17 cmdutil,
18 commandserver,
18 commandserver,
19 error,
19 error,
20 hgweb,
20 hgweb,
21 pycompat,
21 pycompat,
22 util,
22 util,
23 )
23 )
24
24
25 from .utils import (
25 from .utils import (
26 procutil,
26 procutil,
27 )
27 )
28
28
29 def runservice(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
29 def runservice(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
30 runargs=None, appendpid=False):
30 runargs=None, appendpid=False):
31 '''Run a command as a service.'''
31 '''Run a command as a service.'''
32
32
33 # When daemonized on Windows, redirect stdout/stderr to the lockfile (which
34 # gets cleaned up after the child is up and running), so that the parent can
35 # read and print the error if this child dies early. See 594dd384803c. On
36 # other platforms, the child can write to the parent's stdio directly, until
37 # it is redirected prior to runfn().
38 if pycompat.iswindows and opts['daemon_postexec']:
39 for inst in opts['daemon_postexec']:
40 if inst.startswith('unlink:'):
41 lockpath = inst[7:]
42 if os.path.exists(lockpath):
43 procutil.stdout.flush()
44 procutil.stderr.flush()
45
46 fd = os.open(lockpath,
47 os.O_WRONLY | os.O_APPEND | os.O_BINARY)
48 try:
49 os.dup2(fd, 1)
50 os.dup2(fd, 2)
51 finally:
52 os.close(fd)
53
33 def writepid(pid):
54 def writepid(pid):
34 if opts['pid_file']:
55 if opts['pid_file']:
35 if appendpid:
56 if appendpid:
36 mode = 'ab'
57 mode = 'ab'
37 else:
58 else:
38 mode = 'wb'
59 mode = 'wb'
39 fp = open(opts['pid_file'], mode)
60 fp = open(opts['pid_file'], mode)
40 fp.write('%d\n' % pid)
61 fp.write('%d\n' % pid)
41 fp.close()
62 fp.close()
42
63
43 if opts['daemon'] and not opts['daemon_postexec']:
64 if opts['daemon'] and not opts['daemon_postexec']:
44 # Signal child process startup with file removal
65 # Signal child process startup with file removal
45 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
66 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
46 os.close(lockfd)
67 os.close(lockfd)
47 try:
68 try:
48 if not runargs:
69 if not runargs:
49 runargs = procutil.hgcmd() + pycompat.sysargv[1:]
70 runargs = procutil.hgcmd() + pycompat.sysargv[1:]
50 runargs.append('--daemon-postexec=unlink:%s' % lockpath)
71 runargs.append('--daemon-postexec=unlink:%s' % lockpath)
51 # Don't pass --cwd to the child process, because we've already
72 # Don't pass --cwd to the child process, because we've already
52 # changed directory.
73 # changed directory.
53 for i in xrange(1, len(runargs)):
74 for i in xrange(1, len(runargs)):
54 if runargs[i].startswith('--cwd='):
75 if runargs[i].startswith('--cwd='):
55 del runargs[i]
76 del runargs[i]
56 break
77 break
57 elif runargs[i].startswith('--cwd'):
78 elif runargs[i].startswith('--cwd'):
58 del runargs[i:i + 2]
79 del runargs[i:i + 2]
59 break
80 break
60 def condfn():
81 def condfn():
61 return not os.path.exists(lockpath)
82 return not os.path.exists(lockpath)
62 pid = procutil.rundetached(runargs, condfn)
83 pid = procutil.rundetached(runargs, condfn)
63 if pid < 0:
84 if pid < 0:
85 # If the daemonized process managed to write out an error msg,
86 # report it.
87 if pycompat.iswindows and os.path.exists(lockpath):
88 with open(lockpath) as log:
89 for line in log:
90 procutil.stderr.write(line)
64 raise error.Abort(_('child process failed to start'))
91 raise error.Abort(_('child process failed to start'))
65 writepid(pid)
92 writepid(pid)
66 finally:
93 finally:
67 util.tryunlink(lockpath)
94 util.tryunlink(lockpath)
68 if parentfn:
95 if parentfn:
69 return parentfn(pid)
96 return parentfn(pid)
70 else:
97 else:
71 return
98 return
72
99
73 if initfn:
100 if initfn:
74 initfn()
101 initfn()
75
102
76 if not opts['daemon']:
103 if not opts['daemon']:
77 writepid(procutil.getpid())
104 writepid(procutil.getpid())
78
105
79 if opts['daemon_postexec']:
106 if opts['daemon_postexec']:
80 try:
107 try:
81 os.setsid()
108 os.setsid()
82 except AttributeError:
109 except AttributeError:
83 pass
110 pass
111
112 lockpath = None
84 for inst in opts['daemon_postexec']:
113 for inst in opts['daemon_postexec']:
85 if inst.startswith('unlink:'):
114 if inst.startswith('unlink:'):
86 lockpath = inst[7:]
115 lockpath = inst[7:]
87 os.unlink(lockpath)
88 elif inst.startswith('chdir:'):
116 elif inst.startswith('chdir:'):
89 os.chdir(inst[6:])
117 os.chdir(inst[6:])
90 elif inst != 'none':
118 elif inst != 'none':
91 raise error.Abort(_('invalid value for --daemon-postexec: %s')
119 raise error.Abort(_('invalid value for --daemon-postexec: %s')
92 % inst)
120 % inst)
93 procutil.hidewindow()
121 procutil.hidewindow()
94 procutil.stdout.flush()
122 procutil.stdout.flush()
95 procutil.stderr.flush()
123 procutil.stderr.flush()
96
124
97 nullfd = os.open(os.devnull, os.O_RDWR)
125 nullfd = os.open(os.devnull, os.O_RDWR)
98 logfilefd = nullfd
126 logfilefd = nullfd
99 if logfile:
127 if logfile:
100 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND,
128 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND,
101 0o666)
129 0o666)
102 os.dup2(nullfd, 0)
130 os.dup2(nullfd, 0)
103 os.dup2(logfilefd, 1)
131 os.dup2(logfilefd, 1)
104 os.dup2(logfilefd, 2)
132 os.dup2(logfilefd, 2)
105 if nullfd not in (0, 1, 2):
133 if nullfd not in (0, 1, 2):
106 os.close(nullfd)
134 os.close(nullfd)
107 if logfile and logfilefd not in (0, 1, 2):
135 if logfile and logfilefd not in (0, 1, 2):
108 os.close(logfilefd)
136 os.close(logfilefd)
109
137
138 # Only unlink after redirecting stdout/stderr, so Windows doesn't
139 # complain about a sharing violation.
140 if lockpath:
141 os.unlink(lockpath)
142
110 if runfn:
143 if runfn:
111 return runfn()
144 return runfn()
112
145
113 _cmdservicemap = {
146 _cmdservicemap = {
114 'chgunix': chgserver.chgunixservice,
147 'chgunix': chgserver.chgunixservice,
115 'pipe': commandserver.pipeservice,
148 'pipe': commandserver.pipeservice,
116 'unix': commandserver.unixforkingservice,
149 'unix': commandserver.unixforkingservice,
117 }
150 }
118
151
119 def _createcmdservice(ui, repo, opts):
152 def _createcmdservice(ui, repo, opts):
120 mode = opts['cmdserver']
153 mode = opts['cmdserver']
121 try:
154 try:
122 return _cmdservicemap[mode](ui, repo, opts)
155 return _cmdservicemap[mode](ui, repo, opts)
123 except KeyError:
156 except KeyError:
124 raise error.Abort(_('unknown mode %s') % mode)
157 raise error.Abort(_('unknown mode %s') % mode)
125
158
126 def _createhgwebservice(ui, repo, opts):
159 def _createhgwebservice(ui, repo, opts):
127 # this way we can check if something was given in the command-line
160 # this way we can check if something was given in the command-line
128 if opts.get('port'):
161 if opts.get('port'):
129 opts['port'] = util.getport(opts.get('port'))
162 opts['port'] = util.getport(opts.get('port'))
130
163
131 alluis = {ui}
164 alluis = {ui}
132 if repo:
165 if repo:
133 baseui = repo.baseui
166 baseui = repo.baseui
134 alluis.update([repo.baseui, repo.ui])
167 alluis.update([repo.baseui, repo.ui])
135 else:
168 else:
136 baseui = ui
169 baseui = ui
137 webconf = opts.get('web_conf') or opts.get('webdir_conf')
170 webconf = opts.get('web_conf') or opts.get('webdir_conf')
138 if webconf:
171 if webconf:
139 if opts.get('subrepos'):
172 if opts.get('subrepos'):
140 raise error.Abort(_('--web-conf cannot be used with --subrepos'))
173 raise error.Abort(_('--web-conf cannot be used with --subrepos'))
141
174
142 # load server settings (e.g. web.port) to "copied" ui, which allows
175 # load server settings (e.g. web.port) to "copied" ui, which allows
143 # hgwebdir to reload webconf cleanly
176 # hgwebdir to reload webconf cleanly
144 servui = ui.copy()
177 servui = ui.copy()
145 servui.readconfig(webconf, sections=['web'])
178 servui.readconfig(webconf, sections=['web'])
146 alluis.add(servui)
179 alluis.add(servui)
147 elif opts.get('subrepos'):
180 elif opts.get('subrepos'):
148 servui = ui
181 servui = ui
149
182
150 # If repo is None, hgweb.createapp() already raises a proper abort
183 # If repo is None, hgweb.createapp() already raises a proper abort
151 # message as long as webconf is None.
184 # message as long as webconf is None.
152 if repo:
185 if repo:
153 webconf = dict()
186 webconf = dict()
154 cmdutil.addwebdirpath(repo, "", webconf)
187 cmdutil.addwebdirpath(repo, "", webconf)
155 else:
188 else:
156 servui = ui
189 servui = ui
157
190
158 optlist = ("name templates style address port prefix ipv6"
191 optlist = ("name templates style address port prefix ipv6"
159 " accesslog errorlog certificate encoding")
192 " accesslog errorlog certificate encoding")
160 for o in optlist.split():
193 for o in optlist.split():
161 val = opts.get(o, '')
194 val = opts.get(o, '')
162 if val in (None, ''): # should check against default options instead
195 if val in (None, ''): # should check against default options instead
163 continue
196 continue
164 for u in alluis:
197 for u in alluis:
165 u.setconfig("web", o, val, 'serve')
198 u.setconfig("web", o, val, 'serve')
166
199
167 app = hgweb.createapp(baseui, repo, webconf)
200 app = hgweb.createapp(baseui, repo, webconf)
168 return hgweb.httpservice(servui, app, opts)
201 return hgweb.httpservice(servui, app, opts)
169
202
170 def createservice(ui, repo, opts):
203 def createservice(ui, repo, opts):
171 if opts["cmdserver"]:
204 if opts["cmdserver"]:
172 return _createcmdservice(ui, repo, opts)
205 return _createcmdservice(ui, repo, opts)
173 else:
206 else:
174 return _createhgwebservice(ui, repo, opts)
207 return _createhgwebservice(ui, repo, opts)
General Comments 0
You need to be logged in to leave comments. Login now