##// 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 16 from pysqlite2 import dbapi2 as sqlite3
17 17 except ImportError:
18 18 sqlite3 = None
19 import sys
20 19 import threading
21 20
22 21 from traitlets.config.configurable import LoggingConfigurable
@@ -72,27 +71,52 b' else:'
72 71 class OperationalError(Exception):
73 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 78 @decorator
76 79 def catch_corrupt_db(f, self, *a, **kw):
77 80 """A decorator which wraps HistoryAccessor method calls to catch errors from
78 81 a corrupt SQLite database, move the old database out of the way, and create
79 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 87 try:
82 88 return f(self, *a, **kw)
83 89 except (DatabaseError, OperationalError) as e:
84 if os.path.isfile(self.hist_file):
85 # Try to move the file out of the way
86 base,ext = os.path.splitext(self.hist_file)
87 newpath = base + '-corrupt' + ext
88 os.rename(self.hist_file, newpath)
89 print("ERROR! History file wasn't a valid SQLite database (%s)." % e,
90 "It was moved to %s" % newpath, "and a new file created.", file=sys.stderr)
90 self._corrupt_db_counter += 1
91 self.log.error("Failed to open SQLite history %s (%s).", self.hist_file, e)
92 if self.hist_file != ':memory:':
93 if self._corrupt_db_counter > self._corrupt_db_limit:
94 self.hist_file = ':memory:'
95 self.log.error("Failed to load history too many times, history will not be saved.")
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 116 self.init_db()
92 117 return []
93
94 118 else:
95 # The hist_file is probably :memory: or something else.
119 # Failed with :memory:, something serious is wrong
96 120 raise
97 121
98 122 class HistoryAccessorBase(LoggingConfigurable):
@@ -118,6 +142,11 b' class HistoryAccessor(HistoryAccessorBase):'
118 142 This is intended for use by standalone history tools. IPython shells use
119 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 150 # String holding the path to the history file
122 151 hist_file = Unicode(config=True,
123 152 help="""Path to file to use for SQLite history database.
@@ -231,6 +260,8 b' class HistoryAccessor(HistoryAccessorBase):'
231 260 (session integer, line integer, output text,
232 261 PRIMARY KEY (session, line))""")
233 262 self.db.commit()
263 # success! reset corrupt db count
264 self._corrupt_db_counter = 0
234 265
235 266 def writeout_cache(self):
236 267 """Overridden by HistoryManager to dump the cache before certain
General Comments 0
You need to be logged in to leave comments. Login now