Skip to content

Commit ded4eb0

Browse files
committed
Remove stopit to get rid of the pkg_resources import that is broken with the latest setuptools
1 parent 91fb5ec commit ded4eb0

3 files changed

Lines changed: 75 additions & 4 deletions

File tree

bsyncviewer/lib/timeout.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""
2+
Minimal CPython threading timeout helper.
3+
4+
This replaces the small subset of ``stopit`` used by the validator workflow.
5+
``stopit`` imports ``pkg_resources`` at module import time, and that module was
6+
removed from setuptools 82.0.0.
7+
"""
8+
9+
import ctypes
10+
import threading
11+
12+
13+
class TimeoutException(Exception):
14+
"""Raised when a code block exceeds the allowed timeout."""
15+
16+
17+
def _async_raise(thread_id, exception_type):
18+
result = ctypes.pythonapi.PyThreadState_SetAsyncExc(
19+
ctypes.c_long(thread_id), ctypes.py_object(exception_type)
20+
)
21+
if result == 0:
22+
raise ValueError(f"Invalid thread ID {thread_id}")
23+
if result > 1:
24+
ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(thread_id), None)
25+
raise SystemError("PyThreadState_SetAsyncExc failed")
26+
27+
28+
class ThreadingTimeout:
29+
EXECUTED, EXECUTING, TIMED_OUT, INTERRUPTED, CANCELED = range(5)
30+
31+
def __init__(self, seconds, swallow_exc=True):
32+
self.seconds = seconds
33+
self.swallow_exc = swallow_exc
34+
self.state = self.EXECUTED
35+
self._target_tid = threading.current_thread().ident
36+
self._timer = None
37+
38+
def __bool__(self):
39+
return self.state in (self.EXECUTED, self.EXECUTING, self.CANCELED)
40+
41+
__nonzero__ = __bool__
42+
43+
def __enter__(self):
44+
self.state = self.EXECUTING
45+
self._timer = threading.Timer(self.seconds, self._trigger_timeout)
46+
self._timer.start()
47+
return self
48+
49+
def __exit__(self, exc_type, exc_val, exc_tb):
50+
if exc_type is TimeoutException:
51+
if self.state != self.TIMED_OUT:
52+
self.state = self.INTERRUPTED
53+
self._cancel_timer()
54+
return self.swallow_exc
55+
56+
if exc_type is None:
57+
self.state = self.EXECUTED
58+
59+
self._cancel_timer()
60+
return False
61+
62+
def cancel(self):
63+
self.state = self.CANCELED
64+
self._cancel_timer()
65+
66+
def _cancel_timer(self):
67+
if self._timer is not None:
68+
self._timer.cancel()
69+
70+
def _trigger_timeout(self):
71+
self.state = self.TIMED_OUT
72+
_async_raise(self._target_tid, TimeoutException)

bsyncviewer/lib/validator/workflow.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import copy
22
from collections import OrderedDict
33

4-
import stopit
54
import xmlschema
65
from testsuite.validate_sch import validate_schematron
76

7+
from bsyncviewer.lib.timeout import ThreadingTimeout, TimeoutException
88
from bsyncviewer.models.schema import Schema
99
from bsyncviewer.models.use_case import UseCase
1010

@@ -49,7 +49,7 @@ def validate_schema(self):
4949
resp["schema_version"] = self.schema.version
5050

5151
# timeout "to_dict()" after 5 mins and use the less good method of getting errors
52-
with stopit.ThreadingTimeout(300):
52+
with ThreadingTimeout(300):
5353
try:
5454
# this returns all errors
5555
self.xml_dict, errors = my_schema.to_dict(
@@ -74,7 +74,7 @@ def validate_schema(self):
7474
}
7575
resp["errors"].append(tmp_err)
7676

77-
except stopit.utils.TimeoutException:
77+
except TimeoutException:
7878
print(
7979
"TIMEOUT EXCEPTION OCCURRED, trying shorter validation method..."
8080
)

requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ djangorestframework-xml==2.0.0
88
psycopg2-binary==2.9.7
99

1010
semantic-version==2.10.0
11-
stopit==1.1.2
1211
xmlschema==4.1.0
1312
xmltodict==1.0.2
1413

0 commit comments

Comments
 (0)