530 'chdir': chdir, |
530 'chdir': chdir, |
531 'getpager': getpager, |
531 'getpager': getpager, |
532 'setenv': setenv, |
532 'setenv': setenv, |
533 'setumask': setumask}) |
533 'setumask': setumask}) |
534 |
534 |
535 class _requesthandler(commandserver._requesthandler): |
|
536 def _createcmdserver(self, repo, conn, fin, fout): |
|
537 ui = self.server.ui |
|
538 return chgcmdserver(ui, repo, fin, fout, conn, |
|
539 self.server.hashstate, self.server.baseaddress) |
|
540 |
|
541 def _tempaddress(address): |
535 def _tempaddress(address): |
542 return '%s.%d.tmp' % (address, os.getpid()) |
536 return '%s.%d.tmp' % (address, os.getpid()) |
543 |
537 |
544 def _hashaddress(address, hashstr): |
538 def _hashaddress(address, hashstr): |
545 return '%s-%s' % (address, hashstr) |
539 return '%s-%s' % (address, hashstr) |
546 |
|
547 class AutoExitMixIn: # use old-style to comply with SocketServer design |
|
548 lastactive = time.time() |
|
549 idletimeout = 3600 # default 1 hour |
|
550 |
|
551 def startautoexitthread(self): |
|
552 # note: the auto-exit check here is cheap enough to not use a thread, |
|
553 # be done in serve_forever. however SocketServer is hook-unfriendly, |
|
554 # you simply cannot hook serve_forever without copying a lot of code. |
|
555 # besides, serve_forever's docstring suggests using thread. |
|
556 thread = threading.Thread(target=self._autoexitloop) |
|
557 thread.daemon = True |
|
558 thread.start() |
|
559 |
|
560 def _autoexitloop(self, interval=1): |
|
561 while True: |
|
562 time.sleep(interval) |
|
563 if not self.issocketowner(): |
|
564 _log('%s is not owned, exiting.\n' % self.server_address) |
|
565 break |
|
566 if time.time() - self.lastactive > self.idletimeout: |
|
567 _log('being idle too long. exiting.\n') |
|
568 break |
|
569 self.shutdown() |
|
570 |
|
571 def process_request(self, request, address): |
|
572 self.lastactive = time.time() |
|
573 return socketserver.ForkingMixIn.process_request( |
|
574 self, request, address) |
|
575 |
|
576 def server_bind(self): |
|
577 # use a unique temp address so we can stat the file and do ownership |
|
578 # check later |
|
579 tempaddress = _tempaddress(self.server_address) |
|
580 util.bindunixsocket(self.socket, tempaddress) |
|
581 self._socketstat = os.stat(tempaddress) |
|
582 # rename will replace the old socket file if exists atomically. the |
|
583 # old server will detect ownership change and exit. |
|
584 util.rename(tempaddress, self.server_address) |
|
585 |
|
586 def issocketowner(self): |
|
587 try: |
|
588 stat = os.stat(self.server_address) |
|
589 return (stat.st_ino == self._socketstat.st_ino and |
|
590 stat.st_mtime == self._socketstat.st_mtime) |
|
591 except OSError: |
|
592 return False |
|
593 |
|
594 def unlinksocketfile(self): |
|
595 if not self.issocketowner(): |
|
596 return |
|
597 # it is possible to have a race condition here that we may |
|
598 # remove another server's socket file. but that's okay |
|
599 # since that server will detect and exit automatically and |
|
600 # the client will start a new server on demand. |
|
601 try: |
|
602 os.unlink(self.server_address) |
|
603 except OSError as exc: |
|
604 if exc.errno != errno.ENOENT: |
|
605 raise |
|
606 |
540 |
607 class chgunixservice(commandserver.unixservice): |
541 class chgunixservice(commandserver.unixservice): |
608 def __init__(self, ui, repo, opts): |
542 def __init__(self, ui, repo, opts): |
609 super(chgunixservice, self).__init__(ui, repo=None, opts=opts) |
543 super(chgunixservice, self).__init__(ui, repo=None, opts=opts) |
610 if repo: |
544 if repo: |
651 util.rename(tempaddress, self.baseaddress) |
585 util.rename(tempaddress, self.baseaddress) |
652 |
586 |
653 def _cleanup(self): |
587 def _cleanup(self): |
654 self.server.unlinksocketfile() |
588 self.server.unlinksocketfile() |
655 |
589 |
|
590 class AutoExitMixIn: # use old-style to comply with SocketServer design |
|
591 lastactive = time.time() |
|
592 idletimeout = 3600 # default 1 hour |
|
593 |
|
594 def startautoexitthread(self): |
|
595 # note: the auto-exit check here is cheap enough to not use a thread, |
|
596 # be done in serve_forever. however SocketServer is hook-unfriendly, |
|
597 # you simply cannot hook serve_forever without copying a lot of code. |
|
598 # besides, serve_forever's docstring suggests using thread. |
|
599 thread = threading.Thread(target=self._autoexitloop) |
|
600 thread.daemon = True |
|
601 thread.start() |
|
602 |
|
603 def _autoexitloop(self, interval=1): |
|
604 while True: |
|
605 time.sleep(interval) |
|
606 if not self.issocketowner(): |
|
607 _log('%s is not owned, exiting.\n' % self.server_address) |
|
608 break |
|
609 if time.time() - self.lastactive > self.idletimeout: |
|
610 _log('being idle too long. exiting.\n') |
|
611 break |
|
612 self.shutdown() |
|
613 |
|
614 def process_request(self, request, address): |
|
615 self.lastactive = time.time() |
|
616 return socketserver.ForkingMixIn.process_request( |
|
617 self, request, address) |
|
618 |
|
619 def server_bind(self): |
|
620 # use a unique temp address so we can stat the file and do ownership |
|
621 # check later |
|
622 tempaddress = _tempaddress(self.server_address) |
|
623 util.bindunixsocket(self.socket, tempaddress) |
|
624 self._socketstat = os.stat(tempaddress) |
|
625 # rename will replace the old socket file if exists atomically. the |
|
626 # old server will detect ownership change and exit. |
|
627 util.rename(tempaddress, self.server_address) |
|
628 |
|
629 def issocketowner(self): |
|
630 try: |
|
631 stat = os.stat(self.server_address) |
|
632 return (stat.st_ino == self._socketstat.st_ino and |
|
633 stat.st_mtime == self._socketstat.st_mtime) |
|
634 except OSError: |
|
635 return False |
|
636 |
|
637 def unlinksocketfile(self): |
|
638 if not self.issocketowner(): |
|
639 return |
|
640 # it is possible to have a race condition here that we may |
|
641 # remove another server's socket file. but that's okay |
|
642 # since that server will detect and exit automatically and |
|
643 # the client will start a new server on demand. |
|
644 try: |
|
645 os.unlink(self.server_address) |
|
646 except OSError as exc: |
|
647 if exc.errno != errno.ENOENT: |
|
648 raise |
|
649 |
|
650 class _requesthandler(commandserver._requesthandler): |
|
651 def _createcmdserver(self, repo, conn, fin, fout): |
|
652 ui = self.server.ui |
|
653 return chgcmdserver(ui, repo, fin, fout, conn, |
|
654 self.server.hashstate, self.server.baseaddress) |
|
655 |
656 def uisetup(ui): |
656 def uisetup(ui): |
657 commandserver._servicemap['chgunix'] = chgunixservice |
657 commandserver._servicemap['chgunix'] = chgunixservice |
658 |
658 |
659 # CHGINTERNALMARK is temporarily set by chg client to detect if chg will |
659 # CHGINTERNALMARK is temporarily set by chg client to detect if chg will |
660 # start another chg. drop it to avoid possible side effects. |
660 # start another chg. drop it to avoid possible side effects. |