Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

WIP Back to threads#927

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
thomasballinger wants to merge3 commits intomain
base:main
Choose a base branch
Loading
fromback-to-threads
Open
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 67 additions & 55 deletionsbpython/curtsiesfrontend/coderunner.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
"""For running Python code that could interrupt itself at any time in order to,
for example, ask for a read on stdin, or a write on stdout
for example, ask for a read on stdin, or a write on stdout.

The CodeRunner spawns agreenlet to run code in, and thatcode cansuspend its
own executionto ask the maingreenlet to refresh the display or get
The CodeRunner spawns athread to run code in. Thatcode canblock
on a queueto ask the main(UI) thread to refresh the display or get
information.

Greenlets are basically threads that can explicitly switch control to each
other. You can replace the word "greenlet" with "thread" in these docs if that
makes more sense to you.
"""

import code
import greenlet
from six.moves import queue
import threading
import logging
import signal

Expand All@@ -21,12 +18,12 @@


class SigintHappened:
"""If this class is returned, a SIGINT happened while the maingreenlet"""
"""If this class is returned, a SIGINT happened while the mainthread"""


class SystemExitFromCodeRunner(SystemExit):
"""If this class is returned, a SystemExit happened while in the code
greenlet"""
thread"""


class RequestFromCodeRunner:
Expand DownExpand Up@@ -57,30 +54,38 @@ def __init__(self, args):


class CodeRunner:
"""Runs user code in an interpreter.

Running code requests a refresh by calling
request_from_main_context(force_refresh=True), which
suspends execution of the code and switches back to the main greenlet

After load_code() is called with the source code to be run,
the run_code() method should be called to start running the code.
The running code may request screen refreshes and user input
by calling request_from_main_context.
When this are called, the running source code cedes
control, and the current run_code() method call returns.

The return value of run_code() determines whether the method ought
to be called again to complete execution of the source code.
"""Runs user code in a pausable thread.

>>> cr = CodeRunner()
>>> def get_input():
... print('waiting for a number plz')
... return cr.request_from_main_thread()
...
>>> i = InteractiveInterpreter(locals={'get_input': get_input})
>>> cr.interp = i
>>> cr.load_code('x = get_input(); print(x * 2)')
>>> finished = cr.run_code()
waiting for a number plz
>>> # do something else, user code thread is paused
>>> finished = cr.run_code(for_code=21)
42

As user code executes it can make requests for values or simply
request that the screen be refreshed with `request_from_main_thread()`.
This pauses the user code execution thread and wakes up the main thread,
where run_code() returns whether user code has finished executing.
This is cooperative multitasking: even though there are two threads,
the main thread and the user code thread, the two threads work cede
control to one another like like green threads with no parallelism.

Once the screen refresh has occurred or the requested user input
has been gathered, run_code() should be called again, passing in any
requested user input. This continues until run_code returnsDone.
requested user input. This continues until run_code returnsTrue.

The codegreenlet is responsible for telling the maingreenlet
The codethread is responsible for telling the mainthread
what it wants returned in the next run_code call - CodeRunner
just passes whatever is passed in to run_code(for_code) to the
codegreenlet
codethread.
"""

def __init__(self, interp=None, request_refresh=lambda: None):
Expand All@@ -93,65 +98,67 @@ def __init__(self, interp=None, request_refresh=lambda: None):
"""
self.interp = interp or code.InteractiveInterpreter()
self.source = None
self.main_context = greenlet.getcurrent()
self.code_context = None
self.code_thread = None
self.requests_from_code_thread = queue.Queue(maxsize=0)
self.responses_for_code_thread = queue.Queue()
self.request_refresh = request_refresh
# waiting for response from main thread
self.code_is_waiting = False
# sigint happened while in main thread
self.sigint_happened_in_main_context = False
self.sigint_happened_in_main_thread = False
self.orig_sigint_handler = None

@property
def running(self):
"""Returns greenlet if code has been loaded greenlet has been
started"""
return self.source and self.code_context
"""Returns the running thread if code has been loaded and started."""
return self.source and self.code_thread

def load_code(self, source):
"""Prep code to be run"""
assert self.source is None, (
"you shouldn't load code when some is " "already running"
)
self.source = source
self.code_context = None
self.code_thread = None

def _unload_code(self):
"""Called when done running code"""
self.source = None
self.code_context = None
self.code_thread = None
self.code_is_waiting = False

def run_code(self, for_code=None):
"""Returns Truthy values if code finishes, False otherwise

if for_code is provided, send that value to the codegreenlet
if for_code is provided, send that value to the codethread
if source code is complete, returns "done"
if source code is incomplete, returns "unfinished"
"""
if self.code_context is None:
if self.code_thread is None:
assert self.source is not None
self.code_context = greenlet.greenlet(self._blocking_run_code)
self.code_thread = threading.Thread(
target=self._blocking_run_code, name="codethread"
)
self.code_thread.daemon = True
if is_main_thread():
self.orig_sigint_handler = signal.getsignal(signal.SIGINT)
signal.signal(signal.SIGINT, self.sigint_handler)
request =self.code_context.switch()
self.code_thread.start()
else:
assert self.code_is_waiting
self.code_is_waiting = False
if is_main_thread():
signal.signal(signal.SIGINT, self.sigint_handler)
if self.sigint_happened_in_main_context:
self.sigint_happened_in_main_context = False
request =self.code_context.switch(SigintHappened)
if self.sigint_happened_in_main_thread:
self.sigint_happened_in_main_thread = False
self.responses_for_code_thread.put(SigintHappened)
else:
request =self.code_context.switch(for_code)
self.responses_for_code_thread.put(for_code)

request = self.requests_from_code_thread.get()
logger.debug("request received from code was %r", request)
if not isinstance(request, RequestFromCodeRunner):
raise ValueError(
"Not a valid value from code greenlet: %r" % request
)
raise ValueError("Not a valid value from code thread: %r" % request)
if isinstance(request, (Wait, Refresh)):
self.code_is_waiting = True
if isinstance(request, Refresh):
Expand All@@ -170,32 +177,37 @@ def run_code(self, for_code=None):
def sigint_handler(self, *args):
"""SIGINT handler to use while code is running or request being
fulfilled"""
ifgreenlet.getcurrent() is self.code_context:
ifthreading.current_thread() is self.code_thread:
logger.debug("sigint while running user code!")
raise KeyboardInterrupt()
else:
logger.debug(
"sigint while fulfilling code request sigint handler "
"running!"
)
self.sigint_happened_in_main_context = True
self.sigint_happened_in_main_thread = True

def _blocking_run_code(self):
try:
unfinished = self.interp.runsource(self.source)
except SystemExit as e:
return SystemExitRequest(*e.args)
return Unfinished() if unfinished else Done()
self.requests_from_code_thread.push(SystemExitRequest(*e.args))
return
self.requests_from_code_thread.put(
Unfinished() if unfinished else Done()
)

defrequest_from_main_context(self, force_refresh=False):
defrequest_from_main_thread(self, force_refresh=False):
"""Return the argument passed in to .run_code(for_code)

Nothing means calls to run_code must be... ???
"""
if force_refresh:
value = self.main_context.switch(Refresh())
self.requests_from_code_thread.put(Refresh())
value = self.responses_for_code_thread.get()
else:
value = self.main_context.switch(Wait())
self.requests_from_code_thread.put(Wait())
value = self.responses_for_code_thread.get()
if value is SigintHappened:
raise KeyboardInterrupt()
return value
Expand All@@ -216,7 +228,7 @@ def __init__(self, coderunner, on_write, real_fileobj):

def write(self, s, *args, **kwargs):
self.on_write(s, *args, **kwargs)
return self.coderunner.request_from_main_context(force_refresh=True)
return self.coderunner.request_from_main_thread(force_refresh=True)

# Some applications which use curses require that sys.stdout
# have a method called fileno. One example is pwntools. This
Expand Down
43 changes: 27 additions & 16 deletionsbpython/curtsiesfrontend/interaction.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import greenlet
import time
from queue import Queue
from curtsies import events

from ..translations import _
Expand DownExpand Up@@ -43,8 +43,8 @@ def __init__(
self.permanent_stack = []
if permanent_text:
self.permanent_stack.append(permanent_text)
self.main_context =greenlet.getcurrent()
self.request_context =None
self.response_queue =Queue()
self.request_or_notify_queue =Queue()
self.request_refresh = request_refresh
self.schedule_refresh = schedule_refresh

Expand DownExpand Up@@ -83,13 +83,14 @@ def process_event(self, e):
assert self.in_prompt or self.in_confirm or self.waiting_for_refresh
if isinstance(e, RefreshRequestEvent):
self.waiting_for_refresh = False
self.request_context.switch()
self.request_or_notify_queue.put(None)
self.response_queue.get()
elif isinstance(e, events.PasteEvent):
for ee in e.events:
# strip control seq
self.add_normal_character(ee if len(ee) == 1 else ee[-1])
elif e == "<ESC>" or isinstance(e, events.SigIntEvent):
self.request_context.switch(False)
self.request_queue.put(False)
self.escape()
elif e in edit_keys:
self.cursor_offset_in_line, self._current_line = edit_keys[e](
Expand All@@ -102,12 +103,12 @@ def process_event(self, e):
elif self.in_prompt and e in ("\n", "\r", "<Ctrl-j>", "Ctrl-m>"):
line = self._current_line
self.escape()
self.request_context.switch(line)
self.response_queue.put(line)
elif self.in_confirm:
if e.lower() == _("y"):
self.request_context.switch(True)
self.request_queue.put(True)
else:
self.request_context.switch(False)
self.request_queue.put(False)
self.escape()
else: # add normal character
self.add_normal_character(e)
Expand All@@ -126,6 +127,7 @@ def add_normal_character(self, e):

def escape(self):
"""unfocus from statusbar, clear prompt state, wait for notify call"""
self.wait_for_request_or_notify()
self.in_prompt = False
self.in_confirm = False
self.prompt = ""
Expand All@@ -148,27 +150,36 @@ def current_line(self):
def should_show_message(self):
return bool(self.current_line)

# interaction interface - should be called from other greenlets
def wait_for_request_or_notify(self):
try:
r = self.request_or_notify_queue.get(True, 1)
except queue.Empty:
raise Exception(
"Main thread blocked because task thread not calling back"
)
return r

# interaction interface - should be called from other threads
def notify(self, msg, n=3, wait_for_keypress=False):
self.request_context = greenlet.getcurrent()
self.message_time = n
self.message(msg, schedule_refresh=wait_for_keypress)
self.waiting_for_refresh = True
self.request_refresh()
self.main_context.switch(msg)
self.request_or_notify_queue.push(msg)

# below really ought to be called from greenlets other than main because
###################################
# below really ought to be called from threads other than main because
# they block
def confirm(self, q):
"""Expected to return True or False, given question prompt q"""
self.request_context = greenlet.getcurrent()
self.prompt = q
self.in_confirm = True
return self.main_context.switch(q)
self.request_or_notify_queue.put(q)
return self.response_queue.get()

def file_prompt(self, s):
"""Expected to return a file name, given"""
self.request_context = greenlet.getcurrent()
self.prompt = s
self.in_prompt = True
return self.main_context.switch(s)
self.request_or_notify_queue.put(s)
return self.response_queue.get()
Loading

[8]ページ先頭

©2009-2026 Movatter.jp