You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ORPA-pyOpenRPA/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/prompt_toolkit/eventloop/posix.py

282 lines
10 KiB

from __future__ import unicode_literals
import fcntl
import os
import signal
import time
from .base import EventLoop
from .future import Future
from .inputhook import InputHookContext
from .select import AutoSelector, Selector, fd_to_int
from .utils import ThreadWithFuture
from .context import wrap_in_current_context
__all__ = [
'PosixEventLoop',
]
_now = time.time
class PosixEventLoop(EventLoop):
"""
Event loop for posix systems (Linux, Mac os X).
"""
def __init__(self, selector=AutoSelector):
assert issubclass(selector, Selector)
super(PosixEventLoop, self).__init__()
self.closed = False
self._running = False
self._calls_from_executor = []
self._read_fds = {} # Maps fd to handler.
self.selector = selector()
self._signal_handler_mappings = {} # signal: previous_handler
# Create a pipe for inter thread communication.
self._schedule_pipe = os.pipe()
fcntl.fcntl(self._schedule_pipe[0], fcntl.F_SETFL, os.O_NONBLOCK)
self.selector.register(self._schedule_pipe[0])
# Create inputhook context.
self._inputhook_context = None
def run_until_complete(self, future, inputhook=None):
"""
Keep running the event loop until `future` has been set.
:param future: :class:`prompt_toolkit.eventloop.future.Future` object.
"""
assert isinstance(future, Future)
assert inputhook is None or callable(inputhook)
if self._running:
raise Exception('Event loop is already running')
if self.closed:
raise Exception('Event loop already closed.')
try:
self._running = True
while not future.done():
self._run_once(inputhook)
# Run one last time, to flush the pending `_calls_from_executor`s.
if self._calls_from_executor:
self._run_once(inputhook)
finally:
self._running = False
def _run_once(self, inputhook):
# Call inputhook.
if inputhook:
# Create input hook context.
if self._inputhook_context is None:
self._inputhook_context = InputHookContext()
def ready(wait):
" True when there is input ready. The inputhook should return control. "
return self._ready_for_reading(None if wait else 0) != []
self._inputhook_context.call_inputhook(ready, inputhook)
# Wait until input is ready.
fds = self._ready_for_reading(None)
# When any of the FDs are ready. Call the appropriate callback.
if fds:
# Create lists of high/low priority tasks. The main reason for this
# is to allow painting the UI to happen as soon as possible, but
# when there are many events happening, we don't want to call the
# UI renderer 1000x per second. If the eventloop is completely
# saturated with many CPU intensive tasks (like processing
# input/output), we say that drawing the UI can be postponed a
# little, to make CPU available. This will be a low priority task
# in that case.
tasks = []
low_priority_tasks = []
now = None # Lazy load time. (Fewer system calls.)
for fd in fds:
# For the 'call_from_executor' fd, put each pending
# item on either the high or low priority queue.
if fd == self._schedule_pipe[0]:
# Flush all the pipe content.
os.read(self._schedule_pipe[0], 1024)
calls_from_executor = self._calls_from_executor[:]
del self._calls_from_executor[:]
for c, max_postpone_until in calls_from_executor:
if max_postpone_until is None:
# Execute now.
tasks.append(c)
else:
# Execute soon, if `max_postpone_until` is in the future.
now = now or _now()
if max_postpone_until < now:
tasks.append(c)
else:
low_priority_tasks.append((c, max_postpone_until))
else:
handler = self._read_fds.get(fd)
if handler:
tasks.append(handler)
# When there are high priority tasks, run all these.
# Schedule low priority tasks for the next iteration.
if tasks:
for t in tasks:
self._run_task(t)
# Postpone low priority tasks.
for t, max_postpone_until in low_priority_tasks:
self.call_from_executor(t, _max_postpone_until=max_postpone_until)
else:
# Currently there are only low priority tasks -> run them right now.
for t, _ in low_priority_tasks:
self._run_task(t)
def _run_task(self, t):
"""
Run a task in the event loop. If it fails, print the exception.
By default, the event loop is not supposed to exit if one fd callback
raises an exception. Doing so, would leave many coroutines in a not
cleaned-up state. If this happens, this is a bug, and we have to print
the stack.
"""
try:
t()
except BaseException as e:
self.call_exception_handler({
'exception': e
})
def _ready_for_reading(self, timeout=None):
"""
Return the file descriptors that are ready for reading.
"""
fds = self.selector.select(timeout)
return fds
def add_signal_handler(self, signum, handler):
"""
Register a signal handler. Call `handler` when `signal` was received.
The given handler will always be called in the same thread as the
eventloop. (Like `call_from_executor`.)
"""
# Always process signals asynchronously, because these handlers can
# write to the output, and doing this inside the signal handler causes
# easily reentrant calls, giving runtime errors.
# Further, this has to be thread safe. When the Application runs not in
# the main thread, this function will still be called from the main
# thread. (The only place where we can install signal handlers.)
if handler is None:
# Normally, `signal.signal` should never return `None`. For some
# reason it happens here:
# https://github.com/jonathanslenders/python-prompt-toolkit/pull/174
handler = 0
if handler in (signal.SIG_IGN, 0):
# Clear handler.
previous = signal.signal(signum, handler)
self._signal_handler_mappings[signum] = handler
else:
# Set handler.
def call_signal_handler(*a):
self.call_from_executor(handler)
previous = signal.signal(signum, call_signal_handler)
self._signal_handler_mappings[signum] = handler
# Return the previous signal handler.
return self._signal_handler_mappings.get(signum, previous)
def run_in_executor(self, callback, _daemon=False):
"""
Run a long running function in a background thread.
(This is recommended for code that could block the event loop.)
Similar to Twisted's ``deferToThread``.
"""
th = ThreadWithFuture(callback, daemon=_daemon)
# Wait until the main thread is idle.
# We start the thread by using `call_from_executor`. The event loop
# favours processing input over `calls_from_executor`, so the thread
# will not start until there is no more input to process and the main
# thread becomes idle for an instant. This is good, because Python
# threading favours CPU over I/O -- an autocompletion thread in the
# background would cause a significantly slow down of the main thread.
# It is mostly noticeable when pasting large portions of text while
# having real time autocompletion while typing on.
self.call_from_executor(th.start)
return th.future
def call_from_executor(self, callback, _max_postpone_until=None):
"""
Call this function in the main event loop.
Similar to Twisted's ``callFromThread``.
:param _max_postpone_until: `None` or `time.time` value. For internal
use. If the eventloop is saturated, consider this task to be low
priority and postpone maximum until this timestamp. (For instance,
repaint is done using low priority.)
"""
assert _max_postpone_until is None or isinstance(_max_postpone_until, float)
callback = wrap_in_current_context(callback)
self._calls_from_executor.append((callback, _max_postpone_until))
if self._schedule_pipe:
try:
os.write(self._schedule_pipe[1], b'x')
except (AttributeError, IndexError, OSError):
# Handle race condition. We're in a different thread.
# - `_schedule_pipe` could have become None in the meantime.
# - We catch `OSError` (actually BrokenPipeError), because the
# main thread could have closed the pipe already.
pass
def close(self):
"""
Close the event loop. The loop must not be running.
"""
assert not self._running
self.closed = True
# Close pipes.
schedule_pipe = self._schedule_pipe
self._schedule_pipe = None
if schedule_pipe:
os.close(schedule_pipe[0])
os.close(schedule_pipe[1])
if self._inputhook_context:
self._inputhook_context.close()
def add_reader(self, fd, callback):
" Add read file descriptor to the event loop. "
callback = wrap_in_current_context(callback)
fd = fd_to_int(fd)
self._read_fds[fd] = callback
self.selector.register(fd)
def remove_reader(self, fd):
" Remove read file descriptor from the event loop. "
fd = fd_to_int(fd)
if fd in self._read_fds:
del self._read_fds[fd]
self.selector.unregister(fd)