# Copyright 2017 gRPC authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Test times.""" import collections import logging import threading import time as _time import grpc import grpc_testing def _call(behaviors): for behavior in behaviors: try: behavior() except Exception: # pylint: disable=broad-except logging.exception('Exception calling behavior "%r"!', behavior) def _call_in_thread(behaviors): calling = threading.Thread(target=_call, args=(behaviors,)) calling.start() # NOTE(nathaniel): Because this function is called from "strict" Time # implementations, it blocks until after all behaviors have terminated. calling.join() class _State(object): def __init__(self): self.condition = threading.Condition() self.times_to_behaviors = collections.defaultdict(list) class _Delta( collections.namedtuple('_Delta', ( 'mature_behaviors', 'earliest_mature_time', 'earliest_immature_time', ))): pass def _process(state, now): mature_behaviors = [] earliest_mature_time = None while state.times_to_behaviors: earliest_time = min(state.times_to_behaviors) if earliest_time <= now: if earliest_mature_time is None: earliest_mature_time = earliest_time earliest_mature_behaviors = state.times_to_behaviors.pop( earliest_time) mature_behaviors.extend(earliest_mature_behaviors) else: earliest_immature_time = earliest_time break else: earliest_immature_time = None return _Delta(mature_behaviors, earliest_mature_time, earliest_immature_time) class _Future(grpc.Future): def __init__(self, state, behavior, time): self._state = state self._behavior = behavior self._time = time self._cancelled = False def cancel(self): with self._state.condition: if self._cancelled: return True else: behaviors_at_time = self._state.times_to_behaviors.get( self._time) if behaviors_at_time is None: return False else: behaviors_at_time.remove(self._behavior) if not behaviors_at_time: self._state.times_to_behaviors.pop(self._time) self._state.condition.notify_all() self._cancelled = True return True def cancelled(self): with self._state.condition: return self._cancelled def running(self): raise NotImplementedError() def done(self): raise NotImplementedError() def result(self, timeout=None): raise NotImplementedError() def exception(self, timeout=None): raise NotImplementedError() def traceback(self, timeout=None): raise NotImplementedError() def add_done_callback(self, fn): raise NotImplementedError() class StrictRealTime(grpc_testing.Time): def __init__(self): self._state = _State() self._active = False self._calling = None def _activity(self): while True: with self._state.condition: while True: now = _time.time() delta = _process(self._state, now) self._state.condition.notify_all() if delta.mature_behaviors: self._calling = delta.earliest_mature_time break self._calling = None if delta.earliest_immature_time is None: self._active = False return else: timeout = max(0, delta.earliest_immature_time - now) self._state.condition.wait(timeout=timeout) _call(delta.mature_behaviors) def _ensure_called_through(self, time): with self._state.condition: while ((self._state.times_to_behaviors and min(self._state.times_to_behaviors) < time) or (self._calling is not None and self._calling < time)): self._state.condition.wait() def _call_at(self, behavior, time): with self._state.condition: self._state.times_to_behaviors[time].append(behavior) if self._active: self._state.condition.notify_all() else: activity = threading.Thread(target=self._activity) activity.start() self._active = True return _Future(self._state, behavior, time) def time(self): return _time.time() def call_in(self, behavior, delay): return self._call_at(behavior, _time.time() + delay) def call_at(self, behavior, time): return self._call_at(behavior, time) def sleep_for(self, duration): time = _time.time() + duration _time.sleep(duration) self._ensure_called_through(time) def sleep_until(self, time): _time.sleep(max(0, time - _time.time())) self._ensure_called_through(time) class StrictFakeTime(grpc_testing.Time): def __init__(self, time): self._state = _State() self._time = time def time(self): return self._time def call_in(self, behavior, delay): if delay <= 0: _call_in_thread((behavior,)) else: with self._state.condition: time = self._time + delay self._state.times_to_behaviors[time].append(behavior) return _Future(self._state, behavior, time) def call_at(self, behavior, time): with self._state.condition: if time <= self._time: _call_in_thread((behavior,)) else: self._state.times_to_behaviors[time].append(behavior) return _Future(self._state, behavior, time) def sleep_for(self, duration): if 0 < duration: with self._state.condition: self._time += duration delta = _process(self._state, self._time) _call_in_thread(delta.mature_behaviors) def sleep_until(self, time): with self._state.condition: if self._time < time: self._time = time delta = _process(self._state, self._time) _call_in_thread(delta.mature_behaviors)