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 };