import sysv_ipc
from sysv_ipc import MessageQueue
from cis_interface.drivers.Driver import Driver
from cis_interface.interface.PsiInterface import PSI_MSG_MAX
from cis_interface import backwards
# OS X limit is 2kb
maxMsgSize = PSI_MSG_MAX
DEBUG_SLEEPS = True
[docs]class IODriver(Driver):
r"""Base driver for any driver that requires access to a message queue.
Args:
name (str): The name of the message queue that the driver should
connect to.
suffix (str, optional): Suffix added to name to create the environment
variable where the message queue key is stored. Defaults to ''.
\*\*kwargs: Additional keyword arguments are passed to parent class's
__init__ method.
Attributes (in addition to parent class's):
state (str): Description of the last operation performed by the driver.
numSent (int): The number of messages sent to the queue.
numReceived (int): The number of messages received from the queue.
env (dict): Environment variables.
mq (:class:`sysv_ipc.MessageQueue`): Message queue.
"""
def __init__(self, name, suffix="", **kwargs):
super(IODriver, self).__init__(name, **kwargs)
self.debug()
self.state = 'Started'
self.numSent = 0
self.numReceived = 0
self.env = {} # Any addition env that the model needs
self.mq = MessageQueue(None, flags=sysv_ipc.IPC_CREX,
max_message_size=maxMsgSize)
self.env[name + suffix] = str(self.mq.key)
self.debug(".env: %s", self.env)
[docs] def printStatus(self, beg_msg='', end_msg=''):
r"""Print information on the status of the IODriver.
Arguments:
beg_msg (str, optional): Additional message to print at beginning.
end_msg (str, optional): Additional message to print at end.
"""
msg = beg_msg
msg += '%-30s' % (self.__module__ + '(' + self.name + ')')
msg += '%-30s' % ('last action: ' + self.state)
msg += '%-15s' % (str(self.numSent) + ' delivered, ')
msg += '%-15s' % (str(self.numReceived) + ' accepted, ')
with self.lock:
if self.mq:
msg += '%-15s' % (str(self.mq.current_messages) + ' ready')
msg += end_msg
[docs] def recv_wait(self, timeout=0):
r"""Receive a message smaller than maxMsgSize. Unlike ipc_recv,
recv_wait will wait until there is a message to receive or the queue is
closed.
Args:
timeout (float, optional): Max time that should be waited. Defaults
to 0 and is set to attribute timeout.
Returns:
str: The received message.
"""
ret = ''
elapsed = 0.0
if not timeout:
timeout = self.timeout
while True and (not timeout or elapsed < timeout):
ret = self.ipc_recv()
if ret is None or len(ret) > 0:
break
self.debug('.recv_wait(): waiting')
self.sleep()
elapsed += self.sleeptime
if not ret and elapsed >= timeout:
self.debug('.recv_wait_nolimit(): timeout at %f s', timeout)
return ret
[docs] def recv_wait_nolimit(self, timeout=0):
r"""Receive a message larger than maxMsgSize. Unlike ipc_recv,
recv_wait will wait until there is a message to receive or the queue is
closed.
Args:
timeout (float, optional): Max time that should be waited. Defaults
to 0 and is infinite.
Returns:
str: The received message.
"""
ret = ''
elapsed = 0.0
if not timeout:
timeout = self.timeout
while True and (not timeout or elapsed < timeout):
ret = self.ipc_recv_nolimit()
if ret is None or len(ret) > 0:
break
self.debug('.recv_wait_nolimit(): waiting')
self.sleep()
elapsed += self.sleeptime
if not ret and elapsed >= timeout:
self.debug('.recv_wait_nolimit(): timeout at %f s', timeout)
return ret
[docs] def ipc_send(self, data):
r"""Send a message smaller than maxMsgSize.
Args:
str: The message to be sent.
"""
backwards.assert_bytes(data)
with self.lock:
self.state = 'deliver'
self.debug('::ipc_send %d bytes', len(data))
try:
if self.mq is None:
self.debug('.ipc_send(): mq closed')
else:
self.mq.send(data)
self.debug('.ipc_send %d bytes completed', len(data))
self.state = 'delivered'
self.numSent = self.numSent + 1
except: # pragma: debug
self.debug('.ipc_send(): exception')
raise
[docs] def ipc_recv(self):
r"""Receive a message smaller than maxMsgSize.
Returns:
str: The received message.
"""
with self.lock:
self.state = 'accept'
self.debug('.ipc_recv(): reading IPC msg')
ret = None
try:
if self.mq is None:
self.debug('.ipc_recv(): mq closed')
elif self.mq.current_messages > 0:
data, _ = self.mq.receive()
ret = data
self.debug('.ipc_recv ret %d bytes', len(ret))
else:
ret = backwards.unicode2bytes('')
self.debug('.ipc_recv(): no messages in the queue')
except: # pragma: debug
self.error('.ipc_recv(): exception mq')
if ret is not None:
backwards.assert_bytes(ret)
return ret
[docs] def ipc_send_nolimit(self, data):
r"""Send a message larger than maxMsgSize in multiple parts.
Args:
str: The message to be sent.
"""
self.state = 'deliver'
self.debug('::ipc_send_nolimit %d bytes', len(data))
prev = 0
error = False
self.ipc_send(backwards.unicode2bytes("%ld" % len(data)))
while prev < len(data):
try:
next = min(prev + maxMsgSize, len(data))
# next = min(prev + self.mq.max_size, len(data))
self.ipc_send(data[prev:next])
self.debug('.ipc_send_nolimit(): %d of %d bytes sent',
next, len(data))
prev = next
except: # pragma: debug
self.debug('.ipc_send_nolimit(): send interupted at %d of %d bytes.',
prev, len(data))
error = True
break
if not error:
self.debug('.ipc_send_nolimit %d bytes completed', len(data))
self.state = 'delivered'
[docs] def ipc_recv_nolimit(self):
r"""Receive a message larger than maxMsgSize in multiple parts.
Returns:
str: The complete received message.
"""
self.state = 'accept'
self.debug('.ipc_recv_nolimit(): reading IPC msg')
ret = self.ipc_recv()
if ret is None or len(ret) == 0:
return ret
try:
leng_exp = int(float(ret))
data = backwards.unicode2bytes('')
tries_orig = leng_exp / maxMsgSize + 5
tries = tries_orig
while (len(data) < leng_exp) and (tries > 0):
ret = self.ipc_recv()
if ret is None: # pragma: debug
self.debug('.ipc_recv_nolimit: read interupted at %d of %d bytes.',
len(data, leng_exp))
break
data = data + ret
tries -= 1
self.sleep()
if len(data) == leng_exp:
ret, leng = data, len(data)
elif len(data) > leng_exp: # pragma: debug
self.error("%d bytes were recieved, but only %d were expected.",
len(data), leng_exp)
ret, leng = None, -1
else: # pragma: debug
self.error('After %d tries, only %d of %d bytes were received.',
tries_orig, len(data), leng_exp)
ret, leng = None, -1
except: # pragma: debug
raise
ret, leng = None, -1
self.debug('.ipc_recv_nolimit ret %d bytes', leng)
return ret
@property
def n_ipc_msg(self):
r"""int: The number of messages in the queue."""
with self.lock:
if self.mq:
return self.mq.current_messages
else: # pragma: debug
return 0
[docs] def graceful_stop(self, timeout=0, **kwargs):
r"""Stop the IODriver, first draining the message queue.
Args:
timeout (float, optional): Max time that should be waited. Defaults
to 0 and is set to attribute timeout.
\*\*kwargs: Additional keyword arguments are passed to the parent
class's graceful_stop method.
"""
self.debug('.graceful_stop()')
if not timeout:
timeout = self.timeout
try:
while True:
with self.lock:
if (not self.mq) or ((self.mq.current_messages == 0) or
(timeout <= 0)):
break
if DEBUG_SLEEPS:
self.debug('.graceful_stop(): draining %d messages',
self.mq.current_messages)
self.sleep()
timeout -= self.sleeptime
except: # pragma: debug
self.debug("::graceful_stop: exception")
# raise
super(IODriver, self).graceful_stop()
self.debug('.graceful_stop(): done')
[docs] def close_queue(self):
r"""Close the queue."""
self.debug(':close_queue()')
with self.lock:
try:
if self.mq:
self.debug('.close_queue(): remove IPC id %d', self.mq.id)
self.mq.remove()
self.mq = None
except: # pragma: debug
self.debug(':close_queue(): exception')
self.debug(':close_queue(): done')
[docs] def terminate(self):
r"""Stop the IODriver, removing the queue."""
self.debug(':terminate()')
self.close_queue()
super(IODriver, self).terminate()
self.debug(':terminate(): done')
[docs] def on_model_exit(self):
r"""Actions to perform when the associated model driver is finished."""
pass