Source code for ewoksutils.tests.test_logging_sqlite3
import logging
import sqlite3
import threading
import time
from .. import sqlite3_utils
from ..logging_utils.sqlite3 import Sqlite3Handler
FIELD_TYPES = {"field1": 0, "field2": ""}
[docs]
def test_concurrent_read_write(tmp_path):
"""Records emitted from several handlers while a reader is polling
must all be inserted."""
uri = str(tmp_path / "test.db")
nwriters = 4
nrecords = 20
# Barrier which ensures that reading and writing overlaps
started = threading.Barrier(nwriters + 1, timeout=10)
# Publish concurrently
def write_records(value):
handler = Sqlite3Handler(uri, "mytable", FIELD_TYPES)
try:
started.wait()
for i in range(nrecords):
handler.handle(_make_record(field1=value, field2=str(i)))
finally:
handler.close()
writer_threads = [
threading.Thread(target=write_records, args=(value,))
for value in range(nwriters)
]
# Read concurrently
stop_reading = threading.Event()
def read_records():
with sqlite3_utils.connect(uri, uri=True) as conn:
started.wait()
while not stop_reading.is_set():
try:
_ = list(
sqlite3_utils.select(conn, "mytable", field_types=FIELD_TYPES)
)
except sqlite3.OperationalError:
pass
time.sleep(0.01)
reader_thread = threading.Thread(target=read_records)
reader_thread.start()
try:
for thread in writer_threads:
thread.start()
for thread in writer_threads:
thread.join(timeout=60)
finally:
stop_reading.set()
reader_thread.join(timeout=10)
# Check that all records were published
assert len(_select_all_records(uri)) == nwriters * nrecords
[docs]
def test_write_blocked_by_reader(tmp_path):
"""A writer must wait for read locks to be released."""
lock_seconds = 0.5
retry_period = lock_seconds / 5 # do not retry long enough
retry_timeout = 5 * lock_seconds # retry long enough
# Add record to the database
uri = str(tmp_path / "test.db")
handler = Sqlite3Handler(uri, "mytable", FIELD_TYPES)
handler.handle(_make_record(field1=1))
# Reader
read_lock_acquired = threading.Event()
def read_lock_database():
conn = sqlite3.connect(uri)
try:
# Start read transaction
conn.execute("BEGIN")
conn.execute("SELECT COUNT(*) FROM mytable").fetchone()
read_lock_acquired.set()
# Open read transaction locks the db
time.sleep(lock_seconds)
# Finish transaction
conn.rollback()
finally:
conn.close()
# Write while locked: do not retry long enough
read_lock_acquired.clear()
thread = threading.Thread(target=read_lock_database)
thread.start()
handler = Sqlite3Handler(uri, "mytable", FIELD_TYPES, timeout=retry_period)
try:
assert read_lock_acquired.wait(timeout=10)
handler.handle(_make_record(field1=2))
finally:
thread.join(timeout=10)
handler.close()
# Write while locked: retry long enough
read_lock_acquired.clear()
thread = threading.Thread(target=read_lock_database)
thread.start()
handler = Sqlite3Handler(uri, "mytable", FIELD_TYPES, timeout=retry_timeout)
try:
assert read_lock_acquired.wait(timeout=10)
# Write while the reader locks
handler.handle(_make_record(field1=3))
finally:
thread.join(timeout=10)
handler.close()
# Check that the expected records were published
records = _select_all_records(uri)
assert [record["field1"] for record in records] == [1, 3]
def _make_record(**fields) -> logging.LogRecord:
return logging.makeLogRecord(fields)
def _select_all_records(uri: str) -> list:
with sqlite3_utils.connect(uri, uri=True) as conn:
return list(sqlite3_utils.select(conn, "mytable", field_types=FIELD_TYPES))