Commit 851636d1 authored by Kirill Smelkov's avatar Kirill Smelkov

.

parent 416e9ccf
...@@ -2,20 +2,41 @@ ...@@ -2,20 +2,41 @@
"""Program zloadrace.py demonstrates concurrency bug in ZODB Connection.setstate() """Program zloadrace.py demonstrates concurrency bug in ZODB Connection.setstate()
that leads to XXX that leads to XXX
XXX XXX no, there is no load vs invalidation race on ZODB4 (ZODB3 is probably the same):
ZEO
---
ZEO server explicitly guarantees that it does not mix processing load
requests inside tpc_finish + send invalidations. This way if load is processed
after new commit, load reply is guranteed to come to client after invalidation
message. This was explicitly fixed by
https://github.com/zopefoundation/ZEO/commit/71eb1456
(search for callAsyncNoPoll there)
and later again by https://github.com/zopefoundation/ZEO/commit/94f275c3 .
NEO
---
XXX load vs invalidation race is there on ZODB4 and ZODB3, but on ZODB5 there is XXX load vs invalidation race is there on ZODB4 and ZODB3, but on ZODB5 there is
another open vs invalidation race. another open vs invalidation race.
""" """
from __future__ import print_function
from ZODB import DB from ZODB import DB
import transaction import transaction
from persistent import Persistent from persistent import Persistent
from wendelin.lib.testing import TestDB_ZEO, TestDB_NEO from wendelin.lib.testing import TestDB_ZEO, TestDB_NEO
from golang import func, defer from golang import func, defer, chan, select, default
from golang import sync, context from golang import sync, context
import threading
from ZODB.utils import u64
# PInt is persistent integer. # PInt is persistent integer.
...@@ -25,8 +46,8 @@ class PInt(Persistent): ...@@ -25,8 +46,8 @@ class PInt(Persistent):
@func @func
def main(): def main():
tdb = TestDB_ZEO('<zeo>') #tdb = TestDB_ZEO('<zeo>')
#tdb = TestDB_NEO('<neo>') tdb = TestDB_NEO('<neo>')
tdb.setup() tdb.setup()
# XXX defer(tdb.teardown) # XXX defer(tdb.teardown)
...@@ -38,6 +59,9 @@ def main(): ...@@ -38,6 +59,9 @@ def main():
db1 = DB(zstor1) db1 = DB(zstor1)
db2 = DB(zstor2) db2 = DB(zstor2)
zstor1.app.poll_thread.name = 'C1.poll'
zstor2.app.poll_thread.name = 'C2.poll'
# XXX doc # XXX doc
def init(): def init():
...@@ -52,35 +76,64 @@ def main(): ...@@ -52,35 +76,64 @@ def main():
zconn.close() zconn.close()
c2ready = chan() # c1 <- c2 "I'm ready to commit"
c2start = chan() # c1 -> c2 "go on to commit"
def C1(ctx, N): def C1(ctx, N):
threading.current_thread().name = "C1"
def c1(): def c1():
transaction.begin() transaction.begin()
zconn = db1.open() zconn = db1.open()
print('C1: (1) neo.app.last_tid = @%d' % u64(zstor1.app.last_tid))
root = zconn.root() root = zconn.root()
obj1 = root['obj1'] obj1 = root['obj1']
obj2 = root['obj2'] obj2 = root['obj2']
I = obj1.i
print('C1: (2) neo.app.last_tid = @%d' % u64(zstor1.app.last_tid))
print('C1: (2) obj1.serial = @%d' % u64(obj1._p_serial))
c2ready.recv()
c2start.send(1)
import time
time.sleep(0.5)
# obj1 - reload it from zstor # obj1 - reload it from zstor
# obj2 - get it from zconn cache # obj2 - get it from zconn cache
for i in range(10*N): #for i in range(N):
for i in range(15):
obj1._p_invalidate() obj1._p_invalidate()
print('C1: (X) neo.app.last_tid = @%d' % u64(zstor1.app.last_tid))
# both objects must have the same values # both objects must have the same values
i1 = obj1.i i1 = obj1.i
i2 = obj2.i i2 = obj2.i
print('C1: (X) obj1.serial = @%d' % u64(obj1._p_serial))
print('C1: (X) obj2.serial = @%d' % u64(obj2._p_serial))
if i1 != i2: if i1 != i2:
raise AssertionError("C1: obj1.i (%d) != obj2.i (%d)" % (i1, i2)) raise AssertionError("C1: obj1.i (%d) != obj2.i (%d)" % (i1, i2))
if i1 != I:
raise AssertionError(
"C1: obj1.i (%d) mutated inside transaction (started with %d)" % (i1, I))
transaction.abort() transaction.abort()
zconn.close() zconn.close()
for i in range(N): for i in range(N):
if ready(ctx.done()):
break
print('C1.%d' % i) print('C1.%d' % i)
c1() c1()
print('C1.fin')
def C2(ctx, N): def C2(ctx, N):
threading.current_thread().name = "C2"
def c2(): def c2():
transaction.begin() transaction.begin()
zconn = db2.open() zconn = db2.open()
...@@ -92,24 +145,44 @@ def main(): ...@@ -92,24 +145,44 @@ def main():
obj2.i += 1 obj2.i += 1
assert obj1.i == obj2.i assert obj1.i == obj2.i
c2ready.send(1)
c2start.recv()
transaction.commit() transaction.commit()
zconn.close() zconn.close()
for i in range(N): for i in range(N):
if ready(ctx.done()):
break
print('C2.%d' % i) print('C2.%d' % i)
c2() c2()
print('C2.fin')
init() init()
import time
time.sleep(2)
print()
N = 100 N = 1000
wg = sync.WorkGroup(context.background()) wg = sync.WorkGroup(context.background())
wg.go(C1, N) wg.go(C1, N)
wg.go(C2, N) wg.go(C2, N)
wg.wait() wg.wait()
# ready returns whether channel ch is ready.
def ready(ch):
_, _rx = select(
default, # 0
ch.recv, # 1
)
if _ == 0:
return False
return True
if __name__ == '__main__': if __name__ == '__main__':
main() main()
...@@ -87,6 +87,7 @@ XXX but they do have load vs invalidation race. ...@@ -87,6 +87,7 @@ XXX but they do have load vs invalidation race.
[2] https://github.com/zopefoundation/ZODB/blob/5.5.1-29-g0b3db5aee/src/ZODB/mvccadapter.py#L130-L139 [2] https://github.com/zopefoundation/ZODB/blob/5.5.1-29-g0b3db5aee/src/ZODB/mvccadapter.py#L130-L139
""" """
from __future__ import print_function
from ZODB import DB from ZODB import DB
from ZODB.MappingStorage import MappingStorage from ZODB.MappingStorage import MappingStorage
import transaction import transaction
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment