github.com/goplusjs/gopherjs@v1.2.6-0.20211206034512-f187917453b8/compiler/prelude/goroutines.go (about)

     1  package prelude
     2  
     3  const goroutines = `
     4  var $stackDepthOffset = 0;
     5  var $getStackDepth = function() {
     6    var err = new Error();
     7    if (err.stack === undefined) {
     8      return undefined;
     9    }
    10    return $stackDepthOffset + err.stack.split("\n").length;
    11  };
    12  
    13  var $panicStackDepth = null, $panicValue;
    14  var $callDeferred = function(deferred, jsErr, fromPanic) {
    15    if (!fromPanic && deferred !== null && deferred.index >= $curGoroutine.deferStack.length) {
    16      throw jsErr;
    17    }
    18    if (jsErr !== null) {
    19      var newErr = null;
    20      try {
    21        $curGoroutine.deferStack.push(deferred);
    22        $panic(new $jsErrorPtr(jsErr));
    23      } catch (err) {
    24        newErr = err;
    25      }
    26      $curGoroutine.deferStack.pop();
    27      $callDeferred(deferred, newErr);
    28      return;
    29    }
    30    if ($curGoroutine.asleep) {
    31      return;
    32    }
    33  
    34    $stackDepthOffset--;
    35    var outerPanicStackDepth = $panicStackDepth;
    36    var outerPanicValue = $panicValue;
    37  
    38    var localPanicValue = $curGoroutine.panicStack.pop();
    39    if (localPanicValue !== undefined) {
    40      $panicStackDepth = $getStackDepth();
    41      $panicValue = localPanicValue;
    42    }
    43  
    44    try {
    45      while (true) {
    46        if (deferred === null) {
    47          deferred = $curGoroutine.deferStack[$curGoroutine.deferStack.length - 1];
    48          if (deferred === undefined) {
    49            /* The panic reached the top of the stack. Clear it and throw it as a JavaScript error. */
    50            $panicStackDepth = null;
    51            if (localPanicValue.Object instanceof Error) {
    52              throw localPanicValue.Object;
    53            }
    54            var msg;
    55            if (localPanicValue.constructor === $String) {
    56              msg = localPanicValue.$val;
    57            } else if (localPanicValue.Error !== undefined) {
    58              msg = localPanicValue.Error();
    59            } else if (localPanicValue.String !== undefined) {
    60              msg = localPanicValue.String();
    61            } else {
    62              msg = localPanicValue;
    63            }
    64            throw new Error(msg);
    65          }
    66        }
    67        var call = deferred.pop();
    68        if (call === undefined) {
    69          $curGoroutine.deferStack.pop();
    70          if (localPanicValue !== undefined) {
    71            deferred = null;
    72            continue;
    73          }
    74          return;
    75        }
    76        var r = call[0].apply(call[2], call[1]);
    77        if (r && r.$blk !== undefined) {
    78          deferred.push([r.$blk, [], r]);
    79          if (fromPanic) {
    80            throw null;
    81          }
    82          return;
    83        }
    84  
    85        if (localPanicValue !== undefined && $panicStackDepth === null) {
    86          throw null; /* error was recovered */
    87        }
    88      }
    89    } finally {
    90      if (localPanicValue !== undefined) {
    91        if ($panicStackDepth !== null) {
    92          $curGoroutine.panicStack.push(localPanicValue);
    93        }
    94        $panicStackDepth = outerPanicStackDepth;
    95        $panicValue = outerPanicValue;
    96      }
    97      $stackDepthOffset++;
    98    }
    99  };
   100  
   101  var $panic = function(value) {
   102    $curGoroutine.panicStack.push(value);
   103    $callDeferred(null, null, true);
   104  };
   105  var $recover = function() {
   106    if ($panicStackDepth === null || ($panicStackDepth !== undefined && $panicStackDepth !== $getStackDepth() - 2)) {
   107      return $ifaceNil;
   108    }
   109    $panicStackDepth = null;
   110    return $panicValue;
   111  };
   112  var $throw = function(err) { throw err; };
   113  
   114  var $noGoroutine = { asleep: false, exit: false, deferStack: [], panicStack: [] };
   115  var $curGoroutine = $noGoroutine, $totalGoroutines = 0, $awakeGoroutines = 0, $checkForDeadlock = true;
   116  var $mainFinished = false;
   117  var $go = function(fun, args) {
   118    $totalGoroutines++;
   119    $awakeGoroutines++;
   120    var $goroutine = function() {
   121      try {
   122        $curGoroutine = $goroutine;
   123        var r = fun.apply(undefined, args);
   124        if (r && r.$blk !== undefined) {
   125          fun = function() { return r.$blk(); };
   126          args = [];
   127          return;
   128        }
   129        $goroutine.exit = true;
   130      } catch (err) {
   131        if (!$goroutine.exit) {
   132          throw err;
   133        }
   134      } finally {
   135        $curGoroutine = $noGoroutine;
   136        if ($goroutine.exit) { /* also set by runtime.Goexit() */
   137          $totalGoroutines--;
   138          $goroutine.asleep = true;
   139        }
   140        if ($goroutine.asleep) {
   141          $awakeGoroutines--;
   142          if (!$mainFinished && $awakeGoroutines === 0 && $checkForDeadlock) {
   143            console.error("fatal error: all goroutines are asleep - deadlock!");
   144            if ($global.process !== undefined) {
   145              $global.process.exit(2);
   146            }
   147          }
   148        }
   149      }
   150    };
   151    $goroutine.asleep = false;
   152    $goroutine.exit = false;
   153    $goroutine.deferStack = [];
   154    $goroutine.panicStack = [];
   155    $schedule($goroutine);
   156  };
   157  
   158  var $scheduled = [];
   159  var $runScheduled = function() {
   160    try {
   161      var r;
   162      while ((r = $scheduled.shift()) !== undefined) {
   163        r();
   164      }
   165    } finally {
   166      if ($scheduled.length > 0) {
   167        setTimeout($runScheduled, 0);
   168      }
   169    }
   170  };
   171  
   172  var $schedule = function(goroutine) {
   173    if (goroutine.asleep) {
   174      goroutine.asleep = false;
   175      $awakeGoroutines++;
   176    }
   177    $scheduled.push(goroutine);
   178    if ($curGoroutine === $noGoroutine) {
   179      $runScheduled();
   180    }
   181  };
   182  
   183  var $setTimeout = function(f, t) {
   184    $awakeGoroutines++;
   185    return setTimeout(function() {
   186      $awakeGoroutines--;
   187      f();
   188    }, t);
   189  };
   190  
   191  var $block = function() {
   192    if ($curGoroutine === $noGoroutine) {
   193      $throwRuntimeError("cannot block in JavaScript callback, fix by wrapping code in goroutine");
   194    }
   195    $curGoroutine.asleep = true;
   196  };
   197  
   198  var $send = function(chan, value) {
   199    if (chan.$closed) {
   200      $throwRuntimeError("send on closed channel");
   201    }
   202    var queuedRecv = chan.$recvQueue.shift();
   203    if (queuedRecv !== undefined) {
   204      queuedRecv([value, true]);
   205      return;
   206    }
   207    if (chan.$buffer.length < chan.$capacity) {
   208      chan.$buffer.push(value);
   209      return;
   210    }
   211  
   212    var thisGoroutine = $curGoroutine;
   213    var closedDuringSend;
   214    chan.$sendQueue.push(function(closed) {
   215      closedDuringSend = closed;
   216      $schedule(thisGoroutine);
   217      return value;
   218    });
   219    $block();
   220    return {
   221      $blk: function() {
   222        if (closedDuringSend) {
   223          $throwRuntimeError("send on closed channel");
   224        }
   225      }
   226    };
   227  };
   228  var $recv = function(chan) {
   229    var queuedSend = chan.$sendQueue.shift();
   230    if (queuedSend !== undefined) {
   231      chan.$buffer.push(queuedSend(false));
   232    }
   233    var bufferedValue = chan.$buffer.shift();
   234    if (bufferedValue !== undefined) {
   235      return [bufferedValue, true];
   236    }
   237    if (chan.$closed) {
   238      return [chan.$elem.zero(), false];
   239    }
   240  
   241    var thisGoroutine = $curGoroutine;
   242    var f = { $blk: function() { return this.value; } };
   243    var queueEntry = function(v) {
   244      f.value = v;
   245      $schedule(thisGoroutine);
   246    };
   247    chan.$recvQueue.push(queueEntry);
   248    $block();
   249    return f;
   250  };
   251  var $close = function(chan) {
   252    if (chan.$closed) {
   253      $throwRuntimeError("close of closed channel");
   254    }
   255    chan.$closed = true;
   256    while (true) {
   257      var queuedSend = chan.$sendQueue.shift();
   258      if (queuedSend === undefined) {
   259        break;
   260      }
   261      queuedSend(true); /* will panic */
   262    }
   263    while (true) {
   264      var queuedRecv = chan.$recvQueue.shift();
   265      if (queuedRecv === undefined) {
   266        break;
   267      }
   268      queuedRecv([chan.$elem.zero(), false]);
   269    }
   270  };
   271  var $select = function(comms) {
   272    var ready = [];
   273    var selection = -1;
   274    for (var i = 0; i < comms.length; i++) {
   275      var comm = comms[i];
   276      var chan = comm[0];
   277      switch (comm.length) {
   278      case 0: /* default */
   279        selection = i;
   280        break;
   281      case 1: /* recv */
   282        if (chan.$sendQueue.length !== 0 || chan.$buffer.length !== 0 || chan.$closed) {
   283          ready.push(i);
   284        }
   285        break;
   286      case 2: /* send */
   287        if (chan.$closed) {
   288          $throwRuntimeError("send on closed channel");
   289        }
   290        if (chan.$recvQueue.length !== 0 || chan.$buffer.length < chan.$capacity) {
   291          ready.push(i);
   292        }
   293        break;
   294      }
   295    }
   296  
   297    if (ready.length !== 0) {
   298      selection = ready[Math.floor(Math.random() * ready.length)];
   299    }
   300    if (selection !== -1) {
   301      var comm = comms[selection];
   302      switch (comm.length) {
   303      case 0: /* default */
   304        return [selection];
   305      case 1: /* recv */
   306        return [selection, $recv(comm[0])];
   307      case 2: /* send */
   308        $send(comm[0], comm[1]);
   309        return [selection];
   310      }
   311    }
   312  
   313    var entries = [];
   314    var thisGoroutine = $curGoroutine;
   315    var f = { $blk: function() { return this.selection; } };
   316    var removeFromQueues = function() {
   317      for (var i = 0; i < entries.length; i++) {
   318        var entry = entries[i];
   319        var queue = entry[0];
   320        var index = queue.indexOf(entry[1]);
   321        if (index !== -1) {
   322          queue.splice(index, 1);
   323        }
   324      }
   325    };
   326    for (var i = 0; i < comms.length; i++) {
   327      (function(i) {
   328        var comm = comms[i];
   329        switch (comm.length) {
   330        case 1: /* recv */
   331          var queueEntry = function(value) {
   332            f.selection = [i, value];
   333            removeFromQueues();
   334            $schedule(thisGoroutine);
   335          };
   336          entries.push([comm[0].$recvQueue, queueEntry]);
   337          comm[0].$recvQueue.push(queueEntry);
   338          break;
   339        case 2: /* send */
   340          var queueEntry = function() {
   341            if (comm[0].$closed) {
   342              $throwRuntimeError("send on closed channel");
   343            }
   344            f.selection = [i];
   345            removeFromQueues();
   346            $schedule(thisGoroutine);
   347            return comm[1];
   348          };
   349          entries.push([comm[0].$sendQueue, queueEntry]);
   350          comm[0].$sendQueue.push(queueEntry);
   351          break;
   352        }
   353      })(i);
   354    }
   355    $block();
   356    return f;
   357  };
   358  `