From 8553345bcbb35b64c7091def3755a459edd76bfe 2011-11-29 16:18:51 From: Matthias BUSSONNIER Date: 2011-11-29 16:18:51 Subject: [PATCH] qtconsole: fix race-cond, handle multiple exec fix race condition in qtconsole due to a race condition beween 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. Some namedTuple might still carry redundent information but the fix is the first priority fixes #1057 , introduced by #956 --- diff --git a/IPython/frontend/qt/console/frontend_widget.py b/IPython/frontend/qt/console/frontend_widget.py index 1f62432..508cbed 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,14 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): """ Handles replies for code execution. """ self.log.debug("execute: %s", msg.get('content', '')) - info = self._request_info.get('execute') + info_list = self._request_info.get('execute') + msg_id = msg['parent_header']['msg_id'] + if msg_id in info_list: + info = info_list[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,8 +403,8 @@ 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: + info_list.pop(msg_id) + elif info and info.kind == 'silent_exec_callback' and not self._hidden: self._handle_exec_callback(msg) else: super(FrontendWidget, self)._handle_execute_reply(msg) @@ -559,7 +562,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..d095792 100644 --- a/IPython/frontend/qt/console/history_console_widget.py +++ b/IPython/frontend/qt/console/history_console_widget.py @@ -211,18 +211,20 @@ 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: - content = msg['content'] - status = content['status'] - if status == 'ok': - self._max_session_history=(int(content['user_expressions']['hlen'])) + info_list = self._request_info.get('execute') + msg_id = msg['parent_header']['msg_id'] + if msg_id in info_list: + info = info_list.pop(msg_id) + if info.kind == 'save_magic' and not self._hidden: + content = msg['content'] + status = content['status'] + if status == 'ok': + self._max_session_history=(int(content['user_expressions']['hlen'])) def save_magic(self): # update the session history length diff --git a/IPython/frontend/qt/console/ipython_widget.py b/IPython/frontend/qt/console/ipython_widget.py index ae183ab..6913f2d 100644 --- a/IPython/frontend/qt/console/ipython_widget.py +++ b/IPython/frontend/qt/console/ipython_widget.py @@ -165,11 +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']: + info_list = self._request_info.get('execute') + msg_id = msg['parent_header']['msg_id'] + if msg_id in info_list: + info = info_list[msg_id] if info.kind == 'prompt': number = msg['content']['execution_count'] + 1 self._show_interpreter_prompt(number) + info_list.pop(msg_id) else: super(IPythonWidget, self)._handle_execute_reply(msg) @@ -358,7 +361,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 7257d54..7cf24a7 100644 --- a/IPython/frontend/qt/console/mainwindow.py +++ b/IPython/frontend/qt/console/mainwindow.py @@ -629,6 +629,9 @@ class MainWindow(QtGui.QMainWindow): 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, diff --git a/IPython/frontend/qt/console/qtconsoleapp.py b/IPython/frontend/qt/console/qtconsoleapp.py index 21594e8..bd4cec2 100644 --- a/IPython/frontend/qt/console/qtconsoleapp.py +++ b/IPython/frontend/qt/console/qtconsoleapp.py @@ -452,9 +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 - self.kernel_manager.shell_channel.first_reply.connect(self.window.pop.trigger) - self.window.setWindowTitle('Python' if self.pure else 'IPython') def init_colors(self):