Skip to content

Commit

Permalink
Allow connections to be shared between threads (given check_same_thre…
Browse files Browse the repository at this point in the history
…ad=False)
  • Loading branch information
ivknv committed Sep 17, 2017
1 parent 26e312b commit 05914af
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 10 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
.*.sw[a-z]
*.py[co]
build/
dist/
s3m.egg-info/
22 changes: 15 additions & 7 deletions s3m.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

__all__ = ["connect", "Connection", "S3MError", "LockTimeoutError"]

version = "1.0.0"
version = "1.0.1"

# Global lock storage
CONNECTION_LOCKS = {}
Expand Down Expand Up @@ -83,8 +83,11 @@ def __init__(self, path, lock_transactions=True, lock_timeout=-1, *args, **kwarg
# Should parallel transactions be allowed?
self.lock_transactions = lock_transactions

# This lock is used to control sharing of the connection between threads
self.personal_lock = threading.RLock()

if self.path == ":memory:":
# No 2 :memory: connections point to the same database => locks are not needed
# No two :memory: connections point to the same database => locks are not needed
self.lock = FakeLock()
return

Expand Down Expand Up @@ -130,19 +133,24 @@ def __enter__(self):
if not self.lock.acquire(timeout=self.lock_timeout):
raise LockTimeoutError(self)

if not self.personal_lock.acquire(timeout=self.lock_timeout):
raise LockTimeoutError(self)

self.was_in_transaction = self.connection.in_transaction

def __exit__(self, *args, **kwargs):
try:
# if the connection is closed, an exception is thrown
in_transaction = self.in_transaction
except sqlite3.ProgrammingError:
in_transaction = False
self.personal_lock.release()

if not self.lock_transactions:
self.lock.release()
return

try:
# If the connection is closed, an exception is thrown
in_transaction = self.in_transaction
except sqlite3.ProgrammingError:
in_transaction = False

# The lock should be released only if:
# 1) the connection was previously in a transaction and now it isn't
# 2) the connection wasn't previously in a transaction and still isn't
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from setuptools import setup

setup(name="s3m",
version="1.0.0",
version="1.0.1",
description="sqlite3 wrapper for multi-threaded applications",
author="Ivan Konovalov",
py_modules=["s3m"])
27 changes: 25 additions & 2 deletions tests/s3m_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ def test_s3m(self):
self.assertEqual(result, [(1,), (2,), (3,)] * self.n_connections)

def test_s3m_lock_transactions(self):
conn1 = self.connect_db(self.db_path, lock_transactions=False)
conn2 = self.connect_db(self.db_path, lock_transactions=False, check_same_thread=False)
conn1 = self.connect_db(self.db_path, lock_transactions=False, lock_timeout=0.5)
conn2 = self.connect_db(self.db_path, lock_transactions=False, lock_timeout=0.5,
check_same_thread=False)

def thread_func():
conn2.execute("BEGIN TRANSACTION")
Expand Down Expand Up @@ -107,6 +108,28 @@ def test_in_memory2(self):
conn1.commit()
conn2.commit()

def test_sharing(self):
conn = self.connect_db(":memory:", check_same_thread=False)

conn.execute("CREATE TABLE a(id INTEGER)")
conn.execute("BEGIN TRANSACTION")

def func():
for i in range(25):
conn.execute("INSERT INTO a VALUES(?)", (i,))

thread = threading.Thread(target=func)

thread.start()
func()
thread.join()

conn.commit()

conn.execute("SELECT id FROM a")

self.assertEqual(len(conn.fetchall()), 50)

def tearDown(self):
try:
os.remove(self.db_path)
Expand Down

0 comments on commit 05914af

Please sign in to comment.