URI: 
       tMake JsonRPC use persistent connections - electrum-personal-server - Maximally lightweight electrum server for a single user
  HTML git clone https://git.parazyd.org/electrum-personal-server
   DIR Log
   DIR Files
   DIR Refs
   DIR README
       ---
   DIR commit fd9d32deb9d08da41b4098bee9289e9600380917
   DIR parent 196e1452bb3f36469c7b55c4b68d1788027e0ca5
  HTML Author: chris-belcher <chris-belcher@users.noreply.github.com>
       Date:   Fri, 26 Apr 2019 13:37:49 +0100
       
       Make JsonRPC use persistent connections
       
       Now JsonRPC will try to reuse connections instead to creating
       a new connection for each RPC call. This solves a problem on
       windows where Electrum downloading all the block headers resulted
       in crashes because of all the sockets being created and closed.
       
       Diffstat:
         M electrumpersonalserver/server/comm… |       6 ++++--
         M electrumpersonalserver/server/json… |      90 ++++++++++++++++++++++---------
       
       2 files changed, 68 insertions(+), 28 deletions(-)
       ---
   DIR diff --git a/electrumpersonalserver/server/common.py b/electrumpersonalserver/server/common.py
       t@@ -500,7 +500,8 @@ def run_electrum_server(rpc, txmonitor, config):
                    else:
                        logger.error("IOError: " + repr(e))
                    try:
       -                sock.close()
       +                if sock != None:
       +                    sock.close()
                    except IOError:
                        pass
                    sock = None
       t@@ -712,7 +713,8 @@ def main():
            rpc = JsonRpc(host = config.get("bitcoin-rpc", "host"),
                port = int(config.get("bitcoin-rpc", "port")),
                user = rpc_u, password = rpc_p,
       -        wallet_filename=config.get("bitcoin-rpc", "wallet_filename").strip())
       +        wallet_filename=config.get("bitcoin-rpc", "wallet_filename").strip(),
       +        logger=logger)
        
            #TODO somewhere here loop until rpc works and fully sync'd, to allow
            # people to run this script without waiting for their node to fully
   DIR diff --git a/electrumpersonalserver/server/jsonrpc.py b/electrumpersonalserver/server/jsonrpc.py
       t@@ -1,5 +1,7 @@
       -#jsonrpc.py from https://github.com/JoinMarket-Org/joinmarket/blob/master/joinmarket/jsonrpc.py
       -#copyright # Copyright (C) 2013,2015 by Daniel Kraft <d@domob.eu> and phelix / blockchained.com
       +# Copyright (C) 2013,2015 by Daniel Kraft <d@domob.eu>
       +# Copyright (C) 2014 by phelix / blockchained.com
       +
       +#jsonrpc.py from https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/master/jmclient/jmclient/jsonrpc.py
        
        import base64
        import http.client
       t@@ -9,49 +11,85 @@ class JsonRpcError(Exception): pass
        class JsonRpcConnectionError(JsonRpcError): pass
        
        class JsonRpc(object):
       -    def __init__(self, host, port, user, password, wallet_filename=""):
       +    """
       +    Simple implementation of a JSON-RPC client that is used
       +    to connect to Bitcoin.
       +    """
       +    def __init__(self, host, port, user, password, wallet_filename="",
       +            logger=None):
                self.host = host
                self.port = port
       +        self.conn = http.client.HTTPConnection(self.host, self.port)
                self.authstr = "%s:%s" % (user, password)
                if len(wallet_filename) > 0:
                    self.url = "/wallet/" + wallet_filename
                else:
                    self.url = ""
       +        self.logger = logger
                self.queryId = 1
        
            def queryHTTP(self, obj):
       +        """
       +        Send an appropriate HTTP query to the server.  The JSON-RPC
       +        request should be (as object) in 'obj'.  If the call succeeds,
       +        the resulting JSON object is returned.  In case of an error
       +        with the connection (not JSON-RPC itself), an exception is raised.
       +        """
                headers = {"User-Agent": "electrum-personal-server",
                           "Content-Type": "application/json",
                           "Accept": "application/json"}
       -        headers["Authorization"] = "Basic %s" % base64.b64encode(
       -                                    self.authstr.encode()).decode()
       +        headers["Authorization"] = (b"Basic " +
       +            base64.b64encode(self.authstr.encode('utf-8')))
                body = json.dumps(obj)
       -        try:
       -            conn = http.client.HTTPConnection(self.host, self.port)
       -            conn.request("POST", self.url, body, headers)
       -            response = conn.getresponse()
       -            if response.status == 401:
       -                conn.close()
       -                raise JsonRpcConnectionError(
       -                        "authentication for JSON-RPC failed")
       -            # All of the codes below are 'fine' from a JSON-RPC point of view.
       -            if response.status not in [200, 404, 500]:
       -                conn.close()
       -                raise JsonRpcConnectionError("unknown error in JSON-RPC")
       -            data = response.read()
       -            conn.close()
       -            return json.loads(data.decode())
       -        except JsonRpcConnectionError as exc:
       -            raise exc
       -        except Exception as exc:
       -            raise JsonRpcConnectionError("JSON-RPC connection failed. Err:" +
       -                                         repr(exc))
       +        for i in range(20):
       +            try:
       +                self.conn.request("POST", self.url, body, headers)
       +                response = self.conn.getresponse()
       +                if response.status == 401:
       +                    self.conn.close()
       +                    raise JsonRpcConnectionError(
       +                            "authentication for JSON-RPC failed")
       +                #All the codes below are 'fine' from a JSON-RPC point of view.
       +                if response.status not in [200, 404, 500]:
       +                    self.conn.close()
       +                    raise JsonRpcConnectionError("unknown error in JSON-RPC")
       +                data = response.read()
       +                return json.loads(data.decode('utf-8'))
       +            except JsonRpcConnectionError as exc:
       +                raise exc
       +            except http.client.BadStatusLine:
       +                return "CONNFAILURE"
       +            except OSError as e:
       +                    self.logger.debug('Reconnecting RPC after error: ' + repr(e))
       +                    self.conn.close()
       +                    self.conn.connect()
       +                    continue
       +            except Exception as exc:
       +                raise JsonRpcConnectionError("JSON-RPC connection failed. Err:"
       +                    + repr(exc))
       +            break
       +        return None
        
            def call(self, method, params):
                currentId = self.queryId
                self.queryId += 1
       +
                request = {"method": method, "params": params, "id": currentId}
       -        response = self.queryHTTP(request)
       +        #query can fail from keepalive timeout; keep retrying if it does, up
       +        #to a reasonable limit, then raise (failure to access blockchain
       +        #is a critical failure). Note that a real failure to connect (e.g.
       +        #wrong port) is raised in queryHTTP directly.
       +        response_received = False
       +        for i in range(100):
       +            response = self.queryHTTP(request)
       +            if response != "CONNFAILURE":
       +                response_received = True
       +                break
       +            #Failure means keepalive timed out, just make a new one
       +            self.conn = http.client.HTTPConnection(self.host, self.port)
       +            self.logger.debug("Creating new jsonrpc HTTPConnection")
       +        if not response_received:
       +            raise JsonRpcConnectionError("Unable to connect over RPC")
                if response["id"] != currentId:
                    raise JsonRpcConnectionError("invalid id returned by query")
                if response["error"] is not None: