Skip to content

Commit

Permalink
Fix racecondition between write() and spinner
Browse files Browse the repository at this point in the history
We now guard access to sys.stdout with a threading.Lock(), so
write() and the spinner thread no longer mangle up their
ouptut.

Fixes pavdmyt#29
  • Loading branch information
sebageek committed May 10, 2019
1 parent dd2fd21 commit 751bcbe
Showing 1 changed file with 30 additions and 24 deletions.
54 changes: 30 additions & 24 deletions yaspin/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def __init__(
self._hide_spin = None
self._spin_thread = None
self._last_frame = None
self._stdout_lock = threading.Lock()

# Signals

Expand Down Expand Up @@ -247,43 +248,46 @@ def hide(self):
thr_is_alive = self._spin_thread and self._spin_thread.is_alive()

if thr_is_alive and not self._hide_spin.is_set():
# set the hidden spinner flag
self._hide_spin.set()
with self._stdout_lock:
# set the hidden spinner flag
self._hide_spin.set()

# clear the current line
sys.stdout.write("\r")
self._clear_line()
# clear the current line
sys.stdout.write("\r")
self._clear_line()

# flush the stdout buffer so the current line can be rewritten to
sys.stdout.flush()
# flush the stdout buffer so the current line can be rewritten to
sys.stdout.flush()

def show(self):
"""Show the hidden spinner."""
thr_is_alive = self._spin_thread and self._spin_thread.is_alive()

if thr_is_alive and self._hide_spin.is_set():
# clear the hidden spinner flag
self._hide_spin.clear()
with self._stdout_lock:
# clear the hidden spinner flag
self._hide_spin.clear()

# clear the current line so the spinner is not appended to it
sys.stdout.write("\r")
self._clear_line()
# clear the current line so the spinner is not appended to it
sys.stdout.write("\r")
self._clear_line()

def write(self, text):
"""Write text in the terminal without breaking the spinner."""
# similar to tqdm.write()
# https://pypi.python.org/pypi/tqdm#writing-messages
sys.stdout.write("\r")
self._clear_line()
with self._stdout_lock:
sys.stdout.write("\r")
self._clear_line()

_text = to_unicode(text)
if PY2:
_text = _text.encode(ENCODING)
_text = to_unicode(text)
if PY2:
_text = _text.encode(ENCODING)

# Ensure output is bytes for Py2 and Unicode for Py3
assert isinstance(_text, builtin_str)
# Ensure output is bytes for Py2 and Unicode for Py3
assert isinstance(_text, builtin_str)

sys.stdout.write("{0}\n".format(_text))
sys.stdout.write("{0}\n".format(_text))

def ok(self, text="OK"):
"""Set Ok (success) finalizer to a spinner."""
Expand All @@ -306,7 +310,8 @@ def _freeze(self, final_text):
# Should be stopped here, otherwise prints after
# self._freeze call will mess up the spinner
self.stop()
sys.stdout.write(self._last_frame)
with self._stdout_lock:
sys.stdout.write(self._last_frame)

def _spin(self):
while not self._stop_spin.is_set():
Expand All @@ -321,9 +326,10 @@ def _spin(self):
out = self._compose_out(spin_phase)

# Write
sys.stdout.write(out)
self._clear_line()
sys.stdout.flush()
with self._stdout_lock:
sys.stdout.write(out)
self._clear_line()
sys.stdout.flush()

# Wait
time.sleep(self._interval)
Expand Down

0 comments on commit 751bcbe

Please sign in to comment.