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