-
Notifications
You must be signed in to change notification settings - Fork 5.6k
[3006.x] Do not allow duplicate message ids #65247
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
4ca85b7
Do not allow duplicate message ids
dwoz f56094f
Use closure to preserve message future
dwoz 02b592c
Fix timeout test
dwoz ca83e01
Preserve futures with messages
dwoz ccfeed6
Add test coverage
dwoz 9dbe105
Add changelog
dwoz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Fix nonce verification, request server replies do not stomp on eachother. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -15,6 +15,7 @@ | |||||||
| import socket | ||||||||
| import threading | ||||||||
| import urllib | ||||||||
| import uuid | ||||||||
|
|
||||||||
| import salt.ext.tornado | ||||||||
| import salt.ext.tornado.concurrent | ||||||||
|
|
@@ -34,6 +35,7 @@ | |||||||
| import salt.utils.versions | ||||||||
| from salt.exceptions import SaltClientError, SaltReqTimeoutError | ||||||||
| from salt.utils.network import ip_bracket | ||||||||
| from salt.utils.process import SignalHandlingProcess | ||||||||
|
|
||||||||
| if salt.utils.platform.is_windows(): | ||||||||
| USE_LOAD_BALANCER = True | ||||||||
|
|
@@ -42,7 +44,6 @@ | |||||||
|
|
||||||||
| if USE_LOAD_BALANCER: | ||||||||
| import salt.ext.tornado.util | ||||||||
| from salt.utils.process import SignalHandlingProcess | ||||||||
|
|
||||||||
| log = logging.getLogger(__name__) | ||||||||
|
|
||||||||
|
|
@@ -127,69 +128,64 @@ def _set_tcp_keepalive(sock, opts): | |||||||
| sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 0) | ||||||||
|
|
||||||||
|
|
||||||||
| if USE_LOAD_BALANCER: | ||||||||
| class LoadBalancerServer(SignalHandlingProcess): | ||||||||
| """ | ||||||||
| Raw TCP server which runs in its own process and will listen | ||||||||
| for incoming connections. Each incoming connection will be | ||||||||
| sent via multiprocessing queue to the workers. | ||||||||
| Since the queue is shared amongst workers, only one worker will | ||||||||
| handle a given connection. | ||||||||
| """ | ||||||||
|
|
||||||||
| class LoadBalancerServer(SignalHandlingProcess): | ||||||||
| """ | ||||||||
| Raw TCP server which runs in its own process and will listen | ||||||||
| for incoming connections. Each incoming connection will be | ||||||||
| sent via multiprocessing queue to the workers. | ||||||||
| Since the queue is shared amongst workers, only one worker will | ||||||||
| handle a given connection. | ||||||||
| """ | ||||||||
| # TODO: opts! | ||||||||
| # Based on default used in salt.ext.tornado.netutil.bind_sockets() | ||||||||
| backlog = 128 | ||||||||
|
|
||||||||
| # TODO: opts! | ||||||||
| # Based on default used in salt.ext.tornado.netutil.bind_sockets() | ||||||||
| backlog = 128 | ||||||||
| def __init__(self, opts, socket_queue, **kwargs): | ||||||||
| super().__init__(**kwargs) | ||||||||
| self.opts = opts | ||||||||
| self.socket_queue = socket_queue | ||||||||
| self._socket = None | ||||||||
|
|
||||||||
| def __init__(self, opts, socket_queue, **kwargs): | ||||||||
| super().__init__(**kwargs) | ||||||||
| self.opts = opts | ||||||||
| self.socket_queue = socket_queue | ||||||||
| def close(self): | ||||||||
| if self._socket is not None: | ||||||||
| self._socket.shutdown(socket.SHUT_RDWR) | ||||||||
| self._socket.close() | ||||||||
| self._socket = None | ||||||||
|
|
||||||||
| def close(self): | ||||||||
| if self._socket is not None: | ||||||||
| self._socket.shutdown(socket.SHUT_RDWR) | ||||||||
| self._socket.close() | ||||||||
| self._socket = None | ||||||||
|
|
||||||||
| # pylint: disable=W1701 | ||||||||
| def __del__(self): | ||||||||
| self.close() | ||||||||
|
|
||||||||
| # pylint: enable=W1701 | ||||||||
| # pylint: disable=W1701 | ||||||||
| def __del__(self): | ||||||||
| self.close() | ||||||||
|
Comment on lines
+156
to
+158
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
With the |
||||||||
|
|
||||||||
| def run(self): | ||||||||
| """ | ||||||||
| Start the load balancer | ||||||||
| """ | ||||||||
| self._socket = _get_socket(self.opts) | ||||||||
| self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | ||||||||
| _set_tcp_keepalive(self._socket, self.opts) | ||||||||
| self._socket.setblocking(1) | ||||||||
| self._socket.bind(_get_bind_addr(self.opts, "ret_port")) | ||||||||
| self._socket.listen(self.backlog) | ||||||||
| # pylint: enable=W1701 | ||||||||
|
|
||||||||
| while True: | ||||||||
| try: | ||||||||
| # Wait for a connection to occur since the socket is | ||||||||
| # blocking. | ||||||||
| connection, address = self._socket.accept() | ||||||||
| # Wait for a free slot to be available to put | ||||||||
| # the connection into. | ||||||||
| # Sockets are picklable on Windows in Python 3. | ||||||||
| self.socket_queue.put((connection, address), True, None) | ||||||||
| except OSError as e: | ||||||||
| # ECONNABORTED indicates that there was a connection | ||||||||
| # but it was closed while still in the accept queue. | ||||||||
| # (observed on FreeBSD). | ||||||||
| if ( | ||||||||
| salt.ext.tornado.util.errno_from_exception(e) | ||||||||
| == errno.ECONNABORTED | ||||||||
| ): | ||||||||
| continue | ||||||||
| raise | ||||||||
| def run(self): | ||||||||
| """ | ||||||||
| Start the load balancer | ||||||||
| """ | ||||||||
| self._socket = _get_socket(self.opts) | ||||||||
| self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | ||||||||
| _set_tcp_keepalive(self._socket, self.opts) | ||||||||
| self._socket.setblocking(1) | ||||||||
| self._socket.bind(_get_bind_addr(self.opts, "ret_port")) | ||||||||
| self._socket.listen(self.backlog) | ||||||||
|
|
||||||||
| while True: | ||||||||
| try: | ||||||||
| # Wait for a connection to occur since the socket is | ||||||||
| # blocking. | ||||||||
| connection, address = self._socket.accept() | ||||||||
| # Wait for a free slot to be available to put | ||||||||
| # the connection into. | ||||||||
| # Sockets are picklable on Windows in Python 3. | ||||||||
| self.socket_queue.put((connection, address), True, None) | ||||||||
| except OSError as e: | ||||||||
| # ECONNABORTED indicates that there was a connection | ||||||||
| # but it was closed while still in the accept queue. | ||||||||
| # (observed on FreeBSD). | ||||||||
| if salt.ext.tornado.util.errno_from_exception(e) == errno.ECONNABORTED: | ||||||||
| continue | ||||||||
| raise | ||||||||
|
|
||||||||
|
|
||||||||
| class Resolver: | ||||||||
|
|
@@ -467,45 +463,43 @@ def close(self): | |||||||
| raise | ||||||||
|
|
||||||||
|
|
||||||||
| if USE_LOAD_BALANCER: | ||||||||
|
|
||||||||
| class LoadBalancerWorker(SaltMessageServer): | ||||||||
| """ | ||||||||
| This will receive TCP connections from 'LoadBalancerServer' via | ||||||||
| a multiprocessing queue. | ||||||||
| Since the queue is shared amongst workers, only one worker will handle | ||||||||
| a given connection. | ||||||||
| """ | ||||||||
| class LoadBalancerWorker(SaltMessageServer): | ||||||||
| """ | ||||||||
| This will receive TCP connections from 'LoadBalancerServer' via | ||||||||
| a multiprocessing queue. | ||||||||
| Since the queue is shared amongst workers, only one worker will handle | ||||||||
| a given connection. | ||||||||
| """ | ||||||||
|
|
||||||||
| def __init__(self, socket_queue, message_handler, *args, **kwargs): | ||||||||
| super().__init__(message_handler, *args, **kwargs) | ||||||||
| self.socket_queue = socket_queue | ||||||||
| self._stop = threading.Event() | ||||||||
| self.thread = threading.Thread(target=self.socket_queue_thread) | ||||||||
| self.thread.start() | ||||||||
| def __init__(self, socket_queue, message_handler, *args, **kwargs): | ||||||||
| super().__init__(message_handler, *args, **kwargs) | ||||||||
| self.socket_queue = socket_queue | ||||||||
| self._stop = threading.Event() | ||||||||
| self.thread = threading.Thread(target=self.socket_queue_thread) | ||||||||
| self.thread.start() | ||||||||
|
|
||||||||
| def close(self): | ||||||||
| self._stop.set() | ||||||||
| self.thread.join() | ||||||||
| super().close() | ||||||||
| def close(self): | ||||||||
| self._stop.set() | ||||||||
| self.thread.join() | ||||||||
| super().close() | ||||||||
|
|
||||||||
| def socket_queue_thread(self): | ||||||||
| try: | ||||||||
| while True: | ||||||||
| try: | ||||||||
| client_socket, address = self.socket_queue.get(True, 1) | ||||||||
| except queue.Empty: | ||||||||
| if self._stop.is_set(): | ||||||||
| break | ||||||||
| continue | ||||||||
| # 'self.io_loop' initialized in super class | ||||||||
| # 'salt.ext.tornado.tcpserver.TCPServer'. | ||||||||
| # 'self._handle_connection' defined in same super class. | ||||||||
| self.io_loop.spawn_callback( | ||||||||
| self._handle_connection, client_socket, address | ||||||||
| ) | ||||||||
| except (KeyboardInterrupt, SystemExit): | ||||||||
| pass | ||||||||
| def socket_queue_thread(self): | ||||||||
| try: | ||||||||
| while True: | ||||||||
| try: | ||||||||
| client_socket, address = self.socket_queue.get(True, 1) | ||||||||
| except queue.Empty: | ||||||||
| if self._stop.is_set(): | ||||||||
| break | ||||||||
| continue | ||||||||
| # 'self.io_loop' initialized in super class | ||||||||
| # 'salt.ext.tornado.tcpserver.TCPServer'. | ||||||||
| # 'self._handle_connection' defined in same super class. | ||||||||
| self.io_loop.spawn_callback( | ||||||||
| self._handle_connection, client_socket, address | ||||||||
| ) | ||||||||
| except (KeyboardInterrupt, SystemExit): | ||||||||
| pass | ||||||||
|
|
||||||||
|
|
||||||||
| class TCPClientKeepAlive(salt.ext.tornado.tcpclient.TCPClient): | ||||||||
|
|
@@ -569,10 +563,7 @@ def __init__( | |||||||
| self.io_loop = io_loop or salt.ext.tornado.ioloop.IOLoop.current() | ||||||||
| with salt.utils.asynchronous.current_ioloop(self.io_loop): | ||||||||
| self._tcp_client = TCPClientKeepAlive(opts, resolver=resolver) | ||||||||
| self._mid = 1 | ||||||||
| self._max_messages = int((1 << 31) - 2) # number of IDs before we wrap | ||||||||
| # TODO: max queue size | ||||||||
| self.send_queue = [] # queue of messages to be sent | ||||||||
| self.send_future_map = {} # mapping of request_id -> Future | ||||||||
|
|
||||||||
| self._read_until_future = None | ||||||||
|
|
@@ -585,10 +576,6 @@ def __init__( | |||||||
|
|
||||||||
| self.backoff = opts.get("tcp_reconnect_backoff", 1) | ||||||||
|
|
||||||||
| def _stop_io_loop(self): | ||||||||
| if self.io_loop is not None: | ||||||||
| self.io_loop.stop() | ||||||||
|
|
||||||||
| # TODO: timeout inflight sessions | ||||||||
| def close(self): | ||||||||
| if self._closing: | ||||||||
|
|
@@ -722,18 +709,7 @@ def _stream_return(self): | |||||||
| self._stream_return_running = False | ||||||||
|
|
||||||||
| def _message_id(self): | ||||||||
| wrap = False | ||||||||
| while self._mid in self.send_future_map: | ||||||||
| if self._mid >= self._max_messages: | ||||||||
| if wrap: | ||||||||
| # this shouldn't ever happen, but just in case | ||||||||
| raise Exception("Unable to find available messageid") | ||||||||
| self._mid = 1 | ||||||||
| wrap = True | ||||||||
| else: | ||||||||
| self._mid += 1 | ||||||||
|
|
||||||||
| return self._mid | ||||||||
| return str(uuid.uuid4()) | ||||||||
|
|
||||||||
| # TODO: return a message object which takes care of multiplexing? | ||||||||
| def on_recv(self, callback): | ||||||||
|
|
@@ -975,6 +951,7 @@ def publish_daemon( | |||||||
| """ | ||||||||
| io_loop = salt.ext.tornado.ioloop.IOLoop() | ||||||||
| io_loop.make_current() | ||||||||
| self.io_loop = io_loop | ||||||||
|
|
||||||||
| # Spin up the publisher | ||||||||
| self.pub_server = pub_server = PubServer( | ||||||||
|
|
||||||||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.