From a5fd0a3d49f800d6ebf559d54e3a8016b5d179e9 2011-11-30 06:06:27 From: Fernando Perez Date: 2011-11-30 06:06:27 Subject: [PATCH] Merge pull request #1065 from Carreau/qtconsole-racecondition 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 #1057, introduced by #956. --- diff --git a/IPython/frontend/qt/console/frontend_widget.py b/IPython/frontend/qt/console/frontend_widget.py index 1f62432..15eff37 100644 --- a/IPython/frontend/qt/console/frontend_widget.py +++ b/IPython/frontend/qt/console/frontend_widget.py @@ -138,6 +138,7 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): 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. @@ -199,7 +200,7 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): 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) @@ -343,7 +344,7 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): 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`` @@ -373,12 +374,12 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): """ 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() @@ -400,9 +401,10 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): 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) @@ -559,7 +561,7 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): """ if self._executing: self._executing = False - self._request_info['execute'] = None + self._request_info['execute'] = {} self._reading = False self._highlighter.highlighting_on = False diff --git a/IPython/frontend/qt/console/history_console_widget.py b/IPython/frontend/qt/console/history_console_widget.py index 944f9bd..5c7657d 100644 --- a/IPython/frontend/qt/console/history_console_widget.py +++ b/IPython/frontend/qt/console/history_console_widget.py @@ -211,14 +211,14 @@ class HistoryConsoleWidget(ConsoleWidget): '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': diff --git a/IPython/frontend/qt/console/ipython_widget.py b/IPython/frontend/qt/console/ipython_widget.py index ae183ab..5147139 100644 --- a/IPython/frontend/qt/console/ipython_widget.py +++ b/IPython/frontend/qt/console/ipython_widget.py @@ -165,13 +165,14 @@ class IPythonWidget(FrontendWidget): 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 @@ -358,7 +359,7 @@ class IPythonWidget(FrontendWidget): 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 diff --git a/IPython/frontend/qt/console/mainwindow.py b/IPython/frontend/qt/console/mainwindow.py index cd0a721..7cf24a7 100644 --- a/IPython/frontend/qt/console/mainwindow.py +++ b/IPython/frontend/qt/console/mainwindow.py @@ -626,17 +626,12 @@ class MainWindow(QtGui.QMainWindow): # 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, @@ -674,49 +669,6 @@ class MainWindow(QtGui.QMainWindow): 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': diff --git a/IPython/frontend/qt/console/qtconsoleapp.py b/IPython/frontend/qt/console/qtconsoleapp.py index 727cb93..bd4cec2 100644 --- a/IPython/frontend/qt/console/qtconsoleapp.py +++ b/IPython/frontend/qt/console/qtconsoleapp.py @@ -452,16 +452,6 @@ class IPythonQtConsoleApp(BaseIPythonApplication): 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):