def before(self):
     self.initial_delay = datetime.timedelta(seconds=10)
     self.default_delay = datetime.timedelta(seconds=20)
     self.first_pause_time = 3
     self.first_resume_time = 8
     self.mock_queue = MagicMock(name="queue", spec=Queue)
     self.mock_clock = MagicMock(name="clock", spec=time.time)
     self.mock_clock.side_effect = [
         0, self.first_pause_time, self.first_resume_time
     ]
     self.state = ExecutionState(delay_provider=lambda: self.default_delay,
                                 initial_delay=self.initial_delay,
                                 state_changes_queue=self.mock_queue,
                                 clock=self.mock_clock)
     # mock queue so that we simulate a pause command at 3 o'clock
     # then a resume command at 8 o'clock and then no change.
     # With initial delay being 10, we should wait for 7 more after resume.
     self.mock_queue.get.side_effect = \
         [
             {
                 "state": ExecutionState.PAUSED,
                 "current_time": self.first_pause_time
             },
             {
                 "state": ExecutionState.RUNNING,
                 "current_time": self.first_resume_time
             },
             Empty
         ]
Exemple #2
0
    def __init__(self,
                 command,
                 delay_provider=None,
                 initial_delay=datetime.timedelta(),
                 thread_name=None,
                 args=None,
                 kwargs=None):
        """
        Creates and executes a periodic action that will be run first without any delay and subsequently with the
        given delay between the termination of one execution and the commencement of the next.
        Scheduled thread gets terminated if exception thrown, hence subsequent execution will not happen.
        The scheduler can be paused, it will then keep running but not execute the command until resume() is called.

        :param command: a new thread will be spawned for executing this command with arguments
            specified in args and kwargs
        :param delay_provider: function providing the delay between executions as a timedelta, default returns always 1s
        :param initial_delay: delay before first execution as a timedelta, default is 0s.
        :param thread_name: name of the new spawned thread
        :param args: (list) passing argument by its position
        :param kwargs: (dict) passing argument by the arguments' names
        """
        self._command = command
        self._thread = \
            threading.Thread(target=self._schedule_task_execution, name=thread_name)
        self._thread.daemon = True
        self._args = args if args is not None else []
        self._kwargs = kwargs if kwargs is not None else {}
        self._state = ExecutionState(
            delay_provider=delay_provider if delay_provider else lambda: datetime.timedelta(seconds=1),
            initial_delay=initial_delay)
class TestStop:
    @before
    def before(self):
        self.state = ExecutionState()
        self.executor = ExecutorForTest(self.state)

    def test_is_stopped(self):
        self.state.signal_stop(block=True)
        assert self.state.is_stopped()
 def before(self):
     self.initial_delay = datetime.timedelta(seconds=1)
     self.default_delay = datetime.timedelta(seconds=2)
     self.mock_queue = MagicMock(name="queue", spec=Queue)
     self.state = ExecutionState(delay_provider=lambda: self.default_delay,
                                 initial_delay=self.initial_delay,
                                 state_changes_queue=self.mock_queue,
                                 clock=lambda: 1)
     # mock queue so that we instantly return Empty without waiting for timeout
     self.mock_queue.get.side_effect = Empty
Exemple #5
0
class Scheduler:
    def __init__(self,
                 command,
                 delay_provider=None,
                 initial_delay=datetime.timedelta(),
                 thread_name=None,
                 args=None,
                 kwargs=None):
        """
        Creates and executes a periodic action that will be run first without any delay and subsequently with the
        given delay between the termination of one execution and the commencement of the next.
        Scheduled thread gets terminated if exception thrown, hence subsequent execution will not happen.
        The scheduler can be paused, it will then keep running but not execute the command until resume() is called.

        :param command: a new thread will be spawned for executing this command with arguments
            specified in args and kwargs
        :param delay_provider: function providing the delay between executions as a timedelta, default returns always 1s
        :param initial_delay: delay before first execution as a timedelta, default is 0s.
        :param thread_name: name of the new spawned thread
        :param args: (list) passing argument by its position
        :param kwargs: (dict) passing argument by the arguments' names
        """
        self._command = command
        self._thread = \
            threading.Thread(target=self._schedule_task_execution, name=thread_name)
        self._thread.daemon = True
        self._args = args if args is not None else []
        self._kwargs = kwargs if kwargs is not None else {}
        self._state = ExecutionState(
            delay_provider=delay_provider if delay_provider else lambda: datetime.timedelta(seconds=1),
            initial_delay=initial_delay)

    def start(self):
        if self.is_running():
            # nothing to do if we are already running
            logger.info("Ignored Scheduler.start() as it is already running!")
            return
        try:
            self._thread.start()
        except RuntimeError:
            # replace the exception from threading by ours with more explanations.
            raise RuntimeError(
                "Profiler cannot be started again after stop. Use a new Profiler instance or use pause() instead of stop()"
            )

    def is_running(self):
        """
        This tells if the scheduler is currently running.
        It still returns True when we are paused.
        """
        return self._thread.is_alive()

    def is_paused(self):
        return self.is_running() and self._state.is_paused()

    def stop(self):
        """
        Stop the scheduled thread from executing the command and wait for termination.
        """
        self._state.signal_stop()
        if not self.is_running():
            return
        self._thread.join(DEFAULT_TIME_TO_AWAIT_TERMINATION_SECONDS)

    def _schedule_task_execution(self):
        should_run = self._state.wait_for_next_tick_or_stop()
        while should_run:
            should_run = \
                self._command(*self._args, **self._kwargs) and self._state.wait_for_next_tick_or_stop()
        # call set_stopped in case it is the command that returned False.
        self._state.set_stopped()

    def update_delay_provider(self, delay_provider):
        self._state.delay_provider = delay_provider

    def resume(self, block=False):
        """
        Will signal the sampling thread that profiling should resume.

        :param block: if True, we will not return from this function before the change is applied, default is False.
        """
        self._state.signal_resume(block)

    def pause(self, block=False):
        """
        Will signal the sampling thread that profiling should pause.

        :param block: if True, we will not return from this function before the change is applied, default is False.
        """
        self._state.signal_pause(block)

    def _get_next_delay_seconds(self):
        """
        Useful for testing
        """
        return self._state.next_delay_seconds()
 def before(self):
     self.state = ExecutionState()
     self.executor = ExecutorForTest(self.state)
 def before(self):
     self.state = ExecutionState()
     self.executor = ExecutorForTest(self.state)
     yield
     self.state.signal_stop()
class TestPause:
    @before
    def before(self):
        self.state = ExecutionState()
        self.executor = ExecutorForTest(self.state)
        yield
        self.state.signal_stop()

    def test_is_paused(self):
        self.state.signal_pause(block=True)
        assert self.state.is_paused()

    def test_when_resumed_it_continues_to_run(self):
        self.state.signal_resume(block=True)
        assert not self.state.is_paused()
        assert not self.state.is_stopped()

    def test_when_it_is_paused_it_resumes(self):
        self.state.signal_pause(block=True)

        self.state.signal_resume(block=True)
        assert not self.state.is_paused()
        assert not self.state.is_stopped()

    def test_it_resumes_after_pausing_several_times(self):
        self.state.signal_pause(block=True)
        self.state.signal_pause(block=True)

        self.state.signal_resume(block=True)
        assert not self.state.is_paused()
        assert not self.state.is_stopped()
class TestPausedTime:
    @before
    def before(self):
        self.initial_delay = datetime.timedelta(seconds=10)
        self.default_delay = datetime.timedelta(seconds=20)
        self.first_pause_time = 3
        self.first_resume_time = 8
        self.mock_queue = MagicMock(name="queue", spec=Queue)
        self.mock_clock = MagicMock(name="clock", spec=time.time)
        self.mock_clock.side_effect = [
            0, self.first_pause_time, self.first_resume_time
        ]
        self.state = ExecutionState(delay_provider=lambda: self.default_delay,
                                    initial_delay=self.initial_delay,
                                    state_changes_queue=self.mock_queue,
                                    clock=self.mock_clock)
        # mock queue so that we simulate a pause command at 3 o'clock
        # then a resume command at 8 o'clock and then no change.
        # With initial delay being 10, we should wait for 7 more after resume.
        self.mock_queue.get.side_effect = \
            [
                {
                    "state": ExecutionState.PAUSED,
                    "current_time": self.first_pause_time
                },
                {
                    "state": ExecutionState.RUNNING,
                    "current_time": self.first_resume_time
                },
                Empty
            ]

    def test_when_paused_during_initial_delay_we_waited_for_remaining_time_after_resume(
            self):
        is_time_to_execute = self.state.wait_for_next_tick_or_stop()
        self.mock_queue.get.assert_called_with(block=True, timeout=7)
        assert is_time_to_execute

    def test_when_paused_again_after_first_execution_we_waited_for_correct_remaining_time_after_second_resume(
            self):
        self.initial_execution_time = 15
        self.second_pause_time = 19
        self.second_resume_time = 23
        self.mock_queue.get.side_effect = \
            [
                {
                    "state": ExecutionState.PAUSED,
                    "current_time": self.first_pause_time
                },
                {
                    "state": ExecutionState.RUNNING,
                    "current_time": self.first_resume_time
                },
                Empty,
                {
                    "state": ExecutionState.PAUSED,
                    "current_time": self.second_pause_time
                },
                {
                    "state": ExecutionState.RUNNING,
                    "current_time": self.second_resume_time
                },
                Empty
            ]
        self.mock_clock.side_effect = [
            0, self.first_pause_time, self.first_resume_time,
            self.initial_execution_time, self.second_pause_time,
            self.second_resume_time
        ]

        # first wait until we execute at time 15s
        self.state.wait_for_next_tick_or_stop()

        # second call should wait for 20s for normal delay, then wait for 15s remaining after resume
        is_time_to_execute = self.state.wait_for_next_tick_or_stop()
        self.mock_queue.get.assert_has_calls([
            call(block=True,
                 timeout=10),  # wanted to wait for 10s, the initial delay
            call(block=True),  # after pause at 3s, wait until next resume
            call(
                block=True, timeout=7
            ),  # after resume at 8s wait for 7s remaining from initial delay
            call(block=True, timeout=20
                 ),  # at 15s, wanted to wait for 20s, the normal delay
            call(block=True),  # after pause at 19s, wait until next resume
            call(block=True, timeout=16)
        ])  # after resume at 23s wait for 6s remaining from initial delay
        assert is_time_to_execute

    def test_when_paused_multiple_times_in_initial_delay_the_wait_time_accumulates(
            self):
        self.first_pause_time = 3
        self.first_resume_time = 8
        self.second_pause_time = 9
        self.second_resume_time = 10
        self.mock_queue.get.side_effect = \
            [
                {
                    "state": ExecutionState.PAUSED,
                    "current_time": self.first_pause_time
                },
                {
                    "state": ExecutionState.RUNNING,
                    "current_time": self.first_resume_time
                },
                {
                    "state": ExecutionState.PAUSED,
                    "current_time": self.second_pause_time
                },
                {
                    "state": ExecutionState.RUNNING,
                    "current_time": self.second_resume_time
                },
                Empty
            ]
        self.mock_clock.side_effect = [
            0, self.first_pause_time, self.first_resume_time,
            self.second_pause_time, self.second_resume_time
        ]

        is_time_to_execute = self.state.wait_for_next_tick_or_stop()
        self.mock_queue.get.assert_has_calls([
            call(block=True,
                 timeout=10),  # wanted to wait for 10s, the initial delay
            call(block=True),  # after pause at 3s, wait until next resume
            call(
                block=True, timeout=7
            ),  # after resume at 8s wait for 7s remaining from initial delay
            call(block=True),  # after pause at 9s, wait until next resume
            call(block=True, timeout=6)
        ])  # after resume at 10s wait for 6s remaining from initial delay
        assert is_time_to_execute

    def test_when_paused_multiple_times_in_normal_delay_the_wait_time_accumulates_the_wait_time_accumulates(
            self):
        self.first_pause_time = 13
        self.first_resume_time = 18
        self.second_pause_time = 19
        self.second_resume_time = 20
        self.mock_queue.get.side_effect = \
            [
                Empty,
                {
                    "state": ExecutionState.PAUSED,
                    "current_time": self.first_pause_time
                },
                {
                    "state": ExecutionState.RUNNING,
                    "current_time": self.first_resume_time
                },
                {
                    "state": ExecutionState.PAUSED,
                    "current_time": self.second_pause_time
                },
                {
                    "state": ExecutionState.RUNNING,
                    "current_time": self.second_resume_time
                },
                Empty
            ]
        self.mock_clock.side_effect = [
            0, 10, self.first_pause_time, self.first_resume_time,
            self.second_pause_time, self.second_resume_time
        ]

        # first wait until we execute after 10s, the initial delay
        self.state.wait_for_next_tick_or_stop()
        # then wait again for the normal delay and receive multiple pause and resume calls
        is_time_to_execute = self.state.wait_for_next_tick_or_stop()
        self.mock_queue.get.assert_has_calls([
            call(block=True, timeout=10),  # wait for 10s, the initial delay
            call(block=True,
                 timeout=20),  # wanted to wait for 20s, the normal delay
            call(block=True),  # after pause at 13s, wait until next resume
            call(
                block=True, timeout=17
            ),  # after resume at 18s wait for 17s remaining from initial delay
            call(block=True),  # after pause at 19s, wait until next resume
            call(block=True, timeout=16)
        ])  # after resume at 10s wait for 16s remaining from initial delay
        assert is_time_to_execute