Skip to content

Commit

Permalink
Merge pull request ipython#1065 from Carreau/qtconsole-racecondition
Browse files Browse the repository at this point in the history
Fix race condition in qtconsole between the population of the magic menu and the first prompt request. 

Resolved by upgrading the logic of the console to handle several executions request in parallel.

Closes ipython#1057, introduced by ipython#956.
  • Loading branch information
fperez committed Nov 30, 2011
2 parents 39d9d7a + 875fdb5 commit 1d3f5aa
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 84 deletions.
18 changes: 10 additions & 8 deletions IPython/frontend/qt/console/frontend_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ def __init__(self, *args, **kw):
self._input_splitter = self._input_splitter_class(input_mode='cell')
self._kernel_manager = None
self._request_info = {}
self._request_info['execute'] = {};
self._callback_dict = {}

# Configure the ConsoleWidget.
Expand Down Expand Up @@ -199,7 +200,7 @@ def _execute(self, source, hidden):
See parent class :meth:`execute` docstring for full details.
"""
msg_id = self.kernel_manager.shell_channel.execute(source, hidden)
self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
self._hidden = hidden
if not hidden:
self.executing.emit(source)
Expand Down Expand Up @@ -343,7 +344,7 @@ def _silent_exec_callback(self, expr, callback):
msg_id = self.kernel_manager.shell_channel.execute('',
silent=True, user_expressions={ local_uuid:expr })
self._callback_dict[local_uuid] = callback
self._request_info['execute'] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')

def _handle_exec_callback(self, msg):
"""Execute `callback` corresonding to `msg` reply, after ``_silent_exec_callback``
Expand Down Expand Up @@ -373,12 +374,12 @@ def _handle_execute_reply(self, msg):
""" Handles replies for code execution.
"""
self.log.debug("execute: %s", msg.get('content', ''))
info = self._request_info.get('execute')
msg_id = msg['parent_header']['msg_id']
info = self._request_info['execute'].get(msg_id)
# unset reading flag, because if execute finished, raw_input can't
# still be pending.
self._reading = False
if info and info.id == msg['parent_header']['msg_id'] and \
info.kind == 'user' and not self._hidden:
if info and info.kind == 'user' and not self._hidden:
# Make sure that all output from the SUB channel has been processed
# before writing a new prompt.
self.kernel_manager.sub_channel.flush()
Expand All @@ -400,9 +401,10 @@ def _handle_execute_reply(self, msg):

self._show_interpreter_prompt_for_reply(msg)
self.executed.emit(msg)
elif info and info.id == msg['parent_header']['msg_id'] and \
info.kind == 'silent_exec_callback' and not self._hidden:
self._request_info['execute'].pop(msg_id)
elif info and info.kind == 'silent_exec_callback' and not self._hidden:
self._handle_exec_callback(msg)
self._request_info['execute'].pop(msg_id)
else:
super(FrontendWidget, self)._handle_execute_reply(msg)

Expand Down Expand Up @@ -559,7 +561,7 @@ def reset(self):
"""
if self._executing:
self._executing = False
self._request_info['execute'] = None
self._request_info['execute'] = {}
self._reading = False
self._highlighter.highlighting_on = False

Expand Down
8 changes: 4 additions & 4 deletions IPython/frontend/qt/console/history_console_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,14 +211,14 @@ def _request_update_session_history_length(self):
'hlen':'len(get_ipython().history_manager.input_hist_raw)',
}
)
self._request_info['execute'] = self._ExecutionRequest(msg_id, 'save_magic')
self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'save_magic')

def _handle_execute_reply(self, msg):
""" Handles replies for code execution, here only session history length
"""
info = self._request_info.get('execute')
if info and info.id == msg['parent_header']['msg_id'] and \
info.kind == 'save_magic' and not self._hidden:
msg_id = msg['parent_header']['msg_id']
info = self._request_info.get['execute'].pop(msg_id,None)
if info and info.kind == 'save_magic' and not self._hidden:
content = msg['content']
status = content['status']
if status == 'ok':
Expand Down
17 changes: 9 additions & 8 deletions IPython/frontend/qt/console/ipython_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,14 @@ def _handle_complete_reply(self, rep):
def _handle_execute_reply(self, msg):
""" Reimplemented to support prompt requests.
"""
info = self._request_info.get('execute')
if info and info.id == msg['parent_header']['msg_id']:
if info.kind == 'prompt':
number = msg['content']['execution_count'] + 1
self._show_interpreter_prompt(number)
else:
super(IPythonWidget, self)._handle_execute_reply(msg)
msg_id = msg['parent_header'].get('msg_id')
info = self._request_info['execute'].get(msg_id)
if info and info.kind == 'prompt':
number = msg['content']['execution_count'] + 1
self._show_interpreter_prompt(number)
self._request_info['execute'].pop(msg_id)
else:
super(IPythonWidget, self)._handle_execute_reply(msg)

def _handle_history_reply(self, msg):
""" Implemented to handle history tail replies, which are only supported
Expand Down Expand Up @@ -358,7 +359,7 @@ def _show_interpreter_prompt(self, number=None):
if number is None:
msg_id = self.kernel_manager.shell_channel.execute('', silent=True)
info = self._ExecutionRequest(msg_id, 'prompt')
self._request_info['execute'] = info
self._request_info['execute'][msg_id] = info
return

# Show a new prompt and save information about it so that it can be
Expand Down
60 changes: 6 additions & 54 deletions IPython/frontend/qt/console/mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -626,17 +626,12 @@ def init_magic_menu(self):
# is updated at first kernel response. Though, it is necessary when
# connecting through X-forwarding, as in this case, the menu is not
# auto updated, SO DO NOT DELETE.

########################################################################
## TEMPORARILY DISABLED - see #1057 for details. Uncomment this
## section when a proper fix is found

## self.pop = QtGui.QAction("&Update All Magic Menu ",
## self, triggered=self.update_all_magic_menu)
## self.add_menu_action(self.all_magic_menu, self.pop)

## END TEMPORARY FIX
########################################################################
self.pop = QtGui.QAction("&Update All Magic Menu ",
self, triggered=self.update_all_magic_menu)
self.add_menu_action(self.all_magic_menu, self.pop)
# we need to populate the 'Magic Menu' once the kernel has answer at
# least once let's do it immedialy, but it's assured to works
self.pop.trigger()

self.reset_action = QtGui.QAction("&Reset",
self,
Expand Down Expand Up @@ -674,49 +669,6 @@ def init_magic_menu(self):
triggered=self.whos_magic_active_frontend)
self.add_menu_action(self.magic_menu, self.whos_action)


########################################################################
## TEMPORARILY ADDED BACK - see #1057 for details. The magic menu is
## supposed to be dynamic, but the mechanism merged in #1057 has a race
## condition that locks up the console very often. We're putting back
## the static list temporarily/ Remove this code when a proper fix is
## found.

# allmagics submenu.

# for now this is just a copy and paste, but we should get this
# dynamically
magiclist = ["%alias", "%autocall", "%automagic", "%bookmark", "%cd",
"%clear", "%colors", "%debug", "%dhist", "%dirs", "%doctest_mode",
"%ed", "%edit", "%env", "%gui", "%guiref", "%hist", "%history",
"%install_default_config", "%install_profiles", "%less", "%load_ext",
"%loadpy", "%logoff", "%logon", "%logstart", "%logstate", "%logstop",
"%lsmagic", "%macro", "%magic", "%man", "%more", "%notebook", "%page",
"%pastebin", "%pdb", "%pdef", "%pdoc", "%pfile", "%pinfo", "%pinfo2",
"%popd", "%pprint", "%precision", "%profile", "%prun", "%psearch",
"%psource", "%pushd", "%pwd", "%pycat", "%pylab", "%quickref",
"%recall", "%rehashx", "%reload_ext", "%rep", "%rerun", "%reset",
"%reset_selective", "%run", "%save", "%sc", "%sx", "%tb", "%time",
"%timeit", "%unalias", "%unload_ext", "%who", "%who_ls", "%whos",
"%xdel", "%xmode"]

def make_dynamic_magic(i):
def inner_dynamic_magic():
self.active_frontend.execute(i)
inner_dynamic_magic.__name__ = "dynamics_magic_%s" % i
return inner_dynamic_magic

for magic in magiclist:
xaction = QtGui.QAction(magic,
self,
triggered=make_dynamic_magic(magic)
)
self.all_magic_menu.addAction(xaction)

## END TEMPORARY FIX
########################################################################


def init_window_menu(self):
self.window_menu = self.menuBar().addMenu("&Window")
if sys.platform == 'darwin':
Expand Down
10 changes: 0 additions & 10 deletions IPython/frontend/qt/console/qtconsoleapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,16 +452,6 @@ def init_qt_elements(self):
self.window.add_tab_with_frontend(self.widget)
self.window.init_menu_bar()

# we need to populate the 'Magic Menu' once the kernel has answer at
# least once

########################################################################
## TEMPORARILY DISABLED - see #1057 for details, uncomment the next
## line when a proper fix is found:
## self.kernel_manager.shell_channel.first_reply.connect(self.window.pop.trigger)
## END TEMPORARY FIX
########################################################################

self.window.setWindowTitle('Python' if self.pure else 'IPython')

def init_colors(self):
Expand Down

0 comments on commit 1d3f5aa

Please sign in to comment.