venv added, updated
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
from .catchscheduler import CatchScheduler
|
||||
from .currentthreadscheduler import CurrentThreadScheduler
|
||||
from .eventloopscheduler import EventLoopScheduler
|
||||
from .historicalscheduler import HistoricalScheduler
|
||||
from .immediatescheduler import ImmediateScheduler
|
||||
from .newthreadscheduler import NewThreadScheduler
|
||||
from .scheduleditem import ScheduledItem
|
||||
from .threadpoolscheduler import ThreadPoolScheduler
|
||||
from .timeoutscheduler import TimeoutScheduler
|
||||
from .trampolinescheduler import TrampolineScheduler
|
||||
from .virtualtimescheduler import VirtualTimeScheduler
|
||||
|
||||
__all__ = [
|
||||
"CatchScheduler",
|
||||
"CurrentThreadScheduler",
|
||||
"EventLoopScheduler",
|
||||
"HistoricalScheduler",
|
||||
"ImmediateScheduler",
|
||||
"NewThreadScheduler",
|
||||
"ScheduledItem",
|
||||
"ThreadPoolScheduler",
|
||||
"TimeoutScheduler",
|
||||
"TrampolineScheduler",
|
||||
"VirtualTimeScheduler",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,177 @@
|
||||
from datetime import datetime
|
||||
from typing import Callable, Optional, TypeVar, cast
|
||||
|
||||
from reactivex import abc, typing
|
||||
from reactivex.abc.scheduler import SchedulerBase
|
||||
from reactivex.disposable import Disposable, SingleAssignmentDisposable
|
||||
|
||||
from .periodicscheduler import PeriodicScheduler
|
||||
|
||||
_TState = TypeVar("_TState")
|
||||
|
||||
|
||||
class CatchScheduler(PeriodicScheduler):
|
||||
def __init__(
|
||||
self, scheduler: abc.SchedulerBase, handler: Callable[[Exception], bool]
|
||||
) -> None:
|
||||
"""Wraps a scheduler, passed as constructor argument, adding exception
|
||||
handling for scheduled actions. The handler should return True to
|
||||
indicate it handled the exception successfully. Falsy return values will
|
||||
be taken to indicate that the exception should be escalated (raised by
|
||||
this scheduler).
|
||||
|
||||
Args:
|
||||
scheduler: The scheduler to be wrapped.
|
||||
handler: Callable to handle exceptions raised by wrapped scheduler.
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
self._scheduler: abc.SchedulerBase = scheduler
|
||||
self._handler: Callable[[Exception], bool] = handler
|
||||
self._recursive_original: Optional[abc.SchedulerBase] = None
|
||||
self._recursive_wrapper: Optional["CatchScheduler"] = None
|
||||
|
||||
@property
|
||||
def now(self) -> datetime:
|
||||
"""Represents a notion of time for this scheduler. Tasks being
|
||||
scheduled on a scheduler will adhere to the time denoted by this
|
||||
property.
|
||||
|
||||
Returns:
|
||||
The scheduler's current time, as a datetime instance.
|
||||
"""
|
||||
|
||||
return self._scheduler.now
|
||||
|
||||
def schedule(
|
||||
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed.
|
||||
|
||||
Args:
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
action = self._wrap(action)
|
||||
return self._scheduler.schedule(action, state=state)
|
||||
|
||||
def schedule_relative(
|
||||
self,
|
||||
duetime: typing.RelativeTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed after duetime.
|
||||
|
||||
Args:
|
||||
duetime: Relative time after which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
action = self._wrap(action)
|
||||
return self._scheduler.schedule_relative(duetime, action, state=state)
|
||||
|
||||
def schedule_absolute(
|
||||
self,
|
||||
duetime: typing.AbsoluteTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed at duetime.
|
||||
|
||||
Args:
|
||||
duetime: Absolute time at which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
action = self._wrap(action)
|
||||
return self._scheduler.schedule_absolute(duetime, action, state=state)
|
||||
|
||||
def schedule_periodic(
|
||||
self,
|
||||
period: typing.RelativeTime,
|
||||
action: typing.ScheduledPeriodicAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules a periodic piece of work.
|
||||
|
||||
Args:
|
||||
period: Period in seconds or timedelta for running the
|
||||
work periodically.
|
||||
action: Action to be executed.
|
||||
state: [Optional] Initial state passed to the action upon
|
||||
the first iteration.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled
|
||||
recurring action (best effort).
|
||||
"""
|
||||
|
||||
schedule_periodic = getattr(self._scheduler, "schedule_periodic")
|
||||
if not callable(schedule_periodic):
|
||||
raise NotImplementedError
|
||||
|
||||
disp: SingleAssignmentDisposable = SingleAssignmentDisposable()
|
||||
failed: bool = False
|
||||
|
||||
def periodic(state: Optional[_TState] = None) -> Optional[_TState]:
|
||||
nonlocal failed
|
||||
if failed:
|
||||
return None
|
||||
try:
|
||||
return action(state)
|
||||
except Exception as ex:
|
||||
failed = True
|
||||
if not self._handler(ex):
|
||||
raise
|
||||
disp.dispose()
|
||||
return None
|
||||
|
||||
scheduler = cast(PeriodicScheduler, self._scheduler)
|
||||
disp.disposable = scheduler.schedule_periodic(period, periodic, state=state)
|
||||
return disp
|
||||
|
||||
def _clone(self, scheduler: abc.SchedulerBase) -> "CatchScheduler":
|
||||
return CatchScheduler(scheduler, self._handler)
|
||||
|
||||
def _wrap(
|
||||
self, action: typing.ScheduledAction[_TState]
|
||||
) -> typing.ScheduledAction[_TState]:
|
||||
parent = self
|
||||
|
||||
def wrapped_action(
|
||||
self: abc.SchedulerBase, state: Optional[_TState]
|
||||
) -> Optional[abc.DisposableBase]:
|
||||
try:
|
||||
return action(parent._get_recursive_wrapper(self), state)
|
||||
except Exception as ex:
|
||||
if not parent._handler(ex):
|
||||
raise
|
||||
return Disposable()
|
||||
|
||||
return wrapped_action
|
||||
|
||||
def _get_recursive_wrapper(self, scheduler: SchedulerBase) -> "CatchScheduler":
|
||||
if self._recursive_wrapper is None or self._recursive_original != scheduler:
|
||||
self._recursive_original = scheduler
|
||||
wrapper = self._clone(scheduler)
|
||||
wrapper._recursive_original = scheduler
|
||||
wrapper._recursive_wrapper = wrapper
|
||||
self._recursive_wrapper = wrapper
|
||||
|
||||
return self._recursive_wrapper
|
||||
@@ -0,0 +1,81 @@
|
||||
import logging
|
||||
from threading import Thread, current_thread, local
|
||||
from typing import MutableMapping
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
from .trampoline import Trampoline
|
||||
from .trampolinescheduler import TrampolineScheduler
|
||||
|
||||
log = logging.getLogger("Rx")
|
||||
|
||||
|
||||
class CurrentThreadScheduler(TrampolineScheduler):
|
||||
"""Represents an object that schedules units of work on the current thread.
|
||||
You should never schedule timeouts using the *CurrentThreadScheduler*, as
|
||||
that will block the thread while waiting.
|
||||
|
||||
Each instance manages a number of trampolines (and queues), one for each
|
||||
thread that calls a *schedule* method. These trampolines are automatically
|
||||
garbage-collected when threads disappear, because they're stored in a weak
|
||||
key dictionary.
|
||||
"""
|
||||
|
||||
_global: MutableMapping[
|
||||
type, MutableMapping[Thread, "CurrentThreadScheduler"]
|
||||
] = WeakKeyDictionary()
|
||||
|
||||
@classmethod
|
||||
def singleton(cls) -> "CurrentThreadScheduler":
|
||||
"""
|
||||
Obtain a singleton instance for the current thread. Please note, if you
|
||||
pass this instance to another thread, it will effectively behave as
|
||||
if it were created by that other thread (separate trampoline and queue).
|
||||
|
||||
Returns:
|
||||
The singleton *CurrentThreadScheduler* instance.
|
||||
"""
|
||||
thread = current_thread()
|
||||
class_map = CurrentThreadScheduler._global.get(cls)
|
||||
if class_map is None:
|
||||
class_map_: MutableMapping[
|
||||
Thread, "CurrentThreadScheduler"
|
||||
] = WeakKeyDictionary()
|
||||
CurrentThreadScheduler._global[cls] = class_map_
|
||||
else:
|
||||
class_map_ = class_map
|
||||
try:
|
||||
self = class_map_[thread]
|
||||
except KeyError:
|
||||
self = CurrentThreadSchedulerSingleton()
|
||||
class_map_[thread] = self
|
||||
return self
|
||||
|
||||
# pylint: disable=super-init-not-called
|
||||
def __init__(self) -> None:
|
||||
self._tramps: MutableMapping[Thread, Trampoline] = WeakKeyDictionary()
|
||||
|
||||
def get_trampoline(self) -> Trampoline:
|
||||
thread = current_thread()
|
||||
tramp = self._tramps.get(thread)
|
||||
if tramp is None:
|
||||
tramp = Trampoline()
|
||||
self._tramps[thread] = tramp
|
||||
return tramp
|
||||
|
||||
|
||||
class _Local(local):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.tramp = Trampoline()
|
||||
|
||||
|
||||
class CurrentThreadSchedulerSingleton(CurrentThreadScheduler):
|
||||
|
||||
_local = _Local()
|
||||
|
||||
# pylint: disable=super-init-not-called
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
def get_trampoline(self) -> Trampoline:
|
||||
return CurrentThreadSchedulerSingleton._local.tramp
|
||||
@@ -0,0 +1,15 @@
|
||||
from .asyncioscheduler import AsyncIOScheduler
|
||||
from .asynciothreadsafescheduler import AsyncIOThreadSafeScheduler
|
||||
from .eventletscheduler import EventletScheduler
|
||||
from .geventscheduler import GEventScheduler
|
||||
from .ioloopscheduler import IOLoopScheduler
|
||||
from .twistedscheduler import TwistedScheduler
|
||||
|
||||
__all__ = [
|
||||
"AsyncIOScheduler",
|
||||
"AsyncIOThreadSafeScheduler",
|
||||
"EventletScheduler",
|
||||
"GEventScheduler",
|
||||
"IOLoopScheduler",
|
||||
"TwistedScheduler",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,123 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Optional, TypeVar
|
||||
|
||||
from reactivex import abc, typing
|
||||
from reactivex.disposable import (
|
||||
CompositeDisposable,
|
||||
Disposable,
|
||||
SingleAssignmentDisposable,
|
||||
)
|
||||
|
||||
from ..periodicscheduler import PeriodicScheduler
|
||||
|
||||
_TState = TypeVar("_TState")
|
||||
log = logging.getLogger("Rx")
|
||||
|
||||
|
||||
class AsyncIOScheduler(PeriodicScheduler):
|
||||
"""A scheduler that schedules work via the asyncio mainloop. This class
|
||||
does not use the asyncio threadsafe methods, if you need those please use
|
||||
the AsyncIOThreadSafeScheduler class."""
|
||||
|
||||
def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
|
||||
"""Create a new AsyncIOScheduler.
|
||||
|
||||
Args:
|
||||
loop: Instance of asyncio event loop to use; typically, you would
|
||||
get this by asyncio.get_event_loop()
|
||||
"""
|
||||
super().__init__()
|
||||
self._loop: asyncio.AbstractEventLoop = loop
|
||||
|
||||
def schedule(
|
||||
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed.
|
||||
|
||||
Args:
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
sad = SingleAssignmentDisposable()
|
||||
|
||||
def interval() -> None:
|
||||
sad.disposable = self.invoke_action(action, state=state)
|
||||
|
||||
handle = self._loop.call_soon(interval)
|
||||
|
||||
def dispose() -> None:
|
||||
handle.cancel()
|
||||
|
||||
return CompositeDisposable(sad, Disposable(dispose))
|
||||
|
||||
def schedule_relative(
|
||||
self,
|
||||
duetime: typing.RelativeTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed after duetime.
|
||||
|
||||
Args:
|
||||
duetime: Relative time after which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
seconds = self.to_seconds(duetime)
|
||||
if seconds <= 0:
|
||||
return self.schedule(action, state)
|
||||
|
||||
sad = SingleAssignmentDisposable()
|
||||
|
||||
def interval() -> None:
|
||||
sad.disposable = self.invoke_action(action, state=state)
|
||||
|
||||
handle = self._loop.call_later(seconds, interval)
|
||||
|
||||
def dispose() -> None:
|
||||
handle.cancel()
|
||||
|
||||
return CompositeDisposable(sad, Disposable(dispose))
|
||||
|
||||
def schedule_absolute(
|
||||
self,
|
||||
duetime: typing.AbsoluteTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed at duetime.
|
||||
|
||||
Args:
|
||||
duetime: Absolute time at which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
duetime = self.to_datetime(duetime)
|
||||
return self.schedule_relative(duetime - self.now, action, state=state)
|
||||
|
||||
@property
|
||||
def now(self) -> datetime:
|
||||
"""Represents a notion of time for this scheduler. Tasks being
|
||||
scheduled on a scheduler will adhere to the time denoted by this
|
||||
property.
|
||||
|
||||
Returns:
|
||||
The scheduler's current time, as a datetime instance.
|
||||
"""
|
||||
|
||||
return self.to_datetime(self._loop.time())
|
||||
@@ -0,0 +1,157 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from concurrent.futures import Future
|
||||
from typing import List, Optional, TypeVar
|
||||
|
||||
from reactivex import abc, typing
|
||||
from reactivex.disposable import (
|
||||
CompositeDisposable,
|
||||
Disposable,
|
||||
SingleAssignmentDisposable,
|
||||
)
|
||||
|
||||
from .asyncioscheduler import AsyncIOScheduler
|
||||
|
||||
_TState = TypeVar("_TState")
|
||||
|
||||
log = logging.getLogger("Rx")
|
||||
|
||||
|
||||
class AsyncIOThreadSafeScheduler(AsyncIOScheduler):
|
||||
"""A scheduler that schedules work via the asyncio mainloop. This is a
|
||||
subclass of AsyncIOScheduler which uses the threadsafe asyncio methods.
|
||||
"""
|
||||
|
||||
def schedule(
|
||||
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed.
|
||||
|
||||
Args:
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
sad = SingleAssignmentDisposable()
|
||||
|
||||
def interval() -> None:
|
||||
sad.disposable = self.invoke_action(action, state=state)
|
||||
|
||||
handle = self._loop.call_soon_threadsafe(interval)
|
||||
|
||||
def dispose() -> None:
|
||||
if self._on_self_loop_or_not_running():
|
||||
handle.cancel()
|
||||
return
|
||||
|
||||
future: "Future[int]" = Future()
|
||||
|
||||
def cancel_handle() -> None:
|
||||
handle.cancel()
|
||||
future.set_result(0)
|
||||
|
||||
self._loop.call_soon_threadsafe(cancel_handle)
|
||||
future.result()
|
||||
|
||||
return CompositeDisposable(sad, Disposable(dispose))
|
||||
|
||||
def schedule_relative(
|
||||
self,
|
||||
duetime: typing.RelativeTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed after duetime.
|
||||
|
||||
Args:
|
||||
duetime: Relative time after which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
seconds = self.to_seconds(duetime)
|
||||
if seconds <= 0:
|
||||
return self.schedule(action, state=state)
|
||||
|
||||
sad = SingleAssignmentDisposable()
|
||||
|
||||
def interval() -> None:
|
||||
sad.disposable = self.invoke_action(action, state=state)
|
||||
|
||||
# the operations on the list used here are atomic, so there is no
|
||||
# need to protect its access with a lock
|
||||
handle: List[asyncio.Handle] = []
|
||||
|
||||
def stage2() -> None:
|
||||
handle.append(self._loop.call_later(seconds, interval))
|
||||
|
||||
handle.append(self._loop.call_soon_threadsafe(stage2))
|
||||
|
||||
def dispose() -> None:
|
||||
def do_cancel_handles() -> None:
|
||||
try:
|
||||
handle.pop().cancel()
|
||||
handle.pop().cancel()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if self._on_self_loop_or_not_running():
|
||||
do_cancel_handles()
|
||||
return
|
||||
|
||||
future: "Future[int]" = Future()
|
||||
|
||||
def cancel_handle() -> None:
|
||||
do_cancel_handles()
|
||||
future.set_result(0)
|
||||
|
||||
self._loop.call_soon_threadsafe(cancel_handle)
|
||||
future.result()
|
||||
|
||||
return CompositeDisposable(sad, Disposable(dispose))
|
||||
|
||||
def schedule_absolute(
|
||||
self,
|
||||
duetime: typing.AbsoluteTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed at duetime.
|
||||
|
||||
Args:
|
||||
duetime: Absolute time at which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
duetime = self.to_datetime(duetime)
|
||||
return self.schedule_relative(duetime - self.now, action, state=state)
|
||||
|
||||
def _on_self_loop_or_not_running(self) -> bool:
|
||||
"""
|
||||
Returns True if either self._loop is not running, or we're currently
|
||||
executing on self._loop. In both cases, waiting for a future to be
|
||||
resolved on the loop would result in a deadlock.
|
||||
"""
|
||||
if not self._loop.is_running():
|
||||
return True
|
||||
current_loop = None
|
||||
try:
|
||||
# In python 3.7 there asyncio.get_running_loop() is prefered.
|
||||
current_loop = asyncio.get_event_loop()
|
||||
except RuntimeError:
|
||||
# If there is no loop in current thread at all, and it is not main
|
||||
# thread, we get error like:
|
||||
# RuntimeError: There is no current event loop in thread 'Thread-1'
|
||||
pass
|
||||
return self._loop == current_loop
|
||||
@@ -0,0 +1,126 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Any, Optional, TypeVar
|
||||
|
||||
from reactivex import abc, typing
|
||||
from reactivex.disposable import (
|
||||
CompositeDisposable,
|
||||
Disposable,
|
||||
SingleAssignmentDisposable,
|
||||
)
|
||||
|
||||
from ..periodicscheduler import PeriodicScheduler
|
||||
|
||||
_TState = TypeVar("_TState")
|
||||
log = logging.getLogger("Rx")
|
||||
|
||||
|
||||
class EventletScheduler(PeriodicScheduler):
|
||||
"""A scheduler that schedules work via the eventlet event loop.
|
||||
|
||||
http://eventlet.net/
|
||||
"""
|
||||
|
||||
def __init__(self, eventlet: Any) -> None:
|
||||
"""Create a new EventletScheduler.
|
||||
|
||||
Args:
|
||||
eventlet: The eventlet module to use; typically, you would get this
|
||||
by import eventlet
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
self._eventlet = eventlet
|
||||
|
||||
def schedule(
|
||||
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed.
|
||||
|
||||
Args:
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
sad = SingleAssignmentDisposable()
|
||||
|
||||
def interval() -> None:
|
||||
sad.disposable = self.invoke_action(action, state=state)
|
||||
|
||||
timer = self._eventlet.spawn(interval)
|
||||
|
||||
def dispose() -> None:
|
||||
timer.kill()
|
||||
|
||||
return CompositeDisposable(sad, Disposable(dispose))
|
||||
|
||||
def schedule_relative(
|
||||
self,
|
||||
duetime: typing.RelativeTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed after duetime.
|
||||
|
||||
Args:
|
||||
duetime: Relative time after which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
seconds = self.to_seconds(duetime)
|
||||
if seconds <= 0.0:
|
||||
return self.schedule(action, state=state)
|
||||
|
||||
sad = SingleAssignmentDisposable()
|
||||
|
||||
def interval() -> None:
|
||||
sad.disposable = self.invoke_action(action, state=state)
|
||||
|
||||
timer = self._eventlet.spawn_after(seconds, interval)
|
||||
|
||||
def dispose() -> None:
|
||||
timer.kill()
|
||||
|
||||
return CompositeDisposable(sad, Disposable(dispose))
|
||||
|
||||
def schedule_absolute(
|
||||
self,
|
||||
duetime: typing.AbsoluteTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed at duetime.
|
||||
|
||||
Args:
|
||||
duetime: Absolute time at which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
duetime = self.to_datetime(duetime)
|
||||
return self.schedule_relative(duetime - self.now, action, state=state)
|
||||
|
||||
@property
|
||||
def now(self) -> datetime:
|
||||
"""Represents a notion of time for this scheduler. Tasks being
|
||||
scheduled on a scheduler will adhere to the time denoted by this
|
||||
property.
|
||||
|
||||
Returns:
|
||||
The scheduler's current time, as a datetime instance.
|
||||
"""
|
||||
|
||||
return self.to_datetime(self._eventlet.hubs.get_hub().clock())
|
||||
@@ -0,0 +1,127 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Any, Optional, TypeVar
|
||||
|
||||
from reactivex import abc, typing
|
||||
from reactivex.disposable import (
|
||||
CompositeDisposable,
|
||||
Disposable,
|
||||
SingleAssignmentDisposable,
|
||||
)
|
||||
|
||||
from ..periodicscheduler import PeriodicScheduler
|
||||
|
||||
_TState = TypeVar("_TState")
|
||||
log = logging.getLogger("Rx")
|
||||
|
||||
|
||||
class GEventScheduler(PeriodicScheduler):
|
||||
"""A scheduler that schedules work via the GEvent event loop.
|
||||
|
||||
http://www.gevent.org/
|
||||
"""
|
||||
|
||||
def __init__(self, gevent: Any) -> None:
|
||||
"""Create a new GEventScheduler.
|
||||
|
||||
Args:
|
||||
gevent: The gevent module to use; typically ,you would get this by
|
||||
import gevent
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
self._gevent = gevent
|
||||
|
||||
def schedule(
|
||||
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed.
|
||||
|
||||
Args:
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
sad = SingleAssignmentDisposable()
|
||||
|
||||
def interval() -> None:
|
||||
sad.disposable = self.invoke_action(action, state=state)
|
||||
|
||||
timer = self._gevent.spawn(interval)
|
||||
|
||||
def dispose() -> None:
|
||||
timer.kill(block=False)
|
||||
|
||||
return CompositeDisposable(sad, Disposable(dispose))
|
||||
|
||||
def schedule_relative(
|
||||
self,
|
||||
duetime: typing.RelativeTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed after duetime.
|
||||
|
||||
Args:
|
||||
duetime: Relative time after which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
seconds = self.to_seconds(duetime)
|
||||
if seconds <= 0.0:
|
||||
return self.schedule(action, state=state)
|
||||
|
||||
sad = SingleAssignmentDisposable()
|
||||
|
||||
def interval() -> None:
|
||||
sad.disposable = self.invoke_action(action, state=state)
|
||||
|
||||
log.debug("timeout: %s", seconds)
|
||||
timer = self._gevent.spawn_later(seconds, interval)
|
||||
|
||||
def dispose() -> None:
|
||||
timer.kill(block=False)
|
||||
|
||||
return CompositeDisposable(sad, Disposable(dispose))
|
||||
|
||||
def schedule_absolute(
|
||||
self,
|
||||
duetime: typing.AbsoluteTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed at duetime.
|
||||
|
||||
Args:
|
||||
duetime: Absolute time at which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
duetime = self.to_datetime(duetime)
|
||||
return self.schedule_relative(duetime - self.now, action, state=state)
|
||||
|
||||
@property
|
||||
def now(self) -> datetime:
|
||||
"""Represents a notion of time for this scheduler. Tasks being
|
||||
scheduled on a scheduler will adhere to the time denoted by this
|
||||
property.
|
||||
|
||||
Returns:
|
||||
The scheduler's current time, as a datetime instance.
|
||||
"""
|
||||
|
||||
return self.to_datetime(self._gevent.get_hub().loop.now())
|
||||
@@ -0,0 +1,132 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Any, Optional, TypeVar
|
||||
|
||||
from reactivex import abc, typing
|
||||
from reactivex.disposable import (
|
||||
CompositeDisposable,
|
||||
Disposable,
|
||||
SingleAssignmentDisposable,
|
||||
)
|
||||
|
||||
from ..periodicscheduler import PeriodicScheduler
|
||||
|
||||
_TState = TypeVar("_TState")
|
||||
log = logging.getLogger("Rx")
|
||||
|
||||
|
||||
class IOLoopScheduler(PeriodicScheduler):
|
||||
"""A scheduler that schedules work via the Tornado I/O main event loop.
|
||||
|
||||
Note, as of Tornado 6, this is just a wrapper around the asyncio loop.
|
||||
|
||||
http://tornado.readthedocs.org/en/latest/ioloop.html"""
|
||||
|
||||
def __init__(self, loop: Any) -> None:
|
||||
"""Create a new IOLoopScheduler.
|
||||
|
||||
Args:
|
||||
loop: The ioloop to use; typically, you would get this by
|
||||
tornado import ioloop; ioloop.IOLoop.current()
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
self._loop = loop
|
||||
|
||||
def schedule(
|
||||
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed.
|
||||
|
||||
Args:
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
sad = SingleAssignmentDisposable()
|
||||
disposed = False
|
||||
|
||||
def interval() -> None:
|
||||
if not disposed:
|
||||
sad.disposable = self.invoke_action(action, state=state)
|
||||
|
||||
self._loop.add_callback(interval)
|
||||
|
||||
def dispose() -> None:
|
||||
nonlocal disposed
|
||||
disposed = True
|
||||
|
||||
return CompositeDisposable(sad, Disposable(dispose))
|
||||
|
||||
def schedule_relative(
|
||||
self,
|
||||
duetime: typing.RelativeTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed after duetime.
|
||||
|
||||
Args:
|
||||
duetime: Relative time after which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
seconds = self.to_seconds(duetime)
|
||||
if seconds <= 0.0:
|
||||
return self.schedule(action, state=state)
|
||||
|
||||
sad = SingleAssignmentDisposable()
|
||||
|
||||
def interval() -> None:
|
||||
sad.disposable = self.invoke_action(action, state=state)
|
||||
|
||||
log.debug("timeout: %s", seconds)
|
||||
timer = self._loop.call_later(seconds, interval)
|
||||
|
||||
def dispose() -> None:
|
||||
self._loop.remove_timeout(timer)
|
||||
self._loop.remove_timeout(timer)
|
||||
|
||||
return CompositeDisposable(sad, Disposable(dispose))
|
||||
|
||||
def schedule_absolute(
|
||||
self,
|
||||
duetime: typing.AbsoluteTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed at duetime.
|
||||
|
||||
Args:
|
||||
duetime: Absolute time at which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
duetime = self.to_datetime(duetime)
|
||||
return self.schedule_relative(duetime - self.now, action, state=state)
|
||||
|
||||
@property
|
||||
def now(self) -> datetime:
|
||||
"""Represents a notion of time for this scheduler. Tasks being
|
||||
scheduled on a scheduler will adhere to the time denoted by this
|
||||
property.
|
||||
|
||||
Returns:
|
||||
The scheduler's current time, as a datetime instance.
|
||||
"""
|
||||
|
||||
return self.to_datetime(self._loop.time())
|
||||
@@ -0,0 +1,113 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Any, Optional, TypeVar
|
||||
|
||||
from reactivex import abc, typing
|
||||
from reactivex.disposable import (
|
||||
CompositeDisposable,
|
||||
Disposable,
|
||||
SingleAssignmentDisposable,
|
||||
)
|
||||
|
||||
from ..periodicscheduler import PeriodicScheduler
|
||||
|
||||
_TState = TypeVar("_TState")
|
||||
log = logging.getLogger("Rx")
|
||||
|
||||
|
||||
class TwistedScheduler(PeriodicScheduler):
|
||||
"""A scheduler that schedules work via the Twisted reactor mainloop."""
|
||||
|
||||
def __init__(self, reactor: Any) -> None:
|
||||
"""Create a new TwistedScheduler.
|
||||
|
||||
Args:
|
||||
reactor: The reactor to use; typically, you would get this
|
||||
by from twisted.internet import reactor
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
self._reactor = reactor
|
||||
|
||||
def schedule(
|
||||
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed.
|
||||
|
||||
Args:
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
return self.schedule_relative(0.0, action, state=state)
|
||||
|
||||
def schedule_relative(
|
||||
self,
|
||||
duetime: typing.RelativeTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed after duetime.
|
||||
|
||||
Args:
|
||||
duetime: Relative time after which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
seconds = max(0.0, self.to_seconds(duetime))
|
||||
|
||||
sad = SingleAssignmentDisposable()
|
||||
|
||||
def interval() -> None:
|
||||
sad.disposable = action(self, state)
|
||||
|
||||
log.debug("timeout: %s", seconds)
|
||||
timer = self._reactor.callLater(seconds, interval)
|
||||
|
||||
def dispose() -> None:
|
||||
if not timer.called:
|
||||
timer.cancel()
|
||||
|
||||
return CompositeDisposable(sad, Disposable(dispose))
|
||||
|
||||
def schedule_absolute(
|
||||
self,
|
||||
duetime: typing.AbsoluteTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed at duetime.
|
||||
|
||||
Args:
|
||||
duetime: Absolute time at which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
duetime = self.to_datetime(duetime)
|
||||
return self.schedule_relative(duetime - self.now, action, state=state)
|
||||
|
||||
@property
|
||||
def now(self) -> datetime:
|
||||
"""Represents a notion of time for this scheduler. Tasks being
|
||||
scheduled on a scheduler will adhere to the time denoted by this
|
||||
property.
|
||||
|
||||
Returns:
|
||||
The scheduler's current time, as a datetime instance.
|
||||
"""
|
||||
|
||||
return self.to_datetime(float(self._reactor.seconds()))
|
||||
@@ -0,0 +1,218 @@
|
||||
import logging
|
||||
import threading
|
||||
from collections import deque
|
||||
from typing import Deque, Optional, TypeVar
|
||||
|
||||
from reactivex import abc, typing
|
||||
from reactivex.disposable import Disposable
|
||||
from reactivex.internal.concurrency import default_thread_factory
|
||||
from reactivex.internal.constants import DELTA_ZERO
|
||||
from reactivex.internal.exceptions import DisposedException
|
||||
from reactivex.internal.priorityqueue import PriorityQueue
|
||||
|
||||
from .periodicscheduler import PeriodicScheduler
|
||||
from .scheduleditem import ScheduledItem
|
||||
|
||||
log = logging.getLogger("Rx")
|
||||
|
||||
_TState = TypeVar("_TState")
|
||||
|
||||
|
||||
class EventLoopScheduler(PeriodicScheduler, abc.DisposableBase):
|
||||
"""Creates an object that schedules units of work on a designated thread."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
thread_factory: Optional[typing.StartableFactory] = None,
|
||||
exit_if_empty: bool = False,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self._is_disposed = False
|
||||
|
||||
self._thread_factory: typing.StartableFactory = (
|
||||
thread_factory or default_thread_factory
|
||||
)
|
||||
self._thread: Optional[typing.Startable] = None
|
||||
self._condition = threading.Condition(threading.Lock())
|
||||
self._queue: PriorityQueue[ScheduledItem] = PriorityQueue()
|
||||
self._ready_list: Deque[ScheduledItem] = deque()
|
||||
|
||||
self._exit_if_empty = exit_if_empty
|
||||
|
||||
def schedule(
|
||||
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed.
|
||||
|
||||
Args:
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
return self.schedule_absolute(self.now, action, state=state)
|
||||
|
||||
def schedule_relative(
|
||||
self,
|
||||
duetime: typing.RelativeTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed after duetime.
|
||||
|
||||
Args:
|
||||
duetime: Relative time after which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
duetime = max(DELTA_ZERO, self.to_timedelta(duetime))
|
||||
return self.schedule_absolute(self.now + duetime, action, state)
|
||||
|
||||
def schedule_absolute(
|
||||
self,
|
||||
duetime: typing.AbsoluteTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed at duetime.
|
||||
|
||||
Args:
|
||||
duetime: Absolute time at which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
if self._is_disposed:
|
||||
raise DisposedException()
|
||||
|
||||
dt = self.to_datetime(duetime)
|
||||
si: ScheduledItem = ScheduledItem(self, state, action, dt)
|
||||
|
||||
with self._condition:
|
||||
if dt <= self.now:
|
||||
self._ready_list.append(si)
|
||||
else:
|
||||
self._queue.enqueue(si)
|
||||
self._condition.notify() # signal that a new item is available
|
||||
self._ensure_thread()
|
||||
|
||||
return Disposable(si.cancel)
|
||||
|
||||
def schedule_periodic(
|
||||
self,
|
||||
period: typing.RelativeTime,
|
||||
action: typing.ScheduledPeriodicAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules a periodic piece of work.
|
||||
|
||||
Args:
|
||||
period: Period in seconds or timedelta for running the
|
||||
work periodically.
|
||||
action: Action to be executed.
|
||||
state: [Optional] Initial state passed to the action upon
|
||||
the first iteration.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled
|
||||
recurring action (best effort).
|
||||
"""
|
||||
|
||||
if self._is_disposed:
|
||||
raise DisposedException()
|
||||
|
||||
return super().schedule_periodic(period, action, state=state)
|
||||
|
||||
def _has_thread(self) -> bool:
|
||||
"""Checks if there is an event loop thread running."""
|
||||
with self._condition:
|
||||
return not self._is_disposed and self._thread is not None
|
||||
|
||||
def _ensure_thread(self) -> None:
|
||||
"""Ensures there is an event loop thread running. Should be
|
||||
called under the gate."""
|
||||
|
||||
if not self._thread:
|
||||
thread = self._thread_factory(self.run)
|
||||
self._thread = thread
|
||||
thread.start()
|
||||
|
||||
def run(self) -> None:
|
||||
"""Event loop scheduled on the designated event loop thread.
|
||||
The loop is suspended/resumed using the condition which gets notified
|
||||
by calls to Schedule or calls to dispose."""
|
||||
|
||||
ready: Deque[ScheduledItem] = deque()
|
||||
|
||||
while True:
|
||||
|
||||
with self._condition:
|
||||
|
||||
# The notification could be because of a call to dispose. This
|
||||
# takes precedence over everything else: We'll exit the loop
|
||||
# immediately. Subsequent calls to Schedule won't ever create a
|
||||
# new thread.
|
||||
if self._is_disposed:
|
||||
return
|
||||
|
||||
# Sort the ready_list (from recent calls for immediate schedule)
|
||||
# and the due subset of previously queued items.
|
||||
time = self.now
|
||||
while self._queue:
|
||||
due = self._queue.peek().duetime
|
||||
while self._ready_list and due > self._ready_list[0].duetime:
|
||||
ready.append(self._ready_list.popleft())
|
||||
if due > time:
|
||||
break
|
||||
ready.append(self._queue.dequeue())
|
||||
while self._ready_list:
|
||||
ready.append(self._ready_list.popleft())
|
||||
|
||||
# Execute the gathered actions
|
||||
while ready:
|
||||
item = ready.popleft()
|
||||
if not item.is_cancelled():
|
||||
item.invoke()
|
||||
|
||||
# Wait for next cycle, or if we're done let's exit if so configured
|
||||
with self._condition:
|
||||
|
||||
if self._ready_list:
|
||||
continue
|
||||
|
||||
elif self._queue:
|
||||
time = self.now
|
||||
item = self._queue.peek()
|
||||
seconds = (item.duetime - time).total_seconds()
|
||||
if seconds > 0:
|
||||
log.debug("timeout: %s", seconds)
|
||||
self._condition.wait(seconds)
|
||||
|
||||
elif self._exit_if_empty:
|
||||
self._thread = None
|
||||
return
|
||||
|
||||
else:
|
||||
self._condition.wait()
|
||||
|
||||
def dispose(self) -> None:
|
||||
"""Ends the thread associated with this scheduler. All
|
||||
remaining work in the scheduler queue is abandoned.
|
||||
"""
|
||||
|
||||
with self._condition:
|
||||
if not self._is_disposed:
|
||||
self._is_disposed = True
|
||||
self._condition.notify()
|
||||
@@ -0,0 +1,20 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from .scheduler import UTC_ZERO
|
||||
from .virtualtimescheduler import VirtualTimeScheduler
|
||||
|
||||
|
||||
class HistoricalScheduler(VirtualTimeScheduler):
|
||||
"""Provides a virtual time scheduler that uses datetime for absolute time
|
||||
and timedelta for relative time."""
|
||||
|
||||
def __init__(self, initial_clock: Optional[datetime] = None) -> None:
|
||||
"""Creates a new historical scheduler with the specified initial clock
|
||||
value.
|
||||
|
||||
Args:
|
||||
initial_clock: Initial value for the clock.
|
||||
"""
|
||||
|
||||
super().__init__(initial_clock or UTC_ZERO)
|
||||
@@ -0,0 +1,96 @@
|
||||
from threading import Lock
|
||||
from typing import MutableMapping, Optional, TypeVar
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
from reactivex import abc, typing
|
||||
from reactivex.internal.constants import DELTA_ZERO
|
||||
from reactivex.internal.exceptions import WouldBlockException
|
||||
|
||||
from .scheduler import Scheduler
|
||||
|
||||
_TState = TypeVar("_TState")
|
||||
|
||||
|
||||
class ImmediateScheduler(Scheduler):
|
||||
"""Represents an object that schedules units of work to run immediately,
|
||||
on the current thread. You're not allowed to schedule timeouts using the
|
||||
ImmediateScheduler since that will block the current thread while waiting.
|
||||
Attempts to do so will raise a :class:`WouldBlockException`.
|
||||
"""
|
||||
|
||||
_lock = Lock()
|
||||
_global: MutableMapping[type, "ImmediateScheduler"] = WeakKeyDictionary()
|
||||
|
||||
@classmethod
|
||||
def singleton(cls) -> "ImmediateScheduler":
|
||||
with ImmediateScheduler._lock:
|
||||
try:
|
||||
self = ImmediateScheduler._global[cls]
|
||||
except KeyError:
|
||||
self = super().__new__(cls)
|
||||
ImmediateScheduler._global[cls] = self
|
||||
return self
|
||||
|
||||
def __new__(cls) -> "ImmediateScheduler":
|
||||
return cls.singleton()
|
||||
|
||||
def schedule(
|
||||
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed.
|
||||
|
||||
Args:
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
return self.invoke_action(action, state)
|
||||
|
||||
def schedule_relative(
|
||||
self,
|
||||
duetime: typing.RelativeTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed after duetime.
|
||||
|
||||
Args:
|
||||
duetime: Relative time after which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
duetime = self.to_timedelta(duetime)
|
||||
if duetime > DELTA_ZERO:
|
||||
raise WouldBlockException()
|
||||
|
||||
return self.invoke_action(action, state)
|
||||
|
||||
def schedule_absolute(
|
||||
self,
|
||||
duetime: typing.AbsoluteTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed at duetime.
|
||||
|
||||
Args:
|
||||
duetime: Absolute time at which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
duetime = self.to_datetime(duetime)
|
||||
return self.schedule_relative(duetime - self.now, action, state)
|
||||
@@ -0,0 +1,13 @@
|
||||
from .gtkscheduler import GtkScheduler
|
||||
from .pygamescheduler import PyGameScheduler
|
||||
from .qtscheduler import QtScheduler
|
||||
from .tkinterscheduler import TkinterScheduler
|
||||
from .wxscheduler import WxScheduler
|
||||
|
||||
__all__ = [
|
||||
"GtkScheduler",
|
||||
"PyGameScheduler",
|
||||
"QtScheduler",
|
||||
"TkinterScheduler",
|
||||
"WxScheduler",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,144 @@
|
||||
from typing import Any, Optional, TypeVar, cast
|
||||
|
||||
from reactivex import abc, typing
|
||||
from reactivex.disposable import (
|
||||
CompositeDisposable,
|
||||
Disposable,
|
||||
SingleAssignmentDisposable,
|
||||
)
|
||||
|
||||
from ..periodicscheduler import PeriodicScheduler
|
||||
|
||||
_TState = TypeVar("_TState")
|
||||
|
||||
|
||||
class GtkScheduler(PeriodicScheduler):
|
||||
"""A scheduler that schedules work via the GLib main loop
|
||||
used in GTK+ applications.
|
||||
|
||||
See https://wiki.gnome.org/Projects/PyGObject
|
||||
"""
|
||||
|
||||
def __init__(self, glib: Any) -> None:
|
||||
"""Create a new GtkScheduler.
|
||||
|
||||
Args:
|
||||
glib: The GLib module to use; typically, you would get this by
|
||||
>>> import gi
|
||||
>>> gi.require_version('Gtk', '3.0')
|
||||
>>> from gi.repository import GLib
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
self._glib = glib
|
||||
|
||||
def _gtk_schedule(
|
||||
self,
|
||||
time: typing.AbsoluteOrRelativeTime,
|
||||
action: typing.ScheduledSingleOrPeriodicAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
periodic: bool = False,
|
||||
) -> abc.DisposableBase:
|
||||
|
||||
msecs = max(0, int(self.to_seconds(time) * 1000.0))
|
||||
|
||||
sad = SingleAssignmentDisposable()
|
||||
|
||||
stopped = False
|
||||
|
||||
def timer_handler(_: Any) -> bool:
|
||||
if stopped:
|
||||
return False
|
||||
|
||||
nonlocal state
|
||||
if periodic:
|
||||
state = cast(typing.ScheduledPeriodicAction[_TState], action)(state)
|
||||
else:
|
||||
sad.disposable = self.invoke_action(
|
||||
cast(typing.ScheduledAction[_TState], action), state=state
|
||||
)
|
||||
|
||||
return periodic
|
||||
|
||||
self._glib.timeout_add(msecs, timer_handler, None)
|
||||
|
||||
def dispose() -> None:
|
||||
nonlocal stopped
|
||||
stopped = True
|
||||
|
||||
return CompositeDisposable(sad, Disposable(dispose))
|
||||
|
||||
def schedule(
|
||||
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed.
|
||||
|
||||
Args:
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
return self._gtk_schedule(0.0, action, state)
|
||||
|
||||
def schedule_relative(
|
||||
self,
|
||||
duetime: typing.RelativeTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed after duetime.
|
||||
|
||||
Args:
|
||||
duetime: Relative time after which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
return self._gtk_schedule(duetime, action, state=state)
|
||||
|
||||
def schedule_absolute(
|
||||
self,
|
||||
duetime: typing.AbsoluteTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed at duetime.
|
||||
|
||||
Args:
|
||||
duetime: Absolute time at which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
duetime = self.to_datetime(duetime)
|
||||
return self._gtk_schedule(duetime - self.now, action, state=state)
|
||||
|
||||
def schedule_periodic(
|
||||
self,
|
||||
period: typing.RelativeTime,
|
||||
action: typing.ScheduledPeriodicAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules a periodic piece of work to be executed in the loop.
|
||||
|
||||
Args:
|
||||
period: Period in seconds for running the work repeatedly.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
return self._gtk_schedule(period, action, state=state, periodic=True)
|
||||
@@ -0,0 +1,113 @@
|
||||
import logging
|
||||
import threading
|
||||
from typing import Any, Optional, TypeVar
|
||||
|
||||
from reactivex import abc, typing
|
||||
from reactivex.internal import PriorityQueue
|
||||
from reactivex.internal.constants import DELTA_ZERO
|
||||
|
||||
from ..periodicscheduler import PeriodicScheduler
|
||||
from ..scheduleditem import ScheduledItem
|
||||
|
||||
_TState = TypeVar("_TState")
|
||||
|
||||
log = logging.getLogger("Rx")
|
||||
|
||||
|
||||
class PyGameScheduler(PeriodicScheduler):
|
||||
"""A scheduler that schedules works for PyGame.
|
||||
|
||||
Note that this class expects the caller to invoke run() repeatedly.
|
||||
|
||||
http://www.pygame.org/docs/ref/time.html
|
||||
http://www.pygame.org/docs/ref/event.html"""
|
||||
|
||||
def __init__(self, pygame: Any):
|
||||
"""Create a new PyGameScheduler.
|
||||
|
||||
Args:
|
||||
pygame: The PyGame module to use; typically, you would get this by
|
||||
import pygame
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
self._pygame = pygame # TODO not used, refactor to actually use pygame?
|
||||
self._lock = threading.Lock()
|
||||
self._queue: PriorityQueue[ScheduledItem] = PriorityQueue()
|
||||
|
||||
def schedule(
|
||||
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed.
|
||||
|
||||
Args:
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
log.debug("PyGameScheduler.schedule(state=%s)", state)
|
||||
return self.schedule_absolute(self.now, action, state=state)
|
||||
|
||||
def schedule_relative(
|
||||
self,
|
||||
duetime: typing.RelativeTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed after duetime.
|
||||
Args:
|
||||
duetime: Relative time after which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
duetime = max(DELTA_ZERO, self.to_timedelta(duetime))
|
||||
return self.schedule_absolute(self.now + duetime, action, state=state)
|
||||
|
||||
def schedule_absolute(
|
||||
self,
|
||||
duetime: typing.AbsoluteTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed at duetime.
|
||||
|
||||
Args:
|
||||
duetime: Absolute time at which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
duetime = self.to_datetime(duetime)
|
||||
si: ScheduledItem = ScheduledItem(self, state, action, duetime)
|
||||
|
||||
with self._lock:
|
||||
self._queue.enqueue(si)
|
||||
|
||||
return si.disposable
|
||||
|
||||
def run(self) -> None:
|
||||
while self._queue:
|
||||
with self._lock:
|
||||
item: ScheduledItem = self._queue.peek()
|
||||
diff = item.duetime - self.now
|
||||
|
||||
if diff > DELTA_ZERO:
|
||||
break
|
||||
|
||||
item = self._queue.dequeue()
|
||||
|
||||
if not item.is_cancelled():
|
||||
item.invoke()
|
||||
@@ -0,0 +1,143 @@
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from typing import Any, Optional, Set, TypeVar
|
||||
|
||||
from reactivex import abc, typing
|
||||
from reactivex.disposable import (
|
||||
CompositeDisposable,
|
||||
Disposable,
|
||||
SingleAssignmentDisposable,
|
||||
)
|
||||
|
||||
from ..periodicscheduler import PeriodicScheduler
|
||||
|
||||
_TState = TypeVar("_TState")
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class QtScheduler(PeriodicScheduler):
|
||||
"""A scheduler for a PyQt5/PySide2 event loop."""
|
||||
|
||||
def __init__(self, qtcore: Any):
|
||||
"""Create a new QtScheduler.
|
||||
|
||||
Args:
|
||||
qtcore: The QtCore instance to use; typically you would get this by
|
||||
either import PyQt5.QtCore or import PySide2.QtCore
|
||||
"""
|
||||
super().__init__()
|
||||
self._qtcore = qtcore
|
||||
self._periodic_timers: Set[Any] = set()
|
||||
|
||||
def schedule(
|
||||
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed.
|
||||
|
||||
Args:
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
return self.schedule_relative(0.0, action, state=state)
|
||||
|
||||
def schedule_relative(
|
||||
self,
|
||||
duetime: typing.RelativeTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed after duetime.
|
||||
|
||||
Args:
|
||||
duetime: Relative time after which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
msecs = max(0, int(self.to_seconds(duetime) * 1000.0))
|
||||
sad = SingleAssignmentDisposable()
|
||||
is_disposed = False
|
||||
|
||||
def invoke_action() -> None:
|
||||
if not is_disposed:
|
||||
sad.disposable = action(self, state)
|
||||
|
||||
log.debug("relative timeout: %sms", msecs)
|
||||
|
||||
# Use static method, let Qt C++ handle QTimer lifetime
|
||||
self._qtcore.QTimer.singleShot(msecs, invoke_action)
|
||||
|
||||
def dispose() -> None:
|
||||
nonlocal is_disposed
|
||||
is_disposed = True
|
||||
|
||||
return CompositeDisposable(sad, Disposable(dispose))
|
||||
|
||||
def schedule_absolute(
|
||||
self,
|
||||
duetime: typing.AbsoluteTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed at duetime.
|
||||
|
||||
Args:
|
||||
duetime: Absolute time at which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
delta: timedelta = self.to_datetime(duetime) - self.now
|
||||
return self.schedule_relative(delta, action, state=state)
|
||||
|
||||
def schedule_periodic(
|
||||
self,
|
||||
period: typing.RelativeTime,
|
||||
action: typing.ScheduledPeriodicAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules a periodic piece of work to be executed in the loop.
|
||||
|
||||
Args:
|
||||
period: Period in seconds for running the work repeatedly.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
msecs = max(0, int(self.to_seconds(period) * 1000.0))
|
||||
sad = SingleAssignmentDisposable()
|
||||
|
||||
def interval() -> None:
|
||||
nonlocal state
|
||||
state = action(state)
|
||||
|
||||
log.debug("periodic timeout: %sms", msecs)
|
||||
|
||||
timer = self._qtcore.QTimer()
|
||||
timer.setSingleShot(not period)
|
||||
timer.timeout.connect(interval)
|
||||
timer.setInterval(msecs)
|
||||
self._periodic_timers.add(timer)
|
||||
timer.start()
|
||||
|
||||
def dispose() -> None:
|
||||
timer.stop()
|
||||
self._periodic_timers.remove(timer)
|
||||
timer.deleteLater()
|
||||
|
||||
return CompositeDisposable(sad, Disposable(dispose))
|
||||
@@ -0,0 +1,98 @@
|
||||
from typing import Any, Optional, TypeVar
|
||||
|
||||
from reactivex import abc, typing
|
||||
from reactivex.disposable import (
|
||||
CompositeDisposable,
|
||||
Disposable,
|
||||
SingleAssignmentDisposable,
|
||||
)
|
||||
|
||||
from ..periodicscheduler import PeriodicScheduler
|
||||
|
||||
_TState = TypeVar("_TState")
|
||||
|
||||
|
||||
class TkinterScheduler(PeriodicScheduler):
|
||||
"""A scheduler that schedules work via the Tkinter main event loop.
|
||||
|
||||
http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/universal.html
|
||||
http://effbot.org/tkinterbook/widget.htm"""
|
||||
|
||||
def __init__(self, root: Any) -> None:
|
||||
"""Create a new TkinterScheduler.
|
||||
|
||||
Args:
|
||||
root: The Tk instance to use; typically, you would get this by
|
||||
import tkinter; tkinter.Tk()
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
self._root = root
|
||||
|
||||
def schedule(
|
||||
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed.
|
||||
|
||||
Args:
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
return self.schedule_relative(0.0, action, state)
|
||||
|
||||
def schedule_relative(
|
||||
self,
|
||||
duetime: typing.RelativeTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed after duetime.
|
||||
|
||||
Args:
|
||||
duetime: Relative time after which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
sad = SingleAssignmentDisposable()
|
||||
|
||||
def invoke_action() -> None:
|
||||
sad.disposable = self.invoke_action(action, state=state)
|
||||
|
||||
msecs = max(0, int(self.to_seconds(duetime) * 1000.0))
|
||||
timer = self._root.after(msecs, invoke_action)
|
||||
|
||||
def dispose() -> None:
|
||||
self._root.after_cancel(timer)
|
||||
|
||||
return CompositeDisposable(sad, Disposable(dispose))
|
||||
|
||||
def schedule_absolute(
|
||||
self,
|
||||
duetime: typing.AbsoluteTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed at duetime.
|
||||
|
||||
Args:
|
||||
duetime: Absolute time at which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
duetime = self.to_datetime(duetime)
|
||||
return self.schedule_relative(duetime - self.now, action, state=state)
|
||||
@@ -0,0 +1,177 @@
|
||||
import logging
|
||||
from typing import Any, Optional, Set, TypeVar, cast
|
||||
|
||||
from reactivex import abc, typing
|
||||
from reactivex.disposable import (
|
||||
CompositeDisposable,
|
||||
Disposable,
|
||||
SingleAssignmentDisposable,
|
||||
)
|
||||
|
||||
from ..periodicscheduler import PeriodicScheduler
|
||||
|
||||
_TState = TypeVar("_TState")
|
||||
|
||||
log = logging.getLogger("Rx")
|
||||
|
||||
|
||||
class WxScheduler(PeriodicScheduler):
|
||||
"""A scheduler for a wxPython event loop."""
|
||||
|
||||
def __init__(self, wx: Any) -> None:
|
||||
"""Create a new WxScheduler.
|
||||
|
||||
Args:
|
||||
wx: The wx module to use; typically, you would get this by
|
||||
import wx
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
self._wx = wx
|
||||
timer_class: Any = self._wx.Timer
|
||||
|
||||
class Timer(timer_class):
|
||||
def __init__(self, callback: typing.Action) -> None:
|
||||
super().__init__() # type: ignore
|
||||
self.callback = callback
|
||||
|
||||
def Notify(self) -> None:
|
||||
self.callback()
|
||||
|
||||
self._timer_class = Timer
|
||||
self._timers: Set[Timer] = set()
|
||||
|
||||
def cancel_all(self) -> None:
|
||||
"""Cancel all scheduled actions.
|
||||
|
||||
Should be called when destroying wx controls to prevent
|
||||
accessing dead wx objects in actions that might be pending.
|
||||
"""
|
||||
for timer in self._timers:
|
||||
timer.Stop() # type: ignore
|
||||
|
||||
def _wxtimer_schedule(
|
||||
self,
|
||||
time: typing.AbsoluteOrRelativeTime,
|
||||
action: typing.ScheduledSingleOrPeriodicAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
periodic: bool = False,
|
||||
) -> abc.DisposableBase:
|
||||
scheduler = self
|
||||
|
||||
sad = SingleAssignmentDisposable()
|
||||
|
||||
def interval() -> None:
|
||||
nonlocal state
|
||||
if periodic:
|
||||
state = cast(typing.ScheduledPeriodicAction[_TState], action)(state)
|
||||
else:
|
||||
sad.disposable = cast(typing.ScheduledAction[_TState], action)(
|
||||
scheduler, state
|
||||
)
|
||||
|
||||
msecs = max(1, int(self.to_seconds(time) * 1000.0)) # Must be non-zero
|
||||
|
||||
log.debug("timeout wx: %s", msecs)
|
||||
|
||||
timer = self._timer_class(interval)
|
||||
# A timer can only be used from the main thread
|
||||
if self._wx.IsMainThread():
|
||||
timer.Start(msecs, oneShot=not periodic) # type: ignore
|
||||
else:
|
||||
self._wx.CallAfter(timer.Start, msecs, oneShot=not periodic) # type: ignore
|
||||
self._timers.add(timer)
|
||||
|
||||
def dispose() -> None:
|
||||
timer.Stop() # type: ignore
|
||||
self._timers.remove(timer)
|
||||
|
||||
return CompositeDisposable(sad, Disposable(dispose))
|
||||
|
||||
def schedule(
|
||||
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed.
|
||||
|
||||
Args:
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
sad = SingleAssignmentDisposable()
|
||||
is_disposed = False
|
||||
|
||||
def invoke_action() -> None:
|
||||
if not is_disposed:
|
||||
sad.disposable = action(self, state)
|
||||
|
||||
self._wx.CallAfter(invoke_action)
|
||||
|
||||
def dispose() -> None:
|
||||
nonlocal is_disposed
|
||||
is_disposed = True
|
||||
|
||||
return CompositeDisposable(sad, Disposable(dispose))
|
||||
|
||||
def schedule_relative(
|
||||
self,
|
||||
duetime: typing.RelativeTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed after duetime.
|
||||
|
||||
Args:
|
||||
duetime: Relative time after which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
return self._wxtimer_schedule(duetime, action, state=state)
|
||||
|
||||
def schedule_absolute(
|
||||
self,
|
||||
duetime: typing.AbsoluteTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed at duetime.
|
||||
|
||||
Args:
|
||||
duetime: Absolute time at which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
duetime = self.to_datetime(duetime)
|
||||
return self._wxtimer_schedule(duetime - self.now, action, state=state)
|
||||
|
||||
def schedule_periodic(
|
||||
self,
|
||||
period: typing.RelativeTime,
|
||||
action: typing.ScheduledPeriodicAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules a periodic piece of work to be executed in the loop.
|
||||
|
||||
Args:
|
||||
period: Period in seconds for running the work repeatedly.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
return self._wxtimer_schedule(period, action, state=state, periodic=True)
|
||||
@@ -0,0 +1,136 @@
|
||||
import logging
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from typing import Optional, TypeVar
|
||||
|
||||
from reactivex import abc, typing
|
||||
from reactivex.disposable import Disposable
|
||||
from reactivex.internal.concurrency import default_thread_factory
|
||||
|
||||
from .eventloopscheduler import EventLoopScheduler
|
||||
from .periodicscheduler import PeriodicScheduler
|
||||
|
||||
_TState = TypeVar("_TState")
|
||||
|
||||
log = logging.getLogger("Rx")
|
||||
|
||||
|
||||
class NewThreadScheduler(PeriodicScheduler):
|
||||
"""Creates an object that schedules each unit of work on a separate thread."""
|
||||
|
||||
def __init__(
|
||||
self, thread_factory: Optional[typing.StartableFactory] = None
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.thread_factory: typing.StartableFactory = (
|
||||
thread_factory or default_thread_factory
|
||||
)
|
||||
|
||||
def schedule(
|
||||
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed.
|
||||
|
||||
Args:
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
scheduler = EventLoopScheduler(
|
||||
thread_factory=self.thread_factory, exit_if_empty=True
|
||||
)
|
||||
return scheduler.schedule(action, state)
|
||||
|
||||
def schedule_relative(
|
||||
self,
|
||||
duetime: typing.RelativeTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed after duetime.
|
||||
|
||||
Args:
|
||||
duetime: Relative time after which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
scheduler = EventLoopScheduler(
|
||||
thread_factory=self.thread_factory, exit_if_empty=True
|
||||
)
|
||||
return scheduler.schedule_relative(duetime, action, state)
|
||||
|
||||
def schedule_absolute(
|
||||
self,
|
||||
duetime: typing.AbsoluteTime,
|
||||
action: typing.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed at duetime.
|
||||
|
||||
Args:
|
||||
duetime: Absolute time at which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
dt = self.to_datetime(duetime)
|
||||
return self.schedule_relative(dt - self.now, action, state=state)
|
||||
|
||||
def schedule_periodic(
|
||||
self,
|
||||
period: typing.RelativeTime,
|
||||
action: typing.ScheduledPeriodicAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules a periodic piece of work.
|
||||
|
||||
Args:
|
||||
period: Period in seconds or timedelta for running the
|
||||
work periodically.
|
||||
action: Action to be executed.
|
||||
state: [Optional] Initial state passed to the action upon
|
||||
the first iteration.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled
|
||||
recurring action (best effort).
|
||||
"""
|
||||
|
||||
seconds: float = self.to_seconds(period)
|
||||
timeout: float = seconds
|
||||
disposed: threading.Event = threading.Event()
|
||||
|
||||
def run() -> None:
|
||||
nonlocal state, timeout
|
||||
while True:
|
||||
if timeout > 0.0:
|
||||
disposed.wait(timeout)
|
||||
if disposed.is_set():
|
||||
return
|
||||
|
||||
time: datetime = self.now
|
||||
|
||||
state = action(state)
|
||||
|
||||
timeout = seconds - (self.now - time).total_seconds()
|
||||
|
||||
thread = self.thread_factory(run)
|
||||
thread.start()
|
||||
|
||||
def dispose() -> None:
|
||||
disposed.set()
|
||||
|
||||
return Disposable(dispose)
|
||||
@@ -0,0 +1,60 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional, TypeVar
|
||||
|
||||
from reactivex import abc, typing
|
||||
from reactivex.disposable import Disposable, MultipleAssignmentDisposable
|
||||
|
||||
from .scheduler import Scheduler
|
||||
|
||||
_TState = TypeVar("_TState")
|
||||
|
||||
|
||||
class PeriodicScheduler(Scheduler, abc.PeriodicSchedulerBase):
|
||||
"""Base class for the various periodic scheduler implementations in this
|
||||
package as well as the mainloop sub-package.
|
||||
"""
|
||||
|
||||
def schedule_periodic(
|
||||
self,
|
||||
period: typing.RelativeTime,
|
||||
action: typing.ScheduledPeriodicAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules a periodic piece of work.
|
||||
|
||||
Args:
|
||||
period: Period in seconds or timedelta for running the
|
||||
work periodically.
|
||||
action: Action to be executed.
|
||||
state: [Optional] Initial state passed to the action upon
|
||||
the first iteration.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled
|
||||
recurring action (best effort).
|
||||
"""
|
||||
|
||||
disp: MultipleAssignmentDisposable = MultipleAssignmentDisposable()
|
||||
seconds: float = self.to_seconds(period)
|
||||
|
||||
def periodic(
|
||||
scheduler: abc.SchedulerBase, state: Optional[_TState] = None
|
||||
) -> Optional[Disposable]:
|
||||
if disp.is_disposed:
|
||||
return None
|
||||
|
||||
now: datetime = scheduler.now
|
||||
|
||||
try:
|
||||
state = action(state)
|
||||
except Exception:
|
||||
disp.dispose()
|
||||
raise
|
||||
|
||||
time = seconds - (scheduler.now - now).total_seconds()
|
||||
disp.disposable = scheduler.schedule_relative(time, periodic, state=state)
|
||||
|
||||
return None
|
||||
|
||||
disp.disposable = self.schedule_relative(period, periodic, state=state)
|
||||
return disp
|
||||
@@ -0,0 +1,49 @@
|
||||
from datetime import datetime
|
||||
from typing import Any, Optional, TypeVar
|
||||
|
||||
from reactivex import abc
|
||||
from reactivex.disposable import SingleAssignmentDisposable
|
||||
|
||||
from .scheduler import Scheduler
|
||||
|
||||
_TState = TypeVar("_TState")
|
||||
|
||||
|
||||
class ScheduledItem(object):
|
||||
def __init__(
|
||||
self,
|
||||
scheduler: Scheduler,
|
||||
state: Optional[_TState],
|
||||
action: abc.ScheduledAction[_TState],
|
||||
duetime: datetime,
|
||||
) -> None:
|
||||
self.scheduler: Scheduler = scheduler
|
||||
self.state: Optional[Any] = state
|
||||
self.action: abc.ScheduledAction[_TState] = action
|
||||
self.duetime: datetime = duetime
|
||||
self.disposable: SingleAssignmentDisposable = SingleAssignmentDisposable()
|
||||
|
||||
def invoke(self) -> None:
|
||||
ret = self.scheduler.invoke_action(self.action, state=self.state)
|
||||
self.disposable.disposable = ret
|
||||
|
||||
def cancel(self) -> None:
|
||||
"""Cancels the work item by disposing the resource returned by
|
||||
invoke_core as soon as possible."""
|
||||
|
||||
self.disposable.dispose()
|
||||
|
||||
def is_cancelled(self) -> bool:
|
||||
return self.disposable.is_disposed
|
||||
|
||||
def __lt__(self, other: "ScheduledItem") -> bool:
|
||||
return self.duetime < other.duetime
|
||||
|
||||
def __gt__(self, other: "ScheduledItem") -> bool:
|
||||
return self.duetime > other.duetime
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
try:
|
||||
return self.duetime == other.duetime
|
||||
except AttributeError:
|
||||
return NotImplemented
|
||||
@@ -0,0 +1,172 @@
|
||||
from abc import abstractmethod
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional, TypeVar
|
||||
|
||||
from reactivex import abc, typing
|
||||
from reactivex.disposable import Disposable
|
||||
from reactivex.internal.basic import default_now
|
||||
from reactivex.internal.constants import UTC_ZERO
|
||||
|
||||
_TState = TypeVar("_TState")
|
||||
|
||||
|
||||
class Scheduler(abc.SchedulerBase):
|
||||
"""Base class for the various scheduler implementations in this package as
|
||||
well as the mainloop sub-package. This does not include an implementation
|
||||
of schedule_periodic, refer to PeriodicScheduler.
|
||||
"""
|
||||
|
||||
@property
|
||||
def now(self) -> datetime:
|
||||
"""Represents a notion of time for this scheduler. Tasks being
|
||||
scheduled on a scheduler will adhere to the time denoted by this
|
||||
property.
|
||||
|
||||
Returns:
|
||||
The scheduler's current time, as a datetime instance.
|
||||
"""
|
||||
|
||||
return default_now()
|
||||
|
||||
@abstractmethod
|
||||
def schedule(
|
||||
self, action: abc.ScheduledAction[_TState], state: Optional[_TState] = None
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed.
|
||||
|
||||
Args:
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
return NotImplemented
|
||||
|
||||
@abstractmethod
|
||||
def schedule_relative(
|
||||
self,
|
||||
duetime: typing.RelativeTime,
|
||||
action: abc.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed after duetime.
|
||||
|
||||
Args:
|
||||
duetime: Relative time after which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
return NotImplemented
|
||||
|
||||
@abstractmethod
|
||||
def schedule_absolute(
|
||||
self,
|
||||
duetime: typing.AbsoluteTime,
|
||||
action: abc.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed at duetime.
|
||||
|
||||
Args:
|
||||
duetime: Absolute time at which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
return NotImplemented
|
||||
|
||||
def invoke_action(
|
||||
self, action: abc.ScheduledAction[_TState], state: Optional[_TState] = None
|
||||
) -> abc.DisposableBase:
|
||||
"""Invoke the given given action. This is typically called by instances
|
||||
of ScheduledItem.
|
||||
|
||||
Args:
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object returned by the action, if any; or a new
|
||||
(no-op) disposable otherwise.
|
||||
"""
|
||||
|
||||
ret = action(self, state)
|
||||
if isinstance(ret, abc.DisposableBase):
|
||||
return ret
|
||||
|
||||
return Disposable()
|
||||
|
||||
@classmethod
|
||||
def to_seconds(cls, value: typing.AbsoluteOrRelativeTime) -> float:
|
||||
"""Converts time value to seconds. This method handles both absolute
|
||||
(datetime) and relative (timedelta) values. If the argument is already
|
||||
a float, it is simply returned unchanged.
|
||||
|
||||
Args:
|
||||
value: the time value to convert to seconds.
|
||||
|
||||
Returns:
|
||||
The value converted to seconds.
|
||||
"""
|
||||
|
||||
if isinstance(value, datetime):
|
||||
value = value - UTC_ZERO
|
||||
|
||||
if isinstance(value, timedelta):
|
||||
value = value.total_seconds()
|
||||
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def to_datetime(cls, value: typing.AbsoluteOrRelativeTime) -> datetime:
|
||||
"""Converts time value to datetime. This method handles both absolute
|
||||
(float) and relative (timedelta) values. If the argument is already
|
||||
a datetime, it is simply returned unchanged.
|
||||
|
||||
Args:
|
||||
value: the time value to convert to datetime.
|
||||
|
||||
Returns:
|
||||
The value converted to datetime.
|
||||
"""
|
||||
|
||||
if isinstance(value, timedelta):
|
||||
value = UTC_ZERO + value
|
||||
elif not isinstance(value, datetime):
|
||||
value = datetime.utcfromtimestamp(value)
|
||||
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def to_timedelta(cls, value: typing.AbsoluteOrRelativeTime) -> timedelta:
|
||||
"""Converts time value to timedelta. This method handles both absolute
|
||||
(datetime) and relative (float) values. If the argument is already
|
||||
a timedelta, it is simply returned unchanged. If the argument is an
|
||||
absolute time, the result value will be the timedelta since the epoch,
|
||||
January 1st, 1970, 00:00:00.
|
||||
|
||||
Args:
|
||||
value: the time value to convert to timedelta.
|
||||
|
||||
Returns:
|
||||
The value converted to timedelta.
|
||||
"""
|
||||
|
||||
if isinstance(value, datetime):
|
||||
value = value - UTC_ZERO
|
||||
elif not isinstance(value, timedelta):
|
||||
value = timedelta(seconds=value)
|
||||
|
||||
return value
|
||||
@@ -0,0 +1,37 @@
|
||||
from concurrent.futures import Future, ThreadPoolExecutor
|
||||
from typing import Any, Optional
|
||||
|
||||
from reactivex import abc, typing
|
||||
|
||||
from .newthreadscheduler import NewThreadScheduler
|
||||
|
||||
|
||||
class ThreadPoolScheduler(NewThreadScheduler):
|
||||
"""A scheduler that schedules work via the thread pool."""
|
||||
|
||||
class ThreadPoolThread(abc.StartableBase):
|
||||
"""Wraps a concurrent future as a thread."""
|
||||
|
||||
def __init__(
|
||||
self, executor: ThreadPoolExecutor, target: typing.StartableTarget
|
||||
):
|
||||
self.executor: ThreadPoolExecutor = executor
|
||||
self.target: typing.StartableTarget = target
|
||||
self.future: Optional["Future[Any]"] = None
|
||||
|
||||
def start(self) -> None:
|
||||
self.future = self.executor.submit(self.target)
|
||||
|
||||
def cancel(self) -> None:
|
||||
if self.future:
|
||||
self.future.cancel()
|
||||
|
||||
def __init__(self, max_workers: Optional[int] = None) -> None:
|
||||
self.executor: ThreadPoolExecutor = ThreadPoolExecutor(max_workers=max_workers)
|
||||
|
||||
def thread_factory(
|
||||
target: typing.StartableTarget,
|
||||
) -> ThreadPoolScheduler.ThreadPoolThread:
|
||||
return self.ThreadPoolThread(self.executor, target)
|
||||
|
||||
super().__init__(thread_factory)
|
||||
@@ -0,0 +1,122 @@
|
||||
from threading import Lock, Timer
|
||||
from typing import MutableMapping, Optional, TypeVar
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
from reactivex import abc, typing
|
||||
from reactivex.disposable import (
|
||||
CompositeDisposable,
|
||||
Disposable,
|
||||
SingleAssignmentDisposable,
|
||||
)
|
||||
|
||||
from .periodicscheduler import PeriodicScheduler
|
||||
|
||||
_TState = TypeVar("_TState")
|
||||
|
||||
|
||||
class TimeoutScheduler(PeriodicScheduler):
|
||||
"""A scheduler that schedules work via a timed callback."""
|
||||
|
||||
_lock = Lock()
|
||||
_global: MutableMapping[type, "TimeoutScheduler"] = WeakKeyDictionary()
|
||||
|
||||
@classmethod
|
||||
def singleton(cls) -> "TimeoutScheduler":
|
||||
with TimeoutScheduler._lock:
|
||||
try:
|
||||
self = TimeoutScheduler._global[cls]
|
||||
except KeyError:
|
||||
self = super().__new__(cls)
|
||||
TimeoutScheduler._global[cls] = self
|
||||
return self
|
||||
|
||||
def __new__(cls) -> "TimeoutScheduler":
|
||||
return cls.singleton()
|
||||
|
||||
def schedule(
|
||||
self, action: abc.ScheduledAction[_TState], state: Optional[_TState] = None
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed.
|
||||
|
||||
Args:
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
sad = SingleAssignmentDisposable()
|
||||
|
||||
def interval() -> None:
|
||||
sad.disposable = self.invoke_action(action, state)
|
||||
|
||||
timer = Timer(0, interval)
|
||||
timer.daemon = True
|
||||
timer.start()
|
||||
|
||||
def dispose() -> None:
|
||||
timer.cancel()
|
||||
|
||||
return CompositeDisposable(sad, Disposable(dispose))
|
||||
|
||||
def schedule_relative(
|
||||
self,
|
||||
duetime: typing.RelativeTime,
|
||||
action: abc.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed after duetime.
|
||||
|
||||
Args:
|
||||
duetime: Relative time after which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
seconds = self.to_seconds(duetime)
|
||||
if seconds <= 0.0:
|
||||
return self.schedule(action, state)
|
||||
|
||||
sad = SingleAssignmentDisposable()
|
||||
|
||||
def interval() -> None:
|
||||
sad.disposable = self.invoke_action(action, state)
|
||||
|
||||
timer = Timer(seconds, interval)
|
||||
timer.daemon = True
|
||||
timer.start()
|
||||
|
||||
def dispose() -> None:
|
||||
timer.cancel()
|
||||
|
||||
return CompositeDisposable(sad, Disposable(dispose))
|
||||
|
||||
def schedule_absolute(
|
||||
self,
|
||||
duetime: typing.AbsoluteTime,
|
||||
action: abc.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed at duetime.
|
||||
|
||||
Args:
|
||||
duetime: Absolute time at which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
duetime = self.to_datetime(duetime)
|
||||
return self.schedule_relative(duetime - self.now, action, state)
|
||||
|
||||
|
||||
__all__ = ["TimeoutScheduler"]
|
||||
@@ -0,0 +1,62 @@
|
||||
from collections import deque
|
||||
from threading import Condition, Lock
|
||||
from typing import Deque
|
||||
|
||||
from reactivex.internal.priorityqueue import PriorityQueue
|
||||
|
||||
from .scheduleditem import ScheduledItem
|
||||
|
||||
|
||||
class Trampoline:
|
||||
def __init__(self) -> None:
|
||||
self._idle: bool = True
|
||||
self._queue: PriorityQueue[ScheduledItem] = PriorityQueue()
|
||||
self._lock: Lock = Lock()
|
||||
self._condition: Condition = Condition(self._lock)
|
||||
|
||||
def idle(self) -> bool:
|
||||
with self._lock:
|
||||
return self._idle
|
||||
|
||||
def run(self, item: ScheduledItem) -> None:
|
||||
with self._lock:
|
||||
self._queue.enqueue(item)
|
||||
if self._idle:
|
||||
self._idle = False
|
||||
else:
|
||||
self._condition.notify()
|
||||
return
|
||||
try:
|
||||
self._run()
|
||||
finally:
|
||||
with self._lock:
|
||||
self._idle = True
|
||||
self._queue.clear()
|
||||
|
||||
def _run(self) -> None:
|
||||
ready: Deque[ScheduledItem] = deque()
|
||||
while True:
|
||||
with self._lock:
|
||||
while len(self._queue) > 0:
|
||||
item: ScheduledItem = self._queue.peek()
|
||||
if item.duetime <= item.scheduler.now:
|
||||
self._queue.dequeue()
|
||||
ready.append(item)
|
||||
else:
|
||||
break
|
||||
|
||||
while len(ready) > 0:
|
||||
item = ready.popleft()
|
||||
if not item.is_cancelled():
|
||||
item.invoke()
|
||||
|
||||
with self._lock:
|
||||
if len(self._queue) == 0:
|
||||
break
|
||||
item = self._queue.peek()
|
||||
seconds = (item.duetime - item.scheduler.now).total_seconds()
|
||||
if seconds > 0.0:
|
||||
self._condition.wait(seconds)
|
||||
|
||||
|
||||
__all__ = ["Trampoline"]
|
||||
@@ -0,0 +1,117 @@
|
||||
import logging
|
||||
from typing import Optional, TypeVar
|
||||
|
||||
from reactivex import abc, typing
|
||||
from reactivex.abc.disposable import DisposableBase
|
||||
from reactivex.abc.scheduler import ScheduledAction
|
||||
from reactivex.internal.constants import DELTA_ZERO
|
||||
|
||||
from .scheduleditem import ScheduledItem
|
||||
from .scheduler import Scheduler
|
||||
from .trampoline import Trampoline
|
||||
|
||||
_TState = TypeVar("_TState")
|
||||
log = logging.getLogger("Rx")
|
||||
|
||||
|
||||
class TrampolineScheduler(Scheduler):
|
||||
"""Represents an object that schedules units of work on the trampoline.
|
||||
You should never schedule timeouts using the *TrampolineScheduler*, as
|
||||
it will block the thread while waiting.
|
||||
|
||||
Each instance has its own trampoline (and queue), and you can schedule work
|
||||
on it from different threads. Beware though, that the first thread to call
|
||||
a *schedule* method while the trampoline is idle will then remain occupied
|
||||
until the queue is empty.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
|
||||
self._tramp = Trampoline()
|
||||
|
||||
def get_trampoline(self) -> Trampoline:
|
||||
return self._tramp
|
||||
|
||||
def schedule(
|
||||
self, action: abc.ScheduledAction[_TState], state: Optional[_TState] = None
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed.
|
||||
|
||||
Args:
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
return self.schedule_absolute(self.now, action, state=state)
|
||||
|
||||
def schedule_relative(
|
||||
self,
|
||||
duetime: typing.RelativeTime,
|
||||
action: abc.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed after duetime.
|
||||
|
||||
Args:
|
||||
duetime: Relative time after which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
duetime = max(DELTA_ZERO, self.to_timedelta(duetime))
|
||||
return self.schedule_absolute(self.now + duetime, action, state=state)
|
||||
|
||||
def schedule_absolute(
|
||||
self,
|
||||
duetime: typing.AbsoluteTime,
|
||||
action: abc.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed at duetime.
|
||||
|
||||
Args:
|
||||
duetime: Absolute time at which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
dt = self.to_datetime(duetime)
|
||||
if dt > self.now:
|
||||
log.warning("Do not schedule blocking work!")
|
||||
item: ScheduledItem = ScheduledItem(self, state, action, dt)
|
||||
|
||||
self.get_trampoline().run(item)
|
||||
|
||||
return item.disposable
|
||||
|
||||
def schedule_required(self) -> bool:
|
||||
"""Test if scheduling is required.
|
||||
|
||||
Gets a value indicating whether the caller must call a
|
||||
schedule method. If the trampoline is active, then it returns
|
||||
False; otherwise, if the trampoline is not active, then it
|
||||
returns True.
|
||||
"""
|
||||
return self.get_trampoline().idle()
|
||||
|
||||
def ensure_trampoline(
|
||||
self, action: ScheduledAction[_TState]
|
||||
) -> Optional[DisposableBase]:
|
||||
"""Method for testing the TrampolineScheduler."""
|
||||
|
||||
if self.schedule_required():
|
||||
return self.schedule(action)
|
||||
|
||||
return action(self, None)
|
||||
@@ -0,0 +1,251 @@
|
||||
import logging
|
||||
import threading
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Optional, TypeVar
|
||||
|
||||
from reactivex import abc, typing
|
||||
from reactivex.abc.scheduler import AbsoluteTime
|
||||
from reactivex.internal import ArgumentOutOfRangeException, PriorityQueue
|
||||
|
||||
from .periodicscheduler import PeriodicScheduler
|
||||
from .scheduleditem import ScheduledItem
|
||||
|
||||
log = logging.getLogger("Rx")
|
||||
|
||||
MAX_SPINNING = 100
|
||||
|
||||
_TState = TypeVar("_TState")
|
||||
|
||||
|
||||
class VirtualTimeScheduler(PeriodicScheduler):
|
||||
"""Virtual Scheduler. This scheduler should work with either
|
||||
datetime/timespan or ticks as int/int"""
|
||||
|
||||
def __init__(self, initial_clock: typing.AbsoluteTime = 0) -> None:
|
||||
"""Creates a new virtual time scheduler with the specified
|
||||
initial clock value.
|
||||
|
||||
Args:
|
||||
initial_clock: Initial value for the clock.
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
self._clock = initial_clock
|
||||
self._is_enabled = False
|
||||
self._lock: threading.Lock = threading.Lock()
|
||||
self._queue: PriorityQueue[ScheduledItem] = PriorityQueue()
|
||||
|
||||
def _get_clock(self) -> AbsoluteTime:
|
||||
with self._lock:
|
||||
return self._clock
|
||||
|
||||
clock = property(fget=_get_clock)
|
||||
|
||||
@property
|
||||
def now(self) -> datetime:
|
||||
"""Represents a notion of time for this scheduler. Tasks being
|
||||
scheduled on a scheduler will adhere to the time denoted by this
|
||||
property.
|
||||
|
||||
Returns:
|
||||
The scheduler's current time, as a datetime instance.
|
||||
"""
|
||||
|
||||
return self.to_datetime(self._clock)
|
||||
|
||||
def schedule(
|
||||
self, action: typing.ScheduledAction[_TState], state: Optional[_TState] = None
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed.
|
||||
|
||||
Args:
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
return self.schedule_absolute(self._clock, action, state=state)
|
||||
|
||||
def schedule_relative(
|
||||
self,
|
||||
duetime: typing.RelativeTime,
|
||||
action: abc.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed after duetime.
|
||||
|
||||
Args:
|
||||
duetime: Relative time after which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
time: typing.AbsoluteTime = self.add(self._clock, duetime)
|
||||
return self.schedule_absolute(time, action, state=state)
|
||||
|
||||
def schedule_absolute(
|
||||
self,
|
||||
duetime: typing.AbsoluteTime,
|
||||
action: abc.ScheduledAction[_TState],
|
||||
state: Optional[_TState] = None,
|
||||
) -> abc.DisposableBase:
|
||||
"""Schedules an action to be executed at duetime.
|
||||
|
||||
Args:
|
||||
duetime: Absolute time at which to execute the action.
|
||||
action: Action to be executed.
|
||||
state: [Optional] state to be given to the action function.
|
||||
|
||||
Returns:
|
||||
The disposable object used to cancel the scheduled action
|
||||
(best effort).
|
||||
"""
|
||||
|
||||
dt = self.to_datetime(duetime)
|
||||
si: ScheduledItem = ScheduledItem(self, state, action, dt)
|
||||
with self._lock:
|
||||
self._queue.enqueue(si)
|
||||
return si.disposable
|
||||
|
||||
def start(self) -> Any:
|
||||
"""Starts the virtual time scheduler."""
|
||||
|
||||
with self._lock:
|
||||
if self._is_enabled:
|
||||
return
|
||||
self._is_enabled = True
|
||||
|
||||
spinning: int = 0
|
||||
|
||||
while True:
|
||||
with self._lock:
|
||||
if not self._is_enabled or not self._queue:
|
||||
break
|
||||
|
||||
item: ScheduledItem = self._queue.dequeue()
|
||||
|
||||
if item.duetime > self.now:
|
||||
if isinstance(self._clock, datetime):
|
||||
self._clock = item.duetime
|
||||
else:
|
||||
self._clock = self.to_seconds(item.duetime)
|
||||
spinning = 0
|
||||
|
||||
elif spinning > MAX_SPINNING:
|
||||
if isinstance(self._clock, datetime):
|
||||
self.clock += timedelta(microseconds=1000)
|
||||
else:
|
||||
self._clock += 1.0
|
||||
spinning = 0
|
||||
|
||||
if not item.is_cancelled():
|
||||
item.invoke()
|
||||
spinning += 1
|
||||
|
||||
self.stop()
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Stops the virtual time scheduler."""
|
||||
|
||||
with self._lock:
|
||||
self._is_enabled = False
|
||||
|
||||
def advance_to(self, time: typing.AbsoluteTime) -> None:
|
||||
"""Advances the schedulers clock to the specified absolute time,
|
||||
running all work til that point.
|
||||
|
||||
Args:
|
||||
time: Absolute time to advance the schedulers clock to.
|
||||
"""
|
||||
|
||||
dt: datetime = self.to_datetime(time)
|
||||
with self._lock:
|
||||
if self.now > dt:
|
||||
raise ArgumentOutOfRangeException()
|
||||
|
||||
if self.now == dt or self._is_enabled:
|
||||
return
|
||||
|
||||
self._is_enabled = True
|
||||
|
||||
while True:
|
||||
with self._lock:
|
||||
if not self._is_enabled or not self._queue:
|
||||
break
|
||||
|
||||
item: ScheduledItem = self._queue.peek()
|
||||
|
||||
if item.duetime > dt:
|
||||
break
|
||||
|
||||
if item.duetime > self.now:
|
||||
if isinstance(self._clock, datetime):
|
||||
self._clock = item.duetime
|
||||
else:
|
||||
self._clock = self.to_seconds(item.duetime)
|
||||
|
||||
self._queue.dequeue()
|
||||
|
||||
if not item.is_cancelled():
|
||||
item.invoke()
|
||||
|
||||
with self._lock:
|
||||
self._is_enabled = False
|
||||
if isinstance(self._clock, datetime):
|
||||
self._clock = dt
|
||||
else:
|
||||
self._clock = self.to_seconds(dt)
|
||||
|
||||
def advance_by(self, time: typing.RelativeTime) -> None:
|
||||
"""Advances the schedulers clock by the specified relative time,
|
||||
running all work scheduled for that timespan.
|
||||
|
||||
Args:
|
||||
time: Relative time to advance the schedulers clock by.
|
||||
"""
|
||||
|
||||
log.debug("VirtualTimeScheduler.advance_by(time=%s)", time)
|
||||
|
||||
self.advance_to(self.add(self.now, self.to_timedelta(time)))
|
||||
|
||||
def sleep(self, time: typing.RelativeTime) -> None:
|
||||
"""Advances the schedulers clock by the specified relative time.
|
||||
|
||||
Args:
|
||||
time: Relative time to advance the schedulers clock by.
|
||||
"""
|
||||
|
||||
absolute = self.add(self.now, self.to_timedelta(time))
|
||||
dt: datetime = self.to_datetime(absolute)
|
||||
|
||||
if self.now > dt:
|
||||
raise ArgumentOutOfRangeException()
|
||||
|
||||
with self._lock:
|
||||
if isinstance(self._clock, datetime):
|
||||
self._clock = dt
|
||||
else:
|
||||
self._clock = self.to_seconds(dt)
|
||||
|
||||
@classmethod
|
||||
def add(
|
||||
cls, absolute: typing.AbsoluteTime, relative: typing.RelativeTime
|
||||
) -> typing.AbsoluteTime:
|
||||
"""Adds a relative time value to an absolute time value.
|
||||
|
||||
Args:
|
||||
absolute: Absolute virtual time value.
|
||||
relative: Relative virtual time value to add.
|
||||
|
||||
Returns:
|
||||
The resulting absolute virtual time sum value.
|
||||
"""
|
||||
|
||||
return cls.to_datetime(absolute) + cls.to_timedelta(relative)
|
||||
Reference in New Issue
Block a user