def connectionLost(self, reason):
        """
        Mostly handles clean-up of node + candidate structures.
        Avoids memory exhaustion for a large number of connections.
        """
        try:
            self.connected = False
            if debug:
                print(self.log_entry("CLOSED =", "none"))

            # Every five minutes: cleanup
            t = time.time()
            if time.time() - self.factory.last_cleanup >= self.cleanup:
                self.factory.last_cleanup = t

                # Delete old passive nodes.
                old_node_ips = []
                for node_ip in list(self.factory.nodes["passive"]):
                    passive_node = self.factory.nodes["passive"][node_ip]
                    # Gives enough time for passive nodes to receive clients.
                    if t - passive_node["time"] >= self.node_lifetime:
                        old_node_ips.append(node_ip)
                for node_ip in old_node_ips:
                    del self.factory.nodes["passive"][node_ip]

                # Delete old simultaneous nodes.
                old_node_ips = []
                for node_ip in list(self.factory.nodes["simultaneous"]):
                    simultaneous_node =\
                        self.factory.nodes["simultaneous"][node_ip]
                    # Gives enough time for passive nodes to receive clients.
                    if t - simultaneous_node["time"] >= self.node_lifetime:
                        old_node_ips.append(node_ip)
                for node_ip in old_node_ips:
                    del self.factory.nodes["simultaneous"][node_ip]

                # Delete old candidates and candidate structs.
                old_node_ips = []
                for node_ip in list(self.factory.candidates):
                    # Record old candidates.
                    old_candidates = []
                    for candidate in self.factory.candidates[node_ip]:
                        # Hole punching is ms time sensitive.
                        # Candidates older than this is safe to assume
                        # they're not needed.
                        if node_ip not in self.factory.nodes["simultaneous"] \
                                and t - candidate["time"] >= self.challenge_timeout * 5:
                            old_candidates.append(candidate)

                    # Remove old candidates.
                    for candidate in old_candidates:
                        self.factory.candidates[node_ip].remove(candidate)

                    # Record old node IPs.
                    if not len(self.factory.candidates[node_ip]) and \
                            node_ip not in self.factory.nodes["simultaneous"]:
                        old_node_ips.append(node_ip)

                # Remove old node IPs.
                for node_ip in old_node_ips:
                    del self.factory.candidates[node_ip]
        except Exception as e:
            error = parse_exception(e)
            log_exception(error_log_path, error)
            print(self.log_entry("ERROR =", error))
Mostly handles clean-up of node + candidate structures.
        Avoids memory exhaustion for a large number of connections.