Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions openhtf/util/threads.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def run(self):
self._thread_proc()
except Exception: # pylint: disable=broad-except
if not self._thread_exception(*sys.exc_info()):
logging.exception('Thread raised an exception: %s', self.name)
_LOG.exception('Thread raised an exception: %s', self.name)
raise
finally:
self._thread_finished()
Expand All @@ -83,6 +83,14 @@ class KillableThread(ExceptionSafeThread):
"""Killable thread raises an internal exception when killed.

Based on recipe available at http://tomerfiliba.com/recipes/Thread2/

Note: To fully address race conditions involved with the use of
PyThreadState_SetAsyncExc, the GIL must be held from when the thread is
checked to when it's async-raised. In this case, we're not doing that, and
there remains the remote possibility that a thread identifier is reused and we
accidentally kill the wrong thread.


"""

def kill(self):
Expand All @@ -92,15 +100,25 @@ def kill(self):

def async_raise(self, exc_type):
"""Raise the exception."""
assert self.is_alive(), 'Only running threads have a thread identity'
# Should only be called on a started thread so raise otherwise
assert self.ident is not None, 'Only started threads have thread identifier'

# If the thread has died we don't want to raise an exception so log.
if not self.is_alive():
_LOG.debug('Not raising %s because thread %s (%s) is not alive',
exc_type, self.name, self.ident)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs return here

I don't think we should print self.ident since as far as the documentation indicates it's a Python-specific magic cookie to be used e.g. to index a dictionary of thread-specific data. I notice this is the only file where it is used. self.name should be sufficient.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's printed out in case it is special or odd. I think it can be None for threads that aren't started yet

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The none-check is on the previous line, I'm not sure if there are other odd cases

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added return.

As farz pointed out, including ident could potentially be useful debugging information and there is no harm to including it at debug level.

return

result = ctypes.pythonapi.PyThreadState_SetAsyncExc(
ctypes.c_long(self.ident), ctypes.py_object(exc_type))
if result == 0:
if result == 0 and self.is_alive():
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like you actually got bit by this before so I'd leave a comment here so it's emphasized that this isn't redundant with the earlier check: # Do not raise an error unnecessarily if the thread is dead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

# Don't raise an exception an error unnecessarily if the thread is dead.
raise ValueError('Thread ID was invalid.', self.ident)
elif result != 1:
elif result > 1:
# Something bad happened, call with a NULL exception to undo.
ctypes.pythonapi.PyThreadState_SetAsyncExc(self.ident, None)
raise SystemError('PyThreadState_SetAsyncExc failed.', self.ident)
raise RuntimeError('Error: PyThreadState_SetAsyncExc %s %s (%s) %s',
exc_type, self.name, self.ident, result)

def _thread_exception(self, exc_type, exc_val, exc_tb):
"""Suppress the exception when we're kill()'d."""
Expand Down