Merge lp:~jelmer/bzr-svn/sqlite-lock into lp:bzr-svn/1.1

Proposed by Jelmer Vernooij
Status: Merged
Merged at revision: 3807
Proposed branch: lp:~jelmer/bzr-svn/sqlite-lock
Merge into: lp:bzr-svn/1.1
Diff against target: 473 lines (+127/-52)
5 files modified
NEWS (+3/-0)
cache/__init__.py (+5/-0)
cache/sqlitecache.py (+58/-33)
logwalker.py (+34/-7)
revmeta.py (+27/-12)
To merge this branch: bzr merge lp:~jelmer/bzr-svn/sqlite-lock
Reviewer Review Type Date Requested Status
bzr-svn developers Pending
Review via email: mp+71591@code.launchpad.net

Description of the change

When hitting a SQLite lock, simply continue without the cache.

To post a comment you must log in.
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

This looks ok to me. (dunno if you wanted my opinion :p). I bet you didn't mean to leave that "raise ImportError" in though :)

The change in get_revision_paths looks a little strange -- does what happens in the else: clause of the try: not access the db?

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

Thanks for the review, and catching that ImportError. Removed now :)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2011-08-09 15:07:13 +0000
3+++ NEWS 2011-08-15 17:39:32 +0000
4@@ -96,6 +96,9 @@
5 * Fix importing of non-ascii characters in bzr:revprop: values.
6 (Jelmer Vernooij, #821168)
7
8+ * Allow concurrent access of SQLite cache, rather than aborting.
9+ (Jelmer Vernooij, #685251)
10+
11 FEATURES
12
13 * When roundtripping revisions a revision property with the Bazaar testament is
14
15=== modified file 'cache/__init__.py'
16--- cache/__init__.py 2011-08-13 14:22:02 +0000
17+++ cache/__init__.py 2011-08-15 17:39:32 +0000
18@@ -38,6 +38,10 @@
19 from bzrlib.plugins.svn import version_info
20
21
22+class CacheConcurrencyError(Exception):
23+ """Unable to access cache while write is in progress."""
24+
25+
26 def write_cache_readme(path):
27 f = open(path, 'w')
28 try:
29@@ -146,6 +150,7 @@
30 try:
31 from bzrlib.plugins.svn.cache.tdbcache import TdbRepositoryCache
32 cache_cls = TdbRepositoryCache
33+ raise ImportError
34 except ImportError:
35 from bzrlib.plugins.svn.cache.sqlitecache import SqliteRepositoryCache
36 cache_cls = SqliteRepositoryCache
37
38=== modified file 'cache/sqlitecache.py'
39--- cache/sqlitecache.py 2011-07-30 16:44:57 +0000
40+++ cache/sqlitecache.py 2011-08-15 17:39:32 +0000
41@@ -27,6 +27,7 @@
42 )
43
44 from bzrlib.plugins.svn.cache import (
45+ CacheConcurrencyError,
46 RepositoryCache,
47 )
48 from bzrlib.plugins.svn.mapping import (
49@@ -71,7 +72,7 @@
50
51
52 def _connect_sqlite3_file(path):
53- return sqlite3.connect(path, timeout=20.0)
54+ return sqlite3.connect(path, timeout=20.0, isolation_level=None)
55
56
57 connect_cachefile = _connect_sqlite3_file
58@@ -87,8 +88,32 @@
59 self.cachedb = cache_db
60 self._commit_interval = 500
61 self._create_table()
62- self.cachedb.commit()
63- self._commit_countdown = self._commit_interval
64+ self.commit()
65+
66+ @classmethod
67+ def _convert_operational_errors(cls, e):
68+ if "database is locked" in e:
69+ return CacheConcurrencyError()
70+ else:
71+ return e
72+
73+ def execute(self, *args, **kwargs):
74+ try:
75+ return self.cachedb.execute(*args, **kwargs)
76+ except sqlite3.OperationalError, e:
77+ raise self._convert_operational_errors(e)
78+
79+ def executemany(self, *args, **kwargs):
80+ try:
81+ return self.cachedb.executemany(*args, **kwargs)
82+ except sqlite3.OperationalError, e:
83+ raise self._convert_operational_errors(e)
84+
85+ def executescript(self, *args, **kwargs):
86+ try:
87+ return self.cachedb.executescript(*args, **kwargs)
88+ except sqlite3.OperationalError, e:
89+ raise self._convert_operational_errors(e)
90
91 def commit(self):
92 """Commit the changes to the database."""
93@@ -140,13 +165,13 @@
94 """See RevisionIdMapCache.set_last_revnum_checked."""
95
96 self.mutter("set last revnum checked for %r to %r", layout, revnum)
97- self.cachedb.execute("replace into revids_seen (layout, max_revnum) VALUES (?, ?)", (layout, revnum))
98+ self.execute("replace into revids_seen (layout, max_revnum) VALUES (?, ?)", (layout, revnum))
99 self.commit()
100
101 def last_revnum_checked(self, layout):
102 """See RevisionIdMapCache.last_revnum_checked."""
103
104- ret = self.cachedb.execute(
105+ ret = self.execute(
106 "select max_revnum from revids_seen where layout = ?", (layout,)).fetchone()
107 if ret is None:
108 revnum = 0
109@@ -160,7 +185,7 @@
110
111 assert isinstance(revid, str)
112 self.mutter("lookup revid %r", revid)
113- ret = self.cachedb.execute(
114+ ret = self.execute(
115 "select path, min_revnum, max_revnum, mapping from revmap where revid=? order by abs(min_revnum-max_revnum) asc", (revid,)).fetchone()
116 if ret is None:
117 raise errors.NoSuchRevision(self, revid)
118@@ -176,7 +201,7 @@
119 assert isinstance(revnum, int)
120 assert isinstance(path, str)
121 assert isinstance(mapping, str)
122- row = self.cachedb.execute(
123+ row = self.execute(
124 "select revid from revmap where max_revnum=? and min_revnum=? and path=? and mapping=?", (revnum, revnum, path, mapping)).fetchone()
125 if row is not None:
126 ret = str(row[0])
127@@ -195,16 +220,16 @@
128 assert min_revnum <= max_revnum
129 self.mutter("insert revid %r:%r-%r -> %r", branch, min_revnum, max_revnum, revid)
130 if min_revnum == max_revnum:
131- cursor = self.cachedb.execute(
132+ cursor = self.execute(
133 "update revmap set min_revnum = ?, max_revnum = ? WHERE revid=? AND path=? AND mapping=?",
134 (min_revnum, max_revnum, revid, branch, mapping))
135 else:
136- cursor = self.cachedb.execute(
137+ cursor = self.execute(
138 "update revmap set min_revnum = MAX(min_revnum,?), max_revnum = MIN(max_revnum, ?) WHERE revid=? AND path=? AND mapping=?",
139 (min_revnum, max_revnum, revid, branch, mapping))
140 if cursor.rowcount == 0:
141- self.cachedb.execute(
142- "insert into revmap (revid,path,min_revnum,max_revnum,mapping) VALUES (?,?,?,?,?)",
143+ self.execute(
144+ "replace into revmap (revid,path,min_revnum,max_revnum,mapping) VALUES (?,?,?,?,?)",
145 (revid, branch, min_revnum, max_revnum, mapping))
146 self._commit_conditionally()
147
148@@ -212,7 +237,7 @@
149 class SqliteRevisionInfoCache(RevisionInfoCache, CacheTable):
150
151 def _create_table(self):
152- self.cachedb.executescript("""
153+ self.executescript("""
154 create table if not exists revmetainfo (
155 path text not null,
156 revnum integer,
157@@ -240,20 +265,20 @@
158 orig_mapping_name = original_mapping.name
159 else:
160 orig_mapping_name = None
161- self.cachedb.execute("insert into original_mapping (path, revnum, original_mapping) values (?, ?, ?)", (foreign_revid[1].decode("utf-8"), foreign_revid[2], orig_mapping_name))
162+ self.execute("replace into original_mapping (path, revnum, original_mapping) values (?, ?, ?)", (foreign_revid[1].decode("utf-8"), foreign_revid[2], orig_mapping_name))
163
164 def insert_revision(self, foreign_revid, mapping, (revno, revid, hidden),
165 stored_lhs_parent_revid):
166 """See RevisionInfoCache.insert_revision."""
167
168- self.cachedb.execute("insert into revmetainfo (path, revnum, mapping, revid, revno, hidden, stored_lhs_parent_revid) values (?, ?, ?, ?, ?, ?, ?)", (foreign_revid[1], foreign_revid[2], mapping.name, revid, revno, hidden, stored_lhs_parent_revid))
169+ self.execute("replace into revmetainfo (path, revnum, mapping, revid, revno, hidden, stored_lhs_parent_revid) values (?, ?, ?, ?, ?, ?, ?)", (foreign_revid[1], foreign_revid[2], mapping.name, revid, revno, hidden, stored_lhs_parent_revid))
170 self._commit_conditionally()
171
172 def get_revision(self, foreign_revid, mapping):
173 """See RevisionInfoCache.get_revision."""
174
175 # Will raise KeyError if not present
176- row = self.cachedb.execute("select revno, revid, hidden, stored_lhs_parent_revid from revmetainfo where path = ? and revnum = ? and mapping = ?", (foreign_revid[1], foreign_revid[2], mapping.name)).fetchone()
177+ row = self.execute("select revno, revid, hidden, stored_lhs_parent_revid from revmetainfo where path = ? and revnum = ? and mapping = ?", (foreign_revid[1], foreign_revid[2], mapping.name)).fetchone()
178 if row is None:
179 raise KeyError((foreign_revid, mapping))
180 else:
181@@ -270,7 +295,7 @@
182 def get_original_mapping(self, foreign_revid):
183 """See RevisionInfoCache.get_original_mapping."""
184
185- row = self.cachedb.execute("select original_mapping from original_mapping where path = ? and revnum = ?", (foreign_revid[1].decode("utf-8"), foreign_revid[2])).fetchone()
186+ row = self.execute("select original_mapping from original_mapping where path = ? and revnum = ?", (foreign_revid[1].decode("utf-8"), foreign_revid[2])).fetchone()
187 if row is None:
188 raise KeyError(foreign_revid)
189 if row[0] is None:
190@@ -282,7 +307,7 @@
191 class SqliteLogCache(LogCache, CacheTable):
192
193 def _create_table(self):
194- self.cachedb.executescript("""
195+ self.executescript("""
196 create table if not exists changed_path(
197 rev integer,
198 action text not null check(action in ('A', 'D', 'M', 'R')),
199@@ -305,7 +330,7 @@
200 create unique index if not exists revinfo_rev on revinfo(rev);
201 """ % NODE_UNKNOWN)
202 try:
203- self.cachedb.executescript(
204+ self.executescript(
205 "alter table changed_path ADD kind INT DEFAULT %d;" % NODE_UNKNOWN)
206 except sqlite3.OperationalError:
207 pass # Column already exists.
208@@ -314,8 +339,8 @@
209 """See LogCache.find_latest_change."""
210
211 if path == "":
212- return self.cachedb.execute("select max(rev) from changed_path where rev <= ?", (revnum,)).fetchone()[0]
213- return self.cachedb.execute("""
214+ return self.execute("select max(rev) from changed_path where rev <= ?", (revnum,)).fetchone()[0]
215+ return self.execute("""
216 select max(rev) from changed_path
217 where rev <= ?
218 and (path=?
219@@ -327,7 +352,7 @@
220 def get_revision_paths(self, revnum):
221 """See LogCache.get_revision_paths."""
222
223- result = self.cachedb.execute("select path, action, copyfrom_path, copyfrom_rev, kind from changed_path where rev=?", (revnum,))
224+ result = self.execute("select path, action, copyfrom_path, copyfrom_rev, kind from changed_path where rev=?", (revnum,))
225 paths = {}
226 for p, act, cf, cr, kind in result:
227 if cf is not None:
228@@ -353,19 +378,19 @@
229 kind = NODE_UNKNOWN
230 new_paths.append((rev, p.strip("/").decode("utf-8"), v[0], copyfrom_path, v[2], kind))
231
232- self.cachedb.executemany("replace into changed_path (rev, path, action, copyfrom_path, copyfrom_rev, kind) values (?, ?, ?, ?, ?, ?)", new_paths)
233+ self.executemany("replace into changed_path (rev, path, action, copyfrom_path, copyfrom_rev, kind) values (?, ?, ?, ?, ?, ?)", new_paths)
234
235 def drop_revprops(self, revnum):
236 """See LogCache.drop_revprops."""
237
238- self.cachedb.execute("update revinfo set all_revprops = 0 where rev = ?", (revnum,))
239+ self.execute("update revinfo set all_revprops = 0 where rev = ?", (revnum,))
240
241 def get_revprops(self, revnum):
242 """See LogCache.get_revprops."""
243
244- result = self.cachedb.execute("select name, value from revprop where rev = ?", (revnum,))
245+ result = self.execute("select name, value from revprop where rev = ?", (revnum,))
246 revprops = dict((k.encode("utf-8"), v.encode("utf-8")) for (k, v) in result)
247- result = self.cachedb.execute("select all_revprops from revinfo where rev = ?", (revnum,)).fetchone()
248+ result = self.execute("select all_revprops from revinfo where rev = ?", (revnum,)).fetchone()
249 if result is None:
250 all_revprops = False
251 else:
252@@ -377,15 +402,15 @@
253
254 if revprops is None:
255 return
256- self.cachedb.executemany("replace into revprop (rev, name, value) values (?, ?, ?)", [(revision, name.decode("utf-8", "replace"), value.decode("utf-8", "replace")) for (name, value) in revprops.iteritems()])
257- self.cachedb.execute("""
258+ self.executemany("replace into revprop (rev, name, value) values (?, ?, ?)", [(revision, name.decode("utf-8", "replace"), value.decode("utf-8", "replace")) for (name, value) in revprops.iteritems()])
259+ self.execute("""
260 replace into revinfo (rev, all_revprops) values (?, ?)
261 """, (revision, all_revprops))
262
263 def max_revnum(self):
264 """See LogCache.last_revnum."""
265
266- saved_revnum = self.cachedb.execute("SELECT MAX(rev) FROM changed_path").fetchone()[0]
267+ saved_revnum = self.execute("SELECT MAX(rev) FROM changed_path").fetchone()[0]
268 if saved_revnum is None:
269 return 0
270 return saved_revnum
271@@ -393,13 +418,13 @@
272 def min_revnum(self):
273 """See LogCache.min_revnum."""
274
275- return self.cachedb.execute("SELECT MIN(rev) FROM changed_path").fetchone()[0]
276+ return self.execute("SELECT MIN(rev) FROM changed_path").fetchone()[0]
277
278
279 class SqliteParentsCache(ParentsCache, CacheTable):
280
281 def _create_table(self):
282- self.cachedb.executescript("""
283+ self.executescript("""
284 create table if not exists parent (
285 rev text not null,
286 parent text,
287@@ -415,17 +440,17 @@
288
289 self.mutter('insert parents: %r -> %r', revid, parents)
290 if len(parents) == 0:
291- self.cachedb.execute("replace into parent (rev, parent, idx) values (?, NULL, -1)", (revid,))
292+ self.execute("replace into parent (rev, parent, idx) values (?, NULL, -1)", (revid,))
293 else:
294 for i, p in enumerate(parents):
295- self.cachedb.execute("replace into parent (rev, parent, idx) values (?, ?, ?)", (revid, p, i))
296+ self.execute("replace into parent (rev, parent, idx) values (?, ?, ?)", (revid, p, i))
297 self._commit_conditionally()
298
299 def lookup_parents(self, revid):
300 """See ParentsCache.lookup_parents."""
301
302 self.mutter('lookup parents: %r', revid)
303- rows = self.cachedb.execute("select parent from parent where rev = ? order by idx", (revid, )).fetchall()
304+ rows = self.execute("select parent from parent where rev = ? order by idx", (revid, )).fetchall()
305 if len(rows) == 0:
306 return None
307 return tuple([row[0].encode("utf-8") for row in rows if row[0] is not None])
308
309=== modified file 'logwalker.py'
310--- logwalker.py 2011-08-15 01:13:33 +0000
311+++ logwalker.py 2011-08-15 17:39:32 +0000
312@@ -33,6 +33,9 @@
313 from bzrlib.plugins.svn import (
314 changes,
315 )
316+from bzrlib.plugins.svn.cache import (
317+ CacheConcurrencyError,
318+ )
319 from bzrlib.plugins.svn.transport import (
320 SvnRaTransport,
321 )
322@@ -231,6 +234,9 @@
323 if "logwalker" in debug.debug_flags:
324 trace.mutter(text, *args, **kwargs)
325
326+ def _warn_busy_cache(self):
327+ trace.warning("Cache for repository %s busy, ignoring")
328+
329 def find_latest_change(self, path, revnum):
330 """Find latest revision that touched path.
331
332@@ -239,7 +245,11 @@
333 """
334 assert isinstance(path, str)
335 assert isinstance(revnum, int) and revnum >= 0
336- self._fetch_revisions(revnum)
337+ try:
338+ self._fetch_revisions(revnum)
339+ except CacheConcurrencyError:
340+ self._warn_busy_cache()
341+ return self.actual.find_latest_change(path, revnum)
342
343 self.mutter("latest change: %r:%r", path, revnum)
344 try:
345@@ -263,19 +273,33 @@
346 """
347 assert from_revnum >= 0 and to_revnum >= 0, "%r -> %r" % (from_revnum, to_revnum)
348 self.mutter("iter changes %r->%r (%r)", from_revnum, to_revnum, prefixes)
349- self._fetch_revisions(max(from_revnum, to_revnum), pb=pb)
350+ try:
351+ self._fetch_revisions(max(from_revnum, to_revnum), pb=pb)
352+ except CacheConcurrencyError:
353+ self._warn_busy_cache()
354+ return self.actual.iter_changes(prefixes, from_revnum, to_revnum,
355+ limit, pb)
356 return iter(iter_changes(prefixes, from_revnum, to_revnum,
357 self.get_revision_paths, self.revprop_list, limit))
358
359 def get_revision_paths(self, revnum):
360 if revnum == 0:
361 return changes.REV0_CHANGES
362- self._fetch_revisions(revnum)
363- return self.cache.get_revision_paths(revnum)
364+ try:
365+ self._fetch_revisions(revnum)
366+ except CacheConcurrencyError:
367+ self._warn_busy_cache()
368+ return self.actual.get_revision_paths(revnum)
369+ else:
370+ return self.cache.get_revision_paths(revnum)
371
372 def revprop_list(self, revnum):
373 self.mutter('revprop list: %d' % revnum)
374- self._fetch_revisions(revnum)
375+ try:
376+ self._fetch_revisions(revnum)
377+ except CacheConcurrencyError:
378+ self._warn_busy_cache()
379+ return self.actual.revprop_list(revnum)
380
381 if revnum > 0:
382 (known_revprops, has_all_revprops) = self.cache.get_revprops(revnum)
383@@ -290,8 +314,11 @@
384
385 def _caching_revprop_list(self, revnum):
386 revprops = self._transport.revprop_list(revnum)
387- self.cache.insert_revprops(revnum, revprops, True)
388- self.cache.commit()
389+ try:
390+ self.cache.insert_revprops(revnum, revprops, True)
391+ self.cache.commit()
392+ except CacheConcurrencyError:
393+ pass
394 return revprops
395
396 def _fetch_revisions(self, to_revnum, pb=None):
397
398=== modified file 'revmeta.py'
399--- revmeta.py 2011-08-13 11:27:34 +0000
400+++ revmeta.py 2011-08-15 17:39:32 +0000
401@@ -35,6 +35,9 @@
402 errors as svn_errors,
403 util,
404 )
405+from bzrlib.plugins.svn.cache import (
406+ CacheConcurrencyError,
407+ )
408 from bzrlib.plugins.svn.mapping import (
409 SVN_PROP_BZR_HIDDEN,
410 SVN_PROP_BZR_REVPROP_REDIRECT,
411@@ -735,14 +738,19 @@
412 self._stored_lhs_parent_revid = {}
413
414 def _update_cache(self, mapping):
415- if (self.get_original_mapping() is not None and
416- self._revision_info[mapping][1] is not None):
417- self._revid_cache.insert_revid(self._revision_info[mapping][1],
418- self.metarev.branch_path, self.metarev.revnum,
419- self.metarev.revnum, mapping.name)
420- self._revinfo_cache.insert_revision(self.metarev.get_foreign_revid(), mapping,
421- self._revision_info[mapping],
422- self._stored_lhs_parent_revid[mapping])
423+ try:
424+ if (self.get_original_mapping() is not None and
425+ self._revision_info[mapping][1] is not None):
426+ self._revid_cache.insert_revid(self._revision_info[mapping][1],
427+ self.metarev.branch_path, self.metarev.revnum,
428+ self.metarev.revnum, mapping.name)
429+ self._revinfo_cache.insert_revision(
430+ self.metarev.get_foreign_revid(),
431+ mapping,
432+ self._revision_info[mapping],
433+ self._stored_lhs_parent_revid[mapping])
434+ except CacheConcurrencyError:
435+ pass
436
437 def _determine(self, mapping):
438 self._revision_info[mapping] = self.base.get_revision_info(mapping)
439@@ -752,8 +760,8 @@
440 assert mapping is not None
441 (self._revision_info[mapping],
442 self._stored_lhs_parent_revid[mapping]) = \
443- self._revinfo_cache.get_revision(self.metarev.get_foreign_revid(),
444- mapping)
445+ self._revinfo_cache.get_revision(
446+ self.metarev.get_foreign_revid(), mapping)
447
448 def get_original_mapping(self):
449 if self._original_mapping_set:
450@@ -763,8 +771,12 @@
451 self.metarev.get_foreign_revid())
452 except KeyError:
453 self._original_mapping = self.base.get_original_mapping()
454- self._revinfo_cache.set_original_mapping(self.metarev.get_foreign_revid(),
455+ try:
456+ self._revinfo_cache.set_original_mapping(
457+ self.metarev.get_foreign_revid(),
458 self._original_mapping)
459+ except CacheConcurrencyError:
460+ pass
461 self._original_mapping_set = True
462 return self._original_mapping
463
464@@ -815,7 +827,10 @@
465 if parent_ids is not None:
466 return parent_ids
467 parent_ids = self.base.get_parent_ids(mapping, parentrevmeta)
468- self._parents_cache.insert_parents(myrevid, parent_ids)
469+ try:
470+ self._parents_cache.insert_parents(myrevid, parent_ids)
471+ except CacheConcurrencyError:
472+ pass
473 return parent_ids
474
475

Subscribers

People subscribed via source and target branches