mercurial/commandserver.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43089 c59eb1560c44
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
    51         self.out = out
    51         self.out = out
    52         self.channel = channel
    52         self.channel = channel
    53 
    53 
    54     @property
    54     @property
    55     def name(self):
    55     def name(self):
    56         return '<%c-channel>' % self.channel
    56         return b'<%c-channel>' % self.channel
    57 
    57 
    58     def write(self, data):
    58     def write(self, data):
    59         if not data:
    59         if not data:
    60             return
    60             return
    61         # single write() to guarantee the same atomicity as the underlying file
    61         # single write() to guarantee the same atomicity as the underlying file
    62         self.out.write(struct.pack('>cI', self.channel, len(data)) + data)
    62         self.out.write(struct.pack(b'>cI', self.channel, len(data)) + data)
    63         self.out.flush()
    63         self.out.flush()
    64 
    64 
    65     def __getattr__(self, attr):
    65     def __getattr__(self, attr):
    66         if attr in (r'isatty', r'fileno', r'tell', r'seek'):
    66         if attr in (r'isatty', r'fileno', r'tell', r'seek'):
    67             raise AttributeError(attr)
    67             raise AttributeError(attr)
   117         self.out = out
   117         self.out = out
   118         self.channel = channel
   118         self.channel = channel
   119 
   119 
   120     @property
   120     @property
   121     def name(self):
   121     def name(self):
   122         return '<%c-channel>' % self.channel
   122         return b'<%c-channel>' % self.channel
   123 
   123 
   124     def read(self, size=-1):
   124     def read(self, size=-1):
   125         if size < 0:
   125         if size < 0:
   126             # if we need to consume all the clients input, ask for 4k chunks
   126             # if we need to consume all the clients input, ask for 4k chunks
   127             # so the pipe doesn't fill up risking a deadlock
   127             # so the pipe doesn't fill up risking a deadlock
   136         else:
   136         else:
   137             return self._read(size, self.channel)
   137             return self._read(size, self.channel)
   138 
   138 
   139     def _read(self, size, channel):
   139     def _read(self, size, channel):
   140         if not size:
   140         if not size:
   141             return ''
   141             return b''
   142         assert size > 0
   142         assert size > 0
   143 
   143 
   144         # tell the client we need at most size bytes
   144         # tell the client we need at most size bytes
   145         self.out.write(struct.pack('>cI', channel, size))
   145         self.out.write(struct.pack(b'>cI', channel, size))
   146         self.out.flush()
   146         self.out.flush()
   147 
   147 
   148         length = self.in_.read(4)
   148         length = self.in_.read(4)
   149         length = struct.unpack('>I', length)[0]
   149         length = struct.unpack(b'>I', length)[0]
   150         if not length:
   150         if not length:
   151             return ''
   151             return b''
   152         else:
   152         else:
   153             return self.in_.read(length)
   153             return self.in_.read(length)
   154 
   154 
   155     def readline(self, size=-1):
   155     def readline(self, size=-1):
   156         if size < 0:
   156         if size < 0:
   157             size = self.maxchunksize
   157             size = self.maxchunksize
   158             s = self._read(size, 'L')
   158             s = self._read(size, b'L')
   159             buf = s
   159             buf = s
   160             # keep asking for more until there's either no more or
   160             # keep asking for more until there's either no more or
   161             # we got a full line
   161             # we got a full line
   162             while s and s[-1] != '\n':
   162             while s and s[-1] != b'\n':
   163                 s = self._read(size, 'L')
   163                 s = self._read(size, b'L')
   164                 buf += s
   164                 buf += s
   165 
   165 
   166             return buf
   166             return buf
   167         else:
   167         else:
   168             return self._read(size, 'L')
   168             return self._read(size, b'L')
   169 
   169 
   170     def __iter__(self):
   170     def __iter__(self):
   171         return self
   171         return self
   172 
   172 
   173     def next(self):
   173     def next(self):
   219         else:
   219         else:
   220             self.ui = ui
   220             self.ui = ui
   221             self.repo = self.repoui = None
   221             self.repo = self.repoui = None
   222         self._prereposetups = prereposetups
   222         self._prereposetups = prereposetups
   223 
   223 
   224         self.cdebug = channeledoutput(fout, 'd')
   224         self.cdebug = channeledoutput(fout, b'd')
   225         self.cerr = channeledoutput(fout, 'e')
   225         self.cerr = channeledoutput(fout, b'e')
   226         self.cout = channeledoutput(fout, 'o')
   226         self.cout = channeledoutput(fout, b'o')
   227         self.cin = channeledinput(fin, fout, 'I')
   227         self.cin = channeledinput(fin, fout, b'I')
   228         self.cresult = channeledoutput(fout, 'r')
   228         self.cresult = channeledoutput(fout, b'r')
   229 
   229 
   230         if self.ui.config(b'cmdserver', b'log') == b'-':
   230         if self.ui.config(b'cmdserver', b'log') == b'-':
   231             # switch log stream of server's ui to the 'd' (debug) channel
   231             # switch log stream of server's ui to the 'd' (debug) channel
   232             # (don't touch repo.ui as its lifetime is longer than the server)
   232             # (don't touch repo.ui as its lifetime is longer than the server)
   233             self.ui = self.ui.copy()
   233             self.ui = self.ui.copy()
   246     def cleanup(self):
   246     def cleanup(self):
   247         """release and restore resources taken during server session"""
   247         """release and restore resources taken during server session"""
   248 
   248 
   249     def _read(self, size):
   249     def _read(self, size):
   250         if not size:
   250         if not size:
   251             return ''
   251             return b''
   252 
   252 
   253         data = self.client.read(size)
   253         data = self.client.read(size)
   254 
   254 
   255         # is the other end closed?
   255         # is the other end closed?
   256         if not data:
   256         if not data:
   262         """read a string from the channel
   262         """read a string from the channel
   263 
   263 
   264         format:
   264         format:
   265         data length (uint32), data
   265         data length (uint32), data
   266         """
   266         """
   267         length = struct.unpack('>I', self._read(4))[0]
   267         length = struct.unpack(b'>I', self._read(4))[0]
   268         if not length:
   268         if not length:
   269             return ''
   269             return b''
   270         return self._read(length)
   270         return self._read(length)
   271 
   271 
   272     def _readlist(self):
   272     def _readlist(self):
   273         """read a list of NULL separated strings from the channel"""
   273         """read a list of NULL separated strings from the channel"""
   274         s = self._readstr()
   274         s = self._readstr()
   275         if s:
   275         if s:
   276             return s.split('\0')
   276             return s.split(b'\0')
   277         else:
   277         else:
   278             return []
   278             return []
   279 
   279 
   280     def runcommand(self):
   280     def runcommand(self):
   281         """ reads a list of \0 terminated arguments, executes
   281         """ reads a list of \0 terminated arguments, executes
   300         for ui in uis:
   300         for ui in uis:
   301             ui.resetstate()
   301             ui.resetstate()
   302             # any kind of interaction must use server channels, but chg may
   302             # any kind of interaction must use server channels, but chg may
   303             # replace channels by fully functional tty files. so nontty is
   303             # replace channels by fully functional tty files. so nontty is
   304             # enforced only if cin is a channel.
   304             # enforced only if cin is a channel.
   305             if not util.safehasattr(self.cin, 'fileno'):
   305             if not util.safehasattr(self.cin, b'fileno'):
   306                 ui.setconfig('ui', 'nontty', 'true', 'commandserver')
   306                 ui.setconfig(b'ui', b'nontty', b'true', b'commandserver')
   307 
   307 
   308         req = dispatch.request(
   308         req = dispatch.request(
   309             args[:],
   309             args[:],
   310             copiedui,
   310             copiedui,
   311             self.repo,
   311             self.repo,
   316             prereposetups=self._prereposetups,
   316             prereposetups=self._prereposetups,
   317         )
   317         )
   318 
   318 
   319         try:
   319         try:
   320             ret = dispatch.dispatch(req) & 255
   320             ret = dispatch.dispatch(req) & 255
   321             self.cresult.write(struct.pack('>i', int(ret)))
   321             self.cresult.write(struct.pack(b'>i', int(ret)))
   322         finally:
   322         finally:
   323             # restore old cwd
   323             # restore old cwd
   324             if '--cwd' in args:
   324             if b'--cwd' in args:
   325                 os.chdir(self.cwd)
   325                 os.chdir(self.cwd)
   326 
   326 
   327     def getencoding(self):
   327     def getencoding(self):
   328         """ writes the current encoding to the result channel """
   328         """ writes the current encoding to the result channel """
   329         self.cresult.write(encoding.encoding)
   329         self.cresult.write(encoding.encoding)
   335             if handler:
   335             if handler:
   336                 handler(self)
   336                 handler(self)
   337             else:
   337             else:
   338                 # clients are expected to check what commands are supported by
   338                 # clients are expected to check what commands are supported by
   339                 # looking at the servers capabilities
   339                 # looking at the servers capabilities
   340                 raise error.Abort(_('unknown command %s') % cmd)
   340                 raise error.Abort(_(b'unknown command %s') % cmd)
   341 
   341 
   342         return cmd != ''
   342         return cmd != b''
   343 
   343 
   344     capabilities = {'runcommand': runcommand, 'getencoding': getencoding}
   344     capabilities = {b'runcommand': runcommand, b'getencoding': getencoding}
   345 
   345 
   346     def serve(self):
   346     def serve(self):
   347         hellomsg = 'capabilities: ' + ' '.join(sorted(self.capabilities))
   347         hellomsg = b'capabilities: ' + b' '.join(sorted(self.capabilities))
   348         hellomsg += '\n'
   348         hellomsg += b'\n'
   349         hellomsg += 'encoding: ' + encoding.encoding
   349         hellomsg += b'encoding: ' + encoding.encoding
   350         hellomsg += '\n'
   350         hellomsg += b'\n'
   351         if self.cmsg:
   351         if self.cmsg:
   352             hellomsg += 'message-encoding: %s\n' % self.cmsg.encoding
   352             hellomsg += b'message-encoding: %s\n' % self.cmsg.encoding
   353         hellomsg += 'pid: %d' % procutil.getpid()
   353         hellomsg += b'pid: %d' % procutil.getpid()
   354         if util.safehasattr(os, 'getpgid'):
   354         if util.safehasattr(os, b'getpgid'):
   355             hellomsg += '\n'
   355             hellomsg += b'\n'
   356             hellomsg += 'pgid: %d' % os.getpgid(0)
   356             hellomsg += b'pgid: %d' % os.getpgid(0)
   357 
   357 
   358         # write the hello msg in -one- chunk
   358         # write the hello msg in -one- chunk
   359         self.cout.write(hellomsg)
   359         self.cout.write(hellomsg)
   360 
   360 
   361         try:
   361         try:
   457         try:
   457         try:
   458             sv.serve()
   458             sv.serve()
   459         # handle exceptions that may be raised by command server. most of
   459         # handle exceptions that may be raised by command server. most of
   460         # known exceptions are caught by dispatch.
   460         # known exceptions are caught by dispatch.
   461         except error.Abort as inst:
   461         except error.Abort as inst:
   462             ui.error(_('abort: %s\n') % inst)
   462             ui.error(_(b'abort: %s\n') % inst)
   463         except IOError as inst:
   463         except IOError as inst:
   464             if inst.errno != errno.EPIPE:
   464             if inst.errno != errno.EPIPE:
   465                 raise
   465                 raise
   466         except KeyboardInterrupt:
   466         except KeyboardInterrupt:
   467             pass
   467             pass
   471         # also write traceback to error channel. otherwise client cannot
   471         # also write traceback to error channel. otherwise client cannot
   472         # see it because it is written to server's stderr by default.
   472         # see it because it is written to server's stderr by default.
   473         if sv:
   473         if sv:
   474             cerr = sv.cerr
   474             cerr = sv.cerr
   475         else:
   475         else:
   476             cerr = channeledoutput(fout, 'e')
   476             cerr = channeledoutput(fout, b'e')
   477         cerr.write(encoding.strtolocal(traceback.format_exc()))
   477         cerr.write(encoding.strtolocal(traceback.format_exc()))
   478         raise
   478         raise
   479     finally:
   479     finally:
   480         fin.close()
   480         fin.close()
   481         try:
   481         try:
   498         self.ui = ui
   498         self.ui = ui
   499 
   499 
   500     def bindsocket(self, sock, address):
   500     def bindsocket(self, sock, address):
   501         util.bindunixsocket(sock, address)
   501         util.bindunixsocket(sock, address)
   502         sock.listen(socket.SOMAXCONN)
   502         sock.listen(socket.SOMAXCONN)
   503         self.ui.status(_('listening at %s\n') % address)
   503         self.ui.status(_(b'listening at %s\n') % address)
   504         self.ui.flush()  # avoid buffering of status message
   504         self.ui.flush()  # avoid buffering of status message
   505 
   505 
   506     def unlinksocket(self, address):
   506     def unlinksocket(self, address):
   507         os.unlink(address)
   507         os.unlink(address)
   508 
   508 
   525     """
   525     """
   526 
   526 
   527     def __init__(self, ui, repo, opts, handler=None):
   527     def __init__(self, ui, repo, opts, handler=None):
   528         self.ui = ui
   528         self.ui = ui
   529         self.repo = repo
   529         self.repo = repo
   530         self.address = opts['address']
   530         self.address = opts[b'address']
   531         if not util.safehasattr(socket, 'AF_UNIX'):
   531         if not util.safehasattr(socket, b'AF_UNIX'):
   532             raise error.Abort(_('unsupported platform'))
   532             raise error.Abort(_(b'unsupported platform'))
   533         if not self.address:
   533         if not self.address:
   534             raise error.Abort(_('no socket path specified with --address'))
   534             raise error.Abort(_(b'no socket path specified with --address'))
   535         self._servicehandler = handler or unixservicehandler(ui)
   535         self._servicehandler = handler or unixservicehandler(ui)
   536         self._sock = None
   536         self._sock = None
   537         self._mainipc = None
   537         self._mainipc = None
   538         self._workeripc = None
   538         self._workeripc = None
   539         self._oldsigchldhandler = None
   539         self._oldsigchldhandler = None
   540         self._workerpids = set()  # updated by signal handler; do not iterate
   540         self._workerpids = set()  # updated by signal handler; do not iterate
   541         self._socketunlinked = None
   541         self._socketunlinked = None
   542         # experimental config: cmdserver.max-repo-cache
   542         # experimental config: cmdserver.max-repo-cache
   543         maxlen = ui.configint(b'cmdserver', b'max-repo-cache')
   543         maxlen = ui.configint(b'cmdserver', b'max-repo-cache')
   544         if maxlen < 0:
   544         if maxlen < 0:
   545             raise error.Abort(_('negative max-repo-cache size not allowed'))
   545             raise error.Abort(_(b'negative max-repo-cache size not allowed'))
   546         self._repoloader = repocache.repoloader(ui, maxlen)
   546         self._repoloader = repocache.repoloader(ui, maxlen)
   547 
   547 
   548     def init(self):
   548     def init(self):
   549         self._sock = socket.socket(socket.AF_UNIX)
   549         self._sock = socket.socket(socket.AF_UNIX)
   550         # IPC channel from many workers to one main process; this is actually
   550         # IPC channel from many workers to one main process; this is actually
   551         # a uni-directional pipe, but is backed by a DGRAM socket so each
   551         # a uni-directional pipe, but is backed by a DGRAM socket so each
   552         # message can be easily separated.
   552         # message can be easily separated.
   553         o = socket.socketpair(socket.AF_UNIX, socket.SOCK_DGRAM)
   553         o = socket.socketpair(socket.AF_UNIX, socket.SOCK_DGRAM)
   554         self._mainipc, self._workeripc = o
   554         self._mainipc, self._workeripc = o
   555         self._servicehandler.bindsocket(self._sock, self.address)
   555         self._servicehandler.bindsocket(self._sock, self.address)
   556         if util.safehasattr(procutil, 'unblocksignal'):
   556         if util.safehasattr(procutil, b'unblocksignal'):
   557             procutil.unblocksignal(signal.SIGCHLD)
   557             procutil.unblocksignal(signal.SIGCHLD)
   558         o = signal.signal(signal.SIGCHLD, self._sigchldhandler)
   558         o = signal.signal(signal.SIGCHLD, self._sigchldhandler)
   559         self._oldsigchldhandler = o
   559         self._oldsigchldhandler = o
   560         self._socketunlinked = False
   560         self._socketunlinked = False
   561         self._repoloader.start()
   561         self._repoloader.start()