Add locks to fix concurrency issues

This commit is contained in:
Roy Hyunjin Han 2015-04-15 14:38:25 -04:00
commit a6f9260964
5 changed files with 128 additions and 95 deletions

View file

@ -40,10 +40,12 @@ class EngineIO(LoggingMixin):
self._wait_for_connection = wait_for_connection
self._client_transports = transports
self._hurry_interval_in_seconds = hurry_interval_in_seconds
self._kw = kw
self._http_session = prepare_http_session(kw)
self._log_name = self._url
self._wants_to_close = False
self._opened = False
if Namespace:
self.define(Namespace)
self._transport
@ -63,7 +65,6 @@ class EngineIO(LoggingMixin):
def _get_engineIO_session(self):
warning_screen = self._yield_warning_screen()
self._http_session = prepare_http_session(self._kw)
for elapsed_time in warning_screen:
transport = XHR_PollingTransport(
self._http_session, self._is_secure, self._url)
@ -76,7 +77,7 @@ class EngineIO(LoggingMixin):
raise
warning = Exception('[waiting for connection] %s' % e)
warning_screen.throw(warning)
assert engineIO_packet_type == 0
assert engineIO_packet_type == 0 # engineIO_packet_type == open
return parse_engineIO_session(engineIO_packet_data)
def _negotiate_transport(self):
@ -93,6 +94,8 @@ class EngineIO(LoggingMixin):
transport.send_packet(5, '')
self._transport_instance = transport
self.transport_name = 'websocket'
else:
self._warn('unexpected engine.io packet')
except Exception:
pass
self._debug('[transport selected] %s', self.transport_name)
@ -100,8 +103,9 @@ class EngineIO(LoggingMixin):
def _reset_heartbeat(self):
try:
self._heartbeat_thread.halt()
hurried = self._heartbeat_thread.hurried
except AttributeError:
pass
hurried = False
ping_interval = self._engineIO_session.ping_interval
if self.transport_name.endswith('-polling'):
# Use ping/pong to unblock recv for polling transport
@ -114,6 +118,8 @@ class EngineIO(LoggingMixin):
relax_interval_in_seconds=ping_interval,
hurry_interval_in_seconds=hurry_interval_in_seconds)
self._heartbeat_thread.start()
if hurried:
self._heartbeat_thread.hurry()
self._debug('[heartbeat reset]')
def _connect_namespaces(self):
@ -161,7 +167,6 @@ class EngineIO(LoggingMixin):
def send(self, engineIO_packet_data):
self._message(engineIO_packet_data)
@retry
def _open(self):
engineIO_packet_type = 0
self._transport_instance.send_packet(engineIO_packet_type)
@ -172,34 +177,36 @@ class EngineIO(LoggingMixin):
if not self._opened:
return
engineIO_packet_type = 1
self._transport_instance.send_packet(engineIO_packet_type)
try:
self._transport_instance.send_packet(engineIO_packet_type)
except (TimeoutError, ConnectionError):
pass
self._opened = False
@retry
def _ping(self, engineIO_packet_data=''):
engineIO_packet_type = 2
self._transport_instance.send_packet(
engineIO_packet_type, engineIO_packet_data)
@retry
def _pong(self, engineIO_packet_data=''):
engineIO_packet_type = 3
self._transport_instance.send_packet(
engineIO_packet_type, engineIO_packet_data)
@retry
def _message(self, engineIO_packet_data):
def _message(self, engineIO_packet_data, with_transport_instance=False):
engineIO_packet_type = 4
self._transport_instance.send_packet(
engineIO_packet_type, engineIO_packet_data)
if with_transport_instance:
transport = self._transport_instance
else:
transport = self._transport
transport.send_packet(engineIO_packet_type, engineIO_packet_data)
self._debug('[socket.io packet sent] %s', engineIO_packet_data)
@retry
def _upgrade(self):
engineIO_packet_type = 5
self._transport_instance.send_packet(engineIO_packet_type)
@retry
def _noop(self):
engineIO_packet_type = 6
self._transport_instance.send_packet(engineIO_packet_type)
@ -223,6 +230,7 @@ class EngineIO(LoggingMixin):
except TimeoutError:
pass
except ConnectionError as e:
self._opened = False
try:
warning = Exception('[connection error] %s' % e)
warning_screen.throw(warning)
@ -263,31 +271,31 @@ class EngineIO(LoggingMixin):
except KeyError:
raise PacketError(
'unexpected engine.io packet type (%s)' % engineIO_packet_type)
delegate(engineIO_packet_data, namespace._find_packet_callback)
delegate(engineIO_packet_data, namespace)
if engineIO_packet_type is 4:
return engineIO_packet_data
def _on_open(self, data, find_packet_callback):
find_packet_callback('open')()
def _on_open(self, data, namespace):
namespace._find_packet_callback('open')()
def _on_close(self, data, find_packet_callback):
find_packet_callback('close')()
def _on_close(self, data, namespace):
namespace._find_packet_callback('close')()
def _on_ping(self, data, find_packet_callback):
def _on_ping(self, data, namespace):
self._pong(data)
find_packet_callback('ping')(data)
namespace._find_packet_callback('ping')(data)
def _on_pong(self, data, find_packet_callback):
find_packet_callback('pong')(data)
def _on_pong(self, data, namespace):
namespace._find_packet_callback('pong')(data)
def _on_message(self, data, find_packet_callback):
find_packet_callback('message')(data)
def _on_message(self, data, namespace):
namespace._find_packet_callback('message')(data)
def _on_upgrade(self, data, find_packet_callback):
find_packet_callback('upgrade')()
def _on_upgrade(self, data, namespace):
namespace._find_packet_callback('upgrade')()
def _on_noop(self, data, find_packet_callback):
find_packet_callback('noop')()
def _on_noop(self, data, namespace):
namespace._find_packet_callback('noop')()
class SocketIO(EngineIO):
@ -329,7 +337,7 @@ class SocketIO(EngineIO):
for path, namespace in self._namespace_by_path.items():
namespace._transport = self._transport_instance
if path:
self.connect(path)
self.connect(path, with_transport_instance=True)
def __exit__(self, *exception_pack):
self.disconnect()
@ -342,9 +350,10 @@ class SocketIO(EngineIO):
# Define
def define(self, Namespace, path=''):
self._namespace_by_path[path] = namespace = Namespace(self, path)
if path:
self.connect(path)
self._namespace_by_path[path] = namespace = Namespace(self, path)
self.wait(for_connect=True)
return namespace
def on(self, event, callback, path=''):
@ -362,20 +371,23 @@ class SocketIO(EngineIO):
# Act
def connect(self, path):
def connect(self, path, with_transport_instance=False):
socketIO_packet_type = 0
socketIO_packet_data = format_socketIO_packet_data(path)
self._message(str(socketIO_packet_type) + socketIO_packet_data)
self._message(
str(socketIO_packet_type) + socketIO_packet_data,
with_transport_instance)
def disconnect(self, path=''):
if not self._opened:
return
if path:
if not path or not self._opened:
self._close()
elif path:
socketIO_packet_type = 1
socketIO_packet_data = format_socketIO_packet_data(path)
self._message(str(socketIO_packet_type) + socketIO_packet_data)
else:
self._close()
try:
self._message(str(socketIO_packet_type) + socketIO_packet_data)
except (TimeoutError, ConnectionError):
pass
try:
namespace = self._namespace_by_path.pop(path)
namespace.on_disconnect()
@ -405,13 +417,17 @@ class SocketIO(EngineIO):
# React
def wait(self, seconds=None, for_callbacks=False):
super(SocketIO, self).wait(seconds, for_callbacks=for_callbacks)
def wait_for_callbacks(self, seconds=None):
self.wait(seconds, for_callbacks=True)
def _should_stop_waiting(self, for_callbacks):
def _should_stop_waiting(self, for_connect=False, for_callbacks=False):
if for_connect:
for namespace in self._namespace_by_path.values():
is_namespace_connected = getattr(
namespace, '_connected', False)
if not is_namespace_connected:
return False
return True
if for_callbacks and not self._has_ack_callback:
return True
return super(SocketIO, self)._should_stop_waiting()
@ -439,16 +455,18 @@ class SocketIO(EngineIO):
except KeyError:
raise PacketError(
'unexpected socket.io packet type (%s)' % socketIO_packet_type)
delegate(socketIO_packet_data, namespace._find_packet_callback)
delegate(socketIO_packet_data, namespace)
return socketIO_packet_data
def _on_connect(self, data, find_packet_callback):
find_packet_callback('connect')()
def _on_connect(self, data, namespace):
namespace._connected = True
namespace._find_packet_callback('connect')()
def _on_disconnect(self, data, find_packet_callback):
find_packet_callback('disconnect')()
def _on_disconnect(self, data, namespace):
namespace._connected = False
namespace._find_packet_callback('disconnect')()
def _on_event(self, data, find_packet_callback):
def _on_event(self, data, namespace):
data_parsed = parse_socketIO_packet_data(data)
args = data_parsed.args
try:
@ -458,9 +476,9 @@ class SocketIO(EngineIO):
if data_parsed.ack_id is not None:
args.append(self._prepare_to_send_ack(
data_parsed.path, data_parsed.ack_id))
find_packet_callback(event)(*args)
namespace._find_packet_callback(event)(*args)
def _on_ack(self, data, find_packet_callback):
def _on_ack(self, data, namespace):
data_parsed = parse_socketIO_packet_data(data)
try:
ack_callback = self._get_ack_callback(data_parsed.ack_id)
@ -468,13 +486,13 @@ class SocketIO(EngineIO):
return
ack_callback(*data_parsed.args)
def _on_error(self, data, find_packet_callback):
find_packet_callback('error')(data)
def _on_error(self, data, namespace):
namespace._find_packet_callback('error')(data)
def _on_binary_event(self, data, find_packet_callback):
def _on_binary_event(self, data, namespace):
self._warn('[not implemented] binary event')
def _on_binary_ack(self, data, find_packet_callback):
def _on_binary_ack(self, data, namespace):
self._warn('[not implemented] binary ack')
def _prepare_to_send_ack(self, path, ack_id):