Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
16e4f96
update Comms and matplotlib while waiting for input
Sep 17, 2019
071e5f6
change import to import Gcf directly
Sep 17, 2019
2d19be4
make matplotlib dependency facultative
Sep 17, 2019
bad10cc
move matplotlib inside function
Sep 17, 2019
b2bd633
move waiting code to method
Sep 19, 2019
ddc6405
Merge branch '_wait_input_request_reply' into Update_comms_matplotlib
Sep 19, 2019
8bc89a5
move import into new method
Sep 19, 2019
0e485a0
use public API
Sep 19, 2019
0ee0cee
Remove do_one_iteration
Sep 22, 2019
05a2959
Merge branch 'repeat_tests' into _wait_input_request_reply
Oct 5, 2019
2915f56
Merge branch '_wait_input_request_reply' into Update_comms_matplotlib
Oct 5, 2019
3487e7d
Update ipykernel/kernelbase.py
impact27 Oct 6, 2019
9397d53
Check if is main thread
Oct 6, 2019
45cedfc
Merge remote-tracking branch 'upstream/master' into Update_comms_matp…
Oct 9, 2019
b90b5d5
Merge remote-tracking branch 'upstream/master' into Update_comms_matp…
Oct 16, 2019
9f33e69
Update all eventloops
Oct 16, 2019
55f44a1
Block while waiting for reply
Oct 23, 2019
188c2a1
add lock to stdin
Oct 24, 2019
c0fc6b8
move code to Kernel
Oct 25, 2019
404c437
remove whitespace
impact27 Oct 25, 2019
922bb55
skip stdin_stream if dummy socket
Oct 26, 2019
f20c034
Merge remote-tracking branch 'upstream/master' into Update_comms_matp…
Nov 25, 2019
94e36a9
Add magic to enable and disable the eventloop while waiting for input
Dec 4, 2019
190e8ba
Merge remote-tracking branch 'upstream/master' into Update_comms_matp…
Dec 5, 2019
99e3d4f
Change PY2 compatibility code
Dec 5, 2019
bf3cb03
Merge remote-tracking branch 'upstream/master' into Update_comms_matp…
Mar 28, 2020
d16d6e4
add io_streams property to kernel
Mar 28, 2020
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
10 changes: 5 additions & 5 deletions ipykernel/eventloops.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def loop_qt4(kernel):
kernel.app = get_app_qt4([" "])
kernel.app.setQuitOnLastWindowClosed(False)

for s in kernel.shell_streams:
for s in kernel.io_streams:
_notify_stream_qt(kernel, s)

_loop_qt(kernel.app)
Expand Down Expand Up @@ -159,7 +159,7 @@ def loop_wx(kernel):

def wake():
"""wake from wx"""
for stream in kernel.shell_streams:
for stream in kernel.io_streams:
if stream.flush(limit=1):
kernel.app.ExitMainLoop()
return
Expand Down Expand Up @@ -225,7 +225,7 @@ def process_stream_events(stream, *a, **kw):
# For Tkinter, we create a Tk object and call its withdraw method.
kernel.app = app = Tk()
kernel.app.withdraw()
for stream in kernel.shell_streams:
for stream in kernel.io_streams:
notifier = partial(process_stream_events, stream)
# seems to be needed for tk
notifier.__name__ = 'notifier'
Expand Down Expand Up @@ -296,7 +296,7 @@ def handle_int(etype, value, tb):
# don't let interrupts during mainloop invoke crash_handler:
sys.excepthook = handle_int
mainloop(kernel._poll_interval)
for stream in kernel.shell_streams:
for stream in kernel.io_streams:
if stream.flush(limit=1):
# events to process, return control to kernel
return
Expand Down Expand Up @@ -337,7 +337,7 @@ def process_stream_events(stream):
if stream.flush(limit=1):
loop.stop()

for stream in kernel.shell_streams:
for stream in kernel.io_streams:
fd = stream.getsockopt(zmq.FD)
notifier = partial(process_stream_events, stream)
loop.add_reader(fd, notifier)
Expand Down
2 changes: 2 additions & 0 deletions ipykernel/ipkernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ def __init__(self, **kwargs):
import appnope
appnope.nope()

self.shell.magics_manager.register_function(self.input_eventloop)

help_links = List([
{
'text': "Python Reference",
Expand Down
110 changes: 93 additions & 17 deletions ipykernel/kernelbase.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import sys
import time
import uuid
import threading

try:
# jupyter_client >= 5, use tz-aware now
Expand All @@ -28,9 +29,9 @@
from zmq.eventloop.zmqstream import ZMQStream

from traitlets.config.configurable import SingletonConfigurable
from IPython.core.error import StdinNotImplementedError
from IPython.core.error import StdinNotImplementedError, UsageError
from ipython_genutils import py3compat
from ipython_genutils.py3compat import unicode_type, string_types
from ipython_genutils.py3compat import unicode_type, string_types, PY3
from ipykernel.jsonutil import json_clean
from traitlets import (
Any, Instance, Float, Dict, List, Set, Integer, Unicode, Bool,
Expand All @@ -40,6 +41,7 @@
from jupyter_client.session import Session

from ._version import kernel_protocol_version
from .inprocess.socket import DummySocket

CONTROL_PRIORITY = 1
SHELL_PRIORITY = 10
Expand Down Expand Up @@ -170,6 +172,51 @@ def __init__(self, **kwargs):
for msg_type in self.control_msg_types:
self.control_handlers[msg_type] = getattr(self, msg_type)

self._stdin_msg = None
self._stdin_lock = threading.Lock()

if isinstance(self.stdin_socket, DummySocket):
# This is a test
self.stdin_stream = None
return
self.stdin_stream = ZMQStream(self.stdin_socket)

def handle_msg(msg):
idents, msg = self.session.feed_identities(msg, copy=False)
try:
msg = self.session.deserialize(msg, content=True, copy=False)
except Exception:
self.log.error("Invalid Message", exc_info=True)
return
self._stdin_msg = msg
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need to lock here too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The messages are processed by a single thread so a lock would not be useful here.


self.stdin_stream.on_recv(handle_msg, copy=False)
# Should the eventloop be run while waiting for input
self._input_eventloop = False

@property
def io_streams(self):
"""Return all I/O streams."""
return self.shell_streams + [self.stdin_stream]

def input_eventloop(self, active):
"""
Activates and desactivates the eventloop while waiting for input.

active is "True" or "False" (Strings as it is used as a magic)

This allows eg. matplotlib plots to be used while debugging.

This should not be active while debugging a gui application that
uses the same eventloop as the events will be processed.
"""
if active == "True":
self._input_eventloop = True
elif active == "False":
self._input_eventloop = False
else:
raise UsageError('Please use "True" or "False"')

@gen.coroutine
def dispatch_control(self, msg):
"""dispatch control requests"""
Expand Down Expand Up @@ -864,6 +911,7 @@ def raw_input(self, prompt=''):
)

def _input_request(self, prompt, ident, parent, password=False):
"""Send an input request to the frontend and wait for the reply."""
# Flush output before making the request.
sys.stderr.flush()
sys.stdout.flush()
Expand All @@ -877,22 +925,15 @@ def _input_request(self, prompt, ident, parent, password=False):
else:
raise

# Send the input request.
content = json_clean(dict(prompt=prompt, password=password))
self.session.send(self.stdin_socket, u'input_request', content, parent,
ident=ident)
with self._stdin_lock:
self._stdin_msg = None
# Send the input request.
content = json_clean(dict(prompt=prompt, password=password))
self.session.send(self.stdin_socket, u'input_request',
content, parent, ident=ident)
# Await a response.
reply = self._wait_input_request_reply()

# Await a response.
while True:
try:
ident, reply = self.session.recv(self.stdin_socket, 0)
except Exception:
self.log.warning("Invalid Message:", exc_info=True)
except KeyboardInterrupt:
# re-raise KeyboardInterrupt, to truncate traceback
raise KeyboardInterrupt("Interrupted by user") from None
else:
break
try:
value = py3compat.unicode_to_str(reply['content']['value'])
except:
Expand All @@ -903,6 +944,41 @@ def _input_request(self, prompt, ident, parent, password=False):
raise EOFError
return value

def _wait_input_request_reply(self):
"""Wait for an input request reply.

Raises
------
KeyboardInterrupt if a keyboard interrupt is recieved.
"""
# Await a response.
reply = None
while reply is None:
try:
reply = self._input_request_loop_step()
except Exception:
self.log.warning("Invalid Message:", exc_info=True)
except KeyboardInterrupt:
# re-raise KeyboardInterrupt, to truncate traceback
raise KeyboardInterrupt("Interrupted by user") from None
return reply

def _input_request_loop_step(self):
"""Do one step of the input request loop."""
# Allow GUI event loop to update
if PY3:
is_main_thread = (
threading.current_thread() is threading.main_thread())
else:
is_main_thread = isinstance(
threading.current_thread(), threading._MainThread)
if is_main_thread and self.eventloop and self._input_eventloop:
self.eventloop(self)
return self._stdin_msg
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and possibly here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_input_request_loop_step is called from _wait_input_request_reply which is protected by a lock in _input_request, so I don't think a lock here would do anything.

else:
ident, reply = self.session.recv(self.stdin_socket, 0)
return reply

def _at_shutdown(self):
"""Actions taken at shutdown by the kernel, called by python's atexit.
"""
Expand Down