Merge pull request #403 from meejah/shutdown-with-close-message-squash
Shutdown with Goodbye
This commit is contained in:
@@ -25,6 +25,7 @@
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
import signal
|
||||||
|
|
||||||
from autobahn.wamp import protocol
|
from autobahn.wamp import protocol
|
||||||
from autobahn.wamp.types import ComponentConfig
|
from autobahn.wamp.types import ComponentConfig
|
||||||
@@ -158,8 +159,15 @@ class ApplicationRunner(object):
|
|||||||
txaio.use_asyncio()
|
txaio.use_asyncio()
|
||||||
txaio.config.loop = loop
|
txaio.config.loop = loop
|
||||||
coro = loop.create_connection(transport_factory, host, port, ssl=ssl)
|
coro = loop.create_connection(transport_factory, host, port, ssl=ssl)
|
||||||
loop.run_until_complete(coro)
|
(transport, protocol) = loop.run_until_complete(coro)
|
||||||
|
loop.add_signal_handler(signal.SIGTERM, loop.stop)
|
||||||
|
|
||||||
# 4) now enter the asyncio event loop
|
# 4) now enter the asyncio event loop
|
||||||
loop.run_forever()
|
try:
|
||||||
|
loop.run_forever()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
# wait until we send Goodbye if user hit ctrl-c
|
||||||
|
# (done outside this except so SIGTERM gets the same handling)
|
||||||
|
pass
|
||||||
|
loop.run_until_complete(protocol._session.leave())
|
||||||
loop.close()
|
loop.close()
|
||||||
|
|||||||
@@ -88,9 +88,9 @@ if os.environ.get('USE_TWISTED', False):
|
|||||||
|
|
||||||
# shouldn't have actually connected to anything
|
# shouldn't have actually connected to anything
|
||||||
# successfully, and the run() call shouldn't have inserted
|
# successfully, and the run() call shouldn't have inserted
|
||||||
# any of its own call/errbacks.
|
# any of its own call/errbacks. (except the cleanup handler)
|
||||||
self.assertFalse(d.called)
|
self.assertFalse(d.called)
|
||||||
self.assertEqual(0, len(d.callbacks))
|
self.assertEqual(1, len(d.callbacks))
|
||||||
|
|
||||||
# neither reactor.run() NOR reactor.stop() should have been called
|
# neither reactor.run() NOR reactor.stop() should have been called
|
||||||
# (just connectTCP() will have been called)
|
# (just connectTCP() will have been called)
|
||||||
|
|||||||
@@ -208,6 +208,16 @@ class ApplicationRunner(object):
|
|||||||
|
|
||||||
d = client.connect(transport_factory)
|
d = client.connect(transport_factory)
|
||||||
|
|
||||||
|
# as the reactor shuts down, we wish to wait until we've sent
|
||||||
|
# out our "Goodbye" message; leave() returns a Deferred that
|
||||||
|
# fires when the transport gets to STATE_CLOSED
|
||||||
|
def cleanup(proto):
|
||||||
|
if hasattr(proto, '_session') and proto._session is not None:
|
||||||
|
return proto._session.leave()
|
||||||
|
# if we connect successfully, the arg is a WampWebSocketClientProtocol
|
||||||
|
d.addCallback(lambda proto: reactor.addSystemEventTrigger(
|
||||||
|
'before', 'shutdown', cleanup, proto))
|
||||||
|
|
||||||
# if the user didn't ask us to start the reactor, then they
|
# if the user didn't ask us to start the reactor, then they
|
||||||
# get to deal with any connect errors themselves.
|
# get to deal with any connect errors themselves.
|
||||||
if start_reactor:
|
if start_reactor:
|
||||||
|
|||||||
@@ -303,6 +303,8 @@ class ISession(object):
|
|||||||
:param message: An optional (human readable) closing message, intended for
|
:param message: An optional (human readable) closing message, intended for
|
||||||
logging purposes.
|
logging purposes.
|
||||||
:type message: str
|
:type message: str
|
||||||
|
|
||||||
|
:return: may return a Future/Deferred that fires when we've disconnected
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
|
|||||||
@@ -978,6 +978,8 @@ class ApplicationSession(BaseSession):
|
|||||||
msg = wamp.message.Goodbye(reason=reason, message=log_message)
|
msg = wamp.message.Goodbye(reason=reason, message=log_message)
|
||||||
self._transport.send(msg)
|
self._transport.send(msg)
|
||||||
self._goodbye_sent = True
|
self._goodbye_sent = True
|
||||||
|
# deferred that fires when transport actually hits CLOSED
|
||||||
|
return self._transport.is_closed
|
||||||
else:
|
else:
|
||||||
raise SessionNotReady(u"Already requested to close the session")
|
raise SessionNotReady(u"Already requested to close the session")
|
||||||
|
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ else:
|
|||||||
ApplicationRunner.
|
ApplicationRunner.
|
||||||
'''
|
'''
|
||||||
loop = Mock()
|
loop = Mock()
|
||||||
loop.create_connection = Mock()
|
loop.run_until_complete = Mock(return_value=(Mock(), Mock()))
|
||||||
with patch.object(asyncio, 'get_event_loop', return_value=loop):
|
with patch.object(asyncio, 'get_event_loop', return_value=loop):
|
||||||
ssl = {}
|
ssl = {}
|
||||||
runner = ApplicationRunner('ws://127.0.0.1:8080/ws', 'realm',
|
runner = ApplicationRunner('ws://127.0.0.1:8080/ws', 'realm',
|
||||||
@@ -132,7 +132,7 @@ else:
|
|||||||
ApplicationRunner and the websocket URL starts with "ws:".
|
ApplicationRunner and the websocket URL starts with "ws:".
|
||||||
'''
|
'''
|
||||||
loop = Mock()
|
loop = Mock()
|
||||||
loop.create_connection = Mock()
|
loop.run_until_complete = Mock(return_value=(Mock(), Mock()))
|
||||||
with patch.object(asyncio, 'get_event_loop', return_value=loop):
|
with patch.object(asyncio, 'get_event_loop', return_value=loop):
|
||||||
runner = ApplicationRunner('ws://127.0.0.1:8080/ws', 'realm')
|
runner = ApplicationRunner('ws://127.0.0.1:8080/ws', 'realm')
|
||||||
runner.run('_unused_')
|
runner.run('_unused_')
|
||||||
@@ -145,7 +145,7 @@ else:
|
|||||||
ApplicationRunner and the websocket URL starts with "wss:".
|
ApplicationRunner and the websocket URL starts with "wss:".
|
||||||
'''
|
'''
|
||||||
loop = Mock()
|
loop = Mock()
|
||||||
loop.create_connection = Mock()
|
loop.run_until_complete = Mock(return_value=(Mock(), Mock()))
|
||||||
with patch.object(asyncio, 'get_event_loop', return_value=loop):
|
with patch.object(asyncio, 'get_event_loop', return_value=loop):
|
||||||
runner = ApplicationRunner('wss://127.0.0.1:8080/wss', 'realm')
|
runner = ApplicationRunner('wss://127.0.0.1:8080/wss', 'realm')
|
||||||
runner.run('_unused_')
|
runner.run('_unused_')
|
||||||
@@ -157,7 +157,7 @@ else:
|
|||||||
but only a "ws:" URL.
|
but only a "ws:" URL.
|
||||||
'''
|
'''
|
||||||
loop = Mock()
|
loop = Mock()
|
||||||
loop.create_connection = Mock()
|
loop.run_until_complete = Mock(return_value=(Mock(), Mock()))
|
||||||
with patch.object(asyncio, 'get_event_loop', return_value=loop):
|
with patch.object(asyncio, 'get_event_loop', return_value=loop):
|
||||||
runner = ApplicationRunner('ws://127.0.0.1:8080/wss', 'realm',
|
runner = ApplicationRunner('ws://127.0.0.1:8080/wss', 'realm',
|
||||||
ssl=True)
|
ssl=True)
|
||||||
@@ -190,7 +190,7 @@ else:
|
|||||||
context = ssl.create_default_context()
|
context = ssl.create_default_context()
|
||||||
|
|
||||||
loop = Mock()
|
loop = Mock()
|
||||||
loop.create_connection = Mock()
|
loop.run_until_complete = Mock(return_value=(Mock(), Mock()))
|
||||||
with patch.object(asyncio, 'get_event_loop', return_value=loop):
|
with patch.object(asyncio, 'get_event_loop', return_value=loop):
|
||||||
runner = ApplicationRunner('ws://127.0.0.1:8080/wss', 'realm',
|
runner = ApplicationRunner('ws://127.0.0.1:8080/wss', 'realm',
|
||||||
ssl=context)
|
ssl=context)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
#
|
#
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import, print_function
|
||||||
|
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
@@ -79,9 +79,8 @@ class WampWebSocketProtocol(object):
|
|||||||
print("WAMP-over-WebSocket transport lost: wasClean = {0}, code = {1}, reason = '{2}'".format(wasClean, code, reason))
|
print("WAMP-over-WebSocket transport lost: wasClean = {0}, code = {1}, reason = '{2}'".format(wasClean, code, reason))
|
||||||
self._session.onClose(wasClean)
|
self._session.onClose(wasClean)
|
||||||
except Exception:
|
except Exception:
|
||||||
# silently ignore exceptions raised here ..
|
print("Error invoking onClose():")
|
||||||
if self.factory.debug_wamp:
|
traceback.print_exc()
|
||||||
traceback.print_exc()
|
|
||||||
self._session = None
|
self._session = None
|
||||||
|
|
||||||
def onMessage(self, payload, isBinary):
|
def onMessage(self, payload, isBinary):
|
||||||
|
|||||||
@@ -660,6 +660,10 @@ class WebSocketProtocol(object):
|
|||||||
Configuration attributes specific to clients.
|
Configuration attributes specific to clients.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
#: a Future/Deferred that fires when we hit STATE_CLOSED
|
||||||
|
self.is_closed = txaio.create_future()
|
||||||
|
|
||||||
def onOpen(self):
|
def onOpen(self):
|
||||||
"""
|
"""
|
||||||
Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.onOpen`
|
Implements :func:`autobahn.websocket.interfaces.IWebSocketChannel.onOpen`
|
||||||
@@ -967,7 +971,12 @@ class WebSocketProtocol(object):
|
|||||||
if self.debugCodePaths:
|
if self.debugCodePaths:
|
||||||
self.factory._log("dropping connection")
|
self.factory._log("dropping connection")
|
||||||
self.droppedByMe = True
|
self.droppedByMe = True
|
||||||
|
|
||||||
|
# this code-path will be hit (*without* hitting
|
||||||
|
# _connectionLost) in some timeout scenarios (unit-tests
|
||||||
|
# cover these). However, sometimes we hit both.
|
||||||
self.state = WebSocketProtocol.STATE_CLOSED
|
self.state = WebSocketProtocol.STATE_CLOSED
|
||||||
|
txaio.resolve(self.is_closed, self)
|
||||||
|
|
||||||
self._closeConnection(abort)
|
self._closeConnection(abort)
|
||||||
else:
|
else:
|
||||||
@@ -1218,7 +1227,12 @@ class WebSocketProtocol(object):
|
|||||||
self.autoPingTimeoutCall.cancel()
|
self.autoPingTimeoutCall.cancel()
|
||||||
self.autoPingTimeoutCall = None
|
self.autoPingTimeoutCall = None
|
||||||
|
|
||||||
self.state = WebSocketProtocol.STATE_CLOSED
|
# check required here because in some scenarios dropConnection
|
||||||
|
# will already have resolved the Future/Deferred.
|
||||||
|
if self.state != WebSocketProtocol.STATE_CLOSED:
|
||||||
|
self.state = WebSocketProtocol.STATE_CLOSED
|
||||||
|
txaio.resolve(self.is_closed, self)
|
||||||
|
|
||||||
if self.wasServingFlashSocketPolicyFile:
|
if self.wasServingFlashSocketPolicyFile:
|
||||||
if self.debug:
|
if self.debug:
|
||||||
self.factory._log("connection dropped after serving Flash Socket Policy File")
|
self.factory._log("connection dropped after serving Flash Socket Policy File")
|
||||||
|
|||||||
Reference in New Issue
Block a user