mirror of https://github.com/grpc/grpc.git
parent
fcd6c0c922
commit
b68f3d1746
9 changed files with 933 additions and 0 deletions
@ -0,0 +1,145 @@ |
||||
# Copyright 2015, Google Inc. |
||||
# All rights reserved. |
||||
# |
||||
# Redistribution and use in source and binary forms, with or without |
||||
# modification, are permitted provided that the following conditions are |
||||
# met: |
||||
# |
||||
# * Redistributions of source code must retain the above copyright |
||||
# notice, this list of conditions and the following disclaimer. |
||||
# * Redistributions in binary form must reproduce the above |
||||
# copyright notice, this list of conditions and the following disclaimer |
||||
# in the documentation and/or other materials provided with the |
||||
# distribution. |
||||
# * Neither the name of Google Inc. nor the names of its |
||||
# contributors may be used to endorse or promote products derived from |
||||
# this software without specific prior written permission. |
||||
# |
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
"""Tests of the later module.""" |
||||
|
||||
import threading |
||||
import time |
||||
import unittest |
||||
|
||||
from _framework.foundation import future |
||||
from _framework.foundation import later |
||||
|
||||
TICK = 0.1 |
||||
|
||||
|
||||
class LaterTest(unittest.TestCase): |
||||
|
||||
def test_simple_delay(self): |
||||
lock = threading.Lock() |
||||
cell = [0] |
||||
def increment_cell(): |
||||
with lock: |
||||
cell[0] += 1 |
||||
computation_future = later.later(TICK * 2, increment_cell) |
||||
self.assertFalse(computation_future.done()) |
||||
self.assertFalse(computation_future.cancelled()) |
||||
time.sleep(TICK) |
||||
self.assertFalse(computation_future.done()) |
||||
self.assertFalse(computation_future.cancelled()) |
||||
with lock: |
||||
self.assertEqual(0, cell[0]) |
||||
time.sleep(TICK * 2) |
||||
self.assertTrue(computation_future.done()) |
||||
self.assertFalse(computation_future.cancelled()) |
||||
with lock: |
||||
self.assertEqual(1, cell[0]) |
||||
outcome = computation_future.outcome() |
||||
self.assertEqual(future.RETURNED, outcome.category) |
||||
|
||||
def test_callback(self): |
||||
lock = threading.Lock() |
||||
cell = [0] |
||||
callback_called = [False] |
||||
outcome_passed_to_callback = [None] |
||||
def increment_cell(): |
||||
with lock: |
||||
cell[0] += 1 |
||||
computation_future = later.later(TICK * 2, increment_cell) |
||||
def callback(outcome): |
||||
with lock: |
||||
callback_called[0] = True |
||||
outcome_passed_to_callback[0] = outcome |
||||
computation_future.add_done_callback(callback) |
||||
time.sleep(TICK) |
||||
with lock: |
||||
self.assertFalse(callback_called[0]) |
||||
time.sleep(TICK * 2) |
||||
with lock: |
||||
self.assertTrue(callback_called[0]) |
||||
self.assertEqual(future.RETURNED, outcome_passed_to_callback[0].category) |
||||
|
||||
callback_called[0] = False |
||||
outcome_passed_to_callback[0] = None |
||||
|
||||
computation_future.add_done_callback(callback) |
||||
with lock: |
||||
self.assertTrue(callback_called[0]) |
||||
self.assertEqual(future.RETURNED, outcome_passed_to_callback[0].category) |
||||
|
||||
def test_cancel(self): |
||||
lock = threading.Lock() |
||||
cell = [0] |
||||
callback_called = [False] |
||||
outcome_passed_to_callback = [None] |
||||
def increment_cell(): |
||||
with lock: |
||||
cell[0] += 1 |
||||
computation_future = later.later(TICK * 2, increment_cell) |
||||
def callback(outcome): |
||||
with lock: |
||||
callback_called[0] = True |
||||
outcome_passed_to_callback[0] = outcome |
||||
computation_future.add_done_callback(callback) |
||||
time.sleep(TICK) |
||||
with lock: |
||||
self.assertFalse(callback_called[0]) |
||||
computation_future.cancel() |
||||
self.assertTrue(computation_future.cancelled()) |
||||
self.assertFalse(computation_future.done()) |
||||
self.assertEqual(future.ABORTED, computation_future.outcome().category) |
||||
with lock: |
||||
self.assertTrue(callback_called[0]) |
||||
self.assertEqual(future.ABORTED, outcome_passed_to_callback[0].category) |
||||
|
||||
def test_outcome(self): |
||||
lock = threading.Lock() |
||||
cell = [0] |
||||
callback_called = [False] |
||||
outcome_passed_to_callback = [None] |
||||
def increment_cell(): |
||||
with lock: |
||||
cell[0] += 1 |
||||
computation_future = later.later(TICK * 2, increment_cell) |
||||
def callback(outcome): |
||||
with lock: |
||||
callback_called[0] = True |
||||
outcome_passed_to_callback[0] = outcome |
||||
computation_future.add_done_callback(callback) |
||||
returned_outcome = computation_future.outcome() |
||||
self.assertEqual(future.RETURNED, returned_outcome.category) |
||||
|
||||
# The callback may not yet have been called! Sleep a tick. |
||||
time.sleep(TICK) |
||||
with lock: |
||||
self.assertTrue(callback_called[0]) |
||||
self.assertEqual(future.RETURNED, outcome_passed_to_callback[0].category) |
||||
|
||||
if __name__ == '__main__': |
||||
unittest.main() |
@ -0,0 +1,156 @@ |
||||
# Copyright 2015, Google Inc. |
||||
# All rights reserved. |
||||
# |
||||
# Redistribution and use in source and binary forms, with or without |
||||
# modification, are permitted provided that the following conditions are |
||||
# met: |
||||
# |
||||
# * Redistributions of source code must retain the above copyright |
||||
# notice, this list of conditions and the following disclaimer. |
||||
# * Redistributions in binary form must reproduce the above |
||||
# copyright notice, this list of conditions and the following disclaimer |
||||
# in the documentation and/or other materials provided with the |
||||
# distribution. |
||||
# * Neither the name of Google Inc. nor the names of its |
||||
# contributors may be used to endorse or promote products derived from |
||||
# this software without specific prior written permission. |
||||
# |
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
"""Affords a Future implementation based on Python's threading.Timer.""" |
||||
|
||||
import threading |
||||
import time |
||||
|
||||
from _framework.foundation import future |
||||
|
||||
|
||||
class TimerFuture(future.Future): |
||||
"""A Future implementation based around Timer objects.""" |
||||
|
||||
def __init__(self, compute_time, computation): |
||||
"""Constructor. |
||||
|
||||
Args: |
||||
compute_time: The time after which to begin this future's computation. |
||||
computation: The computation to be performed within this Future. |
||||
""" |
||||
self._lock = threading.Lock() |
||||
self._compute_time = compute_time |
||||
self._computation = computation |
||||
self._timer = None |
||||
self._computing = False |
||||
self._computed = False |
||||
self._cancelled = False |
||||
self._outcome = None |
||||
self._waiting = [] |
||||
|
||||
def _compute(self): |
||||
"""Performs the computation embedded in this Future. |
||||
|
||||
Or doesn't, if the time to perform it has not yet arrived. |
||||
""" |
||||
with self._lock: |
||||
time_remaining = self._compute_time - time.time() |
||||
if 0 < time_remaining: |
||||
self._timer = threading.Timer(time_remaining, self._compute) |
||||
self._timer.start() |
||||
return |
||||
else: |
||||
self._computing = True |
||||
|
||||
try: |
||||
returned_value = self._computation() |
||||
outcome = future.returned(returned_value) |
||||
except Exception as e: # pylint: disable=broad-except |
||||
outcome = future.raised(e) |
||||
|
||||
with self._lock: |
||||
self._computing = False |
||||
self._computed = True |
||||
self._outcome = outcome |
||||
waiting = self._waiting |
||||
|
||||
for callback in waiting: |
||||
callback(outcome) |
||||
|
||||
def start(self): |
||||
"""Starts this Future. |
||||
|
||||
This must be called exactly once, immediately after construction. |
||||
""" |
||||
with self._lock: |
||||
self._timer = threading.Timer( |
||||
self._compute_time - time.time(), self._compute) |
||||
self._timer.start() |
||||
|
||||
def cancel(self): |
||||
"""See future.Future.cancel for specification.""" |
||||
with self._lock: |
||||
if self._computing or self._computed: |
||||
return False |
||||
elif self._cancelled: |
||||
return True |
||||
else: |
||||
self._timer.cancel() |
||||
self._cancelled = True |
||||
self._outcome = future.aborted() |
||||
outcome = self._outcome |
||||
waiting = self._waiting |
||||
|
||||
for callback in waiting: |
||||
try: |
||||
callback(outcome) |
||||
except Exception: # pylint: disable=broad-except |
||||
pass |
||||
|
||||
return True |
||||
|
||||
def cancelled(self): |
||||
"""See future.Future.cancelled for specification.""" |
||||
with self._lock: |
||||
return self._cancelled |
||||
|
||||
def done(self): |
||||
"""See future.Future.done for specification.""" |
||||
with self._lock: |
||||
return self._computed |
||||
|
||||
def outcome(self): |
||||
"""See future.Future.outcome for specification.""" |
||||
with self._lock: |
||||
if self._computed or self._cancelled: |
||||
return self._outcome |
||||
|
||||
condition = threading.Condition() |
||||
def notify_condition(unused_outcome): |
||||
with condition: |
||||
condition.notify() |
||||
self._waiting.append(notify_condition) |
||||
|
||||
with condition: |
||||
condition.wait() |
||||
|
||||
with self._lock: |
||||
return self._outcome |
||||
|
||||
def add_done_callback(self, callback): |
||||
"""See future.Future.add_done_callback for specification.""" |
||||
with self._lock: |
||||
if not self._computed and not self._cancelled: |
||||
self._waiting.append(callback) |
||||
return |
||||
else: |
||||
outcome = self._outcome |
||||
|
||||
callback(outcome) |
@ -0,0 +1,38 @@ |
||||
# Copyright 2015, Google Inc. |
||||
# All rights reserved. |
||||
# |
||||
# Redistribution and use in source and binary forms, with or without |
||||
# modification, are permitted provided that the following conditions are |
||||
# met: |
||||
# |
||||
# * Redistributions of source code must retain the above copyright |
||||
# notice, this list of conditions and the following disclaimer. |
||||
# * Redistributions in binary form must reproduce the above |
||||
# copyright notice, this list of conditions and the following disclaimer |
||||
# in the documentation and/or other materials provided with the |
||||
# distribution. |
||||
# * Neither the name of Google Inc. nor the names of its |
||||
# contributors may be used to endorse or promote products derived from |
||||
# this software without specific prior written permission. |
||||
# |
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
"""Utilities for indicating abandonment of computation.""" |
||||
|
||||
|
||||
class Abandoned(Exception): |
||||
"""Indicates that some computation is being abandoned. |
||||
|
||||
Abandoning a computation is different than returning a value or raising |
||||
an exception indicating some operational or programming defect. |
||||
""" |
@ -0,0 +1,78 @@ |
||||
# Copyright 2015, Google Inc. |
||||
# All rights reserved. |
||||
# |
||||
# Redistribution and use in source and binary forms, with or without |
||||
# modification, are permitted provided that the following conditions are |
||||
# met: |
||||
# |
||||
# * Redistributions of source code must retain the above copyright |
||||
# notice, this list of conditions and the following disclaimer. |
||||
# * Redistributions in binary form must reproduce the above |
||||
# copyright notice, this list of conditions and the following disclaimer |
||||
# in the documentation and/or other materials provided with the |
||||
# distribution. |
||||
# * Neither the name of Google Inc. nor the names of its |
||||
# contributors may be used to endorse or promote products derived from |
||||
# this software without specific prior written permission. |
||||
# |
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
"""Utilities for working with callables.""" |
||||
|
||||
import functools |
||||
import logging |
||||
|
||||
from _framework.foundation import future |
||||
|
||||
|
||||
def _call_logging_exceptions(behavior, message, *args, **kwargs): |
||||
try: |
||||
return future.returned(behavior(*args, **kwargs)) |
||||
except Exception as e: # pylint: disable=broad-except |
||||
logging.exception(message) |
||||
return future.raised(e) |
||||
|
||||
|
||||
def with_exceptions_logged(behavior, message): |
||||
"""Wraps a callable in a try-except that logs any exceptions it raises. |
||||
|
||||
Args: |
||||
behavior: Any callable. |
||||
message: A string to log if the behavior raises an exception. |
||||
|
||||
Returns: |
||||
A callable that when executed invokes the given behavior. The returned |
||||
callable takes the same arguments as the given behavior but returns a |
||||
future.Outcome describing whether the given behavior returned a value or |
||||
raised an exception. |
||||
""" |
||||
@functools.wraps(behavior) |
||||
def wrapped_behavior(*args, **kwargs): |
||||
return _call_logging_exceptions(behavior, message, *args, **kwargs) |
||||
return wrapped_behavior |
||||
|
||||
|
||||
def call_logging_exceptions(behavior, message, *args, **kwargs): |
||||
"""Calls a behavior in a try-except that logs any exceptions it raises. |
||||
|
||||
Args: |
||||
behavior: Any callable. |
||||
message: A string to log if the behavior raises an exception. |
||||
*args: Positional arguments to pass to the given behavior. |
||||
**kwargs: Keyword arguments to pass to the given behavior. |
||||
|
||||
Returns: |
||||
A future.Outcome describing whether the given behavior returned a value or |
||||
raised an exception. |
||||
""" |
||||
return _call_logging_exceptions(behavior, message, *args, **kwargs) |
@ -0,0 +1,172 @@ |
||||
# Copyright 2015, Google Inc. |
||||
# All rights reserved. |
||||
# |
||||
# Redistribution and use in source and binary forms, with or without |
||||
# modification, are permitted provided that the following conditions are |
||||
# met: |
||||
# |
||||
# * Redistributions of source code must retain the above copyright |
||||
# notice, this list of conditions and the following disclaimer. |
||||
# * Redistributions in binary form must reproduce the above |
||||
# copyright notice, this list of conditions and the following disclaimer |
||||
# in the documentation and/or other materials provided with the |
||||
# distribution. |
||||
# * Neither the name of Google Inc. nor the names of its |
||||
# contributors may be used to endorse or promote products derived from |
||||
# this software without specific prior written permission. |
||||
# |
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
"""The Future interface missing from Python's standard library. |
||||
|
||||
Python's concurrent.futures library defines a Future class very much like the |
||||
Future defined here, but since that class is concrete and without construction |
||||
semantics it is only available within the concurrent.futures library itself. |
||||
The Future class defined here is an entirely abstract interface that anyone may |
||||
implement and use. |
||||
""" |
||||
|
||||
import abc |
||||
import collections |
||||
|
||||
RETURNED = object() |
||||
RAISED = object() |
||||
ABORTED = object() |
||||
|
||||
|
||||
class Outcome(object): |
||||
"""A sum type describing the outcome of some computation. |
||||
|
||||
Attributes: |
||||
category: One of RETURNED, RAISED, or ABORTED, respectively indicating |
||||
that the computation returned a value, raised an exception, or was |
||||
aborted. |
||||
return_value: The value returned by the computation. Must be present if |
||||
category is RETURNED. |
||||
exception: The exception raised by the computation. Must be present if |
||||
category is RAISED. |
||||
""" |
||||
__metaclass__ = abc.ABCMeta |
||||
|
||||
|
||||
class _EasyOutcome( |
||||
collections.namedtuple('_EasyOutcome', |
||||
['category', 'return_value', 'exception']), |
||||
Outcome): |
||||
"""A trivial implementation of Outcome.""" |
||||
|
||||
# All Outcomes describing abortion are indistinguishable so there might as well |
||||
# be only one. |
||||
_ABORTED_OUTCOME = _EasyOutcome(ABORTED, None, None) |
||||
|
||||
|
||||
def aborted(): |
||||
"""Returns an Outcome indicating that a computation was aborted. |
||||
|
||||
Returns: |
||||
An Outcome indicating that a computation was aborted. |
||||
""" |
||||
return _ABORTED_OUTCOME |
||||
|
||||
|
||||
def raised(exception): |
||||
"""Returns an Outcome indicating that a computation raised an exception. |
||||
|
||||
Args: |
||||
exception: The exception raised by the computation. |
||||
|
||||
Returns: |
||||
An Outcome indicating that a computation raised the given exception. |
||||
""" |
||||
return _EasyOutcome(RAISED, None, exception) |
||||
|
||||
|
||||
def returned(value): |
||||
"""Returns an Outcome indicating that a computation returned a value. |
||||
|
||||
Args: |
||||
value: The value returned by the computation. |
||||
|
||||
Returns: |
||||
An Outcome indicating that a computation returned the given value. |
||||
""" |
||||
return _EasyOutcome(RETURNED, value, None) |
||||
|
||||
|
||||
class Future(object): |
||||
"""A representation of a computation happening in another control flow. |
||||
|
||||
Computations represented by a Future may have already completed, may be |
||||
ongoing, or may be yet to be begun. |
||||
|
||||
Computations represented by a Future are considered uninterruptable; once |
||||
started they will be allowed to terminate either by returning or raising |
||||
an exception. |
||||
""" |
||||
__metaclass__ = abc.ABCMeta |
||||
|
||||
@abc.abstractmethod |
||||
def cancel(self): |
||||
"""Attempts to cancel the computation. |
||||
|
||||
Returns: |
||||
True if the computation will not be allowed to take place or False if |
||||
the computation has already taken place or is currently taking place. |
||||
""" |
||||
raise NotImplementedError() |
||||
|
||||
@abc.abstractmethod |
||||
def cancelled(self): |
||||
"""Describes whether the computation was cancelled. |
||||
|
||||
Returns: |
||||
True if the computation was cancelled and did not take place or False |
||||
if the computation took place, is taking place, or is scheduled to |
||||
take place in the future. |
||||
""" |
||||
raise NotImplementedError() |
||||
|
||||
@abc.abstractmethod |
||||
def done(self): |
||||
"""Describes whether the computation has taken place. |
||||
|
||||
Returns: |
||||
True if the computation took place; False otherwise. |
||||
""" |
||||
raise NotImplementedError() |
||||
|
||||
@abc.abstractmethod |
||||
def outcome(self): |
||||
"""Accesses the outcome of the computation. |
||||
|
||||
If the computation has not yet completed, this method blocks until it has. |
||||
|
||||
Returns: |
||||
An Outcome describing the outcome of the computation. |
||||
""" |
||||
raise NotImplementedError() |
||||
|
||||
@abc.abstractmethod |
||||
def add_done_callback(self, callback): |
||||
"""Adds a function to be called at completion of the computation. |
||||
|
||||
The callback will be passed an Outcome object describing the outcome of |
||||
the computation. |
||||
|
||||
If the computation has already completed, the callback will be called |
||||
immediately. |
||||
|
||||
Args: |
||||
callback: A callable taking an Outcome as its single parameter. |
||||
""" |
||||
raise NotImplementedError() |
@ -0,0 +1,51 @@ |
||||
# Copyright 2015, Google Inc. |
||||
# All rights reserved. |
||||
# |
||||
# Redistribution and use in source and binary forms, with or without |
||||
# modification, are permitted provided that the following conditions are |
||||
# met: |
||||
# |
||||
# * Redistributions of source code must retain the above copyright |
||||
# notice, this list of conditions and the following disclaimer. |
||||
# * Redistributions in binary form must reproduce the above |
||||
# copyright notice, this list of conditions and the following disclaimer |
||||
# in the documentation and/or other materials provided with the |
||||
# distribution. |
||||
# * Neither the name of Google Inc. nor the names of its |
||||
# contributors may be used to endorse or promote products derived from |
||||
# this software without specific prior written permission. |
||||
# |
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
"""Enables scheduling execution at a later time.""" |
||||
|
||||
import time |
||||
|
||||
from _framework.foundation import _timer_future |
||||
|
||||
|
||||
def later(delay, computation): |
||||
"""Schedules later execution of a callable. |
||||
|
||||
Args: |
||||
delay: Any numeric value. Represents the minimum length of time in seconds |
||||
to allow to pass before beginning the computation. No guarantees are made |
||||
about the maximum length of time that will pass. |
||||
computation: A callable that accepts no arguments. |
||||
|
||||
Returns: |
||||
A Future representing the scheduled computation. |
||||
""" |
||||
timer_future = _timer_future.TimerFuture(time.time() + delay, computation) |
||||
timer_future.start() |
||||
return timer_future |
@ -0,0 +1,60 @@ |
||||
# Copyright 2015, Google Inc. |
||||
# All rights reserved. |
||||
# |
||||
# Redistribution and use in source and binary forms, with or without |
||||
# modification, are permitted provided that the following conditions are |
||||
# met: |
||||
# |
||||
# * Redistributions of source code must retain the above copyright |
||||
# notice, this list of conditions and the following disclaimer. |
||||
# * Redistributions in binary form must reproduce the above |
||||
# copyright notice, this list of conditions and the following disclaimer |
||||
# in the documentation and/or other materials provided with the |
||||
# distribution. |
||||
# * Neither the name of Google Inc. nor the names of its |
||||
# contributors may be used to endorse or promote products derived from |
||||
# this software without specific prior written permission. |
||||
# |
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
"""Interfaces related to streams of values or objects.""" |
||||
|
||||
import abc |
||||
|
||||
|
||||
class Consumer(object): |
||||
"""Interface for consumers of finite streams of values or objects.""" |
||||
__metaclass__ = abc.ABCMeta |
||||
|
||||
@abc.abstractmethod |
||||
def consume(self, value): |
||||
"""Accepts a value. |
||||
|
||||
Args: |
||||
value: Any value accepted by this Consumer. |
||||
""" |
||||
raise NotImplementedError() |
||||
|
||||
@abc.abstractmethod |
||||
def terminate(self): |
||||
"""Indicates to this Consumer that no more values will be supplied.""" |
||||
raise NotImplementedError() |
||||
|
||||
@abc.abstractmethod |
||||
def consume_and_terminate(self, value): |
||||
"""Supplies a value and signals that no more values will be supplied. |
||||
|
||||
Args: |
||||
value: Any value accepted by this Consumer. |
||||
""" |
||||
raise NotImplementedError() |
@ -0,0 +1,73 @@ |
||||
# Copyright 2015, Google Inc. |
||||
# All rights reserved. |
||||
# |
||||
# Redistribution and use in source and binary forms, with or without |
||||
# modification, are permitted provided that the following conditions are |
||||
# met: |
||||
# |
||||
# * Redistributions of source code must retain the above copyright |
||||
# notice, this list of conditions and the following disclaimer. |
||||
# * Redistributions in binary form must reproduce the above |
||||
# copyright notice, this list of conditions and the following disclaimer |
||||
# in the documentation and/or other materials provided with the |
||||
# distribution. |
||||
# * Neither the name of Google Inc. nor the names of its |
||||
# contributors may be used to endorse or promote products derived from |
||||
# this software without specific prior written permission. |
||||
# |
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
"""Utilities for testing stream-related code.""" |
||||
|
||||
from _framework.foundation import stream |
||||
|
||||
|
||||
class TestConsumer(stream.Consumer): |
||||
"""A stream.Consumer instrumented for testing. |
||||
|
||||
Attributes: |
||||
calls: A sequence of value-termination pairs describing the history of calls |
||||
made on this object. |
||||
""" |
||||
|
||||
def __init__(self): |
||||
self.calls = [] |
||||
|
||||
def consume(self, value): |
||||
"""See stream.Consumer.consume for specification.""" |
||||
self.calls.append((value, False)) |
||||
|
||||
def terminate(self): |
||||
"""See stream.Consumer.terminate for specification.""" |
||||
self.calls.append((None, True)) |
||||
|
||||
def consume_and_terminate(self, value): |
||||
"""See stream.Consumer.consume_and_terminate for specification.""" |
||||
self.calls.append((value, True)) |
||||
|
||||
def is_legal(self): |
||||
"""Reports whether or not a legal sequence of calls has been made.""" |
||||
terminated = False |
||||
for value, terminal in self.calls: |
||||
if terminated: |
||||
return False |
||||
elif terminal: |
||||
terminated = True |
||||
elif value is None: |
||||
return False |
||||
else: # pylint: disable=useless-else-on-loop |
||||
return True |
||||
|
||||
def values(self): |
||||
"""Returns the sequence of values that have been passed to this Consumer.""" |
||||
return [value for value, _ in self.calls if value] |
@ -0,0 +1,160 @@ |
||||
# Copyright 2015, Google Inc. |
||||
# All rights reserved. |
||||
# |
||||
# Redistribution and use in source and binary forms, with or without |
||||
# modification, are permitted provided that the following conditions are |
||||
# met: |
||||
# |
||||
# * Redistributions of source code must retain the above copyright |
||||
# notice, this list of conditions and the following disclaimer. |
||||
# * Redistributions in binary form must reproduce the above |
||||
# copyright notice, this list of conditions and the following disclaimer |
||||
# in the documentation and/or other materials provided with the |
||||
# distribution. |
||||
# * Neither the name of Google Inc. nor the names of its |
||||
# contributors may be used to endorse or promote products derived from |
||||
# this software without specific prior written permission. |
||||
# |
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
"""Helpful utilities related to the stream module.""" |
||||
|
||||
import logging |
||||
import threading |
||||
|
||||
from _framework.foundation import stream |
||||
|
||||
_NO_VALUE = object() |
||||
|
||||
|
||||
class TransformingConsumer(stream.Consumer): |
||||
"""A stream.Consumer that passes a transformation of its input to another.""" |
||||
|
||||
def __init__(self, transformation, downstream): |
||||
self._transformation = transformation |
||||
self._downstream = downstream |
||||
|
||||
def consume(self, value): |
||||
self._downstream.consume(self._transformation(value)) |
||||
|
||||
def terminate(self): |
||||
self._downstream.terminate() |
||||
|
||||
def consume_and_terminate(self, value): |
||||
self._downstream.consume_and_terminate(self._transformation(value)) |
||||
|
||||
|
||||
class IterableConsumer(stream.Consumer): |
||||
"""A Consumer that when iterated over emits the values it has consumed.""" |
||||
|
||||
def __init__(self): |
||||
self._condition = threading.Condition() |
||||
self._values = [] |
||||
self._active = True |
||||
|
||||
def consume(self, stock_reply): |
||||
with self._condition: |
||||
if self._active: |
||||
self._values.append(stock_reply) |
||||
self._condition.notify() |
||||
|
||||
def terminate(self): |
||||
with self._condition: |
||||
self._active = False |
||||
self._condition.notify() |
||||
|
||||
def consume_and_terminate(self, stock_reply): |
||||
with self._condition: |
||||
if self._active: |
||||
self._values.append(stock_reply) |
||||
self._active = False |
||||
self._condition.notify() |
||||
|
||||
def __iter__(self): |
||||
return self |
||||
|
||||
def next(self): |
||||
with self._condition: |
||||
while self._active and not self._values: |
||||
self._condition.wait() |
||||
if self._values: |
||||
return self._values.pop(0) |
||||
else: |
||||
raise StopIteration() |
||||
|
||||
|
||||
class ThreadSwitchingConsumer(stream.Consumer): |
||||
"""A Consumer decorator that affords serialization and asynchrony.""" |
||||
|
||||
def __init__(self, sink, pool): |
||||
self._lock = threading.Lock() |
||||
self._sink = sink |
||||
self._pool = pool |
||||
# True if self._spin has been submitted to the pool to be called once and |
||||
# that call has not yet returned, False otherwise. |
||||
self._spinning = False |
||||
self._values = [] |
||||
self._active = True |
||||
|
||||
def _spin(self, sink, value, terminate): |
||||
while True: |
||||
try: |
||||
if value is _NO_VALUE: |
||||
sink.terminate() |
||||
elif terminate: |
||||
sink.consume_and_terminate(value) |
||||
else: |
||||
sink.consume(value) |
||||
except Exception as e: # pylint:disable=broad-except |
||||
logging.exception(e) |
||||
|
||||
with self._lock: |
||||
if terminate: |
||||
self._spinning = False |
||||
return |
||||
elif self._values: |
||||
value = self._values.pop(0) |
||||
terminate = not self._values and not self._active |
||||
elif not self._active: |
||||
value = _NO_VALUE |
||||
terminate = True |
||||
else: |
||||
self._spinning = False |
||||
return |
||||
|
||||
def consume(self, value): |
||||
with self._lock: |
||||
if self._active: |
||||
if self._spinning: |
||||
self._values.append(value) |
||||
else: |
||||
self._pool.submit(self._spin, self._sink, value, False) |
||||
self._spinning = True |
||||
|
||||
def terminate(self): |
||||
with self._lock: |
||||
if self._active: |
||||
self._active = False |
||||
if not self._spinning: |
||||
self._pool.submit(self._spin, self._sink, _NO_VALUE, True) |
||||
self._spinning = True |
||||
|
||||
def consume_and_terminate(self, value): |
||||
with self._lock: |
||||
if self._active: |
||||
self._active = False |
||||
if self._spinning: |
||||
self._values.append(value) |
||||
else: |
||||
self._pool.submit(self._spin, self._sink, value, True) |
||||
self._spinning = True |
Loading…
Reference in new issue