cocotb 1.9.2__cp310-cp310-win_amd64.whl → 2.0.0rc2__cp310-cp310-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of cocotb might be problematic. Click here for more details.

Files changed (161) hide show
  1. cocotb/_ANSI.py +65 -0
  2. cocotb/__init__.py +81 -327
  3. cocotb/_base_triggers.py +515 -0
  4. cocotb/_bridge.py +186 -0
  5. cocotb/_decorators.py +515 -0
  6. cocotb/_deprecation.py +3 -3
  7. cocotb/_exceptions.py +7 -0
  8. cocotb/_extended_awaitables.py +419 -0
  9. cocotb/_gpi_triggers.py +385 -0
  10. cocotb/_init.py +301 -0
  11. cocotb/_outcomes.py +54 -0
  12. cocotb/_profiling.py +46 -0
  13. cocotb/_py_compat.py +114 -29
  14. cocotb/_scheduler.py +448 -0
  15. cocotb/_test.py +248 -0
  16. cocotb/_test_factory.py +312 -0
  17. cocotb/_test_functions.py +42 -0
  18. cocotb/_typing.py +7 -0
  19. cocotb/_utils.py +274 -0
  20. cocotb/_version.py +3 -7
  21. cocotb/_xunit_reporter.py +66 -0
  22. cocotb/clock.py +353 -108
  23. cocotb/debug.py +24 -0
  24. cocotb/handle.py +1370 -793
  25. cocotb/libs/cocotb.dll +0 -0
  26. cocotb/libs/cocotb.exp +0 -0
  27. cocotb/libs/cocotb.lib +0 -0
  28. cocotb/libs/cocotbfli_modelsim.dll +0 -0
  29. cocotb/libs/cocotbfli_modelsim.exp +0 -0
  30. cocotb/libs/cocotbfli_modelsim.lib +0 -0
  31. cocotb/libs/cocotbutils.dll +0 -0
  32. cocotb/libs/cocotbutils.exp +0 -0
  33. cocotb/libs/cocotbutils.lib +0 -0
  34. cocotb/libs/cocotbvhpi_aldec.dll +0 -0
  35. cocotb/libs/cocotbvhpi_aldec.exp +0 -0
  36. cocotb/libs/cocotbvhpi_aldec.lib +0 -0
  37. cocotb/libs/cocotbvhpi_modelsim.dll +0 -0
  38. cocotb/libs/cocotbvhpi_modelsim.exp +0 -0
  39. cocotb/libs/cocotbvhpi_modelsim.lib +0 -0
  40. cocotb/libs/cocotbvpi_aldec.dll +0 -0
  41. cocotb/libs/cocotbvpi_aldec.exp +0 -0
  42. cocotb/libs/cocotbvpi_aldec.lib +0 -0
  43. cocotb/libs/cocotbvpi_ghdl.dll +0 -0
  44. cocotb/libs/cocotbvpi_ghdl.exp +0 -0
  45. cocotb/libs/cocotbvpi_ghdl.lib +0 -0
  46. cocotb/libs/cocotbvpi_icarus.exp +0 -0
  47. cocotb/libs/cocotbvpi_icarus.lib +0 -0
  48. cocotb/libs/cocotbvpi_icarus.vpl +0 -0
  49. cocotb/libs/cocotbvpi_modelsim.dll +0 -0
  50. cocotb/libs/cocotbvpi_modelsim.exp +0 -0
  51. cocotb/libs/cocotbvpi_modelsim.lib +0 -0
  52. cocotb/libs/embed.dll +0 -0
  53. cocotb/libs/embed.exp +0 -0
  54. cocotb/libs/embed.lib +0 -0
  55. cocotb/libs/gpi.dll +0 -0
  56. cocotb/libs/gpi.exp +0 -0
  57. cocotb/libs/gpi.lib +0 -0
  58. cocotb/libs/gpilog.dll +0 -0
  59. cocotb/libs/gpilog.exp +0 -0
  60. cocotb/libs/gpilog.lib +0 -0
  61. cocotb/libs/pygpilog.dll +0 -0
  62. cocotb/libs/pygpilog.exp +0 -0
  63. cocotb/libs/pygpilog.lib +0 -0
  64. cocotb/logging.py +424 -0
  65. cocotb/queue.py +103 -57
  66. cocotb/regression.py +680 -717
  67. cocotb/result.py +17 -188
  68. cocotb/share/def/aldec.exp +0 -0
  69. cocotb/share/def/aldec.lib +0 -0
  70. cocotb/share/def/ghdl.exp +0 -0
  71. cocotb/share/def/ghdl.lib +0 -0
  72. cocotb/share/def/icarus.exp +0 -0
  73. cocotb/share/def/icarus.lib +0 -0
  74. cocotb/share/def/modelsim.def +1 -0
  75. cocotb/share/def/modelsim.exp +0 -0
  76. cocotb/share/def/modelsim.lib +0 -0
  77. cocotb/share/include/cocotb_utils.h +9 -32
  78. cocotb/share/include/embed.h +7 -30
  79. cocotb/share/include/gpi.h +331 -137
  80. cocotb/share/include/gpi_logging.h +221 -142
  81. cocotb/share/include/py_gpi_logging.h +8 -5
  82. cocotb/share/include/vpi_user_ext.h +4 -26
  83. cocotb/share/lib/verilator/verilator.cpp +80 -67
  84. cocotb/simtime.py +230 -0
  85. cocotb/simulator.cp310-win_amd64.exp +0 -0
  86. cocotb/simulator.cp310-win_amd64.lib +0 -0
  87. cocotb/simulator.cp310-win_amd64.pyd +0 -0
  88. cocotb/simulator.pyi +107 -0
  89. cocotb/task.py +478 -213
  90. cocotb/triggers.py +55 -1092
  91. cocotb/types/__init__.py +28 -47
  92. cocotb/types/_abstract_array.py +151 -0
  93. cocotb/types/_array.py +295 -0
  94. cocotb/types/_indexing.py +17 -0
  95. cocotb/types/_logic.py +333 -0
  96. cocotb/types/_logic_array.py +868 -0
  97. cocotb/types/{range.py → _range.py} +47 -48
  98. cocotb/types/_resolve.py +76 -0
  99. cocotb/utils.py +58 -646
  100. cocotb-2.0.0rc2.dist-info/METADATA +60 -0
  101. cocotb-2.0.0rc2.dist-info/RECORD +146 -0
  102. {cocotb-1.9.2.dist-info → cocotb-2.0.0rc2.dist-info}/WHEEL +1 -1
  103. cocotb-2.0.0rc2.dist-info/entry_points.txt +2 -0
  104. {cocotb-1.9.2.dist-info → cocotb-2.0.0rc2.dist-info/licenses}/LICENSE +1 -0
  105. {cocotb-1.9.2.dist-info → cocotb-2.0.0rc2.dist-info}/top_level.txt +1 -0
  106. cocotb_tools/__init__.py +0 -0
  107. cocotb_tools/_coverage.py +33 -0
  108. cocotb_tools/_vendor/__init__.py +3 -0
  109. cocotb_tools/check_results.py +65 -0
  110. cocotb_tools/combine_results.py +152 -0
  111. cocotb_tools/config.py +241 -0
  112. {cocotb → cocotb_tools}/ipython_support.py +29 -22
  113. cocotb_tools/makefiles/Makefile.deprecations +27 -0
  114. {cocotb/share → cocotb_tools}/makefiles/Makefile.inc +77 -55
  115. {cocotb/share → cocotb_tools}/makefiles/Makefile.sim +16 -33
  116. {cocotb/share → cocotb_tools}/makefiles/simulators/Makefile.activehdl +9 -16
  117. cocotb_tools/makefiles/simulators/Makefile.cvc +61 -0
  118. cocotb_tools/makefiles/simulators/Makefile.dsim +39 -0
  119. {cocotb/share → cocotb_tools}/makefiles/simulators/Makefile.ghdl +13 -42
  120. cocotb_tools/makefiles/simulators/Makefile.icarus +80 -0
  121. cocotb_tools/makefiles/simulators/Makefile.ius +93 -0
  122. cocotb_tools/makefiles/simulators/Makefile.modelsim +9 -0
  123. cocotb_tools/makefiles/simulators/Makefile.nvc +60 -0
  124. cocotb_tools/makefiles/simulators/Makefile.questa +29 -0
  125. cocotb/share/makefiles/simulators/Makefile.questa → cocotb_tools/makefiles/simulators/Makefile.questa-compat +26 -54
  126. cocotb_tools/makefiles/simulators/Makefile.questa-qisqrun +149 -0
  127. {cocotb/share → cocotb_tools}/makefiles/simulators/Makefile.riviera +17 -56
  128. cocotb_tools/makefiles/simulators/Makefile.vcs +65 -0
  129. {cocotb/share → cocotb_tools}/makefiles/simulators/Makefile.verilator +15 -22
  130. {cocotb/share → cocotb_tools}/makefiles/simulators/Makefile.xcelium +20 -52
  131. cocotb_tools/py.typed +0 -0
  132. cocotb_tools/runner.py +1868 -0
  133. cocotb/_sim_versions.py → cocotb_tools/sim_versions.py +16 -21
  134. pygpi/entry.py +34 -18
  135. pygpi/py.typed +0 -0
  136. cocotb/ANSI.py +0 -92
  137. cocotb/binary.py +0 -858
  138. cocotb/config.py +0 -289
  139. cocotb/decorators.py +0 -332
  140. cocotb/log.py +0 -303
  141. cocotb/memdebug.py +0 -35
  142. cocotb/outcomes.py +0 -56
  143. cocotb/runner.py +0 -1400
  144. cocotb/scheduler.py +0 -1099
  145. cocotb/share/makefiles/Makefile.deprecations +0 -12
  146. cocotb/share/makefiles/simulators/Makefile.cvc +0 -94
  147. cocotb/share/makefiles/simulators/Makefile.icarus +0 -111
  148. cocotb/share/makefiles/simulators/Makefile.ius +0 -125
  149. cocotb/share/makefiles/simulators/Makefile.modelsim +0 -32
  150. cocotb/share/makefiles/simulators/Makefile.nvc +0 -64
  151. cocotb/share/makefiles/simulators/Makefile.vcs +0 -98
  152. cocotb/types/array.py +0 -309
  153. cocotb/types/logic.py +0 -292
  154. cocotb/types/logic_array.py +0 -298
  155. cocotb/wavedrom.py +0 -199
  156. cocotb/xunit_reporter.py +0 -80
  157. cocotb-1.9.2.dist-info/METADATA +0 -168
  158. cocotb-1.9.2.dist-info/RECORD +0 -121
  159. cocotb-1.9.2.dist-info/entry_points.txt +0 -2
  160. /cocotb/{_vendor/__init__.py → py.typed} +0 -0
  161. {cocotb → cocotb_tools}/_vendor/distutils_version.py +0 -0
cocotb/scheduler.py DELETED
@@ -1,1099 +0,0 @@
1
- #!/usr/bin/env python
2
-
3
- # Copyright (c) 2013, 2018 Potential Ventures Ltd
4
- # Copyright (c) 2013 SolarFlare Communications Inc
5
- # All rights reserved.
6
- #
7
- # Redistribution and use in source and binary forms, with or without
8
- # modification, are permitted provided that the following conditions are met:
9
- # * Redistributions of source code must retain the above copyright
10
- # notice, this list of conditions and the following disclaimer.
11
- # * Redistributions in binary form must reproduce the above copyright
12
- # notice, this list of conditions and the following disclaimer in the
13
- # documentation and/or other materials provided with the distribution.
14
- # * Neither the name of Potential Ventures Ltd,
15
- # SolarFlare Communications Inc nor the
16
- # names of its contributors may be used to endorse or promote products
17
- # derived from this software without specific prior written permission.
18
- #
19
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20
- # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
- # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
- # DISCLAIMED. IN NO EVENT SHALL POTENTIAL VENTURES LTD BE LIABLE FOR ANY
23
- # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24
- # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25
- # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26
- # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
- # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28
- # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
-
30
- """Coroutine scheduler.
31
-
32
- FIXME: We have a problem here. If a coroutine schedules a read-only but we
33
- also have pending writes we have to schedule the ReadWrite callback before
34
- the ReadOnly (and this is invalid, at least in Modelsim).
35
- """
36
-
37
- import inspect
38
- import logging
39
- import os
40
- import threading
41
- import warnings
42
- from collections import OrderedDict
43
- from collections.abc import Coroutine
44
- from contextlib import contextmanager
45
- from typing import Any, Callable, Union
46
-
47
- import cocotb
48
- import cocotb.decorators
49
- from cocotb import _py_compat, outcomes
50
- from cocotb._deprecation import deprecated
51
- from cocotb.log import SimLog
52
- from cocotb.result import TestComplete
53
- from cocotb.task import Task
54
- from cocotb.triggers import (
55
- Event,
56
- GPITrigger,
57
- Join,
58
- NextTimeStep,
59
- NullTrigger,
60
- ReadOnly,
61
- ReadWrite,
62
- Timer,
63
- Trigger,
64
- )
65
- from cocotb.utils import remove_traceback_frames
66
-
67
- # Debug mode controlled by environment variables
68
- _profiling = "COCOTB_ENABLE_PROFILING" in os.environ
69
- if _profiling:
70
- import cProfile
71
- import pstats
72
-
73
- _profile = cProfile.Profile()
74
-
75
- # Sadly the Python standard logging module is very slow so it's better not to
76
- # make any calls by testing a boolean flag first
77
- _debug = "COCOTB_SCHEDULER_DEBUG" in os.environ
78
-
79
-
80
- class InternalError(BaseException):
81
- """An error internal to scheduler. If you see this, report a bug!"""
82
-
83
- pass
84
-
85
-
86
- class profiling_context:
87
- """Context manager that profiles its contents"""
88
-
89
- def __enter__(self):
90
- _profile.enable()
91
-
92
- def __exit__(self, *excinfo):
93
- _profile.disable()
94
-
95
-
96
- class external_state:
97
- INIT = 0
98
- RUNNING = 1
99
- PAUSED = 2
100
- EXITED = 3
101
-
102
-
103
- @cocotb.decorators.public
104
- class external_waiter:
105
- def __init__(self):
106
- self._outcome = None
107
- self.thread = None
108
- self.event = Event()
109
- self.state = external_state.INIT
110
- self.cond = threading.Condition()
111
- self._log = SimLog("cocotb.external.thead.%s" % self.thread, id(self))
112
-
113
- @property
114
- def result(self):
115
- return self._outcome.get()
116
-
117
- def _propagate_state(self, new_state):
118
- with self.cond:
119
- if _debug:
120
- self._log.debug(
121
- "Changing state from %d -> %d from %s"
122
- % (self.state, new_state, threading.current_thread())
123
- )
124
- self.state = new_state
125
- self.cond.notify()
126
-
127
- def thread_done(self):
128
- if _debug:
129
- self._log.debug("Thread finished from %s" % (threading.current_thread()))
130
- self._propagate_state(external_state.EXITED)
131
-
132
- def thread_suspend(self):
133
- self._propagate_state(external_state.PAUSED)
134
-
135
- def thread_start(self):
136
- if self.state > external_state.INIT:
137
- return
138
-
139
- if not self.thread.is_alive():
140
- self._propagate_state(external_state.RUNNING)
141
- self.thread.start()
142
-
143
- def thread_resume(self):
144
- self._propagate_state(external_state.RUNNING)
145
-
146
- def thread_wait(self):
147
- if _debug:
148
- self._log.debug(
149
- "Waiting for the condition lock %s" % threading.current_thread()
150
- )
151
-
152
- with self.cond:
153
- while self.state == external_state.RUNNING:
154
- self.cond.wait()
155
-
156
- if _debug:
157
- if self.state == external_state.EXITED:
158
- self._log.debug(
159
- "Thread {} has exited from {}".format(
160
- self.thread, threading.current_thread()
161
- )
162
- )
163
- elif self.state == external_state.PAUSED:
164
- self._log.debug(
165
- "Thread %s has called yield from %s"
166
- % (self.thread, threading.current_thread())
167
- )
168
- elif self.state == external_state.RUNNING:
169
- self._log.debug(
170
- "Thread %s is in RUNNING from %d"
171
- % (self.thread, threading.current_thread())
172
- )
173
-
174
- if self.state == external_state.INIT:
175
- raise Exception(
176
- "Thread %s state was not allowed from %s"
177
- % (self.thread, threading.current_thread())
178
- )
179
-
180
- return self.state
181
-
182
-
183
- class Scheduler:
184
- """The main scheduler.
185
-
186
- Here we accept callbacks from the simulator and schedule the appropriate
187
- coroutines.
188
-
189
- A callback fires, causing the :any:`react` method to be called, with the
190
- trigger that caused the callback as the first argument.
191
-
192
- We look up a list of coroutines to schedule (indexed by the trigger) and
193
- schedule them in turn.
194
-
195
- .. attention::
196
-
197
- Implementors should not depend on the scheduling order!
198
-
199
- Some additional management is required since coroutines can return a list
200
- of triggers, to be scheduled when any one of the triggers fires. To
201
- ensure we don't receive spurious callbacks, we have to un-prime all the
202
- other triggers when any one fires.
203
-
204
- Due to the simulator nuances and fun with delta delays we have the
205
- following modes:
206
-
207
- Normal mode
208
- - Callbacks cause coroutines to be scheduled
209
- - Any pending writes are cached and do not happen immediately
210
-
211
- ReadOnly mode
212
- - Corresponds to ``cbReadOnlySynch`` (VPI) or ``vhpiCbRepEndOfTimeStep``
213
- (VHPI). In this state we are not allowed to perform writes.
214
-
215
- Write mode
216
- - Corresponds to ``cbReadWriteSynch`` (VPI) or ``vhpiCbRepLastKnownDeltaCycle`` (VHPI)
217
- In this mode we play back all the cached write updates.
218
-
219
- We can legally transition from Normal to Write by registering a :class:`~cocotb.triggers.ReadWrite`
220
- callback, however usually once a simulator has entered the ReadOnly phase
221
- of a given timestep then we must move to a new timestep before performing
222
- any writes. The mechanism for moving to a new timestep may not be
223
- consistent across simulators and therefore we provide an abstraction to
224
- assist with compatibility.
225
-
226
-
227
- Unless a coroutine has explicitly requested to be scheduled in ReadOnly
228
- mode (for example wanting to sample the finally settled value after all
229
- delta delays) then it can reasonably be expected to be scheduled during
230
- "normal mode" i.e. where writes are permitted.
231
- """
232
-
233
- _MODE_NORMAL = 1 # noqa
234
- _MODE_READONLY = 2 # noqa
235
- _MODE_WRITE = 3 # noqa
236
- _MODE_TERM = 4 # noqa
237
-
238
- # Singleton events, recycled to avoid spurious object creation
239
- _next_time_step = NextTimeStep()
240
- _read_write = ReadWrite()
241
- _read_only = ReadOnly()
242
- _timer1 = Timer(1)
243
-
244
- def __init__(self, handle_result: Callable[[Task], None]) -> None:
245
- self._handle_result = handle_result
246
-
247
- self.log = SimLog("cocotb.scheduler")
248
- if _debug:
249
- self.log.setLevel(logging.DEBUG)
250
-
251
- # Use OrderedDict here for deterministic behavior (gh-934)
252
-
253
- # A dictionary of pending coroutines for each trigger,
254
- # indexed by trigger
255
- self._trigger2coros = _py_compat.insertion_ordered_dict()
256
-
257
- # Our main state
258
- self._mode = Scheduler._MODE_NORMAL
259
-
260
- # A dictionary of pending (write_func, args), keyed by handle.
261
- # Writes are applied oldest to newest (least recently used).
262
- # Only the last scheduled write to a particular handle in a timestep is performed.
263
- self._write_calls = OrderedDict()
264
-
265
- self._pending_coros = []
266
- self._pending_triggers = []
267
- self._pending_threads = []
268
- self._pending_events = [] # Events we need to call set on once we've unwound
269
- self._scheduling = []
270
-
271
- self._terminate = False
272
- self._test = None
273
- self._main_thread = threading.current_thread()
274
-
275
- self._current_task = None
276
-
277
- self._is_reacting = False
278
-
279
- self._write_coro_inst = None
280
- self._writes_pending = Event()
281
-
282
- async def _do_writes(self):
283
- """An internal coroutine that performs pending writes"""
284
- while True:
285
- await self._writes_pending.wait()
286
- if self._mode != Scheduler._MODE_NORMAL:
287
- await self._next_time_step
288
-
289
- await self._read_write
290
-
291
- while self._write_calls:
292
- handle, (func, args) = self._write_calls.popitem(last=False)
293
- func(*args)
294
- self._writes_pending.clear()
295
-
296
- def _check_termination(self):
297
- """
298
- Handle a termination that causes us to move onto the next test.
299
- """
300
- if self._terminate:
301
- if _debug:
302
- self.log.debug("Test terminating, scheduling Timer")
303
-
304
- if self._write_coro_inst is not None:
305
- self._write_coro_inst.kill()
306
- self._write_coro_inst = None
307
-
308
- for t in self._trigger2coros:
309
- t.unprime()
310
-
311
- if self._timer1.primed:
312
- self._timer1.unprime()
313
-
314
- self._timer1.prime(self._test_completed)
315
- self._trigger2coros = _py_compat.insertion_ordered_dict()
316
- self._terminate = False
317
- self._write_calls = OrderedDict()
318
- self._writes_pending.clear()
319
- self._mode = Scheduler._MODE_TERM
320
-
321
- def _test_completed(self, trigger=None):
322
- """Called after a test and its cleanup have completed"""
323
- if _debug:
324
- self.log.debug("_test_completed called with trigger: %s" % (str(trigger)))
325
- if _profiling:
326
- ps = pstats.Stats(_profile).sort_stats("cumulative")
327
- ps.dump_stats("test_profile.pstat")
328
- ctx = profiling_context()
329
- else:
330
- ctx = _py_compat.nullcontext()
331
-
332
- with ctx:
333
- self._mode = Scheduler._MODE_NORMAL
334
- if trigger is not None:
335
- trigger.unprime()
336
-
337
- # extract the current test, and clear it
338
- test = self._test
339
- self._test = None
340
- if test is None:
341
- raise InternalError("_test_completed called with no active test")
342
- if test._outcome is None:
343
- raise InternalError("_test_completed called with an incomplete test")
344
-
345
- # Issue previous test result
346
- if _debug:
347
- self.log.debug("Issue test result to regression object")
348
-
349
- # this may schedule another test
350
- self._handle_result(test)
351
-
352
- # if it did, make sure we handle the test completing
353
- self._check_termination()
354
-
355
- def react(self, trigger):
356
- """
357
- .. deprecated:: 1.5
358
- This function is now private.
359
- """
360
- warnings.warn("This function is now private.", DeprecationWarning, stacklevel=2)
361
- return self._react(trigger)
362
-
363
- def _react(self, trigger):
364
- """
365
- Called when a trigger fires.
366
-
367
- We ensure that we only start the event loop once, rather than
368
- letting it recurse.
369
- """
370
- if self._is_reacting:
371
- # queue up the trigger, the event loop will get to it
372
- self._pending_triggers.append(trigger)
373
- return
374
-
375
- if self._pending_triggers:
376
- raise InternalError(
377
- "Expected all triggers to be handled but found {}".format(
378
- self._pending_triggers
379
- )
380
- )
381
-
382
- # start the event loop
383
- self._is_reacting = True
384
- try:
385
- self._event_loop(trigger)
386
- finally:
387
- self._is_reacting = False
388
-
389
- def _event_loop(self, trigger):
390
- """
391
- Run an event loop triggered by the given trigger.
392
-
393
- The loop will keep running until no further triggers fire.
394
-
395
- This should be triggered by only:
396
- * The beginning of a test, when there is no trigger to react to
397
- * A GPI trigger
398
- """
399
- if _profiling:
400
- ctx = profiling_context()
401
- else:
402
- ctx = _py_compat.nullcontext()
403
-
404
- with ctx:
405
- # When a trigger fires it is unprimed internally
406
- if _debug:
407
- self.log.debug("Trigger fired: %s" % str(trigger))
408
- # trigger.unprime()
409
-
410
- if self._mode == Scheduler._MODE_TERM:
411
- if _debug:
412
- self.log.debug(
413
- "Ignoring trigger %s since we're terminating" % str(trigger)
414
- )
415
- return
416
-
417
- if trigger is self._read_only:
418
- self._mode = Scheduler._MODE_READONLY
419
- # Only GPI triggers affect the simulator scheduling mode
420
- elif isinstance(trigger, GPITrigger):
421
- self._mode = Scheduler._MODE_NORMAL
422
-
423
- # work through triggers one by one
424
- is_first = True
425
- self._pending_triggers.append(trigger)
426
- while self._pending_triggers:
427
- trigger = self._pending_triggers.pop(0)
428
-
429
- if not is_first and isinstance(trigger, GPITrigger):
430
- self.log.warning(
431
- "A GPI trigger occurred after entering react - this "
432
- "should not happen."
433
- )
434
- assert False
435
-
436
- # this only exists to enable the warning above
437
- is_first = False
438
-
439
- # Scheduled coroutines may append to our waiting list so the first
440
- # thing to do is pop all entries waiting on this trigger.
441
- try:
442
- self._scheduling = self._trigger2coros.pop(trigger)
443
- except KeyError:
444
- # GPI triggers should only be ever pending if there is an
445
- # associated coroutine waiting on that trigger, otherwise it would
446
- # have been unprimed already
447
- if isinstance(trigger, GPITrigger):
448
- self.log.critical(
449
- "No coroutines waiting on trigger that fired: %s"
450
- % str(trigger)
451
- )
452
-
453
- trigger.log.info("I'm the culprit")
454
- # For Python triggers this isn't actually an error - we might do
455
- # event.set() without knowing whether any coroutines are actually
456
- # waiting on this event, for example
457
- elif _debug:
458
- self.log.debug(
459
- "No coroutines waiting on trigger that fired: %s"
460
- % str(trigger)
461
- )
462
-
463
- del trigger
464
- continue
465
-
466
- if _debug:
467
- debugstr = "\n\t".join(
468
- [coro._coro.__qualname__ for coro in self._scheduling]
469
- )
470
- if len(self._scheduling) > 0:
471
- debugstr = "\n\t" + debugstr
472
- self.log.debug(
473
- "%d pending coroutines for event %s%s"
474
- % (len(self._scheduling), str(trigger), debugstr)
475
- )
476
-
477
- # This trigger isn't needed any more
478
- trigger.unprime()
479
-
480
- for coro in self._scheduling:
481
- if coro._outcome is not None:
482
- # coroutine was killed by another coroutine waiting on the same trigger
483
- continue
484
- if _debug:
485
- self.log.debug(
486
- "Scheduling coroutine %s" % (coro._coro.__qualname__)
487
- )
488
- self._schedule(coro, trigger=trigger)
489
- if _debug:
490
- self.log.debug(
491
- "Scheduled coroutine %s" % (coro._coro.__qualname__)
492
- )
493
-
494
- # remove our reference to the objects at the end of each loop,
495
- # to try and avoid them being destroyed at a weird time (as
496
- # happened in gh-957)
497
- del coro
498
-
499
- self._scheduling = []
500
-
501
- # Handle any newly queued coroutines that need to be scheduled
502
- while self._pending_coros:
503
- task = self._pending_coros.pop(0)
504
- if _debug:
505
- self.log.debug(
506
- "Scheduling queued coroutine %s" % (task._coro.__qualname__)
507
- )
508
- self._schedule(task)
509
- if _debug:
510
- self.log.debug(
511
- "Scheduled queued coroutine %s" % (task._coro.__qualname__)
512
- )
513
-
514
- del task
515
-
516
- # Schedule may have queued up some events so we'll burn through those
517
- while self._pending_events:
518
- if _debug:
519
- self.log.debug(
520
- "Scheduling pending event %s"
521
- % (str(self._pending_events[0]))
522
- )
523
- self._pending_events.pop(0).set()
524
-
525
- # remove our reference to the objects at the end of each loop,
526
- # to try and avoid them being destroyed at a weird time (as
527
- # happened in gh-957)
528
- del trigger
529
-
530
- # no more pending triggers
531
- self._check_termination()
532
- if _debug:
533
- self.log.debug(
534
- "All coroutines scheduled, handing control back" " to simulator"
535
- )
536
-
537
- def unschedule(self, coro):
538
- """
539
- .. deprecated:: 1.5
540
- This function is now private.
541
- """
542
- warnings.warn("This function is now private.", DeprecationWarning, stacklevel=2)
543
- return self._unschedule(coro)
544
-
545
- def _unschedule(self, coro):
546
- """Unschedule a coroutine. Unprime any pending triggers"""
547
- if coro in self._pending_coros:
548
- assert not coro.has_started()
549
- self._pending_coros.remove(coro)
550
- # Close coroutine so there is no RuntimeWarning that it was never awaited
551
- coro.close()
552
- return
553
-
554
- # Unprime the trigger this coroutine is waiting on
555
- trigger = coro._trigger
556
- if trigger is not None:
557
- coro._trigger = None
558
- if coro in self._trigger2coros.setdefault(trigger, []):
559
- self._trigger2coros[trigger].remove(coro)
560
- if not self._trigger2coros[trigger]:
561
- trigger.unprime()
562
- del self._trigger2coros[trigger]
563
-
564
- assert self._test is not None
565
-
566
- if coro is self._test:
567
- if _debug:
568
- self.log.debug(f"Unscheduling test {coro}")
569
-
570
- if not self._terminate:
571
- self._terminate = True
572
- self._cleanup()
573
-
574
- elif Join(coro) in self._trigger2coros:
575
- self._react(Join(coro))
576
- else:
577
- try:
578
- # throws an error if the background coroutine errored
579
- # and no one was monitoring it
580
- coro._outcome.get()
581
- except (TestComplete, AssertionError) as e:
582
- coro.log.info("Test stopped by this forked coroutine")
583
- e = remove_traceback_frames(e, ["_unschedule", "get"])
584
- self._abort_test(e)
585
- except BaseException as e:
586
- coro.log.error("Exception raised by this forked coroutine")
587
- e = remove_traceback_frames(e, ["_unschedule", "get"])
588
- self._abort_test(e)
589
-
590
- def _schedule_write(self, handle, write_func, *args):
591
- """Queue `write_func` to be called on the next ReadWrite trigger."""
592
- if self._mode == Scheduler._MODE_READONLY:
593
- raise Exception(
594
- f"Write to object {handle._name} was scheduled during a read-only sync phase."
595
- )
596
-
597
- # TODO: we should be able to better keep track of when this needs to
598
- # be scheduled
599
- if self._write_coro_inst is None:
600
- self._write_coro_inst = self._add(self._do_writes())
601
-
602
- if handle in self._write_calls:
603
- del self._write_calls[handle]
604
- self._write_calls[handle] = (write_func, args)
605
- self._writes_pending.set()
606
-
607
- def _resume_coro_upon(self, coro, trigger):
608
- """Schedule `coro` to be resumed when `trigger` fires."""
609
- coro._trigger = trigger
610
-
611
- trigger_coros = self._trigger2coros.setdefault(trigger, [])
612
- if coro is self._write_coro_inst:
613
- # Our internal write coroutine always runs before any user coroutines.
614
- # This preserves the behavior prior to the refactoring of writes to
615
- # this coroutine.
616
- trigger_coros.insert(0, coro)
617
- else:
618
- # Everything else joins the back of the queue
619
- trigger_coros.append(coro)
620
-
621
- if not trigger.primed:
622
- if trigger_coros != [coro]:
623
- # should never happen
624
- raise InternalError(
625
- "More than one coroutine waiting on an unprimed trigger"
626
- )
627
-
628
- try:
629
- trigger.prime(self._react)
630
- except Exception as e:
631
- # discard the trigger we associated, it will never fire
632
- self._trigger2coros.pop(trigger)
633
-
634
- # replace it with a new trigger that throws back the exception
635
- self._resume_coro_upon(
636
- coro,
637
- NullTrigger(
638
- name="Trigger.prime() Error", _outcome=outcomes.Error(e)
639
- ),
640
- )
641
-
642
- def queue(self, coroutine):
643
- """
644
- .. deprecated:: 1.5
645
- This function is now private.
646
- """
647
- warnings.warn("This function is now private.", DeprecationWarning, stacklevel=2)
648
- return self._queue(coroutine)
649
-
650
- def _queue(self, coroutine):
651
- """Queue a coroutine for execution"""
652
- # Don't queue the same coroutine more than once (gh-2503)
653
- if coroutine not in self._pending_coros:
654
- self._pending_coros.append(coroutine)
655
-
656
- def queue_function(self, coro):
657
- """
658
- .. deprecated:: 1.5
659
- This function is now private.
660
- """
661
- warnings.warn("This function is now private.", DeprecationWarning, stacklevel=2)
662
- return self._queue_function(coro)
663
-
664
- def _queue_function(self, coro):
665
- """Queue a coroutine for execution and move the containing thread
666
- so that it does not block execution of the main thread any longer.
667
- """
668
- # We should be able to find ourselves inside the _pending_threads list
669
- matching_threads = [
670
- t for t in self._pending_threads if t.thread == threading.current_thread()
671
- ]
672
- if len(matching_threads) == 0:
673
- raise RuntimeError("queue_function called from unrecognized thread")
674
-
675
- # Raises if there is more than one match. This can never happen, since
676
- # each entry always has a unique thread.
677
- (t,) = matching_threads
678
-
679
- async def wrapper():
680
- # This function runs in the scheduler thread
681
- try:
682
- _outcome = outcomes.Value(await coro)
683
- except BaseException as e:
684
- _outcome = outcomes.Error(e)
685
- event.outcome = _outcome
686
- # Notify the current (scheduler) thread that we are about to wake
687
- # up the background (`@external`) thread, making sure to do so
688
- # before the background thread gets a chance to go back to sleep by
689
- # calling thread_suspend.
690
- # We need to do this here in the scheduler thread so that no more
691
- # coroutines run until the background thread goes back to sleep.
692
- t.thread_resume()
693
- event.set()
694
-
695
- event = threading.Event()
696
- self._pending_coros.append(Task(wrapper()))
697
- # The scheduler thread blocks in `thread_wait`, and is woken when we
698
- # call `thread_suspend` - so we need to make sure the coroutine is
699
- # queued before that.
700
- t.thread_suspend()
701
- # This blocks the calling `@external` thread until the coroutine finishes
702
- event.wait()
703
- return event.outcome.get()
704
-
705
- def run_in_executor(self, func, *args, **kwargs):
706
- """
707
- .. deprecated:: 1.5
708
- This function is now private.
709
- """
710
- warnings.warn("This function is now private.", DeprecationWarning, stacklevel=2)
711
- return self._run_in_executor(func, *args, **kwargs)
712
-
713
- def _run_in_executor(self, func, *args, **kwargs):
714
- """Run the coroutine in a separate execution thread
715
- and return an awaitable object for the caller.
716
- """
717
- # Create a thread
718
- # Create a trigger that is called as a result of the thread finishing
719
- # Create an Event object that the caller can await on
720
- # Event object set when the thread finishes execution, this blocks the
721
- # calling coroutine (but not the thread) until the external completes
722
-
723
- def execute_external(func, _waiter):
724
- _waiter._outcome = outcomes.capture(func, *args, **kwargs)
725
- if _debug:
726
- self.log.debug(
727
- "Execution of external routine done %s" % threading.current_thread()
728
- )
729
- _waiter.thread_done()
730
-
731
- async def wrapper():
732
- waiter = external_waiter()
733
- thread = threading.Thread(
734
- group=None,
735
- target=execute_external,
736
- name=func.__qualname__ + "_thread",
737
- args=([func, waiter]),
738
- kwargs={},
739
- )
740
-
741
- waiter.thread = thread
742
- self._pending_threads.append(waiter)
743
-
744
- await waiter.event.wait()
745
-
746
- return waiter.result # raises if there was an exception
747
-
748
- return wrapper()
749
-
750
- @staticmethod
751
- def create_task(coroutine: Any) -> Task:
752
- """Check to see if the given object is a schedulable coroutine object and if so, return it."""
753
-
754
- if isinstance(coroutine, Task):
755
- return coroutine
756
- if isinstance(coroutine, Coroutine):
757
- return Task(coroutine)
758
- if inspect.iscoroutinefunction(coroutine):
759
- raise TypeError(
760
- "Coroutine function {} should be called prior to being "
761
- "scheduled.".format(coroutine)
762
- )
763
- if isinstance(coroutine, cocotb.decorators.coroutine):
764
- raise TypeError(
765
- "Attempt to schedule a coroutine that hasn't started: {}.\n"
766
- "Did you forget to add parentheses to the @cocotb.test() "
767
- "decorator?".format(coroutine)
768
- )
769
- if inspect.isasyncgen(coroutine):
770
- raise TypeError(
771
- "{} is an async generator, not a coroutine. "
772
- "You likely used the yield keyword instead of await.".format(
773
- coroutine.__qualname__
774
- )
775
- )
776
- raise TypeError(
777
- "Attempt to add an object of type {} to the scheduler, which "
778
- "isn't a coroutine: {!r}\n"
779
- "Did you forget to use the @cocotb.coroutine decorator?".format(
780
- type(coroutine), coroutine
781
- )
782
- )
783
-
784
- @deprecated("This method is now private.")
785
- def add(self, coroutine: Union[Task, Coroutine]) -> Task:
786
- return self._add(coroutine)
787
-
788
- def _add(self, coroutine: Union[Task, Coroutine]) -> Task:
789
- """Add a new coroutine.
790
-
791
- Just a wrapper around self.schedule which provides some debug and
792
- useful error messages in the event of common gotchas.
793
- """
794
-
795
- task = self.create_task(coroutine)
796
-
797
- if _debug:
798
- self.log.debug("Adding new coroutine %s" % task._coro.__qualname__)
799
-
800
- self._schedule(task)
801
- self._check_termination()
802
- return task
803
-
804
- def start_soon(self, coro: Union[Coroutine, Task]) -> Task:
805
- """
806
- Schedule a coroutine to be run concurrently, starting after the current coroutine yields control.
807
-
808
- In contrast to :func:`~cocotb.fork` which starts the given coroutine immediately, this function
809
- starts the given coroutine only after the current coroutine yields control.
810
- This is useful when the coroutine to be forked has logic before the first
811
- :keyword:`await` that may not be safe to execute immediately.
812
-
813
- .. versionadded:: 1.5
814
- """
815
-
816
- task = self.create_task(coro)
817
-
818
- if _debug:
819
- self.log.debug("Queueing a new coroutine %s" % task._coro.__qualname__)
820
-
821
- self._queue(task)
822
- return task
823
-
824
- def add_test(self, test_coro):
825
- """
826
- .. deprecated:: 1.5
827
- This function is now private.
828
- """
829
- warnings.warn("This function is now private.", DeprecationWarning, stacklevel=2)
830
- return self._add_test(test_coro)
831
-
832
- def _add_test(self, test_coro):
833
- """Called by the regression manager to queue the next test"""
834
- if self._test is not None:
835
- raise InternalError("Test was added while another was in progress")
836
-
837
- self._test = test_coro
838
- self._resume_coro_upon(
839
- test_coro,
840
- NullTrigger(name=f"Start {test_coro!s}", _outcome=outcomes.Value(None)),
841
- )
842
-
843
- # This collection of functions parses a trigger out of the object
844
- # that was yielded by a coroutine, converting `list` -> `Waitable`,
845
- # `Waitable` -> `Task`, `Task` -> `Trigger`.
846
- # Doing them as separate functions allows us to avoid repeating unnecessary
847
- # `isinstance` checks.
848
-
849
- def _trigger_from_started_coro(self, result: Task) -> Trigger:
850
- if _debug:
851
- self.log.debug(
852
- "Joining to already running coroutine: %s" % result._coro.__qualname__
853
- )
854
- return result.join()
855
-
856
- def _trigger_from_unstarted_coro(self, result: Task) -> Trigger:
857
- self._queue(result)
858
- if _debug:
859
- self.log.debug(
860
- "Scheduling nested coroutine: %s" % result._coro.__qualname__
861
- )
862
- return result.join()
863
-
864
- def _trigger_from_waitable(self, result: cocotb.triggers.Waitable) -> Trigger:
865
- return self._trigger_from_unstarted_coro(Task(result._wait()))
866
-
867
- def _trigger_from_list(self, result: list) -> Trigger:
868
- return self._trigger_from_waitable(cocotb.triggers.First(*result))
869
-
870
- def _trigger_from_any(self, result) -> Trigger:
871
- """Convert a yielded object into a Trigger instance"""
872
- # note: the order of these can significantly impact performance
873
-
874
- if isinstance(result, Trigger):
875
- return result
876
-
877
- if isinstance(result, Task):
878
- if not result.has_started():
879
- return self._trigger_from_unstarted_coro(result)
880
- else:
881
- return self._trigger_from_started_coro(result)
882
-
883
- if inspect.iscoroutine(result):
884
- return self._trigger_from_unstarted_coro(Task(result))
885
-
886
- if isinstance(result, list):
887
- return self._trigger_from_list(result)
888
-
889
- if isinstance(result, cocotb.triggers.Waitable):
890
- return self._trigger_from_waitable(result)
891
-
892
- if inspect.isasyncgen(result):
893
- raise TypeError(
894
- "{} is an async generator, not a coroutine. "
895
- "You likely used the yield keyword instead of await.".format(
896
- result.__qualname__
897
- )
898
- )
899
-
900
- raise TypeError(
901
- "Coroutine yielded an object of type {}, which the scheduler can't "
902
- "handle: {!r}\n"
903
- "Did you forget to decorate with @cocotb.coroutine?".format(
904
- type(result), result
905
- )
906
- )
907
-
908
- @contextmanager
909
- def _task_context(self, task):
910
- """Context manager for the currently running task."""
911
- old_task = self._current_task
912
- self._current_task = task
913
- try:
914
- yield
915
- finally:
916
- self._current_task = old_task
917
-
918
- def schedule(self, coroutine, trigger=None):
919
- """
920
- .. deprecated:: 1.5
921
- This function is now private.
922
- """
923
- warnings.warn("This function is now private.", DeprecationWarning, stacklevel=2)
924
- return self._schedule(coroutine, trigger)
925
-
926
- def _schedule(self, coroutine, trigger=None):
927
- """Schedule a coroutine by calling the send method.
928
-
929
- Args:
930
- coroutine (cocotb.decorators.coroutine): The coroutine to schedule.
931
- trigger (cocotb.triggers.Trigger): The trigger that caused this
932
- coroutine to be scheduled.
933
- """
934
- with self._task_context(coroutine):
935
- if trigger is None:
936
- send_outcome = outcomes.Value(None)
937
- else:
938
- send_outcome = trigger._outcome
939
- if _debug:
940
- self.log.debug(f"Scheduling with {send_outcome}")
941
-
942
- coroutine._trigger = None
943
- result = coroutine._advance(send_outcome)
944
-
945
- if coroutine.done():
946
- if _debug:
947
- self.log.debug(
948
- "Coroutine {} completed with {}".format(
949
- coroutine, coroutine._outcome
950
- )
951
- )
952
- assert result is None
953
- self._unschedule(coroutine)
954
-
955
- # Don't handle the result if we're shutting down
956
- if self._terminate:
957
- return
958
-
959
- if not coroutine.done():
960
- if _debug:
961
- self.log.debug(
962
- "Coroutine %s yielded %s (mode %d)"
963
- % (coroutine._coro.__qualname__, str(result), self._mode)
964
- )
965
- try:
966
- result = self._trigger_from_any(result)
967
- except TypeError as exc:
968
- # restart this coroutine with an exception object telling it that
969
- # it wasn't allowed to yield that
970
- result = NullTrigger(_outcome=outcomes.Error(exc))
971
-
972
- self._resume_coro_upon(coroutine, result)
973
-
974
- # We do not return from here until pending threads have completed, but only
975
- # from the main thread, this seems like it could be problematic in cases
976
- # where a sim might change what this thread is.
977
-
978
- if self._main_thread is threading.current_thread():
979
- for ext in self._pending_threads:
980
- ext.thread_start()
981
- if _debug:
982
- self.log.debug(
983
- "Blocking from {} on {}".format(
984
- threading.current_thread(), ext.thread
985
- )
986
- )
987
- state = ext.thread_wait()
988
- if _debug:
989
- self.log.debug(
990
- "Back from wait on self %s with newstate %d"
991
- % (threading.current_thread(), state)
992
- )
993
- if state == external_state.EXITED:
994
- self._pending_threads.remove(ext)
995
- self._pending_events.append(ext.event)
996
-
997
- def finish_test(self, exc):
998
- """
999
- .. deprecated:: 1.5
1000
- This function is now private.
1001
- """
1002
- warnings.warn("This function is now private.", DeprecationWarning, stacklevel=2)
1003
- return self._finish_test(exc)
1004
-
1005
- def _finish_test(self, exc):
1006
- self._abort_test(exc)
1007
- self._check_termination()
1008
-
1009
- def _abort_test(self, exc):
1010
- """Force this test to end early, without executing any cleanup.
1011
-
1012
- This happens when a background task fails, and is consistent with
1013
- how the behavior has always been. In future, we may want to behave
1014
- more gracefully to allow the test body to clean up.
1015
-
1016
- `exc` is the exception that the test should report as its reason for
1017
- aborting.
1018
- """
1019
- if self._test._outcome is not None: # pragma: no cover
1020
- raise InternalError("Outcome already has a value, but is being set again.")
1021
- outcome = outcomes.Error(exc)
1022
- if _debug:
1023
- self._test.log.debug(f"outcome forced to {outcome}")
1024
- self._test._outcome = outcome
1025
- self._unschedule(self._test)
1026
-
1027
- def finish_scheduler(self, exc):
1028
- """
1029
- .. deprecated:: 1.5
1030
- This function is now private.
1031
- """
1032
- warnings.warn("This function is now private.", DeprecationWarning, stacklevel=2)
1033
- return self._finish_scheduler(exc)
1034
-
1035
- def _finish_scheduler(self, exc):
1036
- """Directly call into the regression manager and end test
1037
- once we return the sim will close us so no cleanup is needed.
1038
- """
1039
- # If there is an error during cocotb initialization, self._test may not
1040
- # have been set yet. Don't cause another Python exception here.
1041
-
1042
- if not self._test.done():
1043
- self.log.debug("Issue sim closedown result to regression object")
1044
- self._abort_test(exc)
1045
- self._handle_result(self._test)
1046
-
1047
- def cleanup(self):
1048
- """
1049
- .. deprecated:: 1.5
1050
- This function is now private.
1051
- """
1052
- warnings.warn("This function is now private.", DeprecationWarning, stacklevel=2)
1053
- return self._cleanup()
1054
-
1055
- def _cleanup(self):
1056
- """Clear up all our state.
1057
-
1058
- Unprime all pending triggers and kill off any coroutines, stop all externals.
1059
- """
1060
- # copy since we modify this in kill
1061
- items = list((k, list(v)) for k, v in self._trigger2coros.items())
1062
-
1063
- # reversing seems to fix gh-928, although the order is still somewhat
1064
- # arbitrary.
1065
- for trigger, waiting in items[::-1]:
1066
- for coro in waiting:
1067
- if _debug:
1068
- self.log.debug("Killing %s" % str(coro))
1069
- coro.kill()
1070
- assert not self._trigger2coros
1071
-
1072
- # if there are coroutines being scheduled when the test ends, kill them (gh-1347)
1073
- for coro in self._scheduling:
1074
- if _debug:
1075
- self.log.debug("Killing %s" % str(coro))
1076
- coro.kill()
1077
- self._scheduling = []
1078
-
1079
- # cancel outstanding triggers *before* queued coroutines (gh-3270)
1080
- while self._pending_triggers:
1081
- trigger = self._pending_triggers.pop(0)
1082
- if _debug:
1083
- self.log.debug("Unpriming %r", trigger)
1084
- trigger.unprime()
1085
- assert not self._pending_triggers
1086
-
1087
- # Kill any queued coroutines.
1088
- # We use a while loop because task.kill() calls _unschedule(), which will remove the task from _pending_coros.
1089
- # If that happens a for loop will stop early and then the assert will fail.
1090
- while self._pending_coros:
1091
- # Get first task but leave it in the list so that _unschedule() will correctly close the unstarted coroutine object.
1092
- task = self._pending_coros[0]
1093
- task.kill()
1094
-
1095
- if self._main_thread is not threading.current_thread():
1096
- raise Exception("Cleanup() called outside of the main thread")
1097
-
1098
- for ext in self._pending_threads:
1099
- self.log.warning("Waiting for %s to exit", ext.thread)