Контекст
Недавно я опубликовал класс таймера для обзора обзора кода. У меня было ощущение, что есть ошибки concurrency, как я когда-то видел 1 unit test, но не смог воспроизвести сбой. Отсюда мой пост для проверки кода.
У меня появилась отличная обратная связь, в которой показаны различные условия гонки в коде. (Я думал) Я понял проблему и решение, но прежде чем делать какие-либо исправления, я хотел разоблачить ошибки с помощью unit test. Когда я попытался, я понял, что это сложно. Различные ответы об обмене стеками предполагали, что мне нужно будет контролировать выполнение потоков, чтобы выявить ошибку (-ы), и любое надуманное время не обязательно будет переносимым на другую машину. Это представляло собой много случайной сложности, кроме проблемы, которую я пытался решить.
Вместо этого я попытался использовать лучший инструмент статического анализа (SA) для python, PyLint, чтобы узнать, не выбрал ли он какую-либо ошибку, но это могло бы "т. Почему человек может найти ошибки в обзоре кода (по существу SA), но инструмент SA не смог?
Боясь попробовать заставить Valgrind работать с python (который звучал как як-бритье), я решил иметь bash для исправления ошибок без их воспроизведения первый. Теперь я в рассоле.
Вот код сейчас.
from threading import Timer, Lock
from time import time
class NotRunningError(Exception): pass
class AlreadyRunningError(Exception): pass
class KitchenTimer(object):
'''
Loosely models a clockwork kitchen timer with the following differences:
You can start the timer with arbitrary duration (e.g. 1.2 seconds).
The timer calls back a given function when time up.
Querying the time remaining has 0.1 second accuracy.
'''
PRECISION_NUM_DECIMAL_PLACES = 1
RUNNING = "RUNNING"
STOPPED = "STOPPED"
TIMEUP = "TIMEUP"
def __init__(self):
self._stateLock = Lock()
with self._stateLock:
self._state = self.STOPPED
self._timeRemaining = 0
def start(self, duration=1, whenTimeup=None):
'''
Starts the timer to count down from the given duration and call whenTimeup when time up.
'''
with self._stateLock:
if self.isRunning():
raise AlreadyRunningError
else:
self._state = self.RUNNING
self.duration = duration
self._userWhenTimeup = whenTimeup
self._startTime = time()
self._timer = Timer(duration, self._whenTimeup)
self._timer.start()
def stop(self):
'''
Stops the timer, preventing whenTimeup callback.
'''
with self._stateLock:
if self.isRunning():
self._timer.cancel()
self._state = self.STOPPED
self._timeRemaining = self.duration - self._elapsedTime()
else:
raise NotRunningError()
def isRunning(self):
return self._state == self.RUNNING
def isStopped(self):
return self._state == self.STOPPED
def isTimeup(self):
return self._state == self.TIMEUP
@property
def timeRemaining(self):
if self.isRunning():
self._timeRemaining = self.duration - self._elapsedTime()
return round(self._timeRemaining, self.PRECISION_NUM_DECIMAL_PLACES)
def _whenTimeup(self):
with self._stateLock:
self._state = self.TIMEUP
self._timeRemaining = 0
if callable(self._userWhenTimeup):
self._userWhenTimeup()
def _elapsedTime(self):
return time() - self._startTime
Вопрос
В контексте этого примера кода, как я могу разоблачить условия гонки, исправить их и доказать, что они исправлены?
Дополнительные очки
дополнительные точки для рамки тестирования, подходящие для других реализаций и проблем, а не специально для этого кода.
Вынос
Мой взнос заключается в том, что техническое решение для воспроизведения выявленных условий гонки состоит в том, чтобы контролировать синхронность двух потоков, чтобы обеспечить их выполнение в том порядке, в котором будет обнаружена ошибка. Важным моментом здесь является то, что они уже идентифицировали условия гонки. Лучший способ, который я нашел для определения условий гонки, - это перевести код на проверку кода и побудить больше экспертов анализировать его.