electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 60be02dace2f431de4c7a5fe8ce1d0e8676f6904
parent 656560be7220ead454059e7ac6f82ef84cf6df30
Author: ThomasV <electrumdev@gmail.com>
Date:   Fri,  8 May 2015 07:01:27 +0200

Merge pull request #1205 from kyuupichan/if-cleanup-final

Fix two races in interface.py.
Diffstat:
Mlib/interface.py | 67+++++++++++++++++++++++++++++++++++--------------------------------
1 file changed, 35 insertions(+), 32 deletions(-)

diff --git a/lib/interface.py b/lib/interface.py @@ -17,16 +17,16 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. -import re, errno, os +import copy, re, errno, os import threading, traceback, sys, time, Queue import socket import ssl -import x509 -import util import requests ca_path = requests.certs.where() +import util +import x509 from version import ELECTRUM_VERSION, PROTOCOL_VERSION from simple_config import SimpleConfig @@ -40,8 +40,7 @@ def Interface(server, response_queue, config = None): - Member functions send_request(), stop(), is_connected() - Member variable server. - "is_connected()" is currently racy. "server" is constant for the object's lifetime and hence - synchronization is unnecessary. + "server" is constant for the object's lifetime and hence synchronization is unnecessary. """ host, port, protocol = server.split(':') if protocol in 'st': @@ -55,7 +54,12 @@ class TcpInterface(threading.Thread): threading.Thread.__init__(self) self.daemon = True self.config = config if config is not None else SimpleConfig() - self.connected = False + # Set by stop(); no more data is exchanged and the thread exits after gracefully + # closing the socket + self.disconnect = False + # Initially True to avoid a race; set to False on failure to create a socket or when + # it is closed + self.connected = True self.debug = False # dump network messages. can be changed at runtime using the console self.message_id = 0 self.response_queue = response_queue @@ -250,7 +254,7 @@ class TcpInterface(threading.Thread): def send_request(self, request, response_queue = None): '''Queue a request. Blocking only if called from other threads.''' self.request_time = time.time() - self.request_queue.put((request, response_queue), threading.current_thread() != self) + self.request_queue.put((copy.deepcopy(request), response_queue), threading.current_thread() != self) def send_requests(self): '''Sends all queued requests''' @@ -271,14 +275,11 @@ class TcpInterface(threading.Thread): self.message_id += 1 def is_connected(self): - return self.connected + return self.connected and not self.disconnect def stop(self): - if self.connected and self.protocol in 'st' and self.s: - self.s.shutdown(socket.SHUT_RDWR) - self.s.close() - self.connected = False - self.print_error("stopped") + self.disconnect = True + self.print_error("disconnecting") def maybe_ping(self): # ping the server with server.version @@ -290,7 +291,7 @@ class TcpInterface(threading.Thread): self.print_error("interface timeout", len(self.unanswered_requests)) self.stop() - def get_response(self): + def get_and_process_response(self): if self.is_connected(): try: response = self.pipe.get() @@ -298,28 +299,30 @@ class TcpInterface(threading.Thread): return # If remote side closed the socket, SocketPipe closes our socket and returns None if response is None: - self.connected = False - return response + self.connected = False # Don't re-close the socket + self.print_error("connection closed remotely") + else: + self.process_response(response) def run(self): - self.s = self.get_socket() - if self.s: - self.pipe = util.SocketPipe(self.s) - self.s.settimeout(0.1) - self.connected = True + s = self.get_socket() + if s: + self.pipe = util.SocketPipe(s) + s.settimeout(0.1) self.print_error("connected") + # Indicate to parent that we've connected + self.change_status() + while self.is_connected(): + self.maybe_ping() + self.send_requests() + self.get_and_process_response() + if self.connected: # Don't shutdown() a closed socket + s.shutdown(socket.SHUT_RDWR) + s.close() - self.change_status() - if not self.connected: - return - - while self.connected: - self.maybe_ping() - self.send_requests() - response = self.get_response() - if response: - self.process_response(response) - + # Also for the s is None case + self.connected = False + # Indicate to parent that the connection is now down self.change_status() def change_status(self):