Show More
@@ -34,6 +34,8 b' import os' | |||||
34 | import re |
|
34 | import re | |
35 | import signal |
|
35 | import signal | |
36 | import struct |
|
36 | import struct | |
|
37 | import threading | |||
|
38 | import time | |||
37 | import traceback |
|
39 | import traceback | |
38 |
|
40 | |||
39 | from mercurial.i18n import _ |
|
41 | from mercurial.i18n import _ | |
@@ -380,20 +382,93 b' class _requesthandler(SocketServer.Strea' | |||||
380 | traceback.print_exc(file=sv.cerr) |
|
382 | traceback.print_exc(file=sv.cerr) | |
381 | raise |
|
383 | raise | |
382 |
|
384 | |||
|
385 | def _tempaddress(address): | |||
|
386 | return '%s.%d.tmp' % (address, os.getpid()) | |||
|
387 | ||||
|
388 | class AutoExitMixIn: # use old-style to comply with SocketServer design | |||
|
389 | lastactive = time.time() | |||
|
390 | idletimeout = 3600 # default 1 hour | |||
|
391 | ||||
|
392 | def startautoexitthread(self): | |||
|
393 | # note: the auto-exit check here is cheap enough to not use a thread, | |||
|
394 | # be done in serve_forever. however SocketServer is hook-unfriendly, | |||
|
395 | # you simply cannot hook serve_forever without copying a lot of code. | |||
|
396 | # besides, serve_forever's docstring suggests using thread. | |||
|
397 | thread = threading.Thread(target=self._autoexitloop) | |||
|
398 | thread.daemon = True | |||
|
399 | thread.start() | |||
|
400 | ||||
|
401 | def _autoexitloop(self, interval=1): | |||
|
402 | while True: | |||
|
403 | time.sleep(interval) | |||
|
404 | if not self.issocketowner(): | |||
|
405 | _log('%s is not owned, exiting.\n' % self.server_address) | |||
|
406 | break | |||
|
407 | if time.time() - self.lastactive > self.idletimeout: | |||
|
408 | _log('being idle too long. exiting.\n') | |||
|
409 | break | |||
|
410 | self.shutdown() | |||
|
411 | ||||
|
412 | def process_request(self, request, address): | |||
|
413 | self.lastactive = time.time() | |||
|
414 | return SocketServer.ForkingMixIn.process_request( | |||
|
415 | self, request, address) | |||
|
416 | ||||
|
417 | def server_bind(self): | |||
|
418 | # use a unique temp address so we can stat the file and do ownership | |||
|
419 | # check later | |||
|
420 | tempaddress = _tempaddress(self.server_address) | |||
|
421 | self.socket.bind(tempaddress) | |||
|
422 | self._socketstat = os.stat(tempaddress) | |||
|
423 | # rename will replace the old socket file if exists atomically. the | |||
|
424 | # old server will detect ownership change and exit. | |||
|
425 | util.rename(tempaddress, self.server_address) | |||
|
426 | ||||
|
427 | def issocketowner(self): | |||
|
428 | try: | |||
|
429 | stat = os.stat(self.server_address) | |||
|
430 | return (stat.st_ino == self._socketstat.st_ino and | |||
|
431 | stat.st_mtime == self._socketstat.st_mtime) | |||
|
432 | except OSError: | |||
|
433 | return False | |||
|
434 | ||||
|
435 | def unlinksocketfile(self): | |||
|
436 | if not self.issocketowner(): | |||
|
437 | return | |||
|
438 | # it is possible to have a race condition here that we may | |||
|
439 | # remove another server's socket file. but that's okay | |||
|
440 | # since that server will detect and exit automatically and | |||
|
441 | # the client will start a new server on demand. | |||
|
442 | try: | |||
|
443 | os.unlink(self.server_address) | |||
|
444 | except OSError as exc: | |||
|
445 | if exc.errno != errno.ENOENT: | |||
|
446 | raise | |||
|
447 | ||||
383 | class chgunixservice(commandserver.unixservice): |
|
448 | class chgunixservice(commandserver.unixservice): | |
384 | def init(self): |
|
449 | def init(self): | |
385 | # drop options set for "hg serve --cmdserver" command |
|
450 | # drop options set for "hg serve --cmdserver" command | |
386 | self.ui.setconfig('progress', 'assume-tty', None) |
|
451 | self.ui.setconfig('progress', 'assume-tty', None) | |
387 | signal.signal(signal.SIGHUP, self._reloadconfig) |
|
452 | signal.signal(signal.SIGHUP, self._reloadconfig) | |
388 |
class cls(SocketServer.ForkingMixIn, |
|
453 | class cls(AutoExitMixIn, SocketServer.ForkingMixIn, | |
|
454 | SocketServer.UnixStreamServer): | |||
389 | ui = self.ui |
|
455 | ui = self.ui | |
390 | repo = self.repo |
|
456 | repo = self.repo | |
391 | self.server = cls(self.address, _requesthandler) |
|
457 | self.server = cls(self.address, _requesthandler) | |
|
458 | self.server.idletimeout = self.ui.configint( | |||
|
459 | 'chgserver', 'idletimeout', self.server.idletimeout) | |||
|
460 | self.server.startautoexitthread() | |||
392 | # avoid writing "listening at" message to stdout before attachio |
|
461 | # avoid writing "listening at" message to stdout before attachio | |
393 | # request, which calls setvbuf() |
|
462 | # request, which calls setvbuf() | |
394 |
|
463 | |||
395 | def _reloadconfig(self, signum, frame): |
|
464 | def _reloadconfig(self, signum, frame): | |
396 | self.ui = self.server.ui = _renewui(self.ui) |
|
465 | self.ui = self.server.ui = _renewui(self.ui) | |
397 |
|
466 | |||
|
467 | def run(self): | |||
|
468 | try: | |||
|
469 | self.server.serve_forever() | |||
|
470 | finally: | |||
|
471 | self.server.unlinksocketfile() | |||
|
472 | ||||
398 | def uisetup(ui): |
|
473 | def uisetup(ui): | |
399 | commandserver._servicemap['chgunix'] = chgunixservice |
|
474 | commandserver._servicemap['chgunix'] = chgunixservice |
General Comments 0
You need to be logged in to leave comments.
Login now