github.com/muhammedhassanm/blockchain@v0.0.0-20200120143007-697261defd4d/sawtooth-core-master/sdk/python/sawtooth_sdk/messaging/stream.py (about) 1 # Copyright 2016 Intel Corporation 2 # 3 # Licensed under the Apache License, Version 2.0 (the "License"); 4 # you may not use this file except in compliance with the License. 5 # You may obtain a copy of the License at 6 # 7 # http://www.apache.org/licenses/LICENSE-2.0 8 # 9 # Unless required by applicable law or agreed to in writing, software 10 # distributed under the License is distributed on an "AS IS" BASIS, 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 # See the License for the specific language governing permissions and 13 # limitations under the License. 14 # ------------------------------------------------------------------------------ 15 16 import asyncio 17 import uuid 18 import logging 19 from queue import Queue 20 from threading import Event 21 from threading import Thread 22 from threading import Condition 23 24 import zmq 25 import zmq.asyncio 26 27 import sawtooth_sdk.protobuf.validator_pb2 as validator_pb2 28 29 from sawtooth_sdk.messaging.exceptions import ValidatorConnectionError 30 from sawtooth_sdk.messaging.future import Future 31 from sawtooth_sdk.messaging.future import FutureCollection 32 from sawtooth_sdk.messaging.future import FutureCollectionKeyError 33 from sawtooth_sdk.messaging.future import FutureResult 34 from sawtooth_sdk.messaging.future import FutureError 35 36 LOGGER = logging.getLogger(__file__) 37 38 # Used to send a message to core.TransactionProcessor to reregister with 39 # the validator and wait for TP_PROCESS_REQUEST. All other items on the 40 # queue will be validator_pb2.Message objects, so -1 will be an exceptional 41 # event. 42 RECONNECT_EVENT = -1 43 _NO_ERROR = -1 44 45 46 def _generate_id(): 47 return uuid.uuid4().hex.encode() 48 49 50 class _SendReceiveThread(Thread): 51 """ 52 Internal thread to Stream class that runs the asyncio event loop. 53 """ 54 55 def __init__(self, url, futures, ready_event, error_queue): 56 """constructor for background thread 57 58 :param url (str): the address to connect to the validator on 59 :param futures (FutureCollection): The Futures associated with 60 messages sent through Stream.send 61 :param ready_event (threading.Event): used to notify waiting/asking 62 classes that the background thread of Stream is ready after 63 a disconnect event. 64 """ 65 super(_SendReceiveThread, self).__init__() 66 self._futures = futures 67 self._url = url 68 self._shutdown = False 69 self._event_loop = None 70 self._sock = None 71 self._monitor_sock = None 72 self._monitor_fd = None 73 self._recv_queue = None 74 self._send_queue = None 75 self._context = None 76 self._ready_event = ready_event 77 self._error_queue = error_queue 78 self._condition = Condition() 79 self.identity = _generate_id()[0:16] 80 81 @asyncio.coroutine 82 def _receive_message(self): 83 """ 84 internal coroutine that receives messages and puts 85 them on the recv_queue 86 """ 87 while True: 88 if not self._ready_event.is_set(): 89 break 90 msg_bytes = yield from self._sock.recv() 91 message = validator_pb2.Message() 92 message.ParseFromString(msg_bytes) 93 try: 94 self._futures.set_result( 95 message.correlation_id, 96 FutureResult(message_type=message.message_type, 97 content=message.content)) 98 self._futures.remove(message.correlation_id) 99 except FutureCollectionKeyError: 100 # if we are getting an initial message, not a response 101 if not self._ready_event.is_set(): 102 break 103 self._recv_queue.put_nowait(message) 104 105 @asyncio.coroutine 106 def _send_message(self): 107 """ 108 internal coroutine that sends messages from the send_queue 109 """ 110 while True: 111 if not self._ready_event.is_set(): 112 break 113 msg = yield from self._send_queue.get() 114 yield from self._sock.send_multipart([msg.SerializeToString()]) 115 116 @asyncio.coroutine 117 def _put_message(self, message): 118 """ 119 Puts a message on the send_queue. Not to be accessed directly. 120 :param message: protobuf generated validator_pb2.Message 121 """ 122 self._send_queue.put_nowait(message) 123 124 @asyncio.coroutine 125 def _get_message(self): 126 """ 127 Gets a message from the recv_queue. Not to be accessed directly. 128 """ 129 with self._condition: 130 self._condition.wait_for(lambda: self._recv_queue is not None) 131 msg = yield from self._recv_queue.get() 132 133 return msg 134 135 @asyncio.coroutine 136 def _monitor_disconnects(self): 137 """Monitors the client socket for disconnects 138 """ 139 yield from self._monitor_sock.recv_multipart() 140 self._sock.disable_monitor() 141 self._monitor_sock.disconnect(self._monitor_fd) 142 self._monitor_sock.close(linger=0) 143 self._monitor_sock = None 144 self._sock.disconnect(self._url) 145 self._ready_event.clear() 146 LOGGER.debug("monitor socket received disconnect event") 147 for future in self._futures.future_values(): 148 future.set_result(FutureError()) 149 tasks = list(asyncio.Task.all_tasks(self._event_loop)) 150 for task in tasks: 151 task.cancel() 152 self._event_loop.stop() 153 self._send_queue = None 154 self._recv_queue = None 155 156 def put_message(self, message): 157 """ 158 :param message: protobuf generated validator_pb2.Message 159 """ 160 if not self._ready_event.is_set(): 161 return 162 163 with self._condition: 164 self._condition.wait_for( 165 lambda: self._event_loop is not None 166 and self._send_queue is not None 167 ) 168 169 asyncio.run_coroutine_threadsafe( 170 self._put_message(message), 171 self._event_loop) 172 173 def get_message(self): 174 """ 175 :return message: concurrent.futures.Future 176 """ 177 with self._condition: 178 self._condition.wait_for(lambda: self._event_loop is not None) 179 return asyncio.run_coroutine_threadsafe(self._get_message(), 180 self._event_loop) 181 182 def _cancel_tasks_yet_to_be_done(self): 183 """Cancels all the tasks (pending coroutines and futures) 184 """ 185 tasks = list(asyncio.Task.all_tasks(self._event_loop)) 186 for task in tasks: 187 self._event_loop.call_soon_threadsafe(task.cancel) 188 self._event_loop.call_soon_threadsafe(self._done_callback) 189 190 def shutdown(self): 191 """Shutdown the _SendReceiveThread. Is an irreversible operation. 192 """ 193 194 self._shutdown = True 195 self._cancel_tasks_yet_to_be_done() 196 197 def _done_callback(self): 198 """Stops the event loop, closes the socket, and destroys the context 199 200 :param future: concurrent.futures.Future not used 201 """ 202 self._event_loop.call_soon_threadsafe(self._event_loop.stop) 203 self._sock.close(linger=0) 204 self._monitor_sock.close(linger=0) 205 self._context.destroy(linger=0) 206 207 def run(self): 208 first_time = True 209 while True: 210 try: 211 if self._event_loop is None: 212 self._event_loop = zmq.asyncio.ZMQEventLoop() 213 asyncio.set_event_loop(self._event_loop) 214 if self._context is None: 215 self._context = zmq.asyncio.Context() 216 if self._sock is None: 217 self._sock = self._context.socket(zmq.DEALER) 218 self._sock.identity = self.identity 219 220 self._sock.connect(self._url) 221 222 self._monitor_fd = "inproc://monitor.s-{}".format( 223 _generate_id()[0:5]) 224 self._monitor_sock = self._sock.get_monitor_socket( 225 zmq.EVENT_DISCONNECTED, 226 addr=self._monitor_fd) 227 self._send_queue = asyncio.Queue(loop=self._event_loop) 228 self._recv_queue = asyncio.Queue(loop=self._event_loop) 229 if first_time is False: 230 self._recv_queue.put_nowait(RECONNECT_EVENT) 231 with self._condition: 232 self._condition.notify_all() 233 asyncio.ensure_future(self._send_message(), 234 loop=self._event_loop) 235 asyncio.ensure_future(self._receive_message(), 236 loop=self._event_loop) 237 asyncio.ensure_future(self._monitor_disconnects(), 238 loop=self._event_loop) 239 # pylint: disable=broad-except 240 except Exception as e: 241 LOGGER.error("Exception connecting to validator " 242 "address %s, so shutting down", self._url) 243 self._error_queue.put_nowait(e) 244 break 245 246 self._error_queue.put_nowait(_NO_ERROR) 247 self._ready_event.set() 248 self._event_loop.run_forever() 249 if self._shutdown: 250 self._sock.close(linger=0) 251 self._monitor_sock.close(linger=0) 252 self._context.destroy(linger=0) 253 break 254 if first_time is True: 255 first_time = False 256 257 258 class Stream(object): 259 def __init__(self, url): 260 self._url = url 261 self._futures = FutureCollection() 262 self._event = Event() 263 self._event.set() 264 error_queue = Queue() 265 self._send_recieve_thread = _SendReceiveThread( 266 url, 267 futures=self._futures, 268 ready_event=self._event, 269 error_queue=error_queue) 270 self._send_recieve_thread.start() 271 err = error_queue.get() 272 if err is not _NO_ERROR: 273 raise err 274 275 @property 276 def url(self): 277 """ Get the url of the Stream object. 278 """ 279 return self._url 280 281 @property 282 def zmq_id(self): 283 return self._send_recieve_thread.identity 284 285 def send(self, message_type, content): 286 """Send a message to the validator 287 288 :param: message_type(validator_pb2.Message.MessageType) 289 :param: content(bytes) 290 :return: (future.Future) 291 :raises: (ValidatorConnectionError) 292 """ 293 294 if not self._event.is_set(): 295 raise ValidatorConnectionError() 296 message = validator_pb2.Message( 297 message_type=message_type, 298 correlation_id=_generate_id(), 299 content=content) 300 future = Future(message.correlation_id, request_type=message_type) 301 self._futures.put(future) 302 303 self._send_recieve_thread.put_message(message) 304 return future 305 306 def send_back(self, message_type, correlation_id, content): 307 """ 308 Return a response to a message. 309 :param message_type: validator_pb2.Message.MessageType enum value 310 :param correlation_id: a random str internal to the validator 311 :param content: protobuf bytes 312 :raises (ValidatorConnectionError): 313 """ 314 if not self._event.is_set(): 315 raise ValidatorConnectionError() 316 message = validator_pb2.Message( 317 message_type=message_type, 318 correlation_id=correlation_id, 319 content=content) 320 self._send_recieve_thread.put_message(message) 321 322 def receive(self): 323 """ 324 Receive messages that are not responses 325 :return: concurrent.futures.Future 326 """ 327 return self._send_recieve_thread.get_message() 328 329 def wait_for_ready(self): 330 """Blocks until the background thread has recovered 331 from a disconnect with the validator. 332 """ 333 self._event.wait() 334 335 def is_ready(self): 336 """Whether the background thread has recovered from 337 a disconnect with the validator 338 339 :return: (bool) whether the background thread is ready 340 """ 341 return self._event.is_set() 342 343 def close(self): 344 self._send_recieve_thread.shutdown()