Skip to content

Commit 86eacf0

Browse files
committed
realtime database updates
1 parent 326ded2 commit 86eacf0

File tree

5 files changed

+387
-132
lines changed

5 files changed

+387
-132
lines changed

opcua/common/sqlite3_backend.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
2+
import time
3+
import sqlite3
4+
import threading
5+
from multiprocessing import Lock
6+
7+
class SQLite3Backend(object):
8+
CHECKP_INTERVAL = 90 # [sec] WAL checkpoint
9+
10+
def __init__(self, sqlFile = None, readonly=True):
11+
assert(isinstance(sqlFile, str))
12+
assert(isinstance(readonly, bool))
13+
self._sqlFile = sqlFile # Path to database file.
14+
self._readonly = bool(readonly)
15+
self._lock = Lock() # Database lock.
16+
self._conn = {} # Database connection.
17+
self._lastCheckP = int(0) # Epoch of last checkpoint.
18+
19+
def __enter__(self):
20+
self._lastCheckP = time.time()
21+
return self
22+
23+
def __exit__(self, exc_type, exc_value, traceback):
24+
self._db_disconnect()
25+
26+
def __str__(self):
27+
return self._sqlFile
28+
29+
@property
30+
def readonly(self):
31+
return self._readonly
32+
33+
# PUBLIC METHODS
34+
def execute_read(self, dbCmd = None, params = (), CB = None):
35+
with self._lock:
36+
c = self._getConn().cursor()
37+
for row in c.execute(dbCmd, params):
38+
CB(row)
39+
40+
def execute_write(self, dbCmd = None, params = ()):
41+
with self._lock:
42+
c = self._getConn().cursor()
43+
c.execute(dbCmd, params)
44+
45+
def commit(self):
46+
with self._lock:
47+
self._getConn().commit()
48+
self._wal_throttled()
49+
50+
def wal_checkpoint(self):
51+
"""
52+
Store checkpoint: forces database modifications to be persistent.
53+
Automatically done when sqlite cache runs over the 1000 pages threshold.
54+
IMPORTANT: slow operation, manual syncs are only useful for sporadic
55+
transactions that you really want to survive a power loss.
56+
"""
57+
self._lastCheckP = time.time()
58+
c = self._getConn().cursor()
59+
c.execute('PRAGMA wal_checkpoint')
60+
61+
# PRIVATE METHODS
62+
def _wal_throttled(self):
63+
# commits still require a wal_checkpoint to become persistent.
64+
if abs(time.time() - self._lastCheckP) < self.CHECKP_INTERVAL:
65+
return
66+
self.wal_checkpoint()
67+
68+
def _db_connect(self):
69+
CID = SQLite3Backend._getCID()
70+
# PARSE_DECLTYPES is active so certain data types (such as datetime) will not be BLOBs
71+
assert CID not in self._conn
72+
self._conn[CID] = sqlite3.connect(
73+
self._sqlFile,
74+
detect_types = sqlite3.PARSE_DECLTYPES,
75+
check_same_thread = False
76+
)
77+
c = self._getConn().cursor()
78+
if self.readonly is True:
79+
c.execute('PRAGMA query_only=1')
80+
else:
81+
c.execute('PRAGMA journal_mode=WAL')
82+
c.execute('PRAGMA synchronous=NORMAL')
83+
84+
def _db_disconnect(self):
85+
# Commit, checkpoint.
86+
if self.readonly is False:
87+
with self._lock:
88+
self._getConn().commit()
89+
self.wal_checkpoint()
90+
# Close all connections to database.
91+
for CID in self._conn:
92+
self._conn[CID].close()
93+
# Remove all items from dict.
94+
self._conn.clear()
95+
96+
def _getConn(self):
97+
if self._lock.acquire(False) is True:
98+
self._lock.release()
99+
raise Exception('Forgot to lock?')
100+
# sqlite3 multithreading: http://beets.io/blog/sqlite-nightmare.html
101+
CID = SQLite3Backend._getCID()
102+
try:
103+
return self._conn[CID]
104+
except KeyError:
105+
self._db_connect()
106+
return self._conn[CID]
107+
108+
@staticmethod
109+
def _getCID():
110+
return threading.current_thread().ident

0 commit comments

Comments
 (0)