517 lines
16 KiB
Python
517 lines
16 KiB
Python
"""TestCases for multi-threaded access to a DB.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import time
|
|
import errno
|
|
from random import random
|
|
|
|
DASH = '-'
|
|
|
|
try:
|
|
WindowsError
|
|
except NameError:
|
|
class WindowsError(Exception):
|
|
pass
|
|
|
|
import unittest
|
|
from test_all import db, dbutils, test_support, verbose, have_threads, \
|
|
get_new_environment_path, get_new_database_path
|
|
|
|
if have_threads :
|
|
from threading import Thread
|
|
if sys.version_info[0] < 3 :
|
|
from threading import currentThread
|
|
else :
|
|
from threading import current_thread as currentThread
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
class BaseThreadedTestCase(unittest.TestCase):
|
|
dbtype = db.DB_UNKNOWN # must be set in derived class
|
|
dbopenflags = 0
|
|
dbsetflags = 0
|
|
envflags = 0
|
|
|
|
def setUp(self):
|
|
if verbose:
|
|
dbutils._deadlock_VerboseFile = sys.stdout
|
|
|
|
self.homeDir = get_new_environment_path()
|
|
self.env = db.DBEnv()
|
|
self.setEnvOpts()
|
|
self.env.open(self.homeDir, self.envflags | db.DB_CREATE)
|
|
|
|
self.filename = self.__class__.__name__ + '.db'
|
|
self.d = db.DB(self.env)
|
|
if self.dbsetflags:
|
|
self.d.set_flags(self.dbsetflags)
|
|
self.d.open(self.filename, self.dbtype, self.dbopenflags|db.DB_CREATE)
|
|
|
|
def tearDown(self):
|
|
self.d.close()
|
|
self.env.close()
|
|
test_support.rmtree(self.homeDir)
|
|
|
|
def setEnvOpts(self):
|
|
pass
|
|
|
|
def makeData(self, key):
|
|
return DASH.join([key] * 5)
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
|
|
class ConcurrentDataStoreBase(BaseThreadedTestCase):
|
|
dbopenflags = db.DB_THREAD
|
|
envflags = db.DB_THREAD | db.DB_INIT_CDB | db.DB_INIT_MPOOL
|
|
readers = 0 # derived class should set
|
|
writers = 0
|
|
records = 1000
|
|
|
|
def test01_1WriterMultiReaders(self):
|
|
if verbose:
|
|
print '\n', '-=' * 30
|
|
print "Running %s.test01_1WriterMultiReaders..." % \
|
|
self.__class__.__name__
|
|
|
|
keys=range(self.records)
|
|
import random
|
|
random.shuffle(keys)
|
|
records_per_writer=self.records//self.writers
|
|
readers_per_writer=self.readers//self.writers
|
|
self.assertEqual(self.records,self.writers*records_per_writer)
|
|
self.assertEqual(self.readers,self.writers*readers_per_writer)
|
|
self.assertTrue((records_per_writer%readers_per_writer)==0)
|
|
readers = []
|
|
|
|
for x in xrange(self.readers):
|
|
rt = Thread(target = self.readerThread,
|
|
args = (self.d, x),
|
|
name = 'reader %d' % x,
|
|
)#verbose = verbose)
|
|
if sys.version_info[0] < 3 :
|
|
rt.setDaemon(True)
|
|
else :
|
|
rt.daemon = True
|
|
readers.append(rt)
|
|
|
|
writers=[]
|
|
for x in xrange(self.writers):
|
|
a=keys[records_per_writer*x:records_per_writer*(x+1)]
|
|
a.sort() # Generate conflicts
|
|
b=readers[readers_per_writer*x:readers_per_writer*(x+1)]
|
|
wt = Thread(target = self.writerThread,
|
|
args = (self.d, a, b),
|
|
name = 'writer %d' % x,
|
|
)#verbose = verbose)
|
|
writers.append(wt)
|
|
|
|
for t in writers:
|
|
if sys.version_info[0] < 3 :
|
|
t.setDaemon(True)
|
|
else :
|
|
t.daemon = True
|
|
t.start()
|
|
|
|
for t in writers:
|
|
t.join()
|
|
for t in readers:
|
|
t.join()
|
|
|
|
def writerThread(self, d, keys, readers):
|
|
if sys.version_info[0] < 3 :
|
|
name = currentThread().getName()
|
|
else :
|
|
name = currentThread().name
|
|
|
|
if verbose:
|
|
print "%s: creating records %d - %d" % (name, start, stop)
|
|
|
|
count=len(keys)//len(readers)
|
|
count2=count
|
|
for x in keys :
|
|
key = '%04d' % x
|
|
dbutils.DeadlockWrap(d.put, key, self.makeData(key),
|
|
max_retries=12)
|
|
if verbose and x % 100 == 0:
|
|
print "%s: records %d - %d finished" % (name, start, x)
|
|
|
|
count2-=1
|
|
if not count2 :
|
|
readers.pop().start()
|
|
count2=count
|
|
|
|
if verbose:
|
|
print "%s: finished creating records" % name
|
|
|
|
if verbose:
|
|
print "%s: thread finished" % name
|
|
|
|
def readerThread(self, d, readerNum):
|
|
if sys.version_info[0] < 3 :
|
|
name = currentThread().getName()
|
|
else :
|
|
name = currentThread().name
|
|
|
|
for i in xrange(5) :
|
|
c = d.cursor()
|
|
count = 0
|
|
rec = c.first()
|
|
while rec:
|
|
count += 1
|
|
key, data = rec
|
|
self.assertEqual(self.makeData(key), data)
|
|
rec = c.next()
|
|
if verbose:
|
|
print "%s: found %d records" % (name, count)
|
|
c.close()
|
|
|
|
if verbose:
|
|
print "%s: thread finished" % name
|
|
|
|
|
|
class BTreeConcurrentDataStore(ConcurrentDataStoreBase):
|
|
dbtype = db.DB_BTREE
|
|
writers = 2
|
|
readers = 10
|
|
records = 1000
|
|
|
|
|
|
class HashConcurrentDataStore(ConcurrentDataStoreBase):
|
|
dbtype = db.DB_HASH
|
|
writers = 2
|
|
readers = 10
|
|
records = 1000
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
class SimpleThreadedBase(BaseThreadedTestCase):
|
|
dbopenflags = db.DB_THREAD
|
|
envflags = db.DB_THREAD | db.DB_INIT_MPOOL | db.DB_INIT_LOCK
|
|
readers = 10
|
|
writers = 2
|
|
records = 1000
|
|
|
|
def setEnvOpts(self):
|
|
self.env.set_lk_detect(db.DB_LOCK_DEFAULT)
|
|
|
|
def test02_SimpleLocks(self):
|
|
if verbose:
|
|
print '\n', '-=' * 30
|
|
print "Running %s.test02_SimpleLocks..." % self.__class__.__name__
|
|
|
|
|
|
keys=range(self.records)
|
|
import random
|
|
random.shuffle(keys)
|
|
records_per_writer=self.records//self.writers
|
|
readers_per_writer=self.readers//self.writers
|
|
self.assertEqual(self.records,self.writers*records_per_writer)
|
|
self.assertEqual(self.readers,self.writers*readers_per_writer)
|
|
self.assertTrue((records_per_writer%readers_per_writer)==0)
|
|
|
|
readers = []
|
|
for x in xrange(self.readers):
|
|
rt = Thread(target = self.readerThread,
|
|
args = (self.d, x),
|
|
name = 'reader %d' % x,
|
|
)#verbose = verbose)
|
|
if sys.version_info[0] < 3 :
|
|
rt.setDaemon(True)
|
|
else :
|
|
rt.daemon = True
|
|
readers.append(rt)
|
|
|
|
writers = []
|
|
for x in xrange(self.writers):
|
|
a=keys[records_per_writer*x:records_per_writer*(x+1)]
|
|
a.sort() # Generate conflicts
|
|
b=readers[readers_per_writer*x:readers_per_writer*(x+1)]
|
|
wt = Thread(target = self.writerThread,
|
|
args = (self.d, a, b),
|
|
name = 'writer %d' % x,
|
|
)#verbose = verbose)
|
|
writers.append(wt)
|
|
|
|
for t in writers:
|
|
if sys.version_info[0] < 3 :
|
|
t.setDaemon(True)
|
|
else :
|
|
t.daemon = True
|
|
t.start()
|
|
|
|
for t in writers:
|
|
t.join()
|
|
for t in readers:
|
|
t.join()
|
|
|
|
def writerThread(self, d, keys, readers):
|
|
if sys.version_info[0] < 3 :
|
|
name = currentThread().getName()
|
|
else :
|
|
name = currentThread().name
|
|
if verbose:
|
|
print "%s: creating records %d - %d" % (name, start, stop)
|
|
|
|
count=len(keys)//len(readers)
|
|
count2=count
|
|
for x in keys :
|
|
key = '%04d' % x
|
|
dbutils.DeadlockWrap(d.put, key, self.makeData(key),
|
|
max_retries=12)
|
|
|
|
if verbose and x % 100 == 0:
|
|
print "%s: records %d - %d finished" % (name, start, x)
|
|
|
|
count2-=1
|
|
if not count2 :
|
|
readers.pop().start()
|
|
count2=count
|
|
|
|
if verbose:
|
|
print "%s: thread finished" % name
|
|
|
|
def readerThread(self, d, readerNum):
|
|
if sys.version_info[0] < 3 :
|
|
name = currentThread().getName()
|
|
else :
|
|
name = currentThread().name
|
|
|
|
c = d.cursor()
|
|
count = 0
|
|
rec = dbutils.DeadlockWrap(c.first, max_retries=10)
|
|
while rec:
|
|
count += 1
|
|
key, data = rec
|
|
self.assertEqual(self.makeData(key), data)
|
|
rec = dbutils.DeadlockWrap(c.next, max_retries=10)
|
|
if verbose:
|
|
print "%s: found %d records" % (name, count)
|
|
c.close()
|
|
|
|
if verbose:
|
|
print "%s: thread finished" % name
|
|
|
|
|
|
class BTreeSimpleThreaded(SimpleThreadedBase):
|
|
dbtype = db.DB_BTREE
|
|
|
|
|
|
class HashSimpleThreaded(SimpleThreadedBase):
|
|
dbtype = db.DB_HASH
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
|
|
class ThreadedTransactionsBase(BaseThreadedTestCase):
|
|
dbopenflags = db.DB_THREAD | db.DB_AUTO_COMMIT
|
|
envflags = (db.DB_THREAD |
|
|
db.DB_INIT_MPOOL |
|
|
db.DB_INIT_LOCK |
|
|
db.DB_INIT_LOG |
|
|
db.DB_INIT_TXN
|
|
)
|
|
readers = 0
|
|
writers = 0
|
|
records = 2000
|
|
txnFlag = 0
|
|
|
|
def setEnvOpts(self):
|
|
#self.env.set_lk_detect(db.DB_LOCK_DEFAULT)
|
|
pass
|
|
|
|
def test03_ThreadedTransactions(self):
|
|
if verbose:
|
|
print '\n', '-=' * 30
|
|
print "Running %s.test03_ThreadedTransactions..." % \
|
|
self.__class__.__name__
|
|
|
|
keys=range(self.records)
|
|
import random
|
|
random.shuffle(keys)
|
|
records_per_writer=self.records//self.writers
|
|
readers_per_writer=self.readers//self.writers
|
|
self.assertEqual(self.records,self.writers*records_per_writer)
|
|
self.assertEqual(self.readers,self.writers*readers_per_writer)
|
|
self.assertTrue((records_per_writer%readers_per_writer)==0)
|
|
|
|
readers=[]
|
|
for x in xrange(self.readers):
|
|
rt = Thread(target = self.readerThread,
|
|
args = (self.d, x),
|
|
name = 'reader %d' % x,
|
|
)#verbose = verbose)
|
|
if sys.version_info[0] < 3 :
|
|
rt.setDaemon(True)
|
|
else :
|
|
rt.daemon = True
|
|
readers.append(rt)
|
|
|
|
writers = []
|
|
for x in xrange(self.writers):
|
|
a=keys[records_per_writer*x:records_per_writer*(x+1)]
|
|
b=readers[readers_per_writer*x:readers_per_writer*(x+1)]
|
|
wt = Thread(target = self.writerThread,
|
|
args = (self.d, a, b),
|
|
name = 'writer %d' % x,
|
|
)#verbose = verbose)
|
|
writers.append(wt)
|
|
|
|
dt = Thread(target = self.deadlockThread)
|
|
if sys.version_info[0] < 3 :
|
|
dt.setDaemon(True)
|
|
else :
|
|
dt.daemon = True
|
|
dt.start()
|
|
|
|
for t in writers:
|
|
if sys.version_info[0] < 3 :
|
|
t.setDaemon(True)
|
|
else :
|
|
t.daemon = True
|
|
t.start()
|
|
|
|
for t in writers:
|
|
t.join()
|
|
for t in readers:
|
|
t.join()
|
|
|
|
self.doLockDetect = False
|
|
dt.join()
|
|
|
|
def writerThread(self, d, keys, readers):
|
|
if sys.version_info[0] < 3 :
|
|
name = currentThread().getName()
|
|
else :
|
|
name = currentThread().name
|
|
|
|
count=len(keys)//len(readers)
|
|
while len(keys):
|
|
try:
|
|
txn = self.env.txn_begin(None, self.txnFlag)
|
|
keys2=keys[:count]
|
|
for x in keys2 :
|
|
key = '%04d' % x
|
|
d.put(key, self.makeData(key), txn)
|
|
if verbose and x % 100 == 0:
|
|
print "%s: records %d - %d finished" % (name, start, x)
|
|
txn.commit()
|
|
keys=keys[count:]
|
|
readers.pop().start()
|
|
except (db.DBLockDeadlockError, db.DBLockNotGrantedError), val:
|
|
if verbose:
|
|
if sys.version_info < (2, 6) :
|
|
print "%s: Aborting transaction (%s)" % (name, val[1])
|
|
else :
|
|
print "%s: Aborting transaction (%s)" % (name,
|
|
val.args[1])
|
|
txn.abort()
|
|
|
|
if verbose:
|
|
print "%s: thread finished" % name
|
|
|
|
def readerThread(self, d, readerNum):
|
|
if sys.version_info[0] < 3 :
|
|
name = currentThread().getName()
|
|
else :
|
|
name = currentThread().name
|
|
|
|
finished = False
|
|
while not finished:
|
|
try:
|
|
txn = self.env.txn_begin(None, self.txnFlag)
|
|
c = d.cursor(txn)
|
|
count = 0
|
|
rec = c.first()
|
|
while rec:
|
|
count += 1
|
|
key, data = rec
|
|
self.assertEqual(self.makeData(key), data)
|
|
rec = c.next()
|
|
if verbose: print "%s: found %d records" % (name, count)
|
|
c.close()
|
|
txn.commit()
|
|
finished = True
|
|
except (db.DBLockDeadlockError, db.DBLockNotGrantedError), val:
|
|
if verbose:
|
|
if sys.version_info < (2, 6) :
|
|
print "%s: Aborting transaction (%s)" % (name, val[1])
|
|
else :
|
|
print "%s: Aborting transaction (%s)" % (name,
|
|
val.args[1])
|
|
c.close()
|
|
txn.abort()
|
|
|
|
if verbose:
|
|
print "%s: thread finished" % name
|
|
|
|
def deadlockThread(self):
|
|
self.doLockDetect = True
|
|
while self.doLockDetect:
|
|
time.sleep(0.05)
|
|
try:
|
|
aborted = self.env.lock_detect(
|
|
db.DB_LOCK_RANDOM, db.DB_LOCK_CONFLICT)
|
|
if verbose and aborted:
|
|
print "deadlock: Aborted %d deadlocked transaction(s)" \
|
|
% aborted
|
|
except db.DBError:
|
|
pass
|
|
|
|
|
|
class BTreeThreadedTransactions(ThreadedTransactionsBase):
|
|
dbtype = db.DB_BTREE
|
|
writers = 2
|
|
readers = 10
|
|
records = 1000
|
|
|
|
class HashThreadedTransactions(ThreadedTransactionsBase):
|
|
dbtype = db.DB_HASH
|
|
writers = 2
|
|
readers = 10
|
|
records = 1000
|
|
|
|
class BTreeThreadedNoWaitTransactions(ThreadedTransactionsBase):
|
|
dbtype = db.DB_BTREE
|
|
writers = 2
|
|
readers = 10
|
|
records = 1000
|
|
txnFlag = db.DB_TXN_NOWAIT
|
|
|
|
class HashThreadedNoWaitTransactions(ThreadedTransactionsBase):
|
|
dbtype = db.DB_HASH
|
|
writers = 2
|
|
readers = 10
|
|
records = 1000
|
|
txnFlag = db.DB_TXN_NOWAIT
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
def test_suite():
|
|
suite = unittest.TestSuite()
|
|
|
|
if have_threads:
|
|
suite.addTest(unittest.makeSuite(BTreeConcurrentDataStore))
|
|
suite.addTest(unittest.makeSuite(HashConcurrentDataStore))
|
|
suite.addTest(unittest.makeSuite(BTreeSimpleThreaded))
|
|
suite.addTest(unittest.makeSuite(HashSimpleThreaded))
|
|
suite.addTest(unittest.makeSuite(BTreeThreadedTransactions))
|
|
suite.addTest(unittest.makeSuite(HashThreadedTransactions))
|
|
suite.addTest(unittest.makeSuite(BTreeThreadedNoWaitTransactions))
|
|
suite.addTest(unittest.makeSuite(HashThreadedNoWaitTransactions))
|
|
|
|
else:
|
|
print "Threads not available, skipping thread tests."
|
|
|
|
return suite
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main(defaultTest='test_suite')
|