()
| 2686 | }, 0); |
| 2687 | }, |
| 2688 | drain() { |
| 2689 | if (this.draining) { |
| 2690 | return; |
| 2691 | } |
| 2692 | // Opportunistic wakeup delivery on every outermost drain (i.e. every host |
| 2693 | // event that wakes the worker): when the one-shot wakeup timeout is being |
| 2694 | // throttled by the host (see _ensureWakeupPump), due sleeps/waits still |
| 2695 | // fire with near-zero latency here instead of waiting for the 1s pump. |
| 2696 | // O(pending) once per burst; no-op when nothing is due. |
| 2697 | if (this.timedWakeups.length && !this._processingWakeups |
| 2698 | && this._earliestWakeAt() <= this.schedulerNow() + 1) { |
| 2699 | this._processExpiredTimedWakeups(); |
| 2700 | } |
| 2701 | this.draining = true; |
| 2702 | const deadline = this.schedulerNow() + 8; |
| 2703 | let steps = 0; |
| 2704 | // Threads held aside while the capture gate is owned by another thread (see |
| 2705 | // captureGateOwner). They are NOT lost: restored to runnable in the finally, |
| 2706 | // and endCaptureGate()/scheduleDrain re-runs them once the gate clears. Stays |
| 2707 | // null on every non-capture drain, so the steady-state hot path is untouched. |
| 2708 | let captureDeferred = null; |
| 2709 | try { |
| 2710 | while (this.runnable.length) { |
| 2711 | if (steps++ > 2048 || this.schedulerNow() >= deadline) { |
| 2712 | this.scheduleDrain(); |
| 2713 | break; |
| 2714 | } |
| 2715 | // While a screenshot capture is reading the canvas, defer any OTHER |
| 2716 | // thread so it can't paint mid-capture (the off-by-one race). The owner |
| 2717 | // itself is never deferred, and the gate is null outside captures. |
| 2718 | if (this.captureGateOwner && this.runnable[0] !== this.captureGateOwner) { |
| 2719 | (captureDeferred || (captureDeferred = [])).push(this.runnable.shift()); |
| 2720 | continue; |
| 2721 | } |
| 2722 | // Atomic-thread mode (set by flushGraphics' begin/endGraphicsAtomic |
| 2723 | // pair) used to suppress dispatch of every other green thread. |
| 2724 | // That created a deadlock window with cooperative monitor parking: |
| 2725 | // if the atomic thread blocks on a monitor held by some other |
| 2726 | // green thread, drain refuses to run the holder, the holder |
| 2727 | // never releases, the atomic thread never unparks. Cooperative |
| 2728 | // monitor semantics already prevent the recursive-paint flood |
| 2729 | // the atomic flag was guarding against -- a thread that re-enters |
| 2730 | // synchronized(pendingDisplay) inside flushGraphics naturally |
| 2731 | // queues behind the active flush. Trusting the locks instead of |
| 2732 | // the atomic flag avoids the deadlock without re-introducing |
| 2733 | // the flood. |
| 2734 | const thread = this.runnable.shift(); |
| 2735 | if (thread.done) { |
| 2736 | continue; |
| 2737 | } |
| 2738 | this.currentThread = thread; |
| 2739 | // Worker-liveness probe feeding the heartbeat timer below: a frozen |
| 2740 | // resume count with a live heartbeat means parked/starved, a stopped |
| 2741 | // heartbeat means a synchronous infinite loop in this step. The resume |
| 2742 | // counter is a trivial increment; the (string-building) label is only |
| 2743 | // recorded under diag so production pays nothing. |
| 2744 | this.__cn1ResumeCount = (this.__cn1ResumeCount | 0) + 1; |
| 2745 | if (VM_DIAG_ENABLED) { |
nothing calls this directly
no test coverage detected