##// END OF EJS Templates
chgserver: add an explicit gc to trigger __del__...
Jun Wu -
r28553:5346e9b9 default
parent child Browse files
Show More
@@ -1,692 +1,696 b''
1 1 # chgserver.py - command server extension for cHg
2 2 #
3 3 # Copyright 2011 Yuya Nishihara <yuya@tcha.org>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 """command server extension for cHg (EXPERIMENTAL)
9 9
10 10 'S' channel (read/write)
11 11 propagate ui.system() request to client
12 12
13 13 'attachio' command
14 14 attach client's stdio passed by sendmsg()
15 15
16 16 'chdir' command
17 17 change current directory
18 18
19 19 'getpager' command
20 20 checks if pager is enabled and which pager should be executed
21 21
22 22 'setenv' command
23 23 replace os.environ completely
24 24
25 25 'setumask' command
26 26 set umask
27 27
28 28 'validate' command
29 29 reload the config and check if the server is up to date
30 30
31 31 Config
32 32 ------
33 33
34 34 ::
35 35
36 36 [chgserver]
37 37 idletimeout = 3600 # seconds, after which an idle server will exit
38 38 skiphash = False # whether to skip config or env change checks
39 39 """
40 40
41 41 from __future__ import absolute_import
42 42
43 43 import SocketServer
44 44 import errno
45 import gc
45 46 import inspect
46 47 import os
47 48 import re
48 49 import struct
49 50 import sys
50 51 import threading
51 52 import time
52 53 import traceback
53 54
54 55 from mercurial.i18n import _
55 56
56 57 from mercurial import (
57 58 cmdutil,
58 59 commands,
59 60 commandserver,
60 61 dispatch,
61 62 error,
62 63 extensions,
63 64 osutil,
64 65 util,
65 66 )
66 67
67 68 # Note for extension authors: ONLY specify testedwith = 'internal' for
68 69 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
69 70 # be specifying the version(s) of Mercurial they are tested with, or
70 71 # leave the attribute unspecified.
71 72 testedwith = 'internal'
72 73
73 74 _log = commandserver.log
74 75
75 76 def _hashlist(items):
76 77 """return sha1 hexdigest for a list"""
77 78 return util.sha1(str(items)).hexdigest()
78 79
79 80 # sensitive config sections affecting confighash
80 81 _configsections = [
81 82 'extdiff', # uisetup will register new commands
82 83 'extensions',
83 84 ]
84 85
85 86 # sensitive environment variables affecting confighash
86 87 _envre = re.compile(r'''\A(?:
87 88 CHGHG
88 89 |HG.*
89 90 |LANG(?:UAGE)?
90 91 |LC_.*
91 92 |LD_.*
92 93 |PATH
93 94 |PYTHON.*
94 95 |TERM(?:INFO)?
95 96 |TZ
96 97 )\Z''', re.X)
97 98
98 99 def _confighash(ui):
99 100 """return a quick hash for detecting config/env changes
100 101
101 102 confighash is the hash of sensitive config items and environment variables.
102 103
103 104 for chgserver, it is designed that once confighash changes, the server is
104 105 not qualified to serve its client and should redirect the client to a new
105 106 server. different from mtimehash, confighash change will not mark the
106 107 server outdated and exit since the user can have different configs at the
107 108 same time.
108 109 """
109 110 sectionitems = []
110 111 for section in _configsections:
111 112 sectionitems.append(ui.configitems(section))
112 113 sectionhash = _hashlist(sectionitems)
113 114 envitems = [(k, v) for k, v in os.environ.iteritems() if _envre.match(k)]
114 115 envhash = _hashlist(sorted(envitems))
115 116 return sectionhash[:6] + envhash[:6]
116 117
117 118 def _getmtimepaths(ui):
118 119 """get a list of paths that should be checked to detect change
119 120
120 121 The list will include:
121 122 - extensions (will not cover all files for complex extensions)
122 123 - mercurial/__version__.py
123 124 - python binary
124 125 """
125 126 modules = [m for n, m in extensions.extensions(ui)]
126 127 try:
127 128 from mercurial import __version__
128 129 modules.append(__version__)
129 130 except ImportError:
130 131 pass
131 132 files = [sys.executable]
132 133 for m in modules:
133 134 try:
134 135 files.append(inspect.getabsfile(m))
135 136 except TypeError:
136 137 pass
137 138 return sorted(set(files))
138 139
139 140 def _mtimehash(paths):
140 141 """return a quick hash for detecting file changes
141 142
142 143 mtimehash calls stat on given paths and calculate a hash based on size and
143 144 mtime of each file. mtimehash does not read file content because reading is
144 145 expensive. therefore it's not 100% reliable for detecting content changes.
145 146 it's possible to return different hashes for same file contents.
146 147 it's also possible to return a same hash for different file contents for
147 148 some carefully crafted situation.
148 149
149 150 for chgserver, it is designed that once mtimehash changes, the server is
150 151 considered outdated immediately and should no longer provide service.
151 152 """
152 153 def trystat(path):
153 154 try:
154 155 st = os.stat(path)
155 156 return (st.st_mtime, st.st_size)
156 157 except OSError:
157 158 # could be ENOENT, EPERM etc. not fatal in any case
158 159 pass
159 160 return _hashlist(map(trystat, paths))[:12]
160 161
161 162 class hashstate(object):
162 163 """a structure storing confighash, mtimehash, paths used for mtimehash"""
163 164 def __init__(self, confighash, mtimehash, mtimepaths):
164 165 self.confighash = confighash
165 166 self.mtimehash = mtimehash
166 167 self.mtimepaths = mtimepaths
167 168
168 169 @staticmethod
169 170 def fromui(ui, mtimepaths=None):
170 171 if mtimepaths is None:
171 172 mtimepaths = _getmtimepaths(ui)
172 173 confighash = _confighash(ui)
173 174 mtimehash = _mtimehash(mtimepaths)
174 175 _log('confighash = %s mtimehash = %s\n' % (confighash, mtimehash))
175 176 return hashstate(confighash, mtimehash, mtimepaths)
176 177
177 178 # copied from hgext/pager.py:uisetup()
178 179 def _setuppagercmd(ui, options, cmd):
179 180 if not ui.formatted():
180 181 return
181 182
182 183 p = ui.config("pager", "pager", os.environ.get("PAGER"))
183 184 usepager = False
184 185 always = util.parsebool(options['pager'])
185 186 auto = options['pager'] == 'auto'
186 187
187 188 if not p:
188 189 pass
189 190 elif always:
190 191 usepager = True
191 192 elif not auto:
192 193 usepager = False
193 194 else:
194 195 attended = ['annotate', 'cat', 'diff', 'export', 'glog', 'log', 'qdiff']
195 196 attend = ui.configlist('pager', 'attend', attended)
196 197 ignore = ui.configlist('pager', 'ignore')
197 198 cmds, _ = cmdutil.findcmd(cmd, commands.table)
198 199
199 200 for cmd in cmds:
200 201 var = 'attend-%s' % cmd
201 202 if ui.config('pager', var):
202 203 usepager = ui.configbool('pager', var)
203 204 break
204 205 if (cmd in attend or
205 206 (cmd not in ignore and not attend)):
206 207 usepager = True
207 208 break
208 209
209 210 if usepager:
210 211 ui.setconfig('ui', 'formatted', ui.formatted(), 'pager')
211 212 ui.setconfig('ui', 'interactive', False, 'pager')
212 213 return p
213 214
214 215 _envvarre = re.compile(r'\$[a-zA-Z_]+')
215 216
216 217 def _clearenvaliases(cmdtable):
217 218 """Remove stale command aliases referencing env vars; variable expansion
218 219 is done at dispatch.addaliases()"""
219 220 for name, tab in cmdtable.items():
220 221 cmddef = tab[0]
221 222 if (isinstance(cmddef, dispatch.cmdalias) and
222 223 not cmddef.definition.startswith('!') and # shell alias
223 224 _envvarre.search(cmddef.definition)):
224 225 del cmdtable[name]
225 226
226 227 def _newchgui(srcui, csystem):
227 228 class chgui(srcui.__class__):
228 229 def __init__(self, src=None):
229 230 super(chgui, self).__init__(src)
230 231 if src:
231 232 self._csystem = getattr(src, '_csystem', csystem)
232 233 else:
233 234 self._csystem = csystem
234 235
235 236 def system(self, cmd, environ=None, cwd=None, onerr=None,
236 237 errprefix=None):
237 238 # copied from mercurial/util.py:system()
238 239 self.flush()
239 240 def py2shell(val):
240 241 if val is None or val is False:
241 242 return '0'
242 243 if val is True:
243 244 return '1'
244 245 return str(val)
245 246 env = os.environ.copy()
246 247 if environ:
247 248 env.update((k, py2shell(v)) for k, v in environ.iteritems())
248 249 env['HG'] = util.hgexecutable()
249 250 rc = self._csystem(cmd, env, cwd)
250 251 if rc and onerr:
251 252 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
252 253 util.explainexit(rc)[0])
253 254 if errprefix:
254 255 errmsg = '%s: %s' % (errprefix, errmsg)
255 256 raise onerr(errmsg)
256 257 return rc
257 258
258 259 return chgui(srcui)
259 260
260 261 def _renewui(srcui, args=None):
261 262 if not args:
262 263 args = []
263 264
264 265 newui = srcui.__class__()
265 266 for a in ['fin', 'fout', 'ferr', 'environ']:
266 267 setattr(newui, a, getattr(srcui, a))
267 268 if util.safehasattr(srcui, '_csystem'):
268 269 newui._csystem = srcui._csystem
269 270
270 271 # load wd and repo config, copied from dispatch.py
271 272 cwds = dispatch._earlygetopt(['--cwd'], args)
272 273 cwd = cwds and os.path.realpath(cwds[-1]) or None
273 274 rpath = dispatch._earlygetopt(["-R", "--repository", "--repo"], args)
274 275 path, newui = dispatch._getlocal(newui, rpath, wd=cwd)
275 276
276 277 # internal config: extensions.chgserver
277 278 # copy it. it can only be overrided from command line.
278 279 newui.setconfig('extensions', 'chgserver',
279 280 srcui.config('extensions', 'chgserver'), '--config')
280 281
281 282 # command line args
282 283 dispatch._parseconfig(newui, dispatch._earlygetopt(['--config'], args))
283 284
284 285 # stolen from tortoisehg.util.copydynamicconfig()
285 286 for section, name, value in srcui.walkconfig():
286 287 source = srcui.configsource(section, name)
287 288 if ':' in source or source == '--config':
288 289 # path:line or command line
289 290 continue
290 291 if source == 'none':
291 292 # ui.configsource returns 'none' by default
292 293 source = ''
293 294 newui.setconfig(section, name, value, source)
294 295 return newui
295 296
296 297 class channeledsystem(object):
297 298 """Propagate ui.system() request in the following format:
298 299
299 300 payload length (unsigned int),
300 301 cmd, '\0',
301 302 cwd, '\0',
302 303 envkey, '=', val, '\0',
303 304 ...
304 305 envkey, '=', val
305 306
306 307 and waits:
307 308
308 309 exitcode length (unsigned int),
309 310 exitcode (int)
310 311 """
311 312 def __init__(self, in_, out, channel):
312 313 self.in_ = in_
313 314 self.out = out
314 315 self.channel = channel
315 316
316 317 def __call__(self, cmd, environ, cwd):
317 318 args = [util.quotecommand(cmd), os.path.abspath(cwd or '.')]
318 319 args.extend('%s=%s' % (k, v) for k, v in environ.iteritems())
319 320 data = '\0'.join(args)
320 321 self.out.write(struct.pack('>cI', self.channel, len(data)))
321 322 self.out.write(data)
322 323 self.out.flush()
323 324
324 325 length = self.in_.read(4)
325 326 length, = struct.unpack('>I', length)
326 327 if length != 4:
327 328 raise error.Abort(_('invalid response'))
328 329 rc, = struct.unpack('>i', self.in_.read(4))
329 330 return rc
330 331
331 332 _iochannels = [
332 333 # server.ch, ui.fp, mode
333 334 ('cin', 'fin', 'rb'),
334 335 ('cout', 'fout', 'wb'),
335 336 ('cerr', 'ferr', 'wb'),
336 337 ]
337 338
338 339 class chgcmdserver(commandserver.server):
339 340 def __init__(self, ui, repo, fin, fout, sock, hashstate, baseaddress):
340 341 super(chgcmdserver, self).__init__(
341 342 _newchgui(ui, channeledsystem(fin, fout, 'S')), repo, fin, fout)
342 343 self.clientsock = sock
343 344 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
344 345 self.hashstate = hashstate
345 346 self.baseaddress = baseaddress
346 347 if hashstate is not None:
347 348 self.capabilities = self.capabilities.copy()
348 349 self.capabilities['validate'] = chgcmdserver.validate
349 350
350 351 def cleanup(self):
351 352 # dispatch._runcatch() does not flush outputs if exception is not
352 353 # handled by dispatch._dispatch()
353 354 self.ui.flush()
354 355 self._restoreio()
355 356
356 357 def attachio(self):
357 358 """Attach to client's stdio passed via unix domain socket; all
358 359 channels except cresult will no longer be used
359 360 """
360 361 # tell client to sendmsg() with 1-byte payload, which makes it
361 362 # distinctive from "attachio\n" command consumed by client.read()
362 363 self.clientsock.sendall(struct.pack('>cI', 'I', 1))
363 364 clientfds = osutil.recvfds(self.clientsock.fileno())
364 365 _log('received fds: %r\n' % clientfds)
365 366
366 367 ui = self.ui
367 368 ui.flush()
368 369 first = self._saveio()
369 370 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
370 371 assert fd > 0
371 372 fp = getattr(ui, fn)
372 373 os.dup2(fd, fp.fileno())
373 374 os.close(fd)
374 375 if not first:
375 376 continue
376 377 # reset buffering mode when client is first attached. as we want
377 378 # to see output immediately on pager, the mode stays unchanged
378 379 # when client re-attached. ferr is unchanged because it should
379 380 # be unbuffered no matter if it is a tty or not.
380 381 if fn == 'ferr':
381 382 newfp = fp
382 383 else:
383 384 # make it line buffered explicitly because the default is
384 385 # decided on first write(), where fout could be a pager.
385 386 if fp.isatty():
386 387 bufsize = 1 # line buffered
387 388 else:
388 389 bufsize = -1 # system default
389 390 newfp = os.fdopen(fp.fileno(), mode, bufsize)
390 391 setattr(ui, fn, newfp)
391 392 setattr(self, cn, newfp)
392 393
393 394 self.cresult.write(struct.pack('>i', len(clientfds)))
394 395
395 396 def _saveio(self):
396 397 if self._oldios:
397 398 return False
398 399 ui = self.ui
399 400 for cn, fn, _mode in _iochannels:
400 401 ch = getattr(self, cn)
401 402 fp = getattr(ui, fn)
402 403 fd = os.dup(fp.fileno())
403 404 self._oldios.append((ch, fp, fd))
404 405 return True
405 406
406 407 def _restoreio(self):
407 408 ui = self.ui
408 409 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
409 410 newfp = getattr(ui, fn)
410 411 # close newfp while it's associated with client; otherwise it
411 412 # would be closed when newfp is deleted
412 413 if newfp is not fp:
413 414 newfp.close()
414 415 # restore original fd: fp is open again
415 416 os.dup2(fd, fp.fileno())
416 417 os.close(fd)
417 418 setattr(self, cn, ch)
418 419 setattr(ui, fn, fp)
419 420 del self._oldios[:]
420 421
421 422 def validate(self):
422 423 """Reload the config and check if the server is up to date
423 424
424 425 Read a list of '\0' separated arguments.
425 426 Write a non-empty list of '\0' separated instruction strings or '\0'
426 427 if the list is empty.
427 428 An instruction string could be either:
428 429 - "unlink $path", the client should unlink the path to stop the
429 430 outdated server.
430 431 - "redirect $path", the client should attempt to connect to $path
431 432 first. If it does not work, start a new server. It implies
432 433 "reconnect".
433 434 - "exit $n", the client should exit directly with code n.
434 435 This may happen if we cannot parse the config.
435 436 - "reconnect", the client should close the connection and
436 437 reconnect.
437 438 If neither "reconnect" nor "redirect" is included in the instruction
438 439 list, the client can continue with this server after completing all
439 440 the instructions.
440 441 """
441 442 args = self._readlist()
442 443 try:
443 444 self.ui = _renewui(self.ui, args)
444 445 except error.ParseError as inst:
445 446 dispatch._formatparse(self.ui.warn, inst)
446 447 self.ui.flush()
447 448 self.cresult.write('exit 255')
448 449 return
449 450 newhash = hashstate.fromui(self.ui, self.hashstate.mtimepaths)
450 451 insts = []
451 452 if newhash.mtimehash != self.hashstate.mtimehash:
452 453 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
453 454 insts.append('unlink %s' % addr)
454 455 # mtimehash is empty if one or more extensions fail to load.
455 456 # to be compatible with hg, still serve the client this time.
456 457 if self.hashstate.mtimehash:
457 458 insts.append('reconnect')
458 459 if newhash.confighash != self.hashstate.confighash:
459 460 addr = _hashaddress(self.baseaddress, newhash.confighash)
460 461 insts.append('redirect %s' % addr)
461 462 _log('validate: %s\n' % insts)
462 463 self.cresult.write('\0'.join(insts) or '\0')
463 464
464 465 def chdir(self):
465 466 """Change current directory
466 467
467 468 Note that the behavior of --cwd option is bit different from this.
468 469 It does not affect --config parameter.
469 470 """
470 471 path = self._readstr()
471 472 if not path:
472 473 return
473 474 _log('chdir to %r\n' % path)
474 475 os.chdir(path)
475 476
476 477 def setumask(self):
477 478 """Change umask"""
478 479 mask = struct.unpack('>I', self._read(4))[0]
479 480 _log('setumask %r\n' % mask)
480 481 os.umask(mask)
481 482
482 483 def getpager(self):
483 484 """Read cmdargs and write pager command to r-channel if enabled
484 485
485 486 If pager isn't enabled, this writes '\0' because channeledoutput
486 487 does not allow to write empty data.
487 488 """
488 489 args = self._readlist()
489 490 try:
490 491 cmd, _func, args, options, _cmdoptions = dispatch._parse(self.ui,
491 492 args)
492 493 except (error.Abort, error.AmbiguousCommand, error.CommandError,
493 494 error.UnknownCommand):
494 495 cmd = None
495 496 options = {}
496 497 if not cmd or 'pager' not in options:
497 498 self.cresult.write('\0')
498 499 return
499 500
500 501 pagercmd = _setuppagercmd(self.ui, options, cmd)
501 502 if pagercmd:
502 503 self.cresult.write(pagercmd)
503 504 else:
504 505 self.cresult.write('\0')
505 506
506 507 def setenv(self):
507 508 """Clear and update os.environ
508 509
509 510 Note that not all variables can make an effect on the running process.
510 511 """
511 512 l = self._readlist()
512 513 try:
513 514 newenv = dict(s.split('=', 1) for s in l)
514 515 except ValueError:
515 516 raise ValueError('unexpected value in setenv request')
516 517
517 518 diffkeys = set(k for k in set(os.environ.keys() + newenv.keys())
518 519 if os.environ.get(k) != newenv.get(k))
519 520 _log('change env: %r\n' % sorted(diffkeys))
520 521
521 522 os.environ.clear()
522 523 os.environ.update(newenv)
523 524
524 525 if set(['HGPLAIN', 'HGPLAINEXCEPT']) & diffkeys:
525 526 # reload config so that ui.plain() takes effect
526 527 self.ui = _renewui(self.ui)
527 528
528 529 _clearenvaliases(commands.table)
529 530
530 531 capabilities = commandserver.server.capabilities.copy()
531 532 capabilities.update({'attachio': attachio,
532 533 'chdir': chdir,
533 534 'getpager': getpager,
534 535 'setenv': setenv,
535 536 'setumask': setumask})
536 537
537 538 # copied from mercurial/commandserver.py
538 539 class _requesthandler(SocketServer.StreamRequestHandler):
539 540 def handle(self):
540 541 # use a different process group from the master process, making this
541 542 # process pass kernel "is_current_pgrp_orphaned" check so signals like
542 543 # SIGTSTP, SIGTTIN, SIGTTOU are not ignored.
543 544 os.setpgid(0, 0)
544 545 ui = self.server.ui
545 546 repo = self.server.repo
546 547 sv = None
547 548 try:
548 549 sv = chgcmdserver(ui, repo, self.rfile, self.wfile, self.connection,
549 550 self.server.hashstate, self.server.baseaddress)
550 551 try:
551 552 sv.serve()
552 553 # handle exceptions that may be raised by command server. most of
553 554 # known exceptions are caught by dispatch.
554 555 except error.Abort as inst:
555 556 ui.warn(_('abort: %s\n') % inst)
556 557 except IOError as inst:
557 558 if inst.errno != errno.EPIPE:
558 559 raise
559 560 except KeyboardInterrupt:
560 561 pass
561 562 finally:
562 563 sv.cleanup()
563 564 except: # re-raises
564 565 # also write traceback to error channel. otherwise client cannot
565 566 # see it because it is written to server's stderr by default.
566 567 if sv:
567 568 cerr = sv.cerr
568 569 else:
569 570 cerr = commandserver.channeledoutput(self.wfile, 'e')
570 571 traceback.print_exc(file=cerr)
571 572 raise
573 finally:
574 # trigger __del__ since ForkingMixIn uses os._exit
575 gc.collect()
572 576
573 577 def _tempaddress(address):
574 578 return '%s.%d.tmp' % (address, os.getpid())
575 579
576 580 def _hashaddress(address, hashstr):
577 581 return '%s-%s' % (address, hashstr)
578 582
579 583 class AutoExitMixIn: # use old-style to comply with SocketServer design
580 584 lastactive = time.time()
581 585 idletimeout = 3600 # default 1 hour
582 586
583 587 def startautoexitthread(self):
584 588 # note: the auto-exit check here is cheap enough to not use a thread,
585 589 # be done in serve_forever. however SocketServer is hook-unfriendly,
586 590 # you simply cannot hook serve_forever without copying a lot of code.
587 591 # besides, serve_forever's docstring suggests using thread.
588 592 thread = threading.Thread(target=self._autoexitloop)
589 593 thread.daemon = True
590 594 thread.start()
591 595
592 596 def _autoexitloop(self, interval=1):
593 597 while True:
594 598 time.sleep(interval)
595 599 if not self.issocketowner():
596 600 _log('%s is not owned, exiting.\n' % self.server_address)
597 601 break
598 602 if time.time() - self.lastactive > self.idletimeout:
599 603 _log('being idle too long. exiting.\n')
600 604 break
601 605 self.shutdown()
602 606
603 607 def process_request(self, request, address):
604 608 self.lastactive = time.time()
605 609 return SocketServer.ForkingMixIn.process_request(
606 610 self, request, address)
607 611
608 612 def server_bind(self):
609 613 # use a unique temp address so we can stat the file and do ownership
610 614 # check later
611 615 tempaddress = _tempaddress(self.server_address)
612 616 self.socket.bind(tempaddress)
613 617 self._socketstat = os.stat(tempaddress)
614 618 # rename will replace the old socket file if exists atomically. the
615 619 # old server will detect ownership change and exit.
616 620 util.rename(tempaddress, self.server_address)
617 621
618 622 def issocketowner(self):
619 623 try:
620 624 stat = os.stat(self.server_address)
621 625 return (stat.st_ino == self._socketstat.st_ino and
622 626 stat.st_mtime == self._socketstat.st_mtime)
623 627 except OSError:
624 628 return False
625 629
626 630 def unlinksocketfile(self):
627 631 if not self.issocketowner():
628 632 return
629 633 # it is possible to have a race condition here that we may
630 634 # remove another server's socket file. but that's okay
631 635 # since that server will detect and exit automatically and
632 636 # the client will start a new server on demand.
633 637 try:
634 638 os.unlink(self.server_address)
635 639 except OSError as exc:
636 640 if exc.errno != errno.ENOENT:
637 641 raise
638 642
639 643 class chgunixservice(commandserver.unixservice):
640 644 def init(self):
641 645 self.repo = None
642 646 self._inithashstate()
643 647 self._checkextensions()
644 648 class cls(AutoExitMixIn, SocketServer.ForkingMixIn,
645 649 SocketServer.UnixStreamServer):
646 650 ui = self.ui
647 651 repo = self.repo
648 652 hashstate = self.hashstate
649 653 baseaddress = self.baseaddress
650 654 self.server = cls(self.address, _requesthandler)
651 655 self.server.idletimeout = self.ui.configint(
652 656 'chgserver', 'idletimeout', self.server.idletimeout)
653 657 self.server.startautoexitthread()
654 658 self._createsymlink()
655 659
656 660 def _inithashstate(self):
657 661 self.baseaddress = self.address
658 662 if self.ui.configbool('chgserver', 'skiphash', False):
659 663 self.hashstate = None
660 664 return
661 665 self.hashstate = hashstate.fromui(self.ui)
662 666 self.address = _hashaddress(self.address, self.hashstate.confighash)
663 667
664 668 def _checkextensions(self):
665 669 if not self.hashstate:
666 670 return
667 671 if extensions.notloaded():
668 672 # one or more extensions failed to load. mtimehash becomes
669 673 # meaningless because we do not know the paths of those extensions.
670 674 # set mtimehash to an illegal hash value to invalidate the server.
671 675 self.hashstate.mtimehash = ''
672 676
673 677 def _createsymlink(self):
674 678 if self.baseaddress == self.address:
675 679 return
676 680 tempaddress = _tempaddress(self.baseaddress)
677 681 os.symlink(os.path.basename(self.address), tempaddress)
678 682 util.rename(tempaddress, self.baseaddress)
679 683
680 684 def run(self):
681 685 try:
682 686 self.server.serve_forever()
683 687 finally:
684 688 self.server.unlinksocketfile()
685 689
686 690 def uisetup(ui):
687 691 commandserver._servicemap['chgunix'] = chgunixservice
688 692
689 693 # CHGINTERNALMARK is temporarily set by chg client to detect if chg will
690 694 # start another chg. drop it to avoid possible side effects.
691 695 if 'CHGINTERNALMARK' in os.environ:
692 696 del os.environ['CHGINTERNALMARK']
General Comments 0
You need to be logged in to leave comments. Login now