#!/usr/bin/env python3 import sqlite3 import time import threading DB_FILE = "foo.db" # There are multiple ways to fix this bug # set either or both of the following constants to fix FIX_0 = False FIX_1 = False FIX_2 = True conn = sqlite3.connect(DB_FILE, timeout=60) cursor = conn.cursor() # Init table and add some sample values cursor.execute("create table if not exists t (k int primary key, v int)") with conn: cursor.execute("insert or ignore into t (k, v) values (1, 1)") def fn(): # NB: we use a large timeout but it does *nothing( conn = sqlite3.connect(DB_FILE, timeout=60) with conn: cursor = conn.cursor() cursor2 = conn.cursor() if FIX_0: # This line fixes the code because an DML (like update) # causes the sqlite module to start a transaction via "BEGIN" cursor2.execute("update t set v = 3 where k = ?", (2,)) # This statement will cause SQLite to acquire a SHARED lock cursor.execute("select k from t") if FIX_1: # If FIX_0 was not enabled and we aren't currently in a transaction # then this will release the SHARED lock on the database by completely # stepping the SQLite select statement, thus allowing the following # write transaction to acquire a lock cursor.fetchall() if FIX_2: cursor.close() cursor2.execute("update t set v = 3 where k = ?", (2,)) threads = [] for _ in range(10): threads.append(threading.Thread(target=fn)) threads[-1].start() for thr in threads: thr.join()