diff --git a/IPython/core/history.py b/IPython/core/history.py index aaf8622..ba13aa5 100644 --- a/IPython/core/history.py +++ b/IPython/core/history.py @@ -13,6 +13,7 @@ from __future__ import print_function # Stdlib imports +import atexit import fnmatch import json import os @@ -105,6 +106,14 @@ class HistoryManager(object): # Fill the history zero entry, user counter starts at 1 self.store_inputs('\n', '\n') + + # Create and start the autosaver. + self.autosave_flag = threading.Event() + self.autosave_timer = HistorySaveThread(self.autosave_flag, 60) + self.autosave_timer.start() + # Register the autosave handler to be triggered as a post execute + # callback. + self.shell.register_post_execute(self.autosave_if_due) def _init_shadow_hist(self): try: @@ -142,12 +151,22 @@ class HistoryManager(object): with open(self.hist_file,'wt') as hfile: json.dump(hist, hfile, sort_keys=True, indent=4) + + def autosave_if_due(self): + """Check if the autosave event is set; if so, save history. We do it + this way so that the save takes place in the main thread.""" + if self.autosave_flag.is_set(): + self.save_history() + self.autosave_flag.clear() def reload_history(self): """Reload the input history from disk file.""" with open(self.hist_file,'rt') as hfile: - hist = json.load(hfile) + try: + hist = json.load(hfile) + except ValueError: # Ignore it if JSON is corrupt. + return self.input_hist_parsed = hist['parsed'] self.input_hist_raw = hist['raw'] if self.shell.has_readline: @@ -254,25 +273,37 @@ class HistoryManager(object): self.dir_hist[:] = [os.getcwd()] class HistorySaveThread(threading.Thread): - """Thread to save history periodically""" + """This thread makes IPython save history periodically. - def __init__(self, IPython_object, time_interval, exit_now): + Without this class, IPython would only save the history on a clean exit. + This saves the history periodically (the current default is once per + minute), so that it is not lost in the event of a crash. + + The implementation sets an event to indicate that history should be saved. + The actual save is carried out after executing a user command, to avoid + thread issues. + """ + daemon = True + + def __init__(self, autosave_flag, time_interval=60): threading.Thread.__init__(self) - self.IPython_object = IPython_object self.time_interval = time_interval - self.exit_now = exit_now - self.cond = threading.Condition() + self.autosave_flag = autosave_flag + self.exit_now = threading.Event() + # Ensure the thread is stopped tidily when exiting normally + atexit.register(self.stop) def run(self): - while 1: - self.cond.acquire() - self.cond.wait(self.time_interval) - self.cond.release() - if self.exit_now==True: + while True: + self.exit_now.wait(self.time_interval) + if self.exit_now.is_set(): break - #printing for debug - #print("Saving...") - self.IPython_object.save_history() + self.autosave_flag.set() + + def stop(self): + """Safely and quickly stop the autosave timer thread.""" + self.exit_now.set() + self.join() def magic_history(self, parameter_s = ''): """Print input history (_i variables), with most recent last. diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index d1b2108..52753b3 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -45,7 +45,6 @@ from IPython.core.error import TryNext, UsageError from IPython.core.extensions import ExtensionManager from IPython.core.fakemodule import FakeModule, init_fakemod_dict from IPython.core.history import HistoryManager -from IPython.core.history import HistorySaveThread from IPython.core.inputsplitter import IPythonInputSplitter from IPython.core.logger import Logger from IPython.core.magic import Magic @@ -294,8 +293,6 @@ class InteractiveShell(Configurable, Magic): self.init_payload() self.hooks.late_startup_hook() atexit.register(self.atexit_operations) - self.history_thread = HistorySaveThread(self, 60, False) - self.history_thread.start() # While we're trying to have each part of the code directly access what it # needs without keeping redundant references to objects, we have too much @@ -1238,6 +1235,7 @@ class InteractiveShell(Configurable, Magic): #------------------------------------------------------------------------- def init_history(self): + """Sets up the command history, and starts regular autosaves.""" self.history_manager = HistoryManager(shell=self) def save_history(self): diff --git a/IPython/frontend/terminal/interactiveshell.py b/IPython/frontend/terminal/interactiveshell.py index ef59c38..671f82b 100644 --- a/IPython/frontend/terminal/interactiveshell.py +++ b/IPython/frontend/terminal/interactiveshell.py @@ -24,7 +24,6 @@ import sys from IPython.core.error import TryNext from IPython.core.usage import interactive_usage, default_banner from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC -from IPython.core.history import HistorySaveThread from IPython.lib.inputhook import enable_gui from IPython.lib.pylabtools import pylab_activate from IPython.utils.terminal import toggle_set_term_title, set_term_title @@ -499,10 +498,6 @@ class TerminalInteractiveShell(InteractiveShell): This method calls the ask_exit callback.""" if self.confirm_exit: if self.ask_yes_no('Do you really want to exit ([y]/n)?','y'): - self.shell.history_thread.exit_now=True - self.shell.history_thread.cond.acquire() - self.shell.history_thread.cond.notify() - self.shell.history_thread.cond.release() self.ask_exit() else: self.ask_exit()