Patchwork [6,of,7] chgserver: switch to new forking service

login
register
mail settings
Submitter Yuya Nishihara
Date July 14, 2016, 3:20 p.m.
Message ID <32d05eda38ffd0a224a7.1468509644@mimosa>
Download mbox | patch
Permalink /patch/15859/
State Accepted
Headers show

Comments

Yuya Nishihara - July 14, 2016, 3:20 p.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1463891797 -32400
#      Sun May 22 13:36:37 2016 +0900
# Node ID 32d05eda38ffd0a224a77914eea9fa14322aac32
# Parent  1c47f0361d40b86ec4e6b1aca6d7b7a980ba78fd
chgserver: switch to new forking service

Threading and complex classes are no longer necessary. _autoexitloop() has
been replaced by polling cycle in the main thread.

Patch

diff --git a/hgext/chgserver.py b/hgext/chgserver.py
--- a/hgext/chgserver.py
+++ b/hgext/chgserver.py
@@ -48,7 +48,6 @@  import re
 import signal
 import struct
 import sys
-import threading
 import time
 
 from mercurial.i18n import _
@@ -64,8 +63,6 @@  from mercurial import (
     util,
 )
 
-socketserver = util.socketserver
-
 # Note for extension authors: ONLY specify testedwith = 'internal' for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
 # be specifying the version(s) of Mercurial they are tested with, or
@@ -538,20 +535,21 @@  def _tempaddress(address):
 def _hashaddress(address, hashstr):
     return '%s-%s' % (address, hashstr)
 
-class _chgunixservice(commandserver.unixservice):
-    def init(self):
+class chgunixservicehandler(object):
+    """Set of operations for chg services"""
+
+    pollinterval = 1  # [sec]
+
+    def __init__(self, ui):
+        self.ui = ui
+        self.idletimeout = ui.configint('chgserver', 'idletimeout', 3600)
+        self.lastactive = time.time()
+
+    def bindsocket(self, sock, address):
+        self.address = address
         self._inithashstate()
         self._checkextensions()
-        class cls(AutoExitMixIn, socketserver.ForkingMixIn,
-                  socketserver.UnixStreamServer):
-            ui = self.ui
-            repo = self.repo
-            hashstate = self.hashstate
-            baseaddress = self.baseaddress
-        self.server = cls(self.address, _requesthandler)
-        self.server.idletimeout = self.ui.configint(
-            'chgserver', 'idletimeout', self.server.idletimeout)
-        self.server.startautoexitthread()
+        self._bind(sock)
         self._createsymlink()
 
     def _inithashstate(self):
@@ -578,57 +576,42 @@  class _chgunixservice(commandserver.unix
         os.symlink(os.path.basename(self.address), tempaddress)
         util.rename(tempaddress, self.baseaddress)
 
-    def _cleanup(self):
-        self.server.unlinksocketfile()
-
-class AutoExitMixIn:  # use old-style to comply with SocketServer design
-    lastactive = time.time()
-    idletimeout = 3600  # default 1 hour
+    def printbanner(self, address):
+        # no "listening at" message should be printed to simulate hg behavior
+        pass
 
-    def startautoexitthread(self):
-        # note: the auto-exit check here is cheap enough to not use a thread,
-        # be done in serve_forever. however SocketServer is hook-unfriendly,
-        # you simply cannot hook serve_forever without copying a lot of code.
-        # besides, serve_forever's docstring suggests using thread.
-        thread = threading.Thread(target=self._autoexitloop)
-        thread.daemon = True
-        thread.start()
-
-    def _autoexitloop(self, interval=1):
-        while True:
-            time.sleep(interval)
+    def shouldexit(self):
+        if True:  # TODO: unindent
             if not self.issocketowner():
-                _log('%s is not owned, exiting.\n' % self.server_address)
-                break
+                _log('%s is not owned, exiting.\n' % self.address)
+                return True
             if time.time() - self.lastactive > self.idletimeout:
                 _log('being idle too long. exiting.\n')
-                break
-        self.shutdown()
+                return True
+        return False
 
-    def process_request(self, request, address):
+    def newconnection(self):
         self.lastactive = time.time()
-        return socketserver.ForkingMixIn.process_request(
-            self, request, address)
 
-    def server_bind(self):
+    def _bind(self, sock):
         # use a unique temp address so we can stat the file and do ownership
         # check later
-        tempaddress = _tempaddress(self.server_address)
-        util.bindunixsocket(self.socket, tempaddress)
+        tempaddress = _tempaddress(self.address)
+        util.bindunixsocket(sock, tempaddress)
         self._socketstat = os.stat(tempaddress)
         # rename will replace the old socket file if exists atomically. the
         # old server will detect ownership change and exit.
-        util.rename(tempaddress, self.server_address)
+        util.rename(tempaddress, self.address)
 
     def issocketowner(self):
         try:
-            stat = os.stat(self.server_address)
+            stat = os.stat(self.address)
             return (stat.st_ino == self._socketstat.st_ino and
                     stat.st_mtime == self._socketstat.st_mtime)
         except OSError:
             return False
 
-    def unlinksocketfile(self):
+    def unlinksocket(self, address):
         if not self.issocketowner():
             return
         # it is possible to have a race condition here that we may
@@ -636,22 +619,21 @@  class AutoExitMixIn:  # use old-style to
         # since that server will detect and exit automatically and
         # the client will start a new server on demand.
         try:
-            os.unlink(self.server_address)
+            os.unlink(self.address)
         except OSError as exc:
             if exc.errno != errno.ENOENT:
                 raise
 
-class _requesthandler(commandserver._requesthandler):
-    def _createcmdserver(self, repo, conn, fin, fout):
-        ui = self.server.ui
-        return chgcmdserver(ui, repo, fin, fout, conn,
-                            self.server.hashstate, self.server.baseaddress)
+    def createcmdserver(self, repo, conn, fin, fout):
+        return chgcmdserver(self.ui, repo, fin, fout, conn,
+                            self.hashstate, self.baseaddress)
 
 def chgunixservice(ui, repo, opts):
     if repo:
         # one chgserver can serve multiple repos. drop repo infomation
         ui.setconfig('bundle', 'mainreporoot', '', 'repo')
-    return _chgunixservice(ui, repo=None, opts=opts)
+    h = chgunixservicehandler(ui)
+    return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
 
 def uisetup(ui):
     commandserver._servicemap['chgunix'] = chgunixservice