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()