Address #189 ensuring socket locks are released

The code previous assume exception-free execution of critical blocks
between lock acquire() and lock release(); however, in Python exceptions
can be thrown in many situations which would then result in a dead-lock
of the entire program using pigpio.

This is resolved by using the acquire/try/finally/release pattern to
ensure that the lock is always released, even when an exception occurs.

Also addresses #186, but takes a slightly different approach by using
RLock to handle the nested lock requirement, which overall should be
safer because it handles additional situations that can cause a
deadlock.
This commit is contained in:
Michael 2018-01-31 07:07:00 -05:00
parent 8996b7eaca
commit fb081b7cf6
1 changed files with 181 additions and 145 deletions

110
pigpio.py
View File

@ -860,7 +860,7 @@ class _socklock:
""" """
def __init__(self): def __init__(self):
self.s = None self.s = None
self.l = threading.Lock() self.l = threading.RLock()
class error(Exception): class error(Exception):
"""pigpio module exception""" """pigpio module exception"""
@ -970,7 +970,7 @@ def _u2i(uint32):
raise error(error_text(v)) raise error(error_text(v))
return v return v
def _pigpio_command(sl, cmd, p1, p2, rl=True): def _pigpio_command(sl, cmd, p1, p2):
""" """
Runs a pigpio socket command. Runs a pigpio socket command.
@ -980,12 +980,14 @@ def _pigpio_command(sl, cmd, p1, p2, rl=True):
p2:= command parameter 2 (if applicable). p2:= command parameter 2 (if applicable).
""" """
sl.l.acquire() sl.l.acquire()
try:
sl.s.send(struct.pack('IIII', cmd, p1, p2, 0)) sl.s.send(struct.pack('IIII', cmd, p1, p2, 0))
dummy, res = struct.unpack('12sI', sl.s.recv(_SOCK_CMD_LEN)) dummy, res = struct.unpack('12sI', sl.s.recv(_SOCK_CMD_LEN))
if rl: sl.l.release() finally:
sl.l.release()
return res return res
def _pigpio_command_ext(sl, cmd, p1, p2, p3, extents, rl=True): def _pigpio_command_ext(sl, cmd, p1, p2, p3, extents):
""" """
Runs an extended pigpio socket command. Runs an extended pigpio socket command.
@ -1003,9 +1005,11 @@ def _pigpio_command_ext(sl, cmd, p1, p2, p3, extents, rl=True):
else: else:
ext.extend(x) ext.extend(x)
sl.l.acquire() sl.l.acquire()
try:
sl.s.sendall(ext) sl.s.sendall(ext)
dummy, res = struct.unpack('12sI', sl.s.recv(_SOCK_CMD_LEN)) dummy, res = struct.unpack('12sI', sl.s.recv(_SOCK_CMD_LEN))
if rl: sl.l.release() finally:
sl.l.release()
return res return res
class _event_ADT: class _event_ADT:
@ -2854,12 +2858,14 @@ class pi():
# process read failure # process read failure
... ...
""" """
# Don't raise exception. Must release lock. self.sl.l.acquire()
bytes = u2i(_pigpio_command(self.sl, _PI_CMD_I2CRK, handle, reg, False)) try:
bytes = u2i(_pigpio_command(self.sl, _PI_CMD_I2CRK, handle, reg))
if bytes > 0: if bytes > 0:
data = self._rxbuf(bytes) data = self._rxbuf(bytes)
else: else:
data = "" data = ""
finally:
self.sl.l.release() self.sl.l.release()
return bytes, data return bytes, data
@ -2904,13 +2910,15 @@ class pi():
## extension ## ## extension ##
# s len data bytes # s len data bytes
# Don't raise exception. Must release lock. self.sl.l.acquire()
try:
bytes = u2i(_pigpio_command_ext( bytes = u2i(_pigpio_command_ext(
self.sl, _PI_CMD_I2CPK, handle, reg, len(data), [data], False)) self.sl, _PI_CMD_I2CPK, handle, reg, len(data), [data]))
if bytes > 0: if bytes > 0:
data = self._rxbuf(bytes) data = self._rxbuf(bytes)
else: else:
data = "" data = ""
finally:
self.sl.l.release() self.sl.l.release()
return bytes, data return bytes, data
@ -2982,13 +2990,15 @@ class pi():
# I count # I count
extents = [struct.pack("I", count)] extents = [struct.pack("I", count)]
# Don't raise exception. Must release lock. self.sl.l.acquire()
try:
bytes = u2i(_pigpio_command_ext( bytes = u2i(_pigpio_command_ext(
self.sl, _PI_CMD_I2CRI, handle, reg, 4, extents, False)) self.sl, _PI_CMD_I2CRI, handle, reg, 4, extentse))
if bytes > 0: if bytes > 0:
data = self._rxbuf(bytes) data = self._rxbuf(bytes)
else: else:
data = "" data = ""
finally:
self.sl.l.release() self.sl.l.release()
return bytes, data return bytes, data
@ -3013,13 +3023,15 @@ class pi():
(count, data) = pi.i2c_read_device(h, 12) (count, data) = pi.i2c_read_device(h, 12)
... ...
""" """
# Don't raise exception. Must release lock. self.sl.l.acquire()
try:
bytes = u2i( bytes = u2i(
_pigpio_command(self.sl, _PI_CMD_I2CRD, handle, count, False)) _pigpio_command(self.sl, _PI_CMD_I2CRD, handle, count))
if bytes > 0: if bytes > 0:
data = self._rxbuf(bytes) data = self._rxbuf(bytes)
else: else:
data = "" data = ""
finally:
self.sl.l.release() self.sl.l.release()
return bytes, data return bytes, data
@ -3115,13 +3127,15 @@ class pi():
## extension ## ## extension ##
# s len data bytes # s len data bytes
# Don't raise exception. Must release lock. self.sl.l.acquire()
try:
bytes = u2i(_pigpio_command_ext( bytes = u2i(_pigpio_command_ext(
self.sl, _PI_CMD_I2CZ, handle, 0, len(data), [data], False)) self.sl, _PI_CMD_I2CZ, handle, 0, len(data), [data]))
if bytes > 0: if bytes > 0:
data = self._rxbuf(bytes) data = self._rxbuf(bytes)
else: else:
data = "" data = ""
finally:
self.sl.l.release() self.sl.l.release()
return bytes, data return bytes, data
@ -3284,13 +3298,15 @@ class pi():
## extension ## ## extension ##
# s len data bytes # s len data bytes
# Don't raise exception. Must release lock. self.sl.l.acquire()
try:
bytes = u2i(_pigpio_command_ext( bytes = u2i(_pigpio_command_ext(
self.sl, _PI_CMD_BSPIX, CS, 0, len(data), [data], False)) self.sl, _PI_CMD_BSPIX, CS, 0, len(data), [data]))
if bytes > 0: if bytes > 0:
data = self._rxbuf(bytes) data = self._rxbuf(bytes)
else: else:
data = "" data = ""
finally:
self.sl.l.release() self.sl.l.release()
return bytes, data return bytes, data
@ -3421,13 +3437,15 @@ class pi():
## extension ## ## extension ##
# s len data bytes # s len data bytes
# Don't raise exception. Must release lock. self.sl.l.acquire()
try:
bytes = u2i(_pigpio_command_ext( bytes = u2i(_pigpio_command_ext(
self.sl, _PI_CMD_BI2CZ, SDA, 0, len(data), [data], False)) self.sl, _PI_CMD_BI2CZ, SDA, 0, len(data), [data]))
if bytes > 0: if bytes > 0:
data = self._rxbuf(bytes) data = self._rxbuf(bytes)
else: else:
data = "" data = ""
finally:
self.sl.l.release() self.sl.l.release()
return bytes, data return bytes, data
@ -3551,9 +3569,10 @@ class pi():
## extension ## ## extension ##
# s len data bytes # s len data bytes
# Don't raise exception. Must release lock. self.sl.l.acquire()
try:
bytes = u2i(_pigpio_command_ext( bytes = u2i(_pigpio_command_ext(
self.sl, _PI_CMD_BSCX, bsc_control, 0, len(data), [data], False)) self.sl, _PI_CMD_BSCX, bsc_control, 0, len(data), [data]))
if bytes > 0: if bytes > 0:
rx = self._rxbuf(bytes) rx = self._rxbuf(bytes)
status = struct.unpack('I', rx[0:4])[0] status = struct.unpack('I', rx[0:4])[0]
@ -3563,6 +3582,7 @@ class pi():
status = bytes status = bytes
bytes = 0 bytes = 0
data = bytearray(b'') data = bytearray(b'')
finally:
self.sl.l.release() self.sl.l.release()
return status, bytes, data return status, bytes, data
@ -3818,13 +3838,15 @@ class pi():
# error path # error path
... ...
""" """
# Don't raise exception. Must release lock. self.sl.l.acquire()
try:
bytes = u2i(_pigpio_command( bytes = u2i(_pigpio_command(
self.sl, _PI_CMD_SPIR, handle, count, False)) self.sl, _PI_CMD_SPIR, handle, count))
if bytes > 0: if bytes > 0:
data = self._rxbuf(bytes) data = self._rxbuf(bytes)
else: else:
data = "" data = ""
finally:
self.sl.l.release() self.sl.l.release()
return bytes, data return bytes, data
@ -3882,13 +3904,15 @@ class pi():
## extension ## ## extension ##
# s len data bytes # s len data bytes
# Don't raise exception. Must release lock. self.sl.l.acquire()
try:
bytes = u2i(_pigpio_command_ext( bytes = u2i(_pigpio_command_ext(
self.sl, _PI_CMD_SPIX, handle, 0, len(data), [data], False)) self.sl, _PI_CMD_SPIX, handle, 0, len(data), [data]))
if bytes > 0: if bytes > 0:
data = self._rxbuf(bytes) data = self._rxbuf(bytes)
else: else:
data = "" data = ""
finally:
self.sl.l.release() self.sl.l.release()
return bytes, data return bytes, data
@ -3988,13 +4012,15 @@ class pi():
# process read data # process read data
... ...
""" """
# Don't raise exception. Must release lock. self.sl.l.acquire()
try:
bytes = u2i( bytes = u2i(
_pigpio_command(self.sl, _PI_CMD_SERR, handle, count, False)) _pigpio_command(self.sl, _PI_CMD_SERR, handle, count))
if bytes > 0: if bytes > 0:
data = self._rxbuf(bytes) data = self._rxbuf(bytes)
else: else:
data = "" data = ""
finally:
self.sl.l.release() self.sl.l.release()
return bytes, data return bytes, data
@ -4217,9 +4243,10 @@ class pi():
(s, pars) = pi.script_status(sid) (s, pars) = pi.script_status(sid)
... ...
""" """
# Don't raise exception. Must release lock. self.sl.l.acquire()
try:
bytes = u2i( bytes = u2i(
_pigpio_command(self.sl, _PI_CMD_PROCP, script_id, 0, False)) _pigpio_command(self.sl, _PI_CMD_PROCP, script_id, 0))
if bytes > 0: if bytes > 0:
data = self._rxbuf(bytes) data = self._rxbuf(bytes)
pars = struct.unpack('11i', _str(data)) pars = struct.unpack('11i', _str(data))
@ -4228,6 +4255,7 @@ class pi():
else: else:
status = bytes status = bytes
params = () params = ()
finally:
self.sl.l.release() self.sl.l.release()
return status, params return status, params
@ -4308,13 +4336,15 @@ class pi():
(count, data) = pi.bb_serial_read(4) (count, data) = pi.bb_serial_read(4)
... ...
""" """
# Don't raise exception. Must release lock. self.sl.l.acquire()
try:
bytes = u2i( bytes = u2i(
_pigpio_command(self.sl, _PI_CMD_SLR, user_gpio, 10000, False)) _pigpio_command(self.sl, _PI_CMD_SLR, user_gpio, 10000))
if bytes > 0: if bytes > 0:
data = self._rxbuf(bytes) data = self._rxbuf(bytes)
else: else:
data = "" data = ""
finally:
self.sl.l.release() self.sl.l.release()
return bytes, data return bytes, data
@ -4410,13 +4440,15 @@ class pi():
## extension ## ## extension ##
# s len argx bytes # s len argx bytes
# Don't raise exception. Must release lock. self.sl.l.acquire()
try:
bytes = u2i(_pigpio_command_ext( bytes = u2i(_pigpio_command_ext(
self.sl, _PI_CMD_CF2, arg1, retMax, len(argx), [argx], False)) self.sl, _PI_CMD_CF2, arg1, retMax, len(argx), [argx]))
if bytes > 0: if bytes > 0:
data = self._rxbuf(bytes) data = self._rxbuf(bytes)
else: else:
data = "" data = ""
finally:
self.sl.l.release() self.sl.l.release()
return bytes, data return bytes, data
@ -4610,13 +4642,15 @@ class pi():
# process read data # process read data
... ...
""" """
# Don't raise exception. Must release lock. self.sl.l.acquire()
try:
bytes = u2i( bytes = u2i(
_pigpio_command(self.sl, _PI_CMD_FR, handle, count, False)) _pigpio_command(self.sl, _PI_CMD_FR, handle, count))
if bytes > 0: if bytes > 0:
data = self._rxbuf(bytes) data = self._rxbuf(bytes)
else: else:
data = "" data = ""
finally:
self.sl.l.release() self.sl.l.release()
return bytes, data return bytes, data
@ -4716,13 +4750,15 @@ class pi():
## extension ## ## extension ##
# s len data bytes # s len data bytes
# Don't raise exception. Must release lock. self.sl.l.acquire()
try:
bytes = u2i(_pigpio_command_ext( bytes = u2i(_pigpio_command_ext(
self.sl, _PI_CMD_FL, 60000, 0, len(fpattern), [fpattern], False)) self.sl, _PI_CMD_FL, 60000, 0, len(fpattern), [fpattern]))
if bytes > 0: if bytes > 0:
data = self._rxbuf(bytes) data = self._rxbuf(bytes)
else: else:
data = "" data = ""
finally:
self.sl.l.release() self.sl.l.release()
return bytes, data return bytes, data