Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
N
neo
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Labels
Merge Requests
2
Merge Requests
2
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Jobs
Commits
Open sidebar
Kirill Smelkov
neo
Commits
f5f42522
Commit
f5f42522
authored
Aug 21, 2015
by
Julien Muchembled
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Move code from neo.client to neo.lib, since admins will be also multi-threaded
parent
50d25d00
Changes
10
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
289 additions
and
260 deletions
+289
-260
neo/client/app.py
neo/client/app.py
+42
-104
neo/client/container.py
neo/client/container.py
+0
-104
neo/client/handlers/__init__.py
neo/client/handlers/__init__.py
+2
-43
neo/client/handlers/master.py
neo/client/handlers/master.py
+4
-3
neo/client/handlers/storage.py
neo/client/handlers/storage.py
+3
-2
neo/lib/handler.py
neo/lib/handler.py
+43
-0
neo/lib/locking.py
neo/lib/locking.py
+47
-0
neo/lib/threaded_app.py
neo/lib/threaded_app.py
+144
-0
neo/lib/threaded_poll.py
neo/lib/threaded_poll.py
+2
-2
neo/tests/threaded/__init__.py
neo/tests/threaded/__init__.py
+2
-2
No files found.
neo/client/app.py
View file @
f5f42522
...
@@ -16,7 +16,6 @@
...
@@ -16,7 +16,6 @@
from
cPickle
import
dumps
,
loads
from
cPickle
import
dumps
,
loads
from
zlib
import
compress
,
decompress
from
zlib
import
compress
,
decompress
from
neo.lib.locking
import
Empty
from
random
import
shuffle
from
random
import
shuffle
import
heapq
import
heapq
import
time
import
time
...
@@ -33,19 +32,18 @@ from neo.lib.protocol import NodeTypes, Packets, \
...
@@ -33,19 +32,18 @@ from neo.lib.protocol import NodeTypes, Packets, \
INVALID_PARTITION
,
ZERO_HASH
,
ZERO_TID
INVALID_PARTITION
,
ZERO_HASH
,
ZERO_TID
from
neo.lib.event
import
EventManager
from
neo.lib.event
import
EventManager
from
neo.lib.util
import
makeChecksum
,
dump
from
neo.lib.util
import
makeChecksum
,
dump
from
neo.lib.locking
import
Lock
from
neo.lib.locking
import
Empty
,
Lock
,
SimpleQueue
from
neo.lib.connection
import
MTClientConnection
,
ConnectionClosed
from
neo.lib.connection
import
MTClientConnection
,
ConnectionClosed
from
neo.lib.node
import
NodeManager
from
neo.lib.node
import
NodeManager
from
.exception
import
NEOStorageError
,
NEOStorageCreationUndoneError
from
.exception
import
NEOStorageError
,
NEOStorageCreationUndoneError
from
.exception
import
NEOStorageNotFoundError
from
.exception
import
NEOStorageNotFoundError
from
.handlers
import
storage
,
master
from
.handlers
import
storage
,
master
from
neo.lib.dispatcher
import
Dispatcher
,
ForgottenPacket
from
neo.lib.dispatcher
import
Dispatcher
,
ForgottenPacket
from
.poll
import
ThreadedPoll
,
psThreadedPoll
from
neo.lib.threaded_app
import
ThreadedApplication
from
.cache
import
ClientCache
from
.cache
import
ClientCache
from
.pool
import
ConnectionPool
from
.pool
import
ConnectionPool
from
neo.lib.util
import
p64
,
u64
,
parseMasterList
from
neo.lib.util
import
p64
,
u64
,
parseMasterList
from
neo.lib.debug
import
register
as
registerLiveDebugger
from
neo.lib.debug
import
register
as
registerLiveDebugger
from
.container
import
ThreadContainer
,
TransactionContainer
CHECKED_SERIAL
=
master
.
CHECKED_SERIAL
CHECKED_SERIAL
=
master
.
CHECKED_SERIAL
...
@@ -57,41 +55,55 @@ if SignalHandler:
...
@@ -57,41 +55,55 @@ if SignalHandler:
import
signal
import
signal
SignalHandler
.
registerHandler
(
signal
.
SIGUSR2
,
logging
.
reopen
)
SignalHandler
.
registerHandler
(
signal
.
SIGUSR2
,
logging
.
reopen
)
class
app_set
(
weakref
.
WeakSet
):
def
on_log
(
self
):
class
TransactionContainer
(
dict
):
for
app
in
self
:
app
.
log
()
app_set
=
app_set
()
def
pop
(
self
,
txn
):
registerLiveDebugger
(
app_set
.
on_log
)
return
dict
.
pop
(
self
,
id
(
txn
),
None
)
def
get
(
self
,
txn
):
class
Application
(
object
):
try
:
return
self
[
id
(
txn
)]
except
KeyError
:
raise
StorageTransactionError
(
"unknown transaction %r"
%
txn
)
def
new
(
self
,
txn
):
key
=
id
(
txn
)
if
key
in
self
:
raise
StorageTransactionError
(
"commit of transaction %r"
" already started"
%
txn
)
context
=
self
[
key
]
=
{
'queue'
:
SimpleQueue
(),
'txn'
:
txn
,
'ttid'
:
None
,
'data_dict'
:
{},
'data_size'
:
0
,
'cache_dict'
:
{},
'cache_size'
:
0
,
'object_base_serial_dict'
:
{},
'object_serial_dict'
:
{},
'object_stored_counter_dict'
:
{},
'conflict_serial_dict'
:
{},
'resolved_conflict_serial_dict'
:
{},
'involved_nodes'
:
set
(),
}
return
context
class
Application
(
ThreadedApplication
):
"""The client node application."""
"""The client node application."""
def
__init__
(
self
,
master_nodes
,
name
,
compress
=
True
,
def
__init__
(
self
,
master_nodes
,
name
,
compress
=
True
,
dynamic_master_list
=
None
):
dynamic_master_list
=
None
):
# Start polling thread
super
(
Application
,
self
).
__init__
(
parseMasterList
(
master_nodes
),
self
.
em
=
EventManager
()
name
,
dynamic_master_list
)
self
.
poll_thread
=
ThreadedPoll
(
self
.
em
,
name
=
name
)
psThreadedPoll
()
# Internal Attributes common to all thread
# Internal Attributes common to all thread
self
.
_db
=
None
self
.
_db
=
None
self
.
name
=
name
self
.
dispatcher
=
Dispatcher
(
self
.
poll_thread
)
self
.
nm
=
NodeManager
(
dynamic_master_list
)
self
.
cp
=
ConnectionPool
(
self
)
self
.
cp
=
ConnectionPool
(
self
)
self
.
master_conn
=
None
self
.
primary_master_node
=
None
self
.
primary_master_node
=
None
self
.
trying_master_node
=
None
self
.
trying_master_node
=
None
# load master node list
for
address
in
parseMasterList
(
master_nodes
):
self
.
nm
.
createMaster
(
address
=
address
)
# no self-assigned UUID, primary master will supply us one
# no self-assigned UUID, primary master will supply us one
self
.
uuid
=
None
self
.
_cache
=
ClientCache
()
self
.
_cache
=
ClientCache
()
self
.
_loading_oid
=
None
self
.
_loading_oid
=
None
self
.
new_oid_list
=
()
self
.
new_oid_list
=
()
...
@@ -103,8 +115,6 @@ class Application(object):
...
@@ -103,8 +115,6 @@ class Application(object):
self
.
primary_handler
=
master
.
PrimaryAnswersHandler
(
self
)
self
.
primary_handler
=
master
.
PrimaryAnswersHandler
(
self
)
self
.
primary_bootstrap_handler
=
master
.
PrimaryBootstrapHandler
(
self
)
self
.
primary_bootstrap_handler
=
master
.
PrimaryBootstrapHandler
(
self
)
self
.
notifications_handler
=
master
.
PrimaryNotificationsHandler
(
self
)
self
.
notifications_handler
=
master
.
PrimaryNotificationsHandler
(
self
)
# Internal attribute distinct between thread
self
.
_thread_container
=
ThreadContainer
()
self
.
_txn_container
=
TransactionContainer
()
self
.
_txn_container
=
TransactionContainer
()
# Lock definition :
# Lock definition :
# _load_lock is used to make loading and storing atomic
# _load_lock is used to make loading and storing atomic
...
@@ -124,7 +134,11 @@ class Application(object):
...
@@ -124,7 +134,11 @@ class Application(object):
# node connection attemps
# node connection attemps
self
.
_connecting_to_master_node
=
Lock
()
self
.
_connecting_to_master_node
=
Lock
()
self
.
compress
=
compress
self
.
compress
=
compress
app_set
.
add
(
self
)
# to register self.on_log
def
close
(
self
):
self
.
cp
.
flush
()
self
.
_txn_container
.
clear
()
super
(
Application
,
self
).
close
()
def
__getattr__
(
self
,
attr
):
def
__getattr__
(
self
,
attr
):
if
attr
==
'pt'
:
if
attr
==
'pt'
:
...
@@ -136,45 +150,6 @@ class Application(object):
...
@@ -136,45 +150,6 @@ class Application(object):
# do not iter lazily to avoid race condition
# do not iter lazily to avoid race condition
return
self
.
_txn_container
.
values
return
self
.
_txn_container
.
values
def
getHandlerData
(
self
):
return
self
.
_thread_container
.
answer
def
setHandlerData
(
self
,
data
):
self
.
_thread_container
.
answer
=
data
def
log
(
self
):
self
.
em
.
log
()
self
.
nm
.
log
()
pt
=
self
.
__dict__
.
get
(
'pt'
)
if
pt
is
not
None
:
pt
.
log
()
def
_handlePacket
(
self
,
conn
,
packet
,
kw
=
{},
handler
=
None
):
"""
conn
The connection which received the packet (forwarded to handler).
packet
The packet to handle.
handler
The handler to use to handle packet.
If not given, it will be guessed from connection's not type.
"""
if
handler
is
None
:
# Guess the handler to use based on the type of node on the
# connection
node
=
self
.
nm
.
getByAddress
(
conn
.
getAddress
())
if
node
is
None
:
raise
ValueError
,
'Expecting an answer from a node '
\
'which type is not known... Is this right ?'
if
node
.
isStorage
():
handler
=
self
.
storage_handler
elif
node
.
isMaster
():
handler
=
self
.
primary_handler
else
:
raise
ValueError
,
'Unknown node type: %r'
%
(
node
.
__class__
,
)
with
conn
.
lock
:
handler
.
dispatch
(
conn
,
packet
,
kw
)
def
_waitAnyMessage
(
self
,
queue
,
block
=
True
):
def
_waitAnyMessage
(
self
,
queue
,
block
=
True
):
"""
"""
Handle all pending packets.
Handle all pending packets.
...
@@ -212,29 +187,6 @@ class Application(object):
...
@@ -212,29 +187,6 @@ class Application(object):
# Don't leave access to thread context, even if a raise happens.
# Don't leave access to thread context, even if a raise happens.
self
.
setHandlerData
(
None
)
self
.
setHandlerData
(
None
)
def
_ask
(
self
,
conn
,
packet
,
handler
=
None
,
**
kw
):
self
.
setHandlerData
(
None
)
queue
=
self
.
_thread_container
.
queue
msg_id
=
conn
.
ask
(
packet
,
queue
=
queue
,
**
kw
)
get
=
queue
.
get
_handlePacket
=
self
.
_handlePacket
while
True
:
qconn
,
qpacket
,
kw
=
get
(
True
)
is_forgotten
=
isinstance
(
qpacket
,
ForgottenPacket
)
if
conn
is
qconn
:
# check fake packet
if
qpacket
is
None
:
raise
ConnectionClosed
if
msg_id
==
qpacket
.
getId
():
if
is_forgotten
:
raise
ValueError
,
'ForgottenPacket for an '
\
'explicitely expected packet.'
_handlePacket
(
qconn
,
qpacket
,
kw
,
handler
)
break
if
not
is_forgotten
and
qpacket
is
not
None
:
_handlePacket
(
qconn
,
qpacket
,
kw
)
return
self
.
getHandlerData
()
def
_askStorage
(
self
,
conn
,
packet
,
**
kw
):
def
_askStorage
(
self
,
conn
,
packet
,
**
kw
):
""" Send a request to a storage node and process its answer """
""" Send a request to a storage node and process its answer """
return
self
.
_ask
(
conn
,
packet
,
handler
=
self
.
storage_handler
,
**
kw
)
return
self
.
_ask
(
conn
,
packet
,
handler
=
self
.
storage_handler
,
**
kw
)
...
@@ -946,20 +898,6 @@ class Application(object):
...
@@ -946,20 +898,6 @@ class Application(object):
self
.
_askPrimary
(
Packets
.
AskLastTransaction
())
self
.
_askPrimary
(
Packets
.
AskLastTransaction
())
return
self
.
last_tid
return
self
.
last_tid
def
__del__
(
self
):
"""Clear all connection."""
# Due to bug in ZODB, close is not always called when shutting
# down zope, so use __del__ to close connections
for
conn
in
self
.
em
.
getConnectionList
():
conn
.
close
()
self
.
cp
.
flush
()
self
.
master_conn
=
None
# Stop polling thread
logging
.
debug
(
'Stopping %s'
,
self
.
poll_thread
)
self
.
poll_thread
.
stop
()
psThreadedPoll
()
close
=
__del__
def
pack
(
self
,
t
):
def
pack
(
self
,
t
):
tid
=
repr
(
TimeStamp
(
*
time
.
gmtime
(
t
)[:
5
]
+
(
t
%
60
,
)))
tid
=
repr
(
TimeStamp
(
*
time
.
gmtime
(
t
)[:
5
]
+
(
t
%
60
,
)))
if
tid
==
ZERO_TID
:
if
tid
==
ZERO_TID
:
...
...
neo/client/container.py
deleted
100644 → 0
View file @
50d25d00
#
# Copyright (C) 2011-2015 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import
threading
from
neo.lib.locking
import
Lock
,
Empty
from
collections
import
deque
from
ZODB.POSException
import
StorageTransactionError
class
SimpleQueue
(
object
):
"""
Similar to Queue.Queue but with simpler locking scheme, reducing lock
contention on "put" (benchmark shows 60% less time spent in "put").
As a result:
- only a single consumer possible ("get" vs. "get" race condition)
- only a single producer possible ("put" vs. "put" race condition)
- no blocking size limit possible
- no consumer -> producer notifications (task_done/join API)
Queue is on the critical path: any moment spent here increases client
application wait for object data, transaction completion, etc.
As we have a single consumer (client application's thread) and a single
producer (lib.dispatcher, which can be called from several threads but
serialises calls internally) for each queue, Queue.Queue's locking scheme
can be relaxed to reduce latency.
"""
__slots__
=
(
'_lock'
,
'_unlock'
,
'_popleft'
,
'_append'
,
'_queue'
)
def
__init__
(
self
):
lock
=
Lock
()
self
.
_lock
=
lock
.
acquire
self
.
_unlock
=
lock
.
release
self
.
_queue
=
queue
=
deque
()
self
.
_popleft
=
queue
.
popleft
self
.
_append
=
queue
.
append
def
get
(
self
,
block
):
if
block
:
self
.
_lock
(
False
)
while
True
:
try
:
return
self
.
_popleft
()
except
IndexError
:
if
not
block
:
raise
Empty
self
.
_lock
()
def
put
(
self
,
item
):
self
.
_append
(
item
)
self
.
_lock
(
False
)
self
.
_unlock
()
def
empty
(
self
):
return
not
self
.
_queue
class
ThreadContainer
(
threading
.
local
):
def
__init__
(
self
):
self
.
queue
=
SimpleQueue
()
self
.
answer
=
None
class
TransactionContainer
(
dict
):
def
pop
(
self
,
txn
):
return
dict
.
pop
(
self
,
id
(
txn
),
None
)
def
get
(
self
,
txn
):
try
:
return
self
[
id
(
txn
)]
except
KeyError
:
raise
StorageTransactionError
(
"unknown transaction %r"
%
txn
)
def
new
(
self
,
txn
):
key
=
id
(
txn
)
if
key
in
self
:
raise
StorageTransactionError
(
"commit of transaction %r"
" already started"
%
txn
)
context
=
self
[
key
]
=
{
'queue'
:
SimpleQueue
(),
'txn'
:
txn
,
'ttid'
:
None
,
'data_dict'
:
{},
'data_size'
:
0
,
'cache_dict'
:
{},
'cache_size'
:
0
,
'object_base_serial_dict'
:
{},
'object_serial_dict'
:
{},
'object_stored_counter_dict'
:
{},
'conflict_serial_dict'
:
{},
'resolved_conflict_serial_dict'
:
{},
'involved_nodes'
:
set
(),
}
return
context
neo/client/handlers/__init__.py
View file @
f5f42522
...
@@ -14,51 +14,10 @@
...
@@ -14,51 +14,10 @@
# You should have received a copy of the GNU General Public License
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from
neo.lib.handler
import
EventHandler
from
neo.lib
import
handler
from
neo.lib.protocol
import
ProtocolError
,
Packets
from
ZODB.POSException
import
StorageError
from
ZODB.POSException
import
StorageError
class
BaseHandler
(
EventHandler
):
class
AnswerBaseHandler
(
handler
.
AnswerBaseHandler
):
# XXX
"""Base class for client-side EventHandler implementations."""
def
__init__
(
self
,
app
):
super
(
BaseHandler
,
self
).
__init__
(
app
)
self
.
dispatcher
=
app
.
dispatcher
def
dispatch
(
self
,
conn
,
packet
,
kw
=
{}):
assert
conn
.
lock
.
_is_owned
()
# XXX: see also lockCheckWrapper
super
(
BaseHandler
,
self
).
dispatch
(
conn
,
packet
,
kw
)
def
packetReceived
(
self
,
conn
,
packet
,
kw
=
{}):
"""Redirect all received packet to dispatcher thread."""
if
packet
.
isResponse
()
and
type
(
packet
)
is
not
Packets
.
Pong
:
if
not
self
.
dispatcher
.
dispatch
(
conn
,
packet
.
getId
(),
packet
,
kw
):
raise
ProtocolError
(
'Unexpected response packet from %r: %r'
%
(
conn
,
packet
))
else
:
self
.
dispatch
(
conn
,
packet
,
kw
)
def
connectionLost
(
self
,
conn
,
new_state
):
self
.
app
.
dispatcher
.
unregister
(
conn
)
def
connectionFailed
(
self
,
conn
):
self
.
app
.
dispatcher
.
unregister
(
conn
)
def
unexpectedInAnswerHandler
(
*
args
,
**
kw
):
raise
Exception
(
'Unexpected event in an answer handler'
)
class
AnswerBaseHandler
(
EventHandler
):
connectionStarted
=
unexpectedInAnswerHandler
connectionCompleted
=
unexpectedInAnswerHandler
connectionFailed
=
unexpectedInAnswerHandler
connectionAccepted
=
unexpectedInAnswerHandler
timeoutExpired
=
unexpectedInAnswerHandler
connectionClosed
=
unexpectedInAnswerHandler
packetReceived
=
unexpectedInAnswerHandler
peerBroken
=
unexpectedInAnswerHandler
def
protocolError
(
self
,
conn
,
message
):
def
protocolError
(
self
,
conn
,
message
):
raise
StorageError
(
"protocol error: %s"
%
message
)
raise
StorageError
(
"protocol error: %s"
%
message
)
neo/client/handlers/master.py
View file @
f5f42522
...
@@ -15,10 +15,11 @@
...
@@ -15,10 +15,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from
neo.lib
import
logging
from
neo.lib
import
logging
from
neo.lib.handler
import
MTEventHandler
from
neo.lib.pt
import
MTPartitionTable
as
PartitionTable
from
neo.lib.pt
import
MTPartitionTable
as
PartitionTable
from
neo.lib.protocol
import
NodeStates
,
Packets
,
ProtocolError
from
neo.lib.protocol
import
NodeStates
,
Packets
,
ProtocolError
from
neo.lib.util
import
dump
,
add64
from
neo.lib.util
import
dump
,
add64
from
.
import
BaseHandler
,
AnswerBaseHandler
from
.
import
AnswerBaseHandler
from
..exception
import
NEOStorageError
from
..exception
import
NEOStorageError
CHECKED_SERIAL
=
object
()
CHECKED_SERIAL
=
object
()
...
@@ -91,7 +92,7 @@ class PrimaryBootstrapHandler(AnswerBaseHandler):
...
@@ -91,7 +92,7 @@ class PrimaryBootstrapHandler(AnswerBaseHandler):
def
answerLastTransaction
(
self
,
conn
,
ltid
):
def
answerLastTransaction
(
self
,
conn
,
ltid
):
pass
pass
class
PrimaryNotificationsHandler
(
Base
Handler
):
class
PrimaryNotificationsHandler
(
MTEvent
Handler
):
""" Handler that process the notifications from the primary master """
""" Handler that process the notifications from the primary master """
def
packetReceived
(
self
,
conn
,
packet
,
kw
=
{}):
def
packetReceived
(
self
,
conn
,
packet
,
kw
=
{}):
...
@@ -133,7 +134,7 @@ class PrimaryNotificationsHandler(BaseHandler):
...
@@ -133,7 +134,7 @@ class PrimaryNotificationsHandler(BaseHandler):
callback
(
tid
)
callback
(
tid
)
finally
:
finally
:
app
.
_cache_lock_release
()
app
.
_cache_lock_release
()
Base
Handler
.
packetReceived
(
self
,
conn
,
packet
,
kw
)
MTEvent
Handler
.
packetReceived
(
self
,
conn
,
packet
,
kw
)
def
connectionClosed
(
self
,
conn
):
def
connectionClosed
(
self
,
conn
):
app
=
self
.
app
app
=
self
.
app
...
...
neo/client/handlers/storage.py
View file @
f5f42522
...
@@ -21,11 +21,12 @@ from neo.lib import logging
...
@@ -21,11 +21,12 @@ from neo.lib import logging
from
neo.lib.protocol
import
LockState
,
ZERO_TID
from
neo.lib.protocol
import
LockState
,
ZERO_TID
from
neo.lib.util
import
dump
from
neo.lib.util
import
dump
from
neo.lib.exception
import
NodeNotReady
from
neo.lib.exception
import
NodeNotReady
from
.
import
BaseHandler
,
AnswerBaseHandler
from
neo.lib.handler
import
MTEventHandler
from
.
import
AnswerBaseHandler
from
..exception
import
NEOStorageError
,
NEOStorageNotFoundError
from
..exception
import
NEOStorageError
,
NEOStorageNotFoundError
from
..exception
import
NEOStorageDoesNotExistError
from
..exception
import
NEOStorageDoesNotExistError
class
StorageEventHandler
(
Base
Handler
):
class
StorageEventHandler
(
MTEvent
Handler
):
def
connectionLost
(
self
,
conn
,
new_state
):
def
connectionLost
(
self
,
conn
,
new_state
):
node
=
self
.
app
.
nm
.
getByAddress
(
conn
.
getAddress
())
node
=
self
.
app
.
nm
.
getByAddress
(
conn
.
getAddress
())
...
...
neo/lib/handler.py
View file @
f5f42522
...
@@ -196,3 +196,46 @@ class EventHandler(object):
...
@@ -196,3 +196,46 @@ class EventHandler(object):
def
backendNotImplemented
(
self
,
conn
,
message
):
def
backendNotImplemented
(
self
,
conn
,
message
):
raise
NotImplementedError
(
message
)
raise
NotImplementedError
(
message
)
class
MTEventHandler
(
EventHandler
):
"""Base class of handler implementations for MTClientConnection"""
def
__init__
(
self
,
app
):
super
(
MTEventHandler
,
self
).
__init__
(
app
)
self
.
dispatcher
=
app
.
dispatcher
def
dispatch
(
self
,
conn
,
packet
,
kw
=
{}):
assert
conn
.
lock
.
_is_owned
()
# XXX: see also lockCheckWrapper
super
(
MTEventHandler
,
self
).
dispatch
(
conn
,
packet
,
kw
)
def
packetReceived
(
self
,
conn
,
packet
,
kw
=
{}):
"""Redirect all received packet to dispatcher thread."""
if
packet
.
isResponse
()
and
type
(
packet
)
is
not
Packets
.
Pong
:
if
not
self
.
dispatcher
.
dispatch
(
conn
,
packet
.
getId
(),
packet
,
kw
):
raise
ProtocolError
(
'Unexpected response packet from %r: %r'
%
(
conn
,
packet
))
else
:
self
.
dispatch
(
conn
,
packet
,
kw
)
def
connectionLost
(
self
,
conn
,
new_state
):
self
.
dispatcher
.
unregister
(
conn
)
def
connectionFailed
(
self
,
conn
):
self
.
dispatcher
.
unregister
(
conn
)
def
unexpectedInAnswerHandler
(
*
args
,
**
kw
):
raise
Exception
(
'Unexpected event in an answer handler'
)
class
AnswerBaseHandler
(
EventHandler
):
connectionStarted
=
unexpectedInAnswerHandler
connectionCompleted
=
unexpectedInAnswerHandler
connectionFailed
=
unexpectedInAnswerHandler
connectionAccepted
=
unexpectedInAnswerHandler
timeoutExpired
=
unexpectedInAnswerHandler
connectionClosed
=
unexpectedInAnswerHandler
packetReceived
=
unexpectedInAnswerHandler
peerBroken
=
unexpectedInAnswerHandler
protocolError
=
unexpectedInAnswerHandler
neo/lib/locking.py
View file @
f5f42522
...
@@ -2,6 +2,7 @@ import os
...
@@ -2,6 +2,7 @@ import os
import
sys
import
sys
import
threading
import
threading
import
traceback
import
traceback
from
collections
import
deque
from
time
import
time
from
time
import
time
from
Queue
import
Empty
from
Queue
import
Empty
...
@@ -164,3 +165,49 @@ else:
...
@@ -164,3 +165,49 @@ else:
Lock
=
threading
.
Lock
Lock
=
threading
.
Lock
RLock
=
threading
.
RLock
RLock
=
threading
.
RLock
Semaphore
=
threading
.
Semaphore
Semaphore
=
threading
.
Semaphore
class
SimpleQueue
(
object
):
"""
Similar to Queue.Queue but with simpler locking scheme, reducing lock
contention on "put" (benchmark shows 60% less time spent in "put").
As a result:
- only a single consumer possible ("get" vs. "get" race condition)
- only a single producer possible ("put" vs. "put" race condition)
- no blocking size limit possible
- no consumer -> producer notifications (task_done/join API)
Queue is on the critical path: any moment spent here increases client
application wait for object data, transaction completion, etc.
As we have a single consumer (client application's thread) and a single
producer (lib.dispatcher, which can be called from several threads but
serialises calls internally) for each queue, Queue.Queue's locking scheme
can be relaxed to reduce latency.
"""
__slots__
=
(
'_lock'
,
'_unlock'
,
'_popleft'
,
'_append'
,
'_queue'
)
def
__init__
(
self
):
lock
=
Lock
()
self
.
_lock
=
lock
.
acquire
self
.
_unlock
=
lock
.
release
self
.
_queue
=
queue
=
deque
()
self
.
_popleft
=
queue
.
popleft
self
.
_append
=
queue
.
append
def
get
(
self
,
block
):
if
block
:
self
.
_lock
(
False
)
while
True
:
try
:
return
self
.
_popleft
()
except
IndexError
:
if
not
block
:
raise
Empty
self
.
_lock
()
def
put
(
self
,
item
):
self
.
_append
(
item
)
self
.
_lock
(
False
)
self
.
_unlock
()
def
empty
(
self
):
return
not
self
.
_queue
neo/lib/threaded_app.py
0 → 100644
View file @
f5f42522
#
# Copyright (C) 2006-2015 Nexedi SA
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import
threading
,
weakref
from
.
import
logging
from
.connection
import
ConnectionClosed
from
.debug
import
register
as
registerLiveDebugger
from
.dispatcher
import
Dispatcher
,
ForgottenPacket
from
.event
import
EventManager
from
.locking
import
SimpleQueue
from
.node
import
NodeManager
from
.protocol
import
Packets
from
.threaded_poll
import
ThreadedPoll
,
psThreadedPoll
class
app_set
(
weakref
.
WeakSet
):
def
on_log
(
self
):
for
app
in
self
:
app
.
log
()
app_set
=
app_set
()
registerLiveDebugger
(
app_set
.
on_log
)
class
ThreadContainer
(
threading
.
local
):
def
__init__
(
self
):
self
.
queue
=
SimpleQueue
()
self
.
answer
=
None
class
ThreadedApplication
(
object
):
"""The client node application."""
def
__init__
(
self
,
master_nodes
,
name
,
dynamic_master_list
=
None
):
# Start polling thread
self
.
em
=
EventManager
()
self
.
poll_thread
=
ThreadedPoll
(
self
.
em
,
name
=
name
)
psThreadedPoll
()
# Internal Attributes common to all thread
self
.
name
=
name
self
.
dispatcher
=
Dispatcher
(
self
.
poll_thread
)
self
.
nm
=
NodeManager
(
dynamic_master_list
)
self
.
master_conn
=
None
# load master node list
for
address
in
master_nodes
:
self
.
nm
.
createMaster
(
address
=
address
)
# no self-assigned UUID, primary master will supply us one
self
.
uuid
=
None
# Internal attribute distinct between thread
self
.
_thread_container
=
ThreadContainer
()
app_set
.
add
(
self
)
# to register self.on_log
def
__del__
(
self
):
# Due to bug in ZODB, close is not always called when shutting
# down zope, so use __del__ to close connections
self
.
close
()
def
close
(
self
):
# Clear all connection
self
.
master_conn
=
None
for
conn
in
self
.
em
.
getConnectionList
():
conn
.
close
()
# Stop polling thread
logging
.
debug
(
'Stopping %s'
,
self
.
poll_thread
)
self
.
poll_thread
.
stop
()
psThreadedPoll
()
def
getHandlerData
(
self
):
return
self
.
_thread_container
.
answer
def
setHandlerData
(
self
,
data
):
self
.
_thread_container
.
answer
=
data
def
log
(
self
):
self
.
em
.
log
()
self
.
nm
.
log
()
pt
=
self
.
__dict__
.
get
(
'pt'
)
if
pt
is
not
None
:
pt
.
log
()
def
_handlePacket
(
self
,
conn
,
packet
,
kw
=
{},
handler
=
None
):
"""
conn
The connection which received the packet (forwarded to handler).
packet
The packet to handle.
handler
The handler to use to handle packet.
If not given, it will be guessed from connection's not type.
"""
if
handler
is
None
:
# Guess the handler to use based on the type of node on the
# connection
node
=
self
.
nm
.
getByAddress
(
conn
.
getAddress
())
if
node
is
None
:
raise
ValueError
,
'Expecting an answer from a node '
\
'which type is not known... Is this right ?'
if
node
.
isStorage
():
handler
=
self
.
storage_handler
elif
node
.
isMaster
():
handler
=
self
.
primary_handler
else
:
raise
ValueError
,
'Unknown node type: %r'
%
(
node
.
__class__
,
)
with
conn
.
lock
:
handler
.
dispatch
(
conn
,
packet
,
kw
)
def
_ask
(
self
,
conn
,
packet
,
handler
=
None
,
**
kw
):
self
.
setHandlerData
(
None
)
queue
=
self
.
_thread_container
.
queue
msg_id
=
conn
.
ask
(
packet
,
queue
=
queue
,
**
kw
)
get
=
queue
.
get
_handlePacket
=
self
.
_handlePacket
while
True
:
qconn
,
qpacket
,
kw
=
get
(
True
)
is_forgotten
=
isinstance
(
qpacket
,
ForgottenPacket
)
if
conn
is
qconn
:
# check fake packet
if
qpacket
is
None
:
raise
ConnectionClosed
if
msg_id
==
qpacket
.
getId
():
if
is_forgotten
:
raise
ValueError
,
'ForgottenPacket for an '
\
'explicitely expected packet.'
_handlePacket
(
qconn
,
qpacket
,
kw
,
handler
)
break
if
not
is_forgotten
and
qpacket
is
not
None
:
_handlePacket
(
qconn
,
qpacket
,
kw
)
return
self
.
getHandlerData
()
neo/
client/
poll.py
→
neo/
lib/threaded_
poll.py
View file @
f5f42522
...
@@ -15,8 +15,8 @@
...
@@ -15,8 +15,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from
threading
import
Thread
,
enumerate
as
thread_enum
from
threading
import
Thread
,
enumerate
as
thread_enum
from
neo.lib
import
logging
from
.
import
logging
from
neo.lib
.locking
import
Lock
from
.locking
import
Lock
class
_ThreadedPoll
(
Thread
):
class
_ThreadedPoll
(
Thread
):
"""Polling thread."""
"""Polling thread."""
...
...
neo/tests/threaded/__init__.py
View file @
f5f42522
...
@@ -29,12 +29,12 @@ import transaction, ZODB
...
@@ -29,12 +29,12 @@ import transaction, ZODB
import
neo.admin.app
,
neo
.
master
.
app
,
neo
.
storage
.
app
import
neo.admin.app
,
neo
.
master
.
app
,
neo
.
storage
.
app
import
neo.client.app
,
neo
.
neoctl
.
app
import
neo.client.app
,
neo
.
neoctl
.
app
from
neo.client
import
Storage
from
neo.client
import
Storage
from
neo.client.container
import
SimpleQueue
from
neo.lib.threaded_poll
import
_ThreadedPoll
from
neo.client.poll
import
_ThreadedPoll
from
neo.lib
import
logging
from
neo.lib
import
logging
from
neo.lib.connection
import
BaseConnection
,
Connection
from
neo.lib.connection
import
BaseConnection
,
Connection
from
neo.lib.connector
import
SocketConnector
,
\
from
neo.lib.connector
import
SocketConnector
,
\
ConnectorConnectionRefusedException
ConnectorConnectionRefusedException
from
neo.lib.locking
import
SimpleQueue
from
neo.lib.protocol
import
CellStates
,
ClusterStates
,
NodeStates
,
NodeTypes
from
neo.lib.protocol
import
CellStates
,
ClusterStates
,
NodeStates
,
NodeTypes
from
neo.lib.util
import
parseMasterList
,
p64
from
neo.lib.util
import
parseMasterList
,
p64
from
..
import
NeoTestBase
,
Patch
,
getTempDirectory
,
setupMySQLdb
,
\
from
..
import
NeoTestBase
,
Patch
,
getTempDirectory
,
setupMySQLdb
,
\
...
...
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