##// END OF EJS Templates
Merge pull request #9418 from minrk/catch-bad-db...
Matthias Bussonnier -
r22274:3a1f586b merge
parent child Browse files
Show More
@@ -16,7 +16,6 b' except ImportError:'
16 from pysqlite2 import dbapi2 as sqlite3
16 from pysqlite2 import dbapi2 as sqlite3
17 except ImportError:
17 except ImportError:
18 sqlite3 = None
18 sqlite3 = None
19 import sys
20 import threading
19 import threading
21
20
22 from traitlets.config.configurable import LoggingConfigurable
21 from traitlets.config.configurable import LoggingConfigurable
@@ -72,27 +71,52 b' else:'
72 class OperationalError(Exception):
71 class OperationalError(Exception):
73 "Dummy exception when sqlite could not be imported. Should never occur."
72 "Dummy exception when sqlite could not be imported. Should never occur."
74
73
74 # use 16kB as threshold for whether a corrupt history db should be saved
75 # that should be at least 100 entries or so
76 _SAVE_DB_SIZE = 16384
77
75 @decorator
78 @decorator
76 def catch_corrupt_db(f, self, *a, **kw):
79 def catch_corrupt_db(f, self, *a, **kw):
77 """A decorator which wraps HistoryAccessor method calls to catch errors from
80 """A decorator which wraps HistoryAccessor method calls to catch errors from
78 a corrupt SQLite database, move the old database out of the way, and create
81 a corrupt SQLite database, move the old database out of the way, and create
79 a new one.
82 a new one.
83
84 We avoid clobbering larger databases because this may be triggered due to filesystem issues,
85 not just a corrupt file.
80 """
86 """
81 try:
87 try:
82 return f(self, *a, **kw)
88 return f(self, *a, **kw)
83 except (DatabaseError, OperationalError) as e:
89 except (DatabaseError, OperationalError) as e:
84 if os.path.isfile(self.hist_file):
90 self._corrupt_db_counter += 1
85 # Try to move the file out of the way
91 self.log.error("Failed to open SQLite history %s (%s).", self.hist_file, e)
86 base,ext = os.path.splitext(self.hist_file)
92 if self.hist_file != ':memory:':
87 newpath = base + '-corrupt' + ext
93 if self._corrupt_db_counter > self._corrupt_db_limit:
88 os.rename(self.hist_file, newpath)
94 self.hist_file = ':memory:'
89 print("ERROR! History file wasn't a valid SQLite database (%s)." % e,
95 self.log.error("Failed to load history too many times, history will not be saved.")
90 "It was moved to %s" % newpath, "and a new file created.", file=sys.stderr)
96 elif os.path.isfile(self.hist_file):
97 # move the file out of the way
98 base, ext = os.path.splitext(self.hist_file)
99 size = os.stat(self.hist_file).st_size
100 if size >= _SAVE_DB_SIZE:
101 # if there's significant content, avoid clobbering
102 now = datetime.datetime.now().isoformat().replace(':', '.')
103 newpath = base + '-corrupt-' + now + ext
104 # don't clobber previous corrupt backups
105 for i in range(100):
106 if not os.path.isfile(newpath):
107 break
108 else:
109 newpath = base + '-corrupt-' + now + (u'-%i' % i) + ext
110 else:
111 # not much content, possibly empty; don't worry about clobbering
112 # maybe we should just delete it?
113 newpath = base + '-corrupt' + ext
114 os.rename(self.hist_file, newpath)
115 self.log.error("History file was moved to %s and a new file created.", newpath)
91 self.init_db()
116 self.init_db()
92 return []
117 return []
93
94 else:
118 else:
95 # The hist_file is probably :memory: or something else.
119 # Failed with :memory:, something serious is wrong
96 raise
120 raise
97
121
98 class HistoryAccessorBase(LoggingConfigurable):
122 class HistoryAccessorBase(LoggingConfigurable):
@@ -118,6 +142,11 b' class HistoryAccessor(HistoryAccessorBase):'
118 This is intended for use by standalone history tools. IPython shells use
142 This is intended for use by standalone history tools. IPython shells use
119 HistoryManager, below, which is a subclass of this."""
143 HistoryManager, below, which is a subclass of this."""
120
144
145 # counter for init_db retries, so we don't keep trying over and over
146 _corrupt_db_counter = 0
147 # after two failures, fallback on :memory:
148 _corrupt_db_limit = 2
149
121 # String holding the path to the history file
150 # String holding the path to the history file
122 hist_file = Unicode(config=True,
151 hist_file = Unicode(config=True,
123 help="""Path to file to use for SQLite history database.
152 help="""Path to file to use for SQLite history database.
@@ -231,6 +260,8 b' class HistoryAccessor(HistoryAccessorBase):'
231 (session integer, line integer, output text,
260 (session integer, line integer, output text,
232 PRIMARY KEY (session, line))""")
261 PRIMARY KEY (session, line))""")
233 self.db.commit()
262 self.db.commit()
263 # success! reset corrupt db count
264 self._corrupt_db_counter = 0
234
265
235 def writeout_cache(self):
266 def writeout_cache(self):
236 """Overridden by HistoryManager to dump the cache before certain
267 """Overridden by HistoryManager to dump the cache before certain
General Comments 0
You need to be logged in to leave comments. Login now