Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Z
ZEO
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
ZEO
Commits
a9330b7e
Commit
a9330b7e
authored
Jul 19, 2016
by
Jim Fulton
Committed by
GitHub
Jul 19, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #44 from zopefoundation/simplify-server-commit-lock-management
Simplify server commit lock management
parents
815f39d1
3d02129b
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
236 additions
and
262 deletions
+236
-262
src/ZEO/StorageServer.py
src/ZEO/StorageServer.py
+207
-219
src/ZEO/asyncio/mtacceptor.py
src/ZEO/asyncio/mtacceptor.py
+1
-1
src/ZEO/asyncio/server.py
src/ZEO/asyncio/server.py
+4
-2
src/ZEO/tests/forker.py
src/ZEO/tests/forker.py
+3
-1
src/ZEO/tests/testConversionSupport.py
src/ZEO/tests/testConversionSupport.py
+3
-0
src/ZEO/tests/testZEO2.py
src/ZEO/tests/testZEO2.py
+18
-39
No files found.
src/ZEO/StorageServer.py
View file @
a9330b7e
...
@@ -47,7 +47,7 @@ from ZODB.loglevels import BLATHER
...
@@ -47,7 +47,7 @@ from ZODB.loglevels import BLATHER
from
ZODB.POSException
import
StorageError
,
StorageTransactionError
from
ZODB.POSException
import
StorageError
,
StorageTransactionError
from
ZODB.POSException
import
TransactionError
,
ReadOnlyError
,
ConflictError
from
ZODB.POSException
import
TransactionError
,
ReadOnlyError
,
ConflictError
from
ZODB.serialize
import
referencesf
from
ZODB.serialize
import
referencesf
from
ZODB.utils
import
oid_repr
,
p64
,
u64
,
z64
from
ZODB.utils
import
oid_repr
,
p64
,
u64
,
z64
,
Lock
,
RLock
from
.asyncio.server
import
Acceptor
from
.asyncio.server
import
Acceptor
...
@@ -98,6 +98,7 @@ class ZEOStorage:
...
@@ -98,6 +98,7 @@ class ZEOStorage:
def
notify_connected
(
self
,
conn
):
def
notify_connected
(
self
,
conn
):
self
.
connection
=
conn
self
.
connection
=
conn
self
.
call_soon_threadsafe
=
conn
.
call_soon_threadsafe
self
.
connected
=
True
self
.
connected
=
True
assert
conn
.
protocol_version
is
not
None
assert
conn
.
protocol_version
is
not
None
self
.
log_label
=
_addr_label
(
conn
.
addr
)
self
.
log_label
=
_addr_label
(
conn
.
addr
)
...
@@ -215,6 +216,7 @@ class ZEOStorage:
...
@@ -215,6 +216,7 @@ class ZEOStorage:
self
.
storage
=
storage
self
.
storage
=
storage
self
.
setup_delegation
()
self
.
setup_delegation
()
self
.
stats
=
self
.
server
.
register_connection
(
storage_id
,
self
)
self
.
stats
=
self
.
server
.
register_connection
(
storage_id
,
self
)
self
.
lock_manager
=
self
.
server
.
lock_managers
[
storage_id
]
def
get_info
(
self
):
def
get_info
(
self
):
storage
=
self
.
storage
storage
=
self
.
storage
...
@@ -353,12 +355,8 @@ class ZEOStorage:
...
@@ -353,12 +355,8 @@ class ZEOStorage:
def
_clear_transaction
(
self
):
def
_clear_transaction
(
self
):
# Common code at end of tpc_finish() and tpc_abort()
# Common code at end of tpc_finish() and tpc_abort()
if
self
.
locked
:
self
.
lock_manager
.
release
(
self
)
self
.
server
.
unlock_storage
(
self
)
self
.
transaction
=
None
self
.
locked
=
0
if
self
.
transaction
is
not
None
:
self
.
server
.
stop_waiting
(
self
)
self
.
transaction
=
None
self
.
stats
.
active_txns
-=
1
self
.
stats
.
active_txns
-=
1
if
self
.
txnlog
is
not
None
:
if
self
.
txnlog
is
not
None
:
self
.
txnlog
.
close
()
self
.
txnlog
.
close
()
...
@@ -369,98 +367,69 @@ class ZEOStorage:
...
@@ -369,98 +367,69 @@ class ZEOStorage:
def
vote
(
self
,
tid
):
def
vote
(
self
,
tid
):
self
.
_check_tid
(
tid
,
exc
=
StorageTransactionError
)
self
.
_check_tid
(
tid
,
exc
=
StorageTransactionError
)
if
self
.
locked
or
self
.
server
.
already_waiting
(
self
):
return
self
.
lock_manager
.
lock
(
self
,
self
.
_vote
)
raise
StorageTransactionError
(
'Already voting (%s)'
%
(
self
.
locked
and
'locked'
or
'waiting'
)
def
_vote
(
self
,
delay
=
None
):
)
# Called from client thread
return
self
.
_try_to_vote
()
def
_try_to_vote
(
self
,
delay
=
None
):
if
not
self
.
connected
:
if
not
self
.
connected
:
return
# We're disconnected
return
# We're disconnected
if
delay
is
not
None
and
delay
.
sent
:
try
:
# as a consequence of the unlocking strategy, _try_to_vote
self
.
log
(
# may be called multiple times for delayed
"Preparing to commit transaction: %d objects, %d bytes"
# transactions. The first call will mark the delay as
%
(
self
.
txnlog
.
stores
,
self
.
txnlog
.
size
()),
# sent. We should skip if the delay was already sent.
level
=
BLATHER
)
return
if
(
self
.
tid
is
not
None
)
or
(
self
.
status
!=
' '
):
self
.
storage
.
tpc_begin
(
self
.
transaction
,
self
.
tid
,
self
.
status
)
else
:
self
.
storage
.
tpc_begin
(
self
.
transaction
)
self
.
locked
,
delay
=
self
.
server
.
lock_storage
(
self
,
delay
)
for
op
,
args
in
self
.
txnlog
:
if
self
.
locked
:
getattr
(
self
,
op
)(
*
args
)
result
=
None
try
:
# Blob support
self
.
log
(
while
self
.
blob_log
:
"Preparing to commit transaction: %d objects, %d bytes"
oid
,
oldserial
,
data
,
blobfilename
=
self
.
blob_log
.
pop
()
%
(
self
.
txnlog
.
stores
,
self
.
txnlog
.
size
()),
self
.
_store
(
oid
,
oldserial
,
data
,
blobfilename
)
level
=
BLATHER
)
if
(
self
.
tid
is
not
None
)
or
(
self
.
status
!=
' '
):
self
.
storage
.
tpc_begin
(
self
.
transaction
,
self
.
tid
,
self
.
status
)
else
:
self
.
storage
.
tpc_begin
(
self
.
transaction
)
for
op
,
args
in
self
.
txnlog
:
getattr
(
self
,
op
)(
*
args
)
# Blob support
while
self
.
blob_log
:
oid
,
oldserial
,
data
,
blobfilename
=
self
.
blob_log
.
pop
()
self
.
_store
(
oid
,
oldserial
,
data
,
blobfilename
)
if
not
self
.
conflicts
:
try
:
serials
=
self
.
storage
.
tpc_vote
(
self
.
transaction
)
except
ConflictError
as
err
:
if
(
self
.
client_conflict_resolution
and
err
.
oid
and
err
.
serials
and
err
.
data
):
self
.
conflicts
[
err
.
oid
]
=
dict
(
oid
=
err
.
oid
,
serials
=
err
.
serials
,
data
=
err
.
data
)
else
:
raise
else
:
if
serials
:
self
.
serials
.
extend
(
serials
)
result
=
self
.
serials
if
self
.
conflicts
:
result
=
list
(
self
.
conflicts
.
values
())
self
.
storage
.
tpc_abort
(
self
.
transaction
)
self
.
server
.
unlock_storage
(
self
)
self
.
locked
=
False
self
.
server
.
stop_waiting
(
self
)
except
Exception
as
err
:
self
.
storage
.
tpc_abort
(
self
.
transaction
)
self
.
_clear_transaction
()
if
isinstance
(
err
,
ConflictError
):
self
.
stats
.
conflicts
+=
1
self
.
log
(
"conflict error %s"
%
err
,
BLATHER
)
if
not
isinstance
(
err
,
TransactionError
):
logger
.
exception
(
"While voting"
)
if
delay
is
not
None
:
if
not
self
.
conflicts
:
delay
.
error
(
sys
.
exc_info
())
try
:
serials
=
self
.
storage
.
tpc_vote
(
self
.
transaction
)
except
ConflictError
as
err
:
if
(
self
.
client_conflict_resolution
and
err
.
oid
and
err
.
serials
and
err
.
data
):
self
.
conflicts
[
err
.
oid
]
=
dict
(
oid
=
err
.
oid
,
serials
=
err
.
serials
,
data
=
err
.
data
)
else
:
raise
else
:
else
:
raise
if
serials
:
self
.
serials
.
extend
(
serials
)
if
self
.
conflicts
:
self
.
storage
.
tpc_abort
(
self
.
transaction
)
return
list
(
self
.
conflicts
.
values
())
else
:
else
:
if
delay
is
not
None
:
self
.
locked
=
True
# signal to lock manager to hold lock
delay
.
reply
(
result
)
return
self
.
serials
else
:
return
result
else
:
except
Exception
as
err
:
return
delay
self
.
storage
.
tpc_abort
(
self
.
transaction
)
self
.
_clear_transaction
()
def
_unlock_callback
(
self
,
delay
):
if
isinstance
(
err
,
ConflictError
):
if
self
.
connected
:
self
.
stats
.
conflicts
+=
1
self
.
connection
.
call_soon_threadsafe
(
self
.
_try_to_vote
,
delay
)
self
.
log
(
"conflict error %s"
%
err
,
BLATHER
)
else
:
self
.
server
.
stop_waiting
(
self
)
if
not
isinstance
(
err
,
TransactionError
):
logger
.
exception
(
"While voting"
)
raise
# The public methods of the ZEO client API do not do the real work.
# The public methods of the ZEO client API do not do the real work.
# They defer work until after the storage lock has been acquired.
# They defer work until after the storage lock has been acquired.
...
@@ -741,19 +710,22 @@ class StorageServer:
...
@@ -741,19 +710,22 @@ class StorageServer:
(
self
.
__class__
.
__name__
,
read_only
and
"RO"
or
"RW"
,
msg
))
(
self
.
__class__
.
__name__
,
read_only
and
"RO"
or
"RW"
,
msg
))
self
.
_lock
=
threading
.
Lock
()
self
.
_lock
=
Lock
()
self
.
_commit_locks
=
{}
self
.
_waiting
=
dict
((
name
,
[])
for
name
in
storages
)
self
.
ssl
=
ssl
# For dev convenience
self
.
ssl
=
ssl
# For dev convenience
self
.
read_only
=
read_only
self
.
read_only
=
read_only
self
.
database
=
None
self
.
database
=
None
# A list, by server, of at most invalidation_queue_size invalidations.
# A list, by server, of at most invalidation_queue_size invalidations.
# The list is kept in sorted order with the most recent
# The list is kept in sorted order with the most recent
# invalidation at the front. The list never has more than
# invalidation at the front. The list never has more than
# self.invq_bound elements.
# self.invq_bound elements.
self
.
invq_bound
=
invalidation_queue_size
self
.
invq_bound
=
invalidation_queue_size
self
.
invq
=
{}
self
.
invq
=
{}
self
.
zeo_storages_by_storage_id
=
{}
# {storage_id -> [ZEOStorage]}
self
.
lock_managers
=
{}
# {storage_id -> LockManager}
self
.
stats
=
{}
# {storage_id -> StorageStats}
for
name
,
storage
in
storages
.
items
():
for
name
,
storage
in
storages
.
items
():
self
.
_setup_invq
(
name
,
storage
)
self
.
_setup_invq
(
name
,
storage
)
storage
.
registerDB
(
StorageServerDB
(
self
,
name
))
storage
.
registerDB
(
StorageServerDB
(
self
,
name
))
...
@@ -761,8 +733,19 @@ class StorageServer:
...
@@ -761,8 +733,19 @@ class StorageServer:
# XXX this may go away later, when storages grow
# XXX this may go away later, when storages grow
# configuration for this.
# configuration for this.
storage
.
tryToResolveConflict
=
never_resolve_conflict
storage
.
tryToResolveConflict
=
never_resolve_conflict
self
.
zeo_storages_by_storage_id
[
name
]
=
[]
self
.
stats
[
name
]
=
stats
=
StorageStats
(
self
.
zeo_storages_by_storage_id
[
name
])
if
transaction_timeout
is
None
:
# An object with no-op methods
timeout
=
StubTimeoutThread
()
else
:
timeout
=
TimeoutThread
(
transaction_timeout
)
timeout
.
setName
(
"TimeoutThread for %s"
%
name
)
timeout
.
start
()
self
.
lock_managers
[
name
]
=
LockManager
(
name
,
stats
,
timeout
)
self
.
invalidation_age
=
invalidation_age
self
.
invalidation_age
=
invalidation_age
self
.
zeo_storages_by_storage_id
=
{}
# {storage_id -> [ZEOStorage]}
self
.
client_conflict_resolution
=
client_conflict_resolution
self
.
client_conflict_resolution
=
client_conflict_resolution
if
addr
is
not
None
:
if
addr
is
not
None
:
...
@@ -774,21 +757,6 @@ class StorageServer:
...
@@ -774,21 +757,6 @@ class StorageServer:
self
.
loop
=
self
.
acceptor
.
loop
self
.
loop
=
self
.
acceptor
.
loop
ZODB
.
event
.
notify
(
Serving
(
self
,
address
=
self
.
acceptor
.
addr
))
ZODB
.
event
.
notify
(
Serving
(
self
,
address
=
self
.
acceptor
.
addr
))
self
.
stats
=
{}
self
.
timeouts
=
{}
for
name
in
self
.
storages
.
keys
():
self
.
zeo_storages_by_storage_id
[
name
]
=
[]
self
.
stats
[
name
]
=
StorageStats
(
self
.
zeo_storages_by_storage_id
[
name
])
if
transaction_timeout
is
None
:
# An object with no-op methods
timeout
=
StubTimeoutThread
()
else
:
timeout
=
TimeoutThread
(
transaction_timeout
)
timeout
.
setName
(
"TimeoutThread for %s"
%
name
)
timeout
.
start
()
self
.
timeouts
[
name
]
=
timeout
def
create_client_handler
(
self
):
def
create_client_handler
(
self
):
return
ZEOStorage
(
self
,
self
.
read_only
)
return
ZEOStorage
(
self
,
self
.
read_only
)
...
@@ -855,7 +823,7 @@ class StorageServer:
...
@@ -855,7 +823,7 @@ class StorageServer:
# later transactions since they will have to validate their
# later transactions since they will have to validate their
# caches anyway.
# caches anyway.
for
zs
in
self
.
zeo_storages_by_storage_id
[
storage_id
][:]:
for
zs
in
self
.
zeo_storages_by_storage_id
[
storage_id
][:]:
zs
.
c
onnection
.
c
all_soon_threadsafe
(
zs
.
connection
.
close
)
zs
.
call_soon_threadsafe
(
zs
.
connection
.
close
)
def
invalidate
(
def
invalidate
(
self
,
zeo_storage
,
storage_id
,
tid
,
invalidated
=
(),
info
=
None
):
self
,
zeo_storage
,
storage_id
,
tid
,
invalidated
=
(),
info
=
None
):
...
@@ -993,8 +961,7 @@ class StorageServer:
...
@@ -993,8 +961,7 @@ class StorageServer:
for
zs
in
zeo_storages
[:]:
for
zs
in
zeo_storages
[:]:
try
:
try
:
logger
.
debug
(
"Closing %s"
,
zs
.
connection
)
logger
.
debug
(
"Closing %s"
,
zs
.
connection
)
zs
.
connection
.
call_soon_threadsafe
(
zs
.
call_soon_threadsafe
(
zs
.
connection
.
close
)
zs
.
connection
.
close
)
except
Exception
:
except
Exception
:
logger
.
exception
(
"closing connection %r"
,
zs
)
logger
.
exception
(
"closing connection %r"
,
zs
)
...
@@ -1014,108 +981,12 @@ class StorageServer:
...
@@ -1014,108 +981,12 @@ class StorageServer:
if
zeo_storage
in
zeo_storages
:
if
zeo_storage
in
zeo_storages
:
zeo_storages
.
remove
(
zeo_storage
)
zeo_storages
.
remove
(
zeo_storage
)
def
lock_storage
(
self
,
zeostore
,
delay
):
storage_id
=
zeostore
.
storage_id
waiting
=
self
.
_waiting
[
storage_id
]
with
self
.
_lock
:
if
storage_id
in
self
.
_commit_locks
:
# The lock is held by another zeostore
locked
=
self
.
_commit_locks
[
storage_id
]
assert
locked
is
not
zeostore
,
(
storage_id
,
delay
)
if
not
locked
.
connected
:
locked
.
log
(
"Still locked after disconnected. Unlocking."
,
logging
.
CRITICAL
)
if
locked
.
transaction
:
locked
.
storage
.
tpc_abort
(
locked
.
transaction
)
del
self
.
_commit_locks
[
storage_id
]
# yuck: have to manipulate lock to appease with :(
self
.
_lock
.
release
()
try
:
return
self
.
lock_storage
(
zeostore
,
delay
)
finally
:
self
.
_lock
.
acquire
()
if
delay
is
None
:
# New request, queue it
assert
not
[
i
for
i
in
waiting
if
i
[
0
]
is
zeostore
],
"already waiting"
delay
=
Delay
()
waiting
.
append
((
zeostore
,
delay
))
zeostore
.
log
(
"(%r) queue lock: transactions waiting: %s"
%
(
storage_id
,
len
(
waiting
)),
_level_for_waiting
(
waiting
)
)
return
False
,
delay
else
:
self
.
_commit_locks
[
storage_id
]
=
zeostore
self
.
timeouts
[
storage_id
].
begin
(
zeostore
)
self
.
stats
[
storage_id
].
lock_time
=
time
.
time
()
if
delay
is
not
None
:
# we were waiting, stop
waiting
[:]
=
[
i
for
i
in
waiting
if
i
[
0
]
is
not
zeostore
]
zeostore
.
log
(
"(%r) lock: transactions waiting: %s"
%
(
storage_id
,
len
(
waiting
)),
_level_for_waiting
(
waiting
)
)
return
True
,
delay
def
unlock_storage
(
self
,
zeostore
):
storage_id
=
zeostore
.
storage_id
waiting
=
self
.
_waiting
[
storage_id
]
with
self
.
_lock
:
assert
self
.
_commit_locks
[
storage_id
]
is
zeostore
del
self
.
_commit_locks
[
storage_id
]
self
.
timeouts
[
storage_id
].
end
(
zeostore
)
self
.
stats
[
storage_id
].
lock_time
=
None
callbacks
=
waiting
[:]
if
callbacks
:
assert
not
[
i
for
i
in
waiting
if
i
[
0
]
is
zeostore
],
"waiting while unlocking"
zeostore
.
log
(
"(%r) unlock: transactions waiting: %s"
%
(
storage_id
,
len
(
callbacks
)),
_level_for_waiting
(
callbacks
)
)
for
zeostore
,
delay
in
callbacks
:
try
:
zeostore
.
_unlock_callback
(
delay
)
except
(
SystemExit
,
KeyboardInterrupt
):
raise
except
Exception
:
logger
.
exception
(
"Calling unlock callback"
)
def
stop_waiting
(
self
,
zeostore
):
storage_id
=
zeostore
.
storage_id
waiting
=
self
.
_waiting
[
storage_id
]
with
self
.
_lock
:
new_waiting
=
[
i
for
i
in
waiting
if
i
[
0
]
is
not
zeostore
]
if
len
(
new_waiting
)
==
len
(
waiting
):
return
waiting
[:]
=
new_waiting
zeostore
.
log
(
"(%r) dequeue lock: transactions waiting: %s"
%
(
storage_id
,
len
(
waiting
)),
_level_for_waiting
(
waiting
)
)
def
already_waiting
(
self
,
zeostore
):
storage_id
=
zeostore
.
storage_id
waiting
=
self
.
_waiting
[
storage_id
]
with
self
.
_lock
:
return
bool
([
i
for
i
in
waiting
if
i
[
0
]
is
zeostore
])
def
server_status
(
self
,
storage_id
):
def
server_status
(
self
,
storage_id
):
status
=
self
.
stats
[
storage_id
].
__dict__
.
copy
()
status
=
self
.
stats
[
storage_id
].
__dict__
.
copy
()
status
[
'connections'
]
=
len
(
status
[
'connections'
])
status
[
'connections'
]
=
len
(
status
[
'connections'
])
status
[
'waiting'
]
=
len
(
self
.
_waiting
[
storage_id
])
lock_manager
=
self
.
lock_managers
[
storage_id
]
status
[
'timeout-thread-is-alive'
]
=
self
.
timeouts
[
storage_id
].
isAlive
()
status
[
'waiting'
]
=
len
(
lock_manager
.
waiting
)
status
[
'timeout-thread-is-alive'
]
=
lock_manager
.
timeout
.
isAlive
()
last_transaction
=
self
.
storages
[
storage_id
].
lastTransaction
()
last_transaction
=
self
.
storages
[
storage_id
].
lastTransaction
()
last_transaction_hex
=
codecs
.
encode
(
last_transaction
,
'hex_codec'
)
last_transaction_hex
=
codecs
.
encode
(
last_transaction
,
'hex_codec'
)
if
PY3
:
if
PY3
:
...
@@ -1129,14 +1000,6 @@ class StorageServer:
...
@@ -1129,14 +1000,6 @@ class StorageServer:
for
storage_id
in
self
.
storages
)
for
storage_id
in
self
.
storages
)
def
_level_for_waiting
(
waiting
):
if
len
(
waiting
)
>
9
:
return
logging
.
CRITICAL
if
len
(
waiting
)
>
3
:
return
logging
.
WARNING
else
:
return
logging
.
DEBUG
class
StubTimeoutThread
:
class
StubTimeoutThread
:
def
begin
(
self
,
client
):
def
begin
(
self
,
client
):
...
@@ -1197,8 +1060,7 @@ class TimeoutThread(threading.Thread):
...
@@ -1197,8 +1060,7 @@ class TimeoutThread(threading.Thread):
client
.
log
(
"Transaction timeout after %s seconds"
%
client
.
log
(
"Transaction timeout after %s seconds"
%
self
.
_timeout
,
logging
.
CRITICAL
)
self
.
_timeout
,
logging
.
CRITICAL
)
try
:
try
:
client
.
connection
.
call_soon_threadsafe
(
client
.
call_soon_threadsafe
(
client
.
connection
.
close
)
client
.
connection
.
close
)
except
:
except
:
client
.
log
(
"Timeout failure"
,
logging
.
CRITICAL
,
client
.
log
(
"Timeout failure"
,
logging
.
CRITICAL
,
exc_info
=
sys
.
exc_info
())
exc_info
=
sys
.
exc_info
())
...
@@ -1311,3 +1173,129 @@ def never_resolve_conflict(oid, committedSerial, oldSerial, newpickle,
...
@@ -1311,3 +1173,129 @@ def never_resolve_conflict(oid, committedSerial, oldSerial, newpickle,
committedData
=
b''
):
committedData
=
b''
):
raise
ConflictError
(
oid
=
oid
,
serials
=
(
committedSerial
,
oldSerial
),
raise
ConflictError
(
oid
=
oid
,
serials
=
(
committedSerial
,
oldSerial
),
data
=
newpickle
)
data
=
newpickle
)
class
LockManager
(
object
):
def
__init__
(
self
,
storage_id
,
stats
,
timeout
):
self
.
storage_id
=
storage_id
self
.
stats
=
stats
self
.
timeout
=
timeout
self
.
locked
=
None
self
.
waiting
=
{}
# {ZEOStorage -> (func, delay)}
self
.
_lock
=
RLock
()
def
lock
(
self
,
zs
,
func
):
"""Call the given function with the commit lock.
If we can get the lock right away, return the result of
calling the function.
If we can't get the lock right away, return a delay
The function must set ``locked`` on the zeo-storage to
indicate that the zeo-storage should be locked. Otherwise,
the lock isn't held pas the call.
"""
with
self
.
_lock
:
if
self
.
_can_lock
(
zs
):
self
.
_locked
(
zs
)
else
:
if
any
(
w
for
w
in
self
.
waiting
if
w
is
zs
):
raise
StorageTransactionError
(
"Already voting (waiting)"
)
delay
=
Delay
()
self
.
waiting
[
zs
]
=
(
func
,
delay
)
self
.
_log_waiting
(
zs
,
"(%r) queue lock: transactions waiting: %s"
)
return
delay
try
:
result
=
func
()
except
Exception
:
self
.
release
(
zs
)
raise
else
:
if
not
zs
.
locked
:
self
.
release
(
zs
)
return
result
def
_lock_waiting
(
self
,
zs
):
waiting
=
None
with
self
.
_lock
:
if
self
.
locked
is
zs
:
assert
zs
.
locked
return
if
self
.
_can_lock
(
zs
):
waiting
=
self
.
waiting
.
pop
(
zs
,
None
)
if
waiting
:
self
.
_locked
(
zs
)
if
waiting
:
func
,
delay
=
waiting
try
:
result
=
func
()
except
Exception
:
delay
.
error
(
sys
.
exc_info
())
self
.
release
(
zs
)
else
:
delay
.
reply
(
result
)
if
not
zs
.
locked
:
self
.
release
(
zs
)
def
release
(
self
,
zs
):
with
self
.
_lock
:
locked
=
self
.
locked
if
locked
is
zs
:
self
.
_unlocked
(
zs
)
for
zs
in
list
(
self
.
waiting
):
zs
.
call_soon_threadsafe
(
self
.
_lock_waiting
,
zs
)
else
:
if
self
.
waiting
.
pop
(
zs
,
None
):
self
.
_log_waiting
(
zs
,
"(%r) dequeue lock: transactions waiting: %s"
)
def
_log_waiting
(
self
,
zs
,
message
):
l
=
len
(
self
.
waiting
)
zs
.
log
(
message
%
(
self
.
storage_id
,
l
),
logging
.
CRITICAL
if
l
>
9
else
(
logging
.
WARNING
if
l
>
3
else
logging
.
DEBUG
)
)
def
_can_lock
(
self
,
zs
):
locked
=
self
.
locked
if
locked
is
zs
:
raise
StorageTransactionError
(
"Already voting (locked)"
)
if
locked
is
not
None
:
if
not
locked
.
connected
:
locked
.
log
(
"Still locked after disconnected. Unlocking."
,
logging
.
CRITICAL
)
if
locked
.
transaction
:
locked
.
storage
.
tpc_abort
(
locked
.
transaction
)
self
.
_unlocked
(
locked
)
locked
=
None
else
:
assert
locked
.
locked
return
locked
is
None
def
_locked
(
self
,
zs
):
self
.
locked
=
zs
self
.
stats
.
lock_time
=
time
.
time
()
self
.
_log_waiting
(
zs
,
"(%r) lock: transactions waiting: %s"
)
self
.
timeout
.
begin
(
zs
)
return
True
def
_unlocked
(
self
,
zs
):
assert
self
.
locked
is
zs
self
.
timeout
.
end
(
zs
)
self
.
locked
=
self
.
stats
.
lock_time
=
None
zs
.
locked
=
False
self
.
_log_waiting
(
zs
,
"(%r) unlock: transactions waiting: %s"
)
src/ZEO/asyncio/mtacceptor.py
View file @
a9330b7e
...
@@ -35,7 +35,7 @@ To use this module, replace::
...
@@ -35,7 +35,7 @@ To use this module, replace::
from .asyncio.server import Acceptor
from .asyncio.server import Acceptor
with:
with:
:
from .asyncio.mtacceptor import Acceptor
from .asyncio.mtacceptor import Acceptor
...
...
src/ZEO/asyncio/server.py
View file @
a9330b7e
...
@@ -165,12 +165,14 @@ class Delay(object):
...
@@ -165,12 +165,14 @@ class Delay(object):
def
reply
(
self
,
obj
):
def
reply
(
self
,
obj
):
self
.
sent
=
'reply'
self
.
sent
=
'reply'
self
.
protocol
.
send_reply
(
self
.
msgid
,
obj
)
if
self
.
protocol
:
self
.
protocol
.
send_reply
(
self
.
msgid
,
obj
)
def
error
(
self
,
exc_info
):
def
error
(
self
,
exc_info
):
self
.
sent
=
'error'
self
.
sent
=
'error'
logger
.
error
(
"Error raised in delayed method"
,
exc_info
=
exc_info
)
logger
.
error
(
"Error raised in delayed method"
,
exc_info
=
exc_info
)
self
.
protocol
.
send_error
(
self
.
msgid
,
exc_info
[
1
])
if
self
.
protocol
:
self
.
protocol
.
send_error
(
self
.
msgid
,
exc_info
[
1
])
def
__repr__
(
self
):
def
__repr__
(
self
):
return
"%s[%s, %r, %r, %r]"
%
(
return
"%s[%s, %r, %r, %r]"
%
(
...
...
src/ZEO/tests/forker.py
View file @
a9330b7e
...
@@ -30,6 +30,8 @@ from ZEO._compat import StringIO
...
@@ -30,6 +30,8 @@ from ZEO._compat import StringIO
logger
=
logging
.
getLogger
(
'ZEO.tests.forker'
)
logger
=
logging
.
getLogger
(
'ZEO.tests.forker'
)
DEBUG
=
os
.
environ
.
get
(
'ZEO_TEST_SERVER_DEBUG'
)
class
ZEOConfig
:
class
ZEOConfig
:
"""Class to generate ZEO configuration file. """
"""Class to generate ZEO configuration file. """
...
@@ -88,7 +90,7 @@ def runner(config, qin, qout, timeout=None,
...
@@ -88,7 +90,7 @@ def runner(config, qin, qout, timeout=None,
debug
=
False
,
name
=
None
,
debug
=
False
,
name
=
None
,
keep
=
False
,
protocol
=
None
):
keep
=
False
,
protocol
=
None
):
if
debug
:
if
debug
or
DEBUG
:
debug_logging
()
debug_logging
()
old_protocol
=
None
old_protocol
=
None
...
...
src/ZEO/tests/testConversionSupport.py
View file @
a9330b7e
...
@@ -48,6 +48,7 @@ class FakeServer:
...
@@ -48,6 +48,7 @@ class FakeServer:
'1'
:
FakeStorage
(),
'1'
:
FakeStorage
(),
'2'
:
FakeStorageBase
(),
'2'
:
FakeStorageBase
(),
}
}
lock_managers
=
storages
def
register_connection
(
*
args
):
def
register_connection
(
*
args
):
return
None
,
None
return
None
,
None
...
@@ -58,6 +59,8 @@ class FakeConnection:
...
@@ -58,6 +59,8 @@ class FakeConnection:
protocol_version
=
b'Z4'
protocol_version
=
b'Z4'
addr
=
'test'
addr
=
'test'
call_soon_threadsafe
=
lambda
f
,
*
a
:
f
(
*
a
)
def
test_server_record_iternext
():
def
test_server_record_iternext
():
"""
"""
...
...
src/ZEO/tests/testZEO2.py
View file @
a9330b7e
...
@@ -169,6 +169,7 @@ So, we arrange to get an error in vote:
...
@@ -169,6 +169,7 @@ So, we arrange to get an error in vote:
>>> zs = ZEO.tests.servertesting.client(server, 1)
>>> zs = ZEO.tests.servertesting.client(server, 1)
>>> zs.tpc_begin('0', '', '', {})
>>> zs.tpc_begin('0', '', '', {})
>>> zs.storea(ZODB.utils.p64(99), ZODB.utils.z64, 'x', '0')
>>> zs.storea(ZODB.utils.p64(99), ZODB.utils.z64, 'x', '0')
>>> zs.vote('0')
>>> zs.vote('0')
Traceback (most recent call last):
Traceback (most recent call last):
...
...
...
@@ -176,7 +177,7 @@ So, we arrange to get an error in vote:
...
@@ -176,7 +177,7 @@ So, we arrange to get an error in vote:
When we do, the storage server's transaction lock shouldn't be held:
When we do, the storage server's transaction lock shouldn't be held:
>>>
'1' in server._commit_locks
>>>
zs.lock_manager.locked is not None
False
False
Of course, if vote suceeds, the lock will be held:
Of course, if vote suceeds, the lock will be held:
...
@@ -186,7 +187,7 @@ Of course, if vote suceeds, the lock will be held:
...
@@ -186,7 +187,7 @@ Of course, if vote suceeds, the lock will be held:
>>> zs.storea(ZODB.utils.p64(99), ZODB.utils.z64, 'x', '1')
>>> zs.storea(ZODB.utils.p64(99), ZODB.utils.z64, 'x', '1')
>>> _ = zs.vote('1') # doctest: +ELLIPSIS
>>> _ = zs.vote('1') # doctest: +ELLIPSIS
>>>
'1' in server._commit_locks
>>>
zs.lock_manager.locked is not None
True
True
>>> zs.tpc_abort('1')
>>> zs.tpc_abort('1')
...
@@ -361,13 +362,13 @@ release the lock and one of the waiting clients will get the lock.
...
@@ -361,13 +362,13 @@ release the lock and one of the waiting clients will get the lock.
>>> zs2.notify_disconnected() # doctest: +ELLIPSIS
>>> zs2.notify_disconnected() # doctest: +ELLIPSIS
ZEO.StorageServer INFO
ZEO.StorageServer INFO
(test-addr-
2
) disconnected during locked transaction
(test-addr-
...
) disconnected during locked transaction
ZEO.StorageServer CRITICAL
ZEO.StorageServer CRITICAL
(test-addr-
2
) ('1') unlock: transactions waiting: 10
(test-addr-
...
) ('1') unlock: transactions waiting: 10
ZEO.StorageServer WARNING
ZEO.StorageServer WARNING
(test-addr-
1
) ('1') lock: transactions waiting: 9
(test-addr-
...
) ('1') lock: transactions waiting: 9
ZEO.StorageServer BLATHER
ZEO.StorageServer BLATHER
(test-addr-
1
) Preparing to commit transaction: 1 objects, ... bytes
(test-addr-
...
) Preparing to commit transaction: 1 objects, ... bytes
(In practice, waiting clients won't necessarily get the lock in order.)
(In practice, waiting clients won't necessarily get the lock in order.)
...
@@ -392,45 +393,19 @@ statistics using the server_status method:
...
@@ -392,45 +393,19 @@ statistics using the server_status method:
If clients disconnect while waiting, they will be dequeued:
If clients disconnect while waiting, they will be dequeued:
>>> for client in clients:
>>> for client in clients:
... client.notify_disconnected()
... client.notify_disconnected()
# doctest: +ELLIPSIS
ZEO.StorageServer INFO
ZEO.StorageServer INFO
(test-addr-10) disconnected during unlocked transaction
(test-addr-10) disconnected during unlocked transaction
ZEO.StorageServer WARNING
ZEO.StorageServer WARNING
(test-addr-10) ('1') dequeue lock: transactions waiting: 8
(test-addr-10) ('1') dequeue lock: transactions waiting: 8
ZEO.StorageServer INFO
...
(test-addr-11) disconnected during unlocked transaction
ZEO.StorageServer WARNING
>>> zs1.server_status()['waiting']
(test-addr-11) ('1') dequeue lock: transactions waiting: 7
0
ZEO.StorageServer INFO
(test-addr-12) disconnected during unlocked transaction
ZEO.StorageServer WARNING
(test-addr-12) ('1') dequeue lock: transactions waiting: 6
ZEO.StorageServer INFO
(test-addr-13) disconnected during unlocked transaction
ZEO.StorageServer WARNING
(test-addr-13) ('1') dequeue lock: transactions waiting: 5
ZEO.StorageServer INFO
(test-addr-14) disconnected during unlocked transaction
ZEO.StorageServer WARNING
(test-addr-14) ('1') dequeue lock: transactions waiting: 4
ZEO.StorageServer INFO
(test-addr-15) disconnected during unlocked transaction
ZEO.StorageServer DEBUG
(test-addr-15) ('1') dequeue lock: transactions waiting: 3
ZEO.StorageServer INFO
(test-addr-16) disconnected during unlocked transaction
ZEO.StorageServer DEBUG
(test-addr-16) ('1') dequeue lock: transactions waiting: 2
ZEO.StorageServer INFO
(test-addr-17) disconnected during unlocked transaction
ZEO.StorageServer DEBUG
(test-addr-17) ('1') dequeue lock: transactions waiting: 1
ZEO.StorageServer INFO
(test-addr-18) disconnected during unlocked transaction
ZEO.StorageServer DEBUG
(test-addr-18) ('1') dequeue lock: transactions waiting: 0
>>> zs1.tpc_abort(tid1)
>>> zs1.tpc_abort(tid1)
ZEO.StorageServer DEBUG
(test-addr-1) ('1') unlock: transactions waiting: 0
>>> logging.getLogger('ZEO').setLevel(logging.NOTSET)
>>> logging.getLogger('ZEO').setLevel(logging.NOTSET)
>>> logging.getLogger('ZEO').removeHandler(handler)
>>> logging.getLogger('ZEO').removeHandler(handler)
...
@@ -494,6 +469,8 @@ ZEOStorage as closed and see if trying to get a lock cleans it up:
...
@@ -494,6 +469,8 @@ ZEOStorage as closed and see if trying to get a lock cleans it up:
>>> zs1.connection.connection_lost(None)
>>> zs1.connection.connection_lost(None)
ZEO.StorageServer INFO
ZEO.StorageServer INFO
(test-addr-1) disconnected during locked transaction
(test-addr-1) disconnected during locked transaction
ZEO.StorageServer DEBUG
(test-addr-1) ('1') unlock: transactions waiting: 0
>>> zs2 = ZEO.tests.servertesting.client(server, '2')
>>> zs2 = ZEO.tests.servertesting.client(server, '2')
ZEO.asyncio.base INFO
ZEO.asyncio.base INFO
...
@@ -508,6 +485,8 @@ ZEOStorage as closed and see if trying to get a lock cleans it up:
...
@@ -508,6 +485,8 @@ ZEOStorage as closed and see if trying to get a lock cleans it up:
(test-addr-2) Preparing to commit transaction: 1 objects, ... bytes
(test-addr-2) Preparing to commit transaction: 1 objects, ... bytes
>>> zs2.tpc_abort(tid2)
>>> zs2.tpc_abort(tid2)
ZEO.StorageServer DEBUG
(test-addr-2) ('1') unlock: transactions waiting: 0
>>> logging.getLogger('ZEO').setLevel(logging.NOTSET)
>>> logging.getLogger('ZEO').setLevel(logging.NOTSET)
>>> logging.getLogger('ZEO').removeHandler(handler)
>>> logging.getLogger('ZEO').removeHandler(handler)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment