##// END OF EJS Templates
chgserver: auto exit after being idle for too long or lose the socket file...
Jun Wu -
r28223:0a853dc9 default
parent child Browse files
Show More
@@ -34,6 +34,8 b' import os'
34 34 import re
35 35 import signal
36 36 import struct
37 import threading
38 import time
37 39 import traceback
38 40
39 41 from mercurial.i18n import _
@@ -380,20 +382,93 b' class _requesthandler(SocketServer.Strea'
380 382 traceback.print_exc(file=sv.cerr)
381 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 448 class chgunixservice(commandserver.unixservice):
384 449 def init(self):
385 450 # drop options set for "hg serve --cmdserver" command
386 451 self.ui.setconfig('progress', 'assume-tty', None)
387 452 signal.signal(signal.SIGHUP, self._reloadconfig)
388 class cls(SocketServer.ForkingMixIn, SocketServer.UnixStreamServer):
453 class cls(AutoExitMixIn, SocketServer.ForkingMixIn,
454 SocketServer.UnixStreamServer):
389 455 ui = self.ui
390 456 repo = self.repo
391 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 461 # avoid writing "listening at" message to stdout before attachio
393 462 # request, which calls setvbuf()
394 463
395 464 def _reloadconfig(self, signum, frame):
396 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 473 def uisetup(ui):
399 474 commandserver._servicemap['chgunix'] = chgunixservice
General Comments 0
You need to be logged in to leave comments. Login now