Skip to content

Commit 24beaf2

Browse files
committed
issue2550727: db.newid is broken with sqlite.
Added proper transaction lock around the sql code to get a new id. The the locking that pysqlite attempts had to be defeated because it is broken. Had to explicitly manage transactions with BEGIN IMMEDIATE and call sql_commit. Note that this reduces performance by 30% in return for accuracy. Also use update call set newid=newid+1 rather than incrementing in python.
1 parent 9ed3d9b commit 24beaf2

File tree

2 files changed

+30
-3
lines changed

2 files changed

+30
-3
lines changed

CHANGES.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,12 @@ Fixed:
280280
- issue2550839: Xapian, DatabaseLockError: Unable to get write lock on
281281
db/text-index: already locked. Put in a retry loop that will attempt
282282
to get the lock. Total delay approx 4.5 seconds. (John Rouillard)
283+
- issue2550727: db.newid is broken with sqlite. Added proper transaction
284+
lock around the sql code to get a new id. The the locking
285+
that pysqlite attempts had to be defeated because it is broken.
286+
Had to explicitly manage transactions with BEGIN IMMEDIATE and call
287+
sql_commit. Note that this reduces performance in return for accuracy.
288+
Problem reported by Matt Mackall (mpm) (John Rouillard).
283289

284290
2016-01-11: 1.5.1
285291

roundup/backends/back_sqlite.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -366,16 +366,37 @@ def sql_index_exists(self, table_name, index_name):
366366
def newid(self, classname):
367367
""" Generate a new id for the given class
368368
"""
369+
370+
# Prevent other processes from reading while we increment.
371+
# Otherwise multiple processes can end up with the same
372+
# new id and hilarity results.
373+
#
374+
# Defeat pysqlite's attempts to do locking by setting
375+
# isolation_level to None. Pysqlite can commit
376+
# on it's own even if we don't want it to end the transaction.
377+
# If we rewrite to use another sqlite library like apsw we
378+
# don't have to deal with this autocommit/autotransact foolishness.
379+
self.conn.isolation_level = None;
380+
# Manage the transaction locks manually.
381+
self.sql("BEGIN IMMEDIATE");
382+
369383
# get the next ID
370384
sql = 'select num from ids where name=%s'%self.arg
371385
self.sql(sql, (classname, ))
372386
newid = int(self.cursor.fetchone()[0])
373387

374-
# update the counter
375-
sql = 'update ids set num=%s where name=%s'%(self.arg, self.arg)
376-
vals = (int(newid)+1, classname)
388+
# leave the next larger number as the next newid
389+
sql = 'update ids set num=num+1 where name=%s'%self.arg
390+
vals = (classname,)
377391
self.sql(sql, vals)
378392

393+
# reset pysqlite's auto transact stuff to default since the
394+
# rest of the code expects it.
395+
self.conn.isolation_level = '';
396+
# commit writing the data, clearing locks for other processes
397+
# and create a new cursor to the database.
398+
self.sql_commit();
399+
379400
# return as string
380401
return str(newid)
381402

0 commit comments

Comments
 (0)