github.com/gopherjs/gopherjs@v1.19.0-beta1.0.20240506212314-27071a8796e4/compiler/prelude/goroutines.js (about)

     1  var $stackDepthOffset = 0;
     2  var $getStackDepth = () => {
     3      var err = new Error();
     4      if (err.stack === undefined) {
     5          return undefined;
     6      }
     7      return $stackDepthOffset + err.stack.split("\n").length;
     8  };
     9  
    10  var $panicStackDepth = null, $panicValue;
    11  var $callDeferred = (deferred, jsErr, fromPanic) => {
    12      if (!fromPanic && deferred !== null && $curGoroutine.deferStack.indexOf(deferred) == -1) {
    13          throw jsErr;
    14      }
    15      if (jsErr !== null) {
    16          var newErr = null;
    17          try {
    18              $panic(new $jsErrorPtr(jsErr));
    19          } catch (err) {
    20              newErr = err;
    21          }
    22          $callDeferred(deferred, newErr);
    23          return;
    24      }
    25      if ($curGoroutine.asleep) {
    26          return;
    27      }
    28  
    29      $stackDepthOffset--;
    30      var outerPanicStackDepth = $panicStackDepth;
    31      var outerPanicValue = $panicValue;
    32  
    33      var localPanicValue = $curGoroutine.panicStack.pop();
    34      if (localPanicValue !== undefined) {
    35          $panicStackDepth = $getStackDepth();
    36          $panicValue = localPanicValue;
    37      }
    38  
    39      try {
    40          while (true) {
    41              if (deferred === null) {
    42                  deferred = $curGoroutine.deferStack[$curGoroutine.deferStack.length - 1];
    43                  if (deferred === undefined) {
    44                      /* The panic reached the top of the stack. Clear it and throw it as a JavaScript error. */
    45                      $panicStackDepth = null;
    46                      if (localPanicValue.Object instanceof Error) {
    47                          throw localPanicValue.Object;
    48                      }
    49                      var msg;
    50                      if (localPanicValue.constructor === $String) {
    51                          msg = localPanicValue.$val;
    52                      } else if (localPanicValue.Error !== undefined) {
    53                          msg = localPanicValue.Error();
    54                      } else if (localPanicValue.String !== undefined) {
    55                          msg = localPanicValue.String();
    56                      } else {
    57                          msg = localPanicValue;
    58                      }
    59                      throw new Error(msg);
    60                  }
    61              }
    62              var call = deferred.pop();
    63              if (call === undefined) {
    64                  $curGoroutine.deferStack.pop();
    65                  if (localPanicValue !== undefined) {
    66                      deferred = null;
    67                      continue;
    68                  }
    69                  return;
    70              }
    71              var r = call[0].apply(call[2], call[1]);
    72              if (r && r.$blk !== undefined) {
    73                  deferred.push([r.$blk, [], r]);
    74                  if (fromPanic) {
    75                      throw null;
    76                  }
    77                  return;
    78              }
    79  
    80              if (localPanicValue !== undefined && $panicStackDepth === null) {
    81                  /* error was recovered */
    82                  if (fromPanic) {
    83                      throw null;
    84                  }
    85                  return;
    86              }
    87          }
    88      } catch (e) {
    89          // Deferred function threw a JavaScript exception or tries to unwind stack
    90          // to the point where a panic was handled.
    91          if (fromPanic) {
    92              // Re-throw the exception to reach deferral execution call at the end
    93              // of the function.
    94              throw e;
    95          }
    96          // We are at the end of the function, handle the error or re-throw to
    97          // continue unwinding if necessary, or simply stop unwinding if we got far
    98          // enough.
    99          $callDeferred(deferred, e, fromPanic);
   100      } finally {
   101          if (localPanicValue !== undefined) {
   102              if ($panicStackDepth !== null) {
   103                  $curGoroutine.panicStack.push(localPanicValue);
   104              }
   105              $panicStackDepth = outerPanicStackDepth;
   106              $panicValue = outerPanicValue;
   107          }
   108          $stackDepthOffset++;
   109      }
   110  };
   111  
   112  var $panic = value => {
   113      $curGoroutine.panicStack.push(value);
   114      $callDeferred(null, null, true);
   115  };
   116  var $recover = () => {
   117      if ($panicStackDepth === null || ($panicStackDepth !== undefined && $panicStackDepth !== $getStackDepth() - 2)) {
   118          return $ifaceNil;
   119      }
   120      $panicStackDepth = null;
   121      return $panicValue;
   122  };
   123  var $throw = err => { throw err; };
   124  
   125  var $noGoroutine = { asleep: false, exit: false, deferStack: [], panicStack: [] };
   126  var $curGoroutine = $noGoroutine, $totalGoroutines = 0, $awakeGoroutines = 0, $checkForDeadlock = true, $exportedFunctions = 0;
   127  var $mainFinished = false;
   128  var $go = (fun, args) => {
   129      $totalGoroutines++;
   130      $awakeGoroutines++;
   131      var $goroutine = () => {
   132          try {
   133              $curGoroutine = $goroutine;
   134              var r = fun(...args);
   135              if (r && r.$blk !== undefined) {
   136                  fun = () => { return r.$blk(); };
   137                  args = [];
   138                  return;
   139              }
   140              $goroutine.exit = true;
   141          } catch (err) {
   142              if (!$goroutine.exit) {
   143                  throw err;
   144              }
   145          } finally {
   146              $curGoroutine = $noGoroutine;
   147              if ($goroutine.exit) { /* also set by runtime.Goexit() */
   148                  $totalGoroutines--;
   149                  $goroutine.asleep = true;
   150              }
   151              if ($goroutine.asleep) {
   152                  $awakeGoroutines--;
   153                  if (!$mainFinished && $awakeGoroutines === 0 && $checkForDeadlock && $exportedFunctions === 0) {
   154                      console.error("fatal error: all goroutines are asleep - deadlock!");
   155                      if ($global.process !== undefined) {
   156                          $global.process.exit(2);
   157                      }
   158                  }
   159              }
   160          }
   161      };
   162      $goroutine.asleep = false;
   163      $goroutine.exit = false;
   164      $goroutine.deferStack = [];
   165      $goroutine.panicStack = [];
   166      $schedule($goroutine);
   167  };
   168  
   169  var $scheduled = [];
   170  var $runScheduled = () => {
   171      // For nested setTimeout calls browsers enforce 4ms minimum delay. We minimize
   172      // the effect of this penalty by queueing the timer preemptively before we run
   173      // the goroutines, and later cancelling it if it turns out unneeded. See:
   174      // https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#nested_timeouts
   175      var nextRun = setTimeout($runScheduled);
   176      try {
   177          var start = Date.now();
   178          var r;
   179          while ((r = $scheduled.shift()) !== undefined) {
   180              r();
   181              // We need to interrupt this loop in order to allow the event loop to
   182              // process timers, IO, etc. However, invoking scheduling through
   183              // setTimeout is ~1000 times more expensive, so we amortize this cost by
   184              // looping until the 4ms minimal delay has elapsed (assuming there are
   185              // scheduled goroutines to run), and then yield to the event loop.
   186              var elapsed = Date.now() - start;
   187              if (elapsed > 4 || elapsed < 0) { break; }
   188          }
   189      } finally {
   190          if ($scheduled.length == 0) {
   191              // Cancel scheduling pass if there's nothing to run.
   192              clearTimeout(nextRun);
   193          }
   194      }
   195  };
   196  
   197  var $schedule = goroutine => {
   198      if (goroutine.asleep) {
   199          goroutine.asleep = false;
   200          $awakeGoroutines++;
   201      }
   202      $scheduled.push(goroutine);
   203      if ($curGoroutine === $noGoroutine) {
   204          $runScheduled();
   205      }
   206  };
   207  
   208  var $setTimeout = (f, t) => {
   209      $awakeGoroutines++;
   210      return setTimeout(() => {
   211          $awakeGoroutines--;
   212          f();
   213      }, t);
   214  };
   215  
   216  var $block = () => {
   217      if ($curGoroutine === $noGoroutine) {
   218          $throwRuntimeError("cannot block in JavaScript callback, fix by wrapping code in goroutine");
   219      }
   220      $curGoroutine.asleep = true;
   221  };
   222  
   223  var $restore = (context, params) => {
   224      if (context !== undefined && context.$blk !== undefined) {
   225          return context;
   226      }
   227      return params;
   228  }
   229  
   230  var $send = (chan, value) => {
   231      if (chan.$closed) {
   232          $throwRuntimeError("send on closed channel");
   233      }
   234      var queuedRecv = chan.$recvQueue.shift();
   235      if (queuedRecv !== undefined) {
   236          queuedRecv([value, true]);
   237          return;
   238      }
   239      if (chan.$buffer.length < chan.$capacity) {
   240          chan.$buffer.push(value);
   241          return;
   242      }
   243  
   244      var thisGoroutine = $curGoroutine;
   245      var closedDuringSend;
   246      chan.$sendQueue.push(closed => {
   247          closedDuringSend = closed;
   248          $schedule(thisGoroutine);
   249          return value;
   250      });
   251      $block();
   252      return {
   253          $blk() {
   254              if (closedDuringSend) {
   255                  $throwRuntimeError("send on closed channel");
   256              }
   257          }
   258      };
   259  };
   260  var $recv = chan => {
   261      var queuedSend = chan.$sendQueue.shift();
   262      if (queuedSend !== undefined) {
   263          chan.$buffer.push(queuedSend(false));
   264      }
   265      var bufferedValue = chan.$buffer.shift();
   266      if (bufferedValue !== undefined) {
   267          return [bufferedValue, true];
   268      }
   269      if (chan.$closed) {
   270          return [chan.$elem.zero(), false];
   271      }
   272  
   273      var thisGoroutine = $curGoroutine;
   274      var f = { $blk() { return this.value; } };
   275      var queueEntry = v => {
   276          f.value = v;
   277          $schedule(thisGoroutine);
   278      };
   279      chan.$recvQueue.push(queueEntry);
   280      $block();
   281      return f;
   282  };
   283  var $close = chan => {
   284      if (chan.$closed) {
   285          $throwRuntimeError("close of closed channel");
   286      }
   287      chan.$closed = true;
   288      while (true) {
   289          var queuedSend = chan.$sendQueue.shift();
   290          if (queuedSend === undefined) {
   291              break;
   292          }
   293          queuedSend(true); /* will panic */
   294      }
   295      while (true) {
   296          var queuedRecv = chan.$recvQueue.shift();
   297          if (queuedRecv === undefined) {
   298              break;
   299          }
   300          queuedRecv([chan.$elem.zero(), false]);
   301      }
   302  };
   303  var $select = comms => {
   304      var ready = [];
   305      var selection = -1;
   306      for (var i = 0; i < comms.length; i++) {
   307          var comm = comms[i];
   308          var chan = comm[0];
   309          switch (comm.length) {
   310              case 0: /* default */
   311                  selection = i;
   312                  break;
   313              case 1: /* recv */
   314                  if (chan.$sendQueue.length !== 0 || chan.$buffer.length !== 0 || chan.$closed) {
   315                      ready.push(i);
   316                  }
   317                  break;
   318              case 2: /* send */
   319                  if (chan.$closed) {
   320                      $throwRuntimeError("send on closed channel");
   321                  }
   322                  if (chan.$recvQueue.length !== 0 || chan.$buffer.length < chan.$capacity) {
   323                      ready.push(i);
   324                  }
   325                  break;
   326          }
   327      }
   328  
   329      if (ready.length !== 0) {
   330          selection = ready[Math.floor(Math.random() * ready.length)];
   331      }
   332      if (selection !== -1) {
   333          var comm = comms[selection];
   334          switch (comm.length) {
   335              case 0: /* default */
   336                  return [selection];
   337              case 1: /* recv */
   338                  return [selection, $recv(comm[0])];
   339              case 2: /* send */
   340                  $send(comm[0], comm[1]);
   341                  return [selection];
   342          }
   343      }
   344  
   345      var entries = [];
   346      var thisGoroutine = $curGoroutine;
   347      var f = { $blk() { return this.selection; } };
   348      var removeFromQueues = () => {
   349          for (var i = 0; i < entries.length; i++) {
   350              var entry = entries[i];
   351              var queue = entry[0];
   352              var index = queue.indexOf(entry[1]);
   353              if (index !== -1) {
   354                  queue.splice(index, 1);
   355              }
   356          }
   357      };
   358      for (var i = 0; i < comms.length; i++) {
   359          (i => {
   360              var comm = comms[i];
   361              switch (comm.length) {
   362                  case 1: /* recv */
   363                      var queueEntry = value => {
   364                          f.selection = [i, value];
   365                          removeFromQueues();
   366                          $schedule(thisGoroutine);
   367                      };
   368                      entries.push([comm[0].$recvQueue, queueEntry]);
   369                      comm[0].$recvQueue.push(queueEntry);
   370                      break;
   371                  case 2: /* send */
   372                      var queueEntry = () => {
   373                          if (comm[0].$closed) {
   374                              $throwRuntimeError("send on closed channel");
   375                          }
   376                          f.selection = [i];
   377                          removeFromQueues();
   378                          $schedule(thisGoroutine);
   379                          return comm[1];
   380                      };
   381                      entries.push([comm[0].$sendQueue, queueEntry]);
   382                      comm[0].$sendQueue.push(queueEntry);
   383                      break;
   384              }
   385          })(i);
   386      }
   387      $block();
   388      return f;
   389  };