825 lines
26 KiB
Python
825 lines
26 KiB
Python
"""PEP 3156 event loop based on CoreFoundation."""
|
|
|
|
import contextvars
|
|
import inspect
|
|
import sys
|
|
import threading
|
|
import warnings
|
|
from asyncio import (
|
|
coroutines,
|
|
events,
|
|
tasks,
|
|
unix_events,
|
|
)
|
|
from ctypes import CFUNCTYPE, POINTER, Structure, c_double, c_int, c_ulong, c_void_p
|
|
|
|
from .api import ObjCClass, objc_const
|
|
from .runtime import load_library, objc_id
|
|
from .types import CFIndex
|
|
|
|
if sys.version_info < (3, 14):
|
|
from asyncio import (
|
|
AbstractEventLoopPolicy,
|
|
DefaultEventLoopPolicy,
|
|
SafeChildWatcher,
|
|
set_event_loop_policy,
|
|
)
|
|
elif sys.version_info < (3, 16):
|
|
# Python 3.14 finalized the deprecation of SafeChildWatcher. There's no
|
|
# replacement API; the feature can be removed.
|
|
#
|
|
# Python 3.14 also started the deprecation of event loop policies, to be
|
|
# finalized in Python 3.16; there was some symbol renaming to assist in
|
|
# making the deprecation visible. See
|
|
# https://github.com/python/cpython/issues/127949 for details.
|
|
from asyncio import (
|
|
_AbstractEventLoopPolicy as AbstractEventLoopPolicy,
|
|
_DefaultEventLoopPolicy as DefaultEventLoopPolicy,
|
|
)
|
|
|
|
__all__ = [
|
|
"EventLoopPolicy",
|
|
"CocoaLifecycle",
|
|
"RubiconEventLoop",
|
|
"iOSLifecycle",
|
|
]
|
|
|
|
|
|
###########################################################################
|
|
# CoreFoundation types and constants needed for async handlers
|
|
###########################################################################
|
|
|
|
libcf = load_library("CoreFoundation")
|
|
|
|
CFAllocatorRef = objc_id
|
|
kCFAllocatorDefault = None
|
|
|
|
CFDataRef = objc_id
|
|
CFOptionFlags = c_ulong
|
|
CFStringRef = objc_id
|
|
CFTypeRef = objc_id
|
|
|
|
CFRunLoopRef = objc_id
|
|
CFRunLoopMode = CFStringRef
|
|
CFRunLoopSourceRef = objc_id
|
|
|
|
CFRunLoopTimerRef = objc_id
|
|
CFRunLoopTimerCallBack = CFUNCTYPE(None, CFRunLoopTimerRef, c_void_p)
|
|
|
|
CFSocketRef = objc_id
|
|
CFSocketCallbackType = c_int
|
|
CFSocketCallback = CFUNCTYPE(
|
|
None, CFSocketRef, CFSocketCallbackType, CFDataRef, c_void_p, c_void_p
|
|
)
|
|
CFSocketNativeHandle = c_int
|
|
|
|
CFTimeInterval = c_double
|
|
CFAbsoluteTime = CFTimeInterval
|
|
|
|
|
|
class CFRunLoopTimerContext(Structure):
|
|
_fields_ = [
|
|
# CFStringRef (*copyDescription)(const void *info)
|
|
("copyDescription", CFUNCTYPE(CFStringRef, c_void_p)),
|
|
("info", c_void_p),
|
|
# void (*release)(const void *info)
|
|
("release", CFUNCTYPE(None, c_void_p)),
|
|
# const void *(*retain)(const void *info)
|
|
("retain", CFUNCTYPE(None, c_void_p)),
|
|
("version", CFIndex),
|
|
]
|
|
|
|
|
|
kCFRunLoopCommonModes = objc_const(libcf, "kCFRunLoopCommonModes")
|
|
|
|
kCFSocketNoCallBack = 0
|
|
kCFSocketReadCallBack = 1
|
|
kCFSocketAcceptCallBack = 2
|
|
kCFSocketDataCallBack = 3
|
|
kCFSocketConnectCallBack = 4
|
|
kCFSocketWriteCallBack = 8
|
|
|
|
kCFSocketAutomaticallyReenableReadCallBack = 1
|
|
kCFSocketAutomaticallyReenableWriteCallBack = 8
|
|
|
|
NSRunLoop = ObjCClass("NSRunLoop")
|
|
|
|
###########################################################################
|
|
# CoreFoundation methods for async handlers
|
|
###########################################################################
|
|
|
|
|
|
class CFSocketContext(Structure):
|
|
_fields_ = [
|
|
# CFStringRef (*copyDescription)(const void *info)
|
|
("copyDescription", CFUNCTYPE(CFStringRef, c_void_p)),
|
|
("info", c_void_p),
|
|
# void (*release)(const void *info)
|
|
("release", CFUNCTYPE(None, c_void_p)),
|
|
# const void *(*retain)(const void *info)
|
|
("retain", CFUNCTYPE(None, c_void_p)),
|
|
("version", CFIndex),
|
|
]
|
|
|
|
|
|
libcf.CFAbsoluteTimeGetCurrent.restype = CFAbsoluteTime
|
|
libcf.CFAbsoluteTimeGetCurrent.argtypes = []
|
|
|
|
libcf.CFRelease.restype = CFTypeRef
|
|
libcf.CFRelease.argtypes = [CFTypeRef]
|
|
|
|
libcf.CFRetain.restype = CFTypeRef
|
|
libcf.CFRetain.argtypes = [CFTypeRef]
|
|
|
|
libcf.CFRunLoopAddSource.restype = None
|
|
libcf.CFRunLoopAddSource.argtypes = [CFRunLoopRef, CFRunLoopSourceRef, CFRunLoopMode]
|
|
|
|
libcf.CFRunLoopAddTimer.restype = None
|
|
libcf.CFRunLoopAddTimer.argtypes = [CFRunLoopRef, CFRunLoopTimerRef, CFRunLoopMode]
|
|
|
|
libcf.CFRunLoopGetMain.restype = CFRunLoopRef
|
|
libcf.CFRunLoopGetMain.argtypes = []
|
|
|
|
libcf.CFRunLoopGetCurrent.restype = CFRunLoopRef
|
|
libcf.CFRunLoopGetCurrent.argtypes = []
|
|
|
|
libcf.CFRunLoopRemoveSource.restype = None
|
|
libcf.CFRunLoopRemoveSource.argtypes = [CFRunLoopRef, CFRunLoopSourceRef, CFRunLoopMode]
|
|
|
|
libcf.CFRunLoopRemoveTimer.restype = None
|
|
libcf.CFRunLoopRemoveTimer.argtypes = [CFRunLoopRef, CFRunLoopTimerRef, CFRunLoopMode]
|
|
|
|
libcf.CFRunLoopRun.restype = None
|
|
libcf.CFRunLoopRun.argtypes = []
|
|
|
|
libcf.CFRunLoopStop.restype = None
|
|
libcf.CFRunLoopStop.argtypes = [CFRunLoopRef]
|
|
|
|
libcf.CFRunLoopTimerCreate.restype = CFRunLoopTimerRef
|
|
libcf.CFRunLoopTimerCreate.argtypes = [
|
|
CFAllocatorRef,
|
|
CFAbsoluteTime,
|
|
CFTimeInterval,
|
|
CFOptionFlags,
|
|
CFIndex,
|
|
CFRunLoopTimerCallBack,
|
|
POINTER(CFRunLoopTimerContext),
|
|
]
|
|
|
|
libcf.CFSocketCreateRunLoopSource.restype = CFRunLoopSourceRef
|
|
libcf.CFSocketCreateRunLoopSource.argtypes = [CFAllocatorRef, CFSocketRef, CFIndex]
|
|
|
|
libcf.CFSocketCreateWithNative.restype = CFSocketRef
|
|
libcf.CFSocketCreateWithNative.argtypes = [
|
|
CFAllocatorRef,
|
|
CFSocketNativeHandle,
|
|
CFOptionFlags,
|
|
CFSocketCallback,
|
|
POINTER(CFSocketContext),
|
|
]
|
|
|
|
libcf.CFSocketDisableCallBacks.restype = None
|
|
libcf.CFSocketDisableCallBacks.argtypes = [CFSocketRef, CFOptionFlags]
|
|
|
|
libcf.CFSocketEnableCallBacks.restype = None
|
|
libcf.CFSocketEnableCallBacks.argtypes = [CFSocketRef, CFOptionFlags]
|
|
|
|
libcf.CFSocketInvalidate.restype = None
|
|
libcf.CFSocketInvalidate.argtypes = [CFSocketRef]
|
|
|
|
libcf.CFSocketSetSocketFlags.restype = None
|
|
libcf.CFSocketSetSocketFlags.argtypes = [CFSocketRef, CFOptionFlags]
|
|
|
|
|
|
###########################################################################
|
|
# CoreFoundation types needed for async handlers
|
|
###########################################################################
|
|
|
|
|
|
class CFTimerHandle(events.TimerHandle):
|
|
def _cf_timer_callback(self, callback, args):
|
|
# Create a CF-compatible callback for a timer event
|
|
def cf_timer_callback(cftimer, extra):
|
|
callback(*args)
|
|
# Deregister the callback after it has been performed.
|
|
self._loop._timers.discard(self)
|
|
|
|
return CFRunLoopTimerCallBack(cf_timer_callback)
|
|
|
|
def __init__(self, *, loop, timeout, callback, args):
|
|
super().__init__(
|
|
libcf.CFAbsoluteTimeGetCurrent() + timeout,
|
|
self._cf_timer_callback(callback, args),
|
|
None,
|
|
loop,
|
|
)
|
|
|
|
self._timeout = timeout
|
|
|
|
# Retain a reference to the Handle
|
|
self._loop._timers.add(self)
|
|
|
|
# Create the timer event, and add it to the run loop.
|
|
self._timer = libcf.CFRunLoopTimerCreate(
|
|
kCFAllocatorDefault,
|
|
self._when,
|
|
0, # interval
|
|
0, # flags
|
|
0, # order
|
|
self._callback, # callout
|
|
None, # context
|
|
)
|
|
|
|
libcf.CFRunLoopAddTimer(
|
|
self._loop._cfrunloop, self._timer, kCFRunLoopCommonModes
|
|
)
|
|
|
|
def cancel(self):
|
|
"""Cancel the Timer handle."""
|
|
super().cancel()
|
|
libcf.CFRunLoopRemoveTimer(
|
|
self._loop._cfrunloop, self._timer, kCFRunLoopCommonModes
|
|
)
|
|
self._loop._timers.discard(self)
|
|
|
|
|
|
class CFSocketHandle(events.Handle):
|
|
# Create a CF-compatible callback for a source event
|
|
def _cf_socket_callback(
|
|
self, cfSocket, callbackType, ignoredAddress, ignoredData, context
|
|
):
|
|
if self._fd not in self._loop._sockets:
|
|
# Spurious notifications seem to be generated sometimes if you
|
|
# CFSocketDisableCallBacks in the middle of an event. I don't know
|
|
# about this FD any more, so let's get rid of it.
|
|
libcf.CFRunLoopRemoveSource(
|
|
self._loop._cfrunloop, self._src, kCFRunLoopCommonModes
|
|
)
|
|
self._src = None
|
|
return
|
|
|
|
if callbackType == kCFSocketReadCallBack and self._reader:
|
|
callback, args = self._reader
|
|
elif callbackType == kCFSocketWriteCallBack and self._writer:
|
|
callback, args = self._writer
|
|
else:
|
|
callback = None
|
|
|
|
if callback:
|
|
callback(*args)
|
|
|
|
def __init__(self, *, loop, fd):
|
|
"""Register a file descriptor with the CFRunLoop, or modify its state
|
|
so that it's listening for both notifications (read and write) rather
|
|
than just one; used to implement add_reader and add_writer."""
|
|
super().__init__(CFSocketCallback(self._cf_socket_callback), None, loop)
|
|
|
|
# Retain a reference to the Handle
|
|
self._loop._sockets[fd] = self
|
|
self._reader = None
|
|
self._writer = None
|
|
|
|
self._fd = fd
|
|
self._cf_socket = libcf.CFSocketCreateWithNative(
|
|
kCFAllocatorDefault,
|
|
self._fd,
|
|
kCFSocketReadCallBack | kCFSocketWriteCallBack | kCFSocketConnectCallBack,
|
|
self._callback,
|
|
None,
|
|
)
|
|
libcf.CFSocketSetSocketFlags(
|
|
self._cf_socket,
|
|
kCFSocketAutomaticallyReenableReadCallBack
|
|
| kCFSocketAutomaticallyReenableWriteCallBack,
|
|
# # This extra flag is to ensure that CF doesn't (destructively,
|
|
# # because destructively is the only way to do it) retrieve
|
|
# # SO_ERROR
|
|
# 1 << 6
|
|
)
|
|
self._src = libcf.CFSocketCreateRunLoopSource(
|
|
kCFAllocatorDefault, self._cf_socket, 0
|
|
)
|
|
libcf.CFRunLoopAddSource(
|
|
self._loop._cfrunloop, self._src, kCFRunLoopCommonModes
|
|
)
|
|
libcf.CFSocketDisableCallBacks(
|
|
self._cf_socket,
|
|
kCFSocketReadCallBack | kCFSocketWriteCallBack | kCFSocketConnectCallBack,
|
|
)
|
|
|
|
def enable_read(self, callback, args):
|
|
"""Add a callback for read activity on the socket."""
|
|
libcf.CFSocketEnableCallBacks(self._cf_socket, kCFSocketReadCallBack)
|
|
self._reader = (callback, args)
|
|
|
|
def disable_read(self):
|
|
"""Remove the callback for read activity on the socket."""
|
|
libcf.CFSocketDisableCallBacks(self._cf_socket, kCFSocketReadCallBack)
|
|
self._reader = None
|
|
self.cancel()
|
|
|
|
def enable_write(self, callback, args):
|
|
"""Add a callback for write activity on the socket."""
|
|
libcf.CFSocketEnableCallBacks(self._cf_socket, kCFSocketWriteCallBack)
|
|
self._writer = (callback, args)
|
|
|
|
def disable_write(self):
|
|
"""Remove the callback for write activity on the socket."""
|
|
libcf.CFSocketDisableCallBacks(self._cf_socket, kCFSocketWriteCallBack)
|
|
self._writer = None
|
|
self.cancel()
|
|
|
|
def cancel(self):
|
|
"""(Potentially) cancel the socket handle.
|
|
|
|
A socket handle can have both reader and writer components; a call to cancel a
|
|
socket handle will only be successful if *both* the reader and writer component
|
|
have been disabled. If either is still active, cancel() will be a no-op.
|
|
"""
|
|
if self._reader is None and self._writer is None and self._src:
|
|
super().cancel()
|
|
del self._loop._sockets[self._fd]
|
|
|
|
libcf.CFRunLoopRemoveSource(
|
|
self._loop._cfrunloop, self._src, kCFRunLoopCommonModes
|
|
)
|
|
libcf.CFSocketInvalidate(self._cf_socket)
|
|
|
|
|
|
def context_callback(context, callback):
|
|
if context is None:
|
|
context = contextvars.copy_context()
|
|
|
|
def _callback(*args):
|
|
context.run(callback, *args)
|
|
|
|
return _callback
|
|
|
|
|
|
class CFEventLoop(unix_events.SelectorEventLoop):
|
|
def __init__(self, lifecycle=None):
|
|
self._lifecycle = lifecycle
|
|
self._cfrunloop = libcf.CFRetain(libcf.CFRunLoopGetCurrent())
|
|
self._running = False
|
|
|
|
self._timers = set()
|
|
self._accept_futures = {}
|
|
self._sockets = {}
|
|
|
|
super().__init__()
|
|
|
|
def __del__(self):
|
|
libcf.CFRelease(self._cfrunloop)
|
|
super().__del__()
|
|
|
|
def _add_reader(self, fd, callback, *args):
|
|
try:
|
|
handle = self._sockets[fd]
|
|
except KeyError:
|
|
handle = CFSocketHandle(loop=self, fd=fd)
|
|
self._sockets[fd] = handle
|
|
|
|
handle.enable_read(callback, args)
|
|
|
|
def add_reader(self, fd, callback, *args):
|
|
"""Add a reader callback.
|
|
|
|
Method is a direct call through to _add_reader to reflect an
|
|
internal implementation detail added in Python3.5.
|
|
"""
|
|
self._add_reader(fd, callback, *args)
|
|
|
|
def _remove_reader(self, fd):
|
|
try:
|
|
self._sockets[fd].disable_read()
|
|
return True
|
|
except KeyError:
|
|
return False
|
|
|
|
def remove_reader(self, fd):
|
|
"""Remove a reader callback.
|
|
|
|
Method is a direct call through to _remove_reader to reflect an
|
|
internal implementation detail added in Python3.5.
|
|
"""
|
|
self._remove_reader(fd)
|
|
|
|
def _add_writer(self, fd, callback, *args):
|
|
try:
|
|
handle = self._sockets[fd]
|
|
except KeyError:
|
|
handle = CFSocketHandle(loop=self, fd=fd)
|
|
self._sockets[fd] = handle
|
|
|
|
handle.enable_write(callback, args)
|
|
|
|
def add_writer(self, fd, callback, *args):
|
|
"""Add a writer callback.
|
|
|
|
Method is a direct call through to _add_writer to reflect an
|
|
internal implementation detail added in Python3.5.
|
|
"""
|
|
self._add_writer(fd, callback, *args)
|
|
|
|
def _remove_writer(self, fd):
|
|
try:
|
|
self._sockets[fd].disable_write()
|
|
return True
|
|
except KeyError:
|
|
return False
|
|
|
|
def remove_writer(self, fd):
|
|
"""Remove a writer callback.
|
|
|
|
Method is a direct call through to _remove_writer to reflect an
|
|
internal implementation detail added in Python3.5.
|
|
"""
|
|
self._remove_writer(fd)
|
|
|
|
######################################################################
|
|
# Lifecycle and execution
|
|
######################################################################
|
|
def _check_not_coroutine(self, callback, name):
|
|
"""Check whether the given callback is a coroutine or not."""
|
|
if coroutines.iscoroutine(callback) or inspect.iscoroutinefunction(callback):
|
|
raise TypeError(f"coroutines cannot be used with {name}()")
|
|
|
|
def is_running(self):
|
|
"""Returns True if the event loop is running."""
|
|
return self._running
|
|
|
|
def run(self):
|
|
"""Internal implementation of run using the CoreFoundation event
|
|
loop."""
|
|
recursive = self.is_running()
|
|
if (
|
|
not recursive
|
|
and hasattr(events, "_get_running_loop")
|
|
and events._get_running_loop()
|
|
):
|
|
raise RuntimeError(
|
|
"Cannot run the event loop while another loop is running"
|
|
)
|
|
|
|
if not recursive:
|
|
self._running = True
|
|
if hasattr(events, "_set_running_loop"):
|
|
events._set_running_loop(self)
|
|
|
|
try:
|
|
self._lifecycle.start()
|
|
finally:
|
|
if not recursive:
|
|
self._running = False
|
|
if hasattr(events, "_set_running_loop"):
|
|
events._set_running_loop(None)
|
|
|
|
def run_until_complete(self, future, **kw):
|
|
"""Run until the Future is done.
|
|
|
|
If the argument is a coroutine, it is wrapped in a Task.
|
|
|
|
WARNING: It would be disastrous to call run_until_complete()
|
|
with the same coroutine twice -- it would wrap it in two
|
|
different Tasks and that can't be good.
|
|
|
|
Return the Future's result, or raise its exception.
|
|
"""
|
|
|
|
def stop(f):
|
|
self.stop()
|
|
|
|
future = tasks.ensure_future(future, loop=self)
|
|
future.add_done_callback(stop)
|
|
try:
|
|
self.run_forever(**kw)
|
|
finally:
|
|
future.remove_done_callback(stop)
|
|
|
|
if not future.done():
|
|
raise RuntimeError("Event loop stopped before Future completed.")
|
|
|
|
return future.result()
|
|
|
|
def run_forever(self, lifecycle=None):
|
|
"""Run until stop() is called."""
|
|
if not self._lifecycle:
|
|
self._set_lifecycle(
|
|
lifecycle if lifecycle else CFLifecycle(self._cfrunloop)
|
|
)
|
|
|
|
if self.is_running():
|
|
raise RuntimeError(
|
|
"Recursively calling run_forever is forbidden. "
|
|
"To recursively run the event loop, call run()."
|
|
)
|
|
|
|
try:
|
|
self.run()
|
|
finally:
|
|
self.stop()
|
|
|
|
def run_forever_cooperatively(self, lifecycle=None):
|
|
"""A non-blocking version of :meth:`run_forever`.
|
|
|
|
This may seem like nonsense; however, an iOS app is not expected to
|
|
invoke a blocking "main event loop" method. As a result, we need to
|
|
be able to *start* Python event loop handling, but then return control
|
|
to the main app to start the actual event loop.
|
|
|
|
The implementation is effectively all the parts of a call to
|
|
:meth:`run_forever()`, but without any of the shutdown/cleanup logic.
|
|
"""
|
|
if not self._lifecycle:
|
|
self._set_lifecycle(
|
|
lifecycle if lifecycle else CFLifecycle(self._cfrunloop)
|
|
)
|
|
|
|
if self.is_running():
|
|
raise RuntimeError(
|
|
"Recursively calling run_forever is forbidden. "
|
|
"To recursively run the event loop, call run()."
|
|
)
|
|
|
|
self._running = True
|
|
if hasattr(events, "_set_running_loop"):
|
|
events._set_running_loop(self)
|
|
|
|
# Start the lifecycle, but invoke it as a deferred event on the event
|
|
# loop. iOSLifeCycle.start() starts the NSRunLoop; this ensures
|
|
# that a full NSRunLoop is running, not just one that responds to
|
|
# iOS events. See #228 for the sort of behavior that occurs on threads
|
|
# if the NSRunLoop isn't started.
|
|
self.call_soon(self._lifecycle.start)
|
|
|
|
def call_soon(self, callback, *args, context=None):
|
|
"""Arrange for a callback to be called as soon as possible.
|
|
|
|
This operates as a FIFO queue: callbacks are called in the
|
|
order in which they are registered. Each callback will be
|
|
called exactly once.
|
|
|
|
Any positional arguments after the callback will be passed to
|
|
the callback when it is called.
|
|
"""
|
|
self._check_not_coroutine(callback, "call_soon")
|
|
|
|
return CFTimerHandle(
|
|
loop=self,
|
|
timeout=0,
|
|
callback=context_callback(context, callback),
|
|
args=args,
|
|
)
|
|
|
|
call_soon_threadsafe = call_soon
|
|
|
|
def call_later(self, delay, callback, *args, context=None):
|
|
"""Arrange for a callback to be called at a given time.
|
|
|
|
Return a Handle: an opaque object with a cancel() method that
|
|
can be used to cancel the call.
|
|
|
|
The delay can be an int or float, expressed in seconds. It is
|
|
always relative to the current time.
|
|
|
|
Each callback will be called exactly once. If two callbacks
|
|
are scheduled for exactly the same time, it undefined which
|
|
will be called first.
|
|
|
|
Any positional arguments after the callback will be passed to
|
|
the callback when it is called.
|
|
"""
|
|
self._check_not_coroutine(callback, "call_later")
|
|
|
|
return CFTimerHandle(
|
|
loop=self,
|
|
timeout=delay,
|
|
callback=context_callback(context, callback),
|
|
args=args,
|
|
)
|
|
|
|
def call_at(self, when, callback, *args, context=None):
|
|
"""Like call_later(), but uses an absolute time.
|
|
|
|
Absolute time corresponds to the event loop's time() method.
|
|
"""
|
|
self._check_not_coroutine(callback, "call_at")
|
|
|
|
return CFTimerHandle(
|
|
loop=self,
|
|
timeout=when - self.time(),
|
|
callback=context_callback(context, callback),
|
|
args=args,
|
|
)
|
|
|
|
def time(self):
|
|
"""Return the time according to the event loop's clock.
|
|
|
|
This is a float expressed in seconds since an epoch, but the
|
|
epoch, precision, accuracy and drift are unspecified and may
|
|
differ per event loop.
|
|
"""
|
|
return libcf.CFAbsoluteTimeGetCurrent()
|
|
|
|
def stop(self):
|
|
"""Stop running the event loop.
|
|
|
|
Every callback already scheduled will still run. This simply
|
|
informs run_forever to stop looping after a complete iteration.
|
|
"""
|
|
super().stop()
|
|
self._lifecycle.stop()
|
|
|
|
def close(self):
|
|
"""Close the event loop.
|
|
|
|
This clears the queues and shuts down the executor,
|
|
but does not wait for the executor to finish.
|
|
|
|
The event loop must not be running.
|
|
"""
|
|
while self._accept_futures:
|
|
future = self._accept_futures.pop()
|
|
future.cancel()
|
|
|
|
while self._timers:
|
|
handler = self._timers.pop()
|
|
handler.cancel()
|
|
|
|
super().close()
|
|
|
|
def _set_lifecycle(self, lifecycle):
|
|
"""Set the application lifecycle that is controlling this loop."""
|
|
if self._lifecycle is not None:
|
|
raise ValueError("Lifecycle is already set")
|
|
if self.is_running():
|
|
raise RuntimeError(
|
|
"You can't set a lifecycle on a loop that's already running."
|
|
)
|
|
self._lifecycle = lifecycle
|
|
if sys.version_info < (3, 14):
|
|
self._policy._lifecycle = lifecycle
|
|
|
|
def _add_callback(self, handle):
|
|
"""Add a callback to be invoked ASAP.
|
|
|
|
The inherited behavior uses a self-pipe to wake up the event loop
|
|
in a thread-safe fashion, which causes the logic in run_once() to
|
|
empty the list of handlers that are awaiting invocation.
|
|
|
|
CFEventLoop doesn't use run_once(), so adding handlers to
|
|
self._ready results in handlers that aren't invoked. Instead, we
|
|
create a 0-interval timer to invoke the callback as soon as
|
|
possible.
|
|
"""
|
|
if handle._cancelled:
|
|
return
|
|
self.call_soon(handle._callback, *handle._args)
|
|
|
|
|
|
if sys.version_info < (3, 16):
|
|
|
|
class EventLoopPolicy(AbstractEventLoopPolicy):
|
|
"""Rubicon event loop policy.
|
|
|
|
In this policy, each thread has its own event loop. However, we only
|
|
automatically create an event loop by default for the main thread; other
|
|
threads by default have no event loop.
|
|
|
|
**DEPRECATED** - Python 3.14 deprecated the concept of manually creating
|
|
EventLoopPolicies. Create and use a ``RubiconEventLoop`` instance instead of
|
|
installing an event loop policy and calling ``asyncio.new_event_loop()``.
|
|
"""
|
|
|
|
def __init__(self):
|
|
warnings.warn(
|
|
"Custom EventLoopPolicy instances have been deprecated by Python 3.14. "
|
|
"Create and use a `RubiconEventLoop` instance directly instead of "
|
|
"installing an event loop policy and calling `asyncio.new_event_loop()`.",
|
|
DeprecationWarning,
|
|
stacklevel=2,
|
|
)
|
|
|
|
self._lifecycle = None
|
|
self._default_loop = None
|
|
if sys.version_info < (3, 14):
|
|
self._watcher_lock = threading.Lock()
|
|
self._watcher = None
|
|
self._policy = DefaultEventLoopPolicy()
|
|
self._policy.new_event_loop = self.new_event_loop
|
|
self.get_event_loop = self._policy.get_event_loop
|
|
self.set_event_loop = self._policy.set_event_loop
|
|
|
|
def new_event_loop(self):
|
|
"""Create a new event loop and return it."""
|
|
if (
|
|
not self._default_loop
|
|
and threading.current_thread() == threading.main_thread()
|
|
):
|
|
loop = self.get_default_loop()
|
|
else:
|
|
loop = CFEventLoop(self._lifecycle)
|
|
loop._policy = self
|
|
|
|
return loop
|
|
|
|
def get_default_loop(self):
|
|
"""Get the default event loop."""
|
|
if not self._default_loop:
|
|
self._default_loop = self._new_default_loop()
|
|
return self._default_loop
|
|
|
|
def _new_default_loop(self):
|
|
loop = CFEventLoop(self._lifecycle)
|
|
loop._policy = self
|
|
return loop
|
|
|
|
if sys.version_info < (3, 14):
|
|
|
|
def _init_watcher(self):
|
|
with events._lock:
|
|
if self._watcher is None: # pragma: no branch
|
|
self._watcher = SafeChildWatcher()
|
|
if threading.current_thread() == threading.main_thread():
|
|
self._watcher.attach_loop(self._default_loop)
|
|
|
|
def get_child_watcher(self):
|
|
"""Get the watcher for child processes.
|
|
|
|
If not yet set, a :class:`~asyncio.SafeChildWatcher` object is
|
|
automatically created.
|
|
|
|
.. note::
|
|
Child watcher support was removed in Python 3.14
|
|
"""
|
|
if self._watcher is None:
|
|
self._init_watcher()
|
|
|
|
return self._watcher
|
|
|
|
def set_child_watcher(self, watcher):
|
|
"""Set the watcher for child processes.
|
|
|
|
.. note::
|
|
Child watcher support was removed in Python 3.14
|
|
"""
|
|
if self._watcher is not None:
|
|
self._watcher.close()
|
|
|
|
self._watcher = watcher
|
|
|
|
|
|
if sys.version_info < (3, 14):
|
|
|
|
def RubiconEventLoop():
|
|
"""Create a new Rubicon CFEventLoop instance."""
|
|
# If they're using RubiconEventLoop(), they've done the necessary adaptation.
|
|
with warnings.catch_warnings():
|
|
warnings.filterwarnings(
|
|
"ignore",
|
|
message=r"^Custom EventLoopPolicy instances have been deprecated by Python 3.14",
|
|
category=DeprecationWarning,
|
|
)
|
|
policy = EventLoopPolicy()
|
|
set_event_loop_policy(policy)
|
|
return policy.new_event_loop()
|
|
|
|
else:
|
|
RubiconEventLoop = CFEventLoop
|
|
|
|
|
|
class CFLifecycle:
|
|
"""A lifecycle manager for raw CoreFoundation apps."""
|
|
|
|
def __init__(self, cfrunloop):
|
|
self._cfrunloop = cfrunloop
|
|
|
|
def start(self):
|
|
libcf.CFRunLoopRun()
|
|
|
|
def stop(self):
|
|
libcf.CFRunLoopStop(self._cfrunloop)
|
|
|
|
|
|
class CocoaLifecycle:
|
|
"""A life cycle manager for Cocoa (``NSApplication``) apps."""
|
|
|
|
def __init__(self, application):
|
|
self._application = application
|
|
|
|
def start(self):
|
|
self._application.run()
|
|
|
|
def stop(self):
|
|
self._application.terminate(None)
|
|
|
|
|
|
class iOSLifecycle:
|
|
"""A life cycle manager for iOS (``UIApplication``) apps."""
|
|
|
|
def start(self):
|
|
NSRunLoop.currentRunLoop.run()
|
|
|
|
def stop(self):
|
|
libcf.CFRunLoopStop(libcf.CFRunLoopGetMain())
|