Repository: cocotb/cocotb

Issue:
Revert "Always respond to triggers through react, so that the event loop is always involved"
Reverts potentialventures/cocotb#917

Just to check if this is really the cause of CI failure OF #928


Discussion:
Seems like Travis was just acting up when #917 was merged - I'm not able to reproduce the passing build any more.
I think we should go ahead and merge this reversion, and re-evaluate #917 against a non-broken travis

Resolving patch:
diff --git a/cocotb/scheduler.py b/cocotb/scheduler.py
--- a/cocotb/scheduler.py
+++ b/cocotb/scheduler.py
@@ -216,18 +216,20 @@ def __init__(self):
         if _debug:
             self.log.setLevel(logging.DEBUG)
 
+        # Use OrderedDict here for deterministic behavior (gh-934)
+
         # A dictionary of pending coroutines for each trigger,
         # indexed by trigger
-        self._trigger2coros = collections.defaultdict(list)
+        self._trigger2coros = collections.OrderedDict()
 
         # A dictionary mapping coroutines to the trigger they are waiting for
-        self._coro2trigger = {}
+        self._coro2trigger = collections.OrderedDict()
 
         # Our main state
         self._mode = Scheduler._MODE_NORMAL
 
         # A dictionary of pending writes
-        self._writes = {}
+        self._writes = collections.OrderedDict()
 
         self._pending_coros = []
         self._pending_callbacks = []
@@ -279,10 +281,10 @@ def _check_termination(self):
                 self._timer1.unprime()
 
             self._timer1.prime(self.begin_test)
-            self._trigger2coros = collections.defaultdict(list)
-            self._coro2trigger = {}
+            self._trigger2coros = collections.OrderedDict()
+            self._coro2trigger = collections.OrderedDict()
             self._terminate = False
-            self._writes = {}
+            self._writes = collections.OrderedDict()
             self._writes_pending.clear()
             self._mode = Scheduler._MODE_TERM
 
@@ -455,7 +457,7 @@ def unschedule(self, coro):
             # coroutine probably finished
             pass
         else:
-            if coro in self._trigger2coros[trigger]:
+            if coro in self._trigger2coros.setdefault(trigger, []):
                 self._trigger2coros[trigger].remove(coro)
             if not self._trigger2coros[trigger]:
                 trigger.unprime()
@@ -492,14 +494,15 @@ def _coroutine_yielded(self, coro, trigger):
         """Prime the trigger and update our internal mappings."""
         self._coro2trigger[coro] = trigger
 
+        trigger_coros = self._trigger2coros.setdefault(trigger, [])
         if coro is self._write_coro_inst:
             # Our internal write coroutine always runs before any user coroutines.
             # This preserves the behavior prior to the refactoring of writes to
             # this coroutine.
-            self._trigger2coros[trigger].insert(0, coro)
+            trigger_coros.insert(0, coro)
         else:
             # Everything else joins the back of the queue
-            self._trigger2coros[trigger].append(coro)
+            trigger_coros.append(coro)
 
         if not trigger.primed:
             try:
@@ -720,7 +723,12 @@ def cleanup(self):
 
         Unprime all pending triggers and kill off any coroutines stop all externals.
         """
-        for trigger, waiting in dict(self._trigger2coros).items():
+        # copy since we modify this in kill
+        items = list(self._trigger2coros.items())
+
+        # reversing seems to fix gh-928, although the order is still somewhat
+        # arbitrary.
+        for trigger, waiting in items[::-1]:
             for coro in waiting:
                 if _debug:
                     self.log.debug("Killing %s" % str(coro))


Test patch:
diff --git a/tests/test_cases/test_closedown/test_closedown.py b/tests/test_cases/test_closedown/test_closedown.py
--- a/tests/test_cases/test_closedown/test_closedown.py
+++ b/tests/test_cases/test_closedown/test_closedown.py
@@ -56,7 +56,7 @@ def clock_mon(dut):
     yield RisingEdge(dut.clk)
 
 
-@cocotb.test(expect_error=True)
+@cocotb.test(expect_fail=True)
 def test_failure_from_system_task(dut):
     """Allow the dut to call $fail_test() from verilog"""
     clock = Clock(dut.clk, 100)