about summary refs log tree commit diff stats
path: root/073wait.cc
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2016-08-26 13:27:57 -0700
committerKartik K. Agaram <vc@akkartik.com>2016-08-26 13:27:57 -0700
commit029a3bdf53b523eded91a30177affdcace3bb2a1 (patch)
tree8984ca1073ee20950d74d091eca4fb07e04f321f /073wait.cc
parentad2604e893edbb3cde3d5e14cf4418acd3ef7d65 (diff)
downloadmu-029a3bdf53b523eded91a30177affdcace3bb2a1.tar.gz
3258
In the process of debugging the last couple of commits (though I no
longer remember exactly how) I noticed that 'wait-for-routine' only
waits until the target routine stops running for any reason, including
when it blocks on something. That's not the synchronization primitive we
want in production code, even if it's necessary for some scenarios like
'buffer-lines-blocks-until-newline'. So we rename the old 'wait-for-routine'
primitive to 'wait-for-routine-to-block', and create a new version of
'wait-for-routine' that say callers of 'start-writing' can safely use,
because it waits until a target routine actually completes (either
successfully or not).
Diffstat (limited to '073wait.cc')
-rw-r--r--073wait.cc125
1 files changed, 108 insertions, 17 deletions
diff --git a/073wait.cc b/073wait.cc
index 78a5c46f..0d635cc3 100644
--- a/073wait.cc
+++ b/073wait.cc
@@ -224,26 +224,112 @@ def main [
 ]
 +mem: storing 11 in location 21
 
-//: also allow waiting on a routine to stop running
+//: also allow waiting on a routine to block
+//: (just for tests; use wait_for_routine below wherever possible)
+
+:(scenario wait_for_routine_to_block)
+def f1 [
+  1:number/routine <- start-running f2
+  wait-for-routine-to-block 1:number/routine
+  # now wait for f2 to run and modify location 10 before using its value
+  11:number <- copy 10:number
+]
+def f2 [
+  10:number <- copy 34
+]
++schedule: f1
++run: waiting for routine 2 to block
++schedule: f2
++schedule: waking up blocked routine 1
++schedule: f1
+# if we got the synchronization wrong we'd be storing 0 in location 11
++mem: storing 34 in location 11
+
+:(before "End routine Fields")
+// only if state == WAITING
+int waiting_on_routine_to_block;
+:(before "End routine Constructor")
+waiting_on_routine_to_block = 0;
+
+:(before "End Primitive Recipe Declarations")
+WAIT_FOR_ROUTINE_TO_BLOCK,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "wait-for-routine-to-block", WAIT_FOR_ROUTINE_TO_BLOCK);
+:(before "End Primitive Recipe Checks")
+case WAIT_FOR_ROUTINE_TO_BLOCK: {
+  if (SIZE(inst.ingredients) != 1) {
+    raise << maybe(get(Recipe, r).name) << "'wait-for-routine-to-block' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end();
+    break;
+  }
+  if (!is_mu_number(inst.ingredients.at(0))) {
+    raise << maybe(get(Recipe, r).name) << "first ingredient of 'wait-for-routine-to-block' should be a routine id generated by 'start-running', but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
+    break;
+  }
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case WAIT_FOR_ROUTINE_TO_BLOCK: {
+  if (ingredients.at(0).at(0) == Current_routine->id) {
+    raise << maybe(current_recipe_name()) << "routine can't wait for itself! '" << to_original_string(current_instruction()) << "'\n" << end();
+    break;
+  }
+  Current_routine->state = WAITING;
+  Current_routine->waiting_on_routine_to_block = ingredients.at(0).at(0);
+  trace(9998, "run") << "waiting for routine " << ingredients.at(0).at(0) << " to block" << end();
+//?   cerr << Current_routine->id << ": waiting for routine " << ingredients.at(0).at(0) << " to block\n";
+  break;
+}
+
+:(before "End Scheduler State Transitions")
+// Wake up any routines waiting for other routines to stop running.
+for (int i = 0; i < SIZE(Routines); ++i) {
+  if (Routines.at(i)->state != WAITING) continue;
+  routine* waiter = Routines.at(i);
+  if (!waiter->waiting_on_routine_to_block) continue;
+  int id = waiter->waiting_on_routine_to_block;
+  assert(id != waiter->id);  // routine can't wait on itself
+  for (int j = 0; j < SIZE(Routines); ++j) {
+    const routine* waitee = Routines.at(j);
+    if (waitee->id == id && waitee->state != RUNNING) {
+      // routine is WAITING or COMPLETED or DISCONTINUED
+      trace(9999, "schedule") << "waking up blocked routine " << waiter->id << end();
+//?       cerr << id << " is now unblocked (" << waitee->state << "); waking up waiting routine " << waiter->id << '\n';
+      waiter->state = RUNNING;
+      waiter->waiting_on_routine_to_block = 0;
+    }
+  }
+}
+
+//: allow waiting on a routine to complete
 
 :(scenario wait_for_routine)
 def f1 [
-  1:number <- copy 0
-  12:number/routine <- start-running f2
-  wait-for-routine 12:number/routine
-  # now wait for f2 to run and modify location 1 before using its value
-  3:number <- copy 1:number
+  # add a few routines to run
+  1:number/routine <- start-running f2
+  2:number/routine <- start-running f3
+  wait-for-routine 1:number/routine
+  # now wait for f2 to *complete* and modify location 13 before using its value
+  20:number <- copy 13:number
 ]
 def f2 [
-  1:number <- copy 34
+  10:number <- copy 0  # just padding
+  switch  # simulate a block; routine f1 shouldn't restart at this point
+  13:number <- copy 34
+]
+def f3 [
+  # padding routine just to help simulate the block in f2 using 'switch'
+  11:number <- copy 0
+  12:number <- copy 0
 ]
 +schedule: f1
 +run: waiting for routine 2
 +schedule: f2
++schedule: f3
++schedule: f2
 +schedule: waking up routine 1
 +schedule: f1
-# if we got the synchronization wrong we'd be storing 0 in location 3
-+mem: storing 34 in location 3
+# if we got the synchronization wrong we'd be storing 0 in location 20
++mem: storing 34 in location 20
 
 :(before "End routine Fields")
 // only if state == WAITING
@@ -276,23 +362,28 @@ case WAIT_FOR_ROUTINE: {
   Current_routine->state = WAITING;
   Current_routine->waiting_on_routine = ingredients.at(0).at(0);
   trace(9998, "run") << "waiting for routine " << ingredients.at(0).at(0) << end();
+//?   cerr << Current_routine->id << ": waiting for routine " << ingredients.at(0).at(0) << '\n';
   break;
 }
 
 :(before "End Scheduler State Transitions")
-// Wake up any routines waiting for other routines to go to sleep.
+// Wake up any routines waiting for other routines to complete.
 // Important: this must come after the scheduler loop above giving routines
 // waiting for locations to change a chance to wake up.
 for (int i = 0; i < SIZE(Routines); ++i) {
   if (Routines.at(i)->state != WAITING) continue;
-  if (!Routines.at(i)->waiting_on_routine) continue;
-  int id = Routines.at(i)->waiting_on_routine;
-  assert(id != Routines.at(i)->id);  // routine can't wait on itself
+  routine* waiter = Routines.at(i);
+  if (!waiter->waiting_on_routine) continue;
+  int id = waiter->waiting_on_routine;
+  assert(id != waiter->id);  // routine can't wait on itself
   for (int j = 0; j < SIZE(Routines); ++j) {
-    if (Routines.at(j)->id == id && Routines.at(j)->state != RUNNING) {
-      trace(9999, "schedule") << "waking up routine " << Routines.at(i)->id << end();
-      Routines.at(i)->state = RUNNING;
-      Routines.at(i)->waiting_on_routine = 0;
+    const routine* waitee = Routines.at(j);
+    if (waitee->id == id && waitee->state != RUNNING && waitee->state != WAITING) {
+      // routine is COMPLETED or DISCONTINUED
+      trace(9999, "schedule") << "waking up routine " << waiter->id << end();
+//?       cerr << id << " is now done (" << waitee->state << "); waking up waiting routine " << waiter->id << '\n';
+      waiter->state = RUNNING;
+      waiter->waiting_on_routine = 0;
     }
   }
 }