issue #156: handle multiple _put() before wake of first sleeper

- If latch.get() is called and the queue is empty, a thread is put to
  sleep.

- If Latch.put() from another thread then appends an item to the queue and
  wakes the sleeping thread, and

- If a subsequent Latch.put() from the same or another thread manages to
  acquire `lock` before the sleeping thread is scheduled,

- The sleeping thread's wake socket would have multiple bytes written to
  it.

Therefore create a new _pending variable to track the only item assigned
to each thread (keyed by its write socket), and remove the socket from
`sleeping` from within put.
pull/167/head
David Wilson 7 years ago
parent 168a954d90
commit 001e0163fe

@ -911,13 +911,14 @@ class Latch(object):
def __init__(self): def __init__(self):
self._lock = threading.Lock() self._lock = threading.Lock()
self._queue = [] self._queue = []
self._waiters = [] self._sleeping = []
self._pending = {}
def close(self): def close(self):
self._lock.acquire() self._lock.acquire()
try: try:
self.closed = True self.closed = True
for wsock in self._waiters: for wsock in self._sleeping:
self._wake(wsock) self._wake(wsock)
finally: finally:
self._lock.release() self._lock.release()
@ -939,13 +940,13 @@ class Latch(object):
try: try:
if self.closed: if self.closed:
raise LatchError() raise LatchError()
if self._queue and not self._waiters: if self._queue:
_vv and IOLOG.debug('%r.get() -> %r', self, self._queue[0]) _vv and IOLOG.debug('%r.get() -> %r', self, self._queue[0])
return self._queue.pop(0) return self._queue.pop(0)
if not block: if not block:
raise TimeoutError() raise TimeoutError()
self._tls_init() self._tls_init()
self._waiters.append(_tls.wsock) self._sleeping.append(_tls.wsock)
finally: finally:
self._lock.release() self._lock.release()
@ -955,15 +956,16 @@ class Latch(object):
self._lock.acquire() self._lock.acquire()
try: try:
self._waiters.remove(_tls.wsock)
if self.closed: if self.closed:
raise LatchError() raise LatchError()
if not rfds: if not rfds:
raise TimeoutError() raise TimeoutError()
assert _tls.rsock.recv(1) == '\x7f' if _tls.rsock.recv(2) != '\x7f':
raise LatchError('internal error: received >1 wakeups')
try: try:
_vv and IOLOG.debug('%r.get() wake -> %r', self, self._queue[0]) obj = self._pending.pop(_tls.wsock)
return self._queue.pop(0) _vv and IOLOG.debug('%r.get() wake -> %r', self, obj)
return obj
except IndexError: except IndexError:
IOLOG.exception('%r.get() INDEX ERROR', self) IOLOG.exception('%r.get() INDEX ERROR', self)
raise raise
@ -976,11 +978,14 @@ class Latch(object):
try: try:
if self.closed: if self.closed:
raise LatchError() raise LatchError()
self._queue.append(obj) if self._sleeping:
if self._waiters: sock = self._sleeping.pop(0)
self._pending[sock] = obj
_vv and IOLOG.debug('%r.put() -> waking wfd=%r', _vv and IOLOG.debug('%r.put() -> waking wfd=%r',
self, self._waiters[0].fileno()) self, sock.fileno())
self._wake(self._waiters[0]) self._wake(sock)
else:
self._queue.append(obj)
finally: finally:
self._lock.release() self._lock.release()

Loading…
Cancel
Save