Exemplo n.º 1
0
class Invocation(object):
    sent_connection = None
    timer = None

    def __init__(self, request, partition_id=-1, address=None, connection=None, timeout=INVOCATION_TIMEOUT):
        self._event = threading.Event()
        self.timeout = timeout + time.time()
        self.address = address
        self.connection = connection
        self.partition_id = partition_id
        self.request = request
        self.future = Future()

    def has_connection(self):
        return self.connection is not None

    def has_partition_id(self):
        return self.partition_id >= 0

    def has_address(self):
        return self.address is not None

    def set_response(self, response):
        if self.timer:
            self.timer.cancel()
        self.future.set_result(response)

    def set_exception(self, exception, traceback=None):
        if self.timer:
            self.timer.cancel()
        self.future.set_exception(exception, traceback)

    def on_timeout(self):
        self.set_exception(TimeoutError("Request timed out after %d seconds." % INVOCATION_TIMEOUT))
class Invocation(object):
    sent_connection = None
    timer = None

    def __init__(self, request, partition_id=-1, address=None, connection=None, timeout=INVOCATION_TIMEOUT):
        self._event = threading.Event()
        self.timeout = timeout + time.time()
        self.address = address
        self.connection = connection
        self.partition_id = partition_id
        self.request = request
        self.future = Future()

    def has_connection(self):
        return self.connection is not None

    def has_partition_id(self):
        return self.partition_id >= 0

    def has_address(self):
        return self.address is not None

    def set_response(self, response):
        if self.timer:
            self.timer.cancel()
        self.future.set_result(response)

    def set_exception(self, exception, traceback=None):
        if self.timer:
            self.timer.cancel()
        self.future.set_exception(exception, traceback)

    def on_timeout(self):
        self.set_exception(TimeoutError("Request timed out after %d seconds." % INVOCATION_TIMEOUT))
Exemplo n.º 3
0
class Invocation(object):
    __slots__ = ("request", "timeout", "partition_id", "uuid", "connection",
                 "event_handler", "future", "sent_connection", "urgent",
                 "response_handler")

    def __init__(self,
                 request,
                 partition_id=-1,
                 uuid=None,
                 connection=None,
                 event_handler=None,
                 urgent=False,
                 timeout=None,
                 response_handler=_no_op_response_handler):
        self.request = request
        self.partition_id = partition_id
        self.uuid = uuid
        self.connection = connection
        self.event_handler = event_handler
        self.urgent = urgent
        self.timeout = timeout
        self.future = Future()
        self.timeout = None
        self.sent_connection = None
        self.response_handler = response_handler

    def set_response(self, response):
        try:
            result = self.response_handler(response)
            self.future.set_result(result)
        except Exception as e:
            self.future.set_exception(e)

    def set_exception(self, exception, traceback=None):
        self.future.set_exception(exception, traceback)
Exemplo n.º 4
0
    def test_continue_with_with_rejected_future(self):
        f1 = Future()
        f2 = Future()
        f3 = f1.continue_with(lambda _: f2)
        f1.set_result(0)
        f2.set_exception(RuntimeError("error"))

        with self.assertRaises(RuntimeError):
            f3.result()
Exemplo n.º 5
0
    def test_continue_with_on_failure(self):
        f = Future()
        f.set_exception(RuntimeError("error"))

        def continuation(future):
            if future.is_success():
                return 0
            else:
                return 1

        result = f.continue_with(continuation).result()
        self.assertEqual(1, result)
    def test_continue_with_on_failure(self):
        f = Future()
        f.set_exception(RuntimeError("error"))

        def continuation(future):
            if future.is_success():
                return 0
            else:
                return 1

        result = f.continue_with(continuation).result()
        self.assertEqual(1, result)
Exemplo n.º 7
0
    def test_add_callback_after_completion_with_error(self):
        f = Future()
        error = RuntimeError("error")
        f.set_exception(error, None)

        counter = [0]

        def callback(future):
            counter[0] += 1
            self.assertEqual(future.exception(), error)

        f.add_done_callback(callback)
        self.assertEqual(counter[0], 1)
    def test_add_callback_after_completion_with_error(self):
        f = Future()
        error = RuntimeError("error")
        f.set_exception(error, None)

        counter = [0]

        def callback(future):
            counter[0] += 1
            self.assertEqual(future.exception(), error)

        f.add_done_callback(callback)
        self.assertEqual(counter[0], 1)
Exemplo n.º 9
0
class Invocation(object):
    sent_connection = None
    timer = None

    def __init__(self,
                 invocation_service,
                 request,
                 partition_id=-1,
                 address=None,
                 connection=None,
                 event_handler=None):
        self._event = threading.Event()
        self._invocation_timeout = invocation_service.invocation_timeout
        self._client = invocation_service._client
        self.timeout = self._invocation_timeout + time.time()
        self.address = address
        self.connection = connection
        self.partition_id = partition_id
        self.request = request
        self.future = Future()
        self.event_handler = event_handler

    def has_connection(self):
        return self.connection is not None

    def has_partition_id(self):
        return self.partition_id >= 0

    def has_address(self):
        return self.address is not None

    def set_response(self, response):
        if self.timer:
            self._client.reactor.stop_timer(self.timer)
        self.future.set_result(response)

    def set_exception(self, exception, traceback=None):
        if self.timer:
            self._client.reactor.stop_timer(self.timer)
        self.future.set_exception(exception, traceback)

    def set_timeout(self, timeout):
        self._invocation_timeout = timeout
        self.timeout = self._invocation_timeout + time.time()

    def on_timeout(self):
        self.set_exception(
            TimeoutError("Request timed out after %d seconds." %
                         self._invocation_timeout))
Exemplo n.º 10
0
    def test_waiting_for_all_inputs(self):
        # Verifies that we wait for all futures, even if some
        # of them fails.
        f1, f2, f3 = Future(), Future(), Future()
        combined = combine_futures([f1, f2, f3])

        e = RuntimeError("expected")
        f1.set_exception(e)
        self.assertFalse(combined.done())
        f2.set_result(1)
        self.assertFalse(combined.done())
        f3.set_result(2)
        self.assertTrue(combined.done())
        self.assertFalse(combined.is_success())
        self.assertEqual(e, combined.exception())
Exemplo n.º 11
0
    def test_returning_first_error(self):
        # Verifies that we return the first error occurred, even
        # if more than one of the input futures completed
        # exceptionally.
        f1, f2, f3 = Future(), Future(), Future()
        combined = combine_futures([f1, f2, f3])

        e1 = RuntimeError("expected")
        f1.set_exception(e1)
        self.assertFalse(combined.done())
        e2 = RuntimeError("expected2")
        f2.set_exception(e2)
        self.assertFalse(combined.done())
        e3 = RuntimeError("expected3")
        f3.set_exception(e3)
        self.assertTrue(combined.done())
        self.assertFalse(combined.is_success())
        self.assertEqual(e1, combined.exception())
Exemplo n.º 12
0
    def test_result_raises_exception_with_traceback(self):
        f = Future()
        exc_info = None
        try:
            {}["invalid_key"]
        except KeyError as e:
            exc_info = sys.exc_info()
            f.set_exception(e, sys.exc_info()[2])

        try:
            f.result()
            self.fail("Future.result() should raise exception")
        except:
            info = sys.exc_info()
            self.assertEqual(info[1], exc_info[1])

            original_tb = traceback.extract_tb(exc_info[2])
            # shift traceback by one to discard the last frame
            actual_tb = traceback.extract_tb(info[2])[1:]

            self.assertEqual(original_tb, actual_tb)
Exemplo n.º 13
0
 def test_set_exception_with_non_exception(self):
     f = Future()
     with self.assertRaises(RuntimeError):
         f.set_exception("non-exception")
 def test_set_exception_with_non_exception(self):
     f = Future()
     with self.assertRaises(RuntimeError):
         f.set_exception("non-exception")
Exemplo n.º 15
0
class SqlResult(typing.Iterable[SqlRow]):
    """SQL query result.

    Depending on the statement type it represents a stream of
    rows or an update count.

    To iterate over the stream of rows, there are two possible options.

    The first, and the easiest one is to iterate over the rows
    in a blocking fashion. ::

        result = client.sql.execute("SELECT ...").result()
        for row in result:
            # Process the row.
            print(row)

    The second option is to use the non-blocking API with callbacks. ::

        future = client.sql.execute("SELECT ...")  # Future of SqlResult

        def on_response(sql_result_future):
            iterator = sql_result_future.result().iterator()

            def on_next_row(row_future):
                try:
                    row = row_future.result()
                    # Process the row.
                    print(row)

                    # Iterate over the next row.
                    next(iterator).add_done_callback(on_next_row)
                except StopIteration:
                    # Exhausted the iterator. No more rows are left.
                    pass

            next(iterator).add_done_callback(on_next_row)

        future.add_done_callback(on_response)

    When in doubt, use the blocking API shown in the first code sample.

    Note that, iterators can be requested at most once per SqlResult.

    One can call :func:`close` method of a result object to
    release the resources associated with the result on the server side.
    It might also be used to cancel query execution on the server side
    if it is still active.

    When the blocking API is used, one might also use ``with``
    statement to automatically close the query even if an exception
    is thrown in the iteration. ::

        with client.sql.execute("SELECT ...").result() as result:
            for row in result:
                # Process the row.
                print(row)


    To get the number of rows updated by the query, use the
    :func:`update_count`. ::

        update_count = client.sql.execute("UPDATE ...").result().update_count()

    One does not have to call :func:`close` in this case, because the result
    will already be closed in the server-side.
    """
    def __init__(self, sql_service, connection, query_id, cursor_buffer_size,
                 execute_response):
        self._sql_service = sql_service
        """_InternalSqlService: Reference to the SQL service."""

        self._connection = connection
        """hazelcast.connection.Connection: Reference to the connection
        that the execute request is made to."""

        self._query_id = query_id
        """_SqlQueryId: Unique id of the SQL query."""

        self._cursor_buffer_size = cursor_buffer_size
        """int: Size of the cursor buffer measured in the number of rows."""

        self._lock = RLock()
        """RLock: Protects the shared access to instance variables below."""

        self._execute_response = execute_response
        """_ExecuteResponse: Response for the first execute request."""

        self._iterator_requested = False
        """bool: Flag that shows whether an iterator is already requested."""

        self._closed = self._is_closed(execute_response)
        """bool: Flag that shows whether the query execution is still active
        on the server side. When ``True``, there is no need to send the "close" 
        request to the server."""

        self._fetch_future = None
        """Future: Will be set, if there are more pages to fetch on the server
        side. It should be set to ``None`` once the fetch is completed."""

    def iterator(self) -> typing.Iterator[Future[SqlRow]]:
        """Returns the iterator over the result rows.

        The iterator may be requested only once.

        Raises:
            ValueError: If the result only contains an update count, or the
                iterator is already requested.

        Returns:
            Iterator that produces Future of :class:`SqlRow` s. See the class
            documentation for the correct way to use this.
        """
        return self._get_iterator(False)

    def is_row_set(self) -> bool:
        """Returns whether this result has rows to iterate."""
        # By design, if the row_metadata (or row_page) is None,
        # we only got the update count.
        return self._execute_response.row_metadata is not None

    def update_count(self) -> int:
        """Returns the number of rows updated by the statement or ``-1`` if
        this result is a row set. In case the result doesn't contain rows but
        the update count isn't applicable or known, ``0`` is returned.
        """
        # This will be set to -1, when we got row set on the client side.
        # See _on_execute_response.
        return self._execute_response.update_count

    def get_row_metadata(self) -> SqlRowMetadata:
        """Gets the row metadata.

        Raises:
            ValueError: If the result only contains an update count.
        """

        response = self._execute_response
        if not response.row_metadata:
            raise ValueError("This result contains only update count")

        return response.row_metadata

    def close(self) -> Future[None]:
        """Release the resources associated with the query result.

        The query engine delivers the rows asynchronously. The query may
        become inactive even before all rows are consumed. The invocation
        of this command will cancel the execution of the query on all members
        if the query is still active. Otherwise it is no-op. For a result
        with an update count it is always no-op.

        The returned Future results with:

        - :class:`HazelcastSqlError`: In case there is an error closing the
          result.
        """

        with self._lock:
            if self._closed:
                # Do nothing if the result is already closed.
                return ImmediateFuture(None)

            error = HazelcastSqlError(
                self._sql_service.get_client_id(),
                _SqlErrorCode.CANCELLED_BY_USER,
                "Query was cancelled by the user",
                None,
            )

            if not self._fetch_future:
                # Make sure that all subsequent fetches will fail.
                self._fetch_future = Future()

            self._on_fetch_error(error)

            def wrap_error_on_failure(f):
                # If the close request is failed somehow,
                # wrap it in a HazelcastSqlError.
                try:
                    return f.result()
                except Exception as e:
                    raise self._sql_service.re_raise(e, self._connection)

            self._closed = True

            # Send the close request
            return self._sql_service.close(
                self._connection,
                self._query_id).continue_with(wrap_error_on_failure)

    def __iter__(self):
        # Get the blocking iterator
        return self._get_iterator(True)

    def _get_iterator(self, should_get_blocking):
        """Gets the iterator after the execute request finishes.

        Args:
            should_get_blocking (bool): Whether to get a blocking iterator.

        Returns:
            Iterator:
        """
        response = self._execute_response
        if not response.row_metadata:
            # Can't get an iterator when we only have update count
            raise ValueError("This result contains only update count")

        with self._lock:
            if self._iterator_requested:
                # Can't get an iterator when we already get one
                raise ValueError("Iterator can be requested only once")

            self._iterator_requested = True

        if should_get_blocking:
            iterator = _BlockingIterator(
                response.row_metadata,
                self._fetch_next_page,
                self._sql_service.deserialize_object,
            )
        else:
            iterator = _FutureProducingIterator(
                response.row_metadata,
                self._fetch_next_page,
                self._sql_service.deserialize_object,
            )

        # Pass the first page information to the iterator
        iterator.on_next_page(response.row_page)
        return iterator

    def _fetch_next_page(self):
        """Fetches the next page, if there is no fetch request
        in-flight.

        Returns:
            Future[_SqlPage]:
        """
        with self._lock:
            if self._fetch_future:
                # A fetch request is already in-flight, return it.
                return self._fetch_future

            future = Future()
            self._fetch_future = future

            self._sql_service.fetch(
                self._connection, self._query_id,
                self._cursor_buffer_size).add_done_callback(
                    self._handle_fetch_response)

            # Need to return future, not self._fetch_future, because through
            # some unlucky timing, we might call _handle_fetch_response
            # before returning, which could set self._fetch_future to
            # None.
            return future

    def _handle_fetch_response(self, future):
        """Handles the result of the fetch request, by either:

        - setting it to exception, so that the future calls to
          fetch fails immediately.
        - setting it to next page, and setting self._fetch_future
          to None so that the next fetch request might actually
          try to fetch something from the server.

        Args:
            future (Future): The response from the server for
            the fetch request.
        """
        try:
            response = future.result()

            response_error = response["error"]
            if response_error:
                # There is a server side error sent to client.
                sql_error = HazelcastSqlError(
                    response_error.originating_member_uuid,
                    response_error.code,
                    response_error.message,
                    None,
                    response_error.suggestion,
                )
                self._on_fetch_error(sql_error)
                return

            # The result contains the next page, as expected.
            self._on_fetch_response(response["row_page"])
        except Exception as e:
            # Something went bad, we couldn't get response from
            # the server, invocation failed.
            self._on_fetch_error(
                self._sql_service.re_raise(e, self._connection))

    def _on_fetch_error(self, error):
        """Sets the fetch future with exception, but not resetting it
        so that the next fetch request fails immediately.

        Args:
            error (Exception): The error.
        """
        with self._lock:
            self._fetch_future.set_exception(error)

    def _on_fetch_response(self, page):
        """Sets the fetch future with the next page,
        resets it, and if this is the last page,
        marks the result as closed.

        Args:
            page (_SqlPage): The next page.
        """
        with self._lock:
            future = self._fetch_future
            self._fetch_future = None

            if page.is_last:
                # This is the last page, there is nothing
                # more on the server.
                self._closed = True

            # Resolving the future before resetting self._fetch_future
            # might result in an infinite loop for non-blocking iterators
            future.set_result(page)

    @staticmethod
    def _is_closed(execute_response):
        """Returns whether the result is already
        closed or not.

        Result might be closed if the first response

        - contains the last page of the rowset (single page rowset)
        - contains just the update count

        Args:
            execute_response (_ExecuteResponse): The first response

        Returns:
            bool: ``True`` if the result is already closed, ``False``
            otherwise.
        """
        return (execute_response.row_metadata is None  # Just an update count
                or execute_response.row_page.is_last  # Single page result
                )

    def __enter__(self):
        # The response for the execute request is already
        # received. There is nothing more to do.
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        # Ignoring the possible exception details
        # since we close the query regardless of that.
        self.close().result()