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 `