A single throw inside render() permanently stops the render loop, and a consumer cannot restart it
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 65/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- typescript, wasm
- Lĩnh vực
- frontend, performance
Hướng nghiên cứu
Bắt đầu trong lib/terminal.ts tại startRenderLoop() và kiểm tra lib/renderer.ts:648 cùng với bộ bảo vệ phạm vi getChars() hiện có của xterm-compat. Tái hiện lỗi xung quanh term.resize(), sau đó xác định vòng lặp nên hoạt động như thế nào sau một ngoại lệ kết xuất và tín hiệu khôi phục hoặc lỗi công khai nào là phù hợp. Hoàn thành có nghĩa là một frame lỗi không còn khiến terminal bị trống vĩnh viễn và các codepoint không hợp lệ không đến String.fromCodePoint khi chưa được kiểm tra.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Hi! First off, thanks for ghostty-web — we run it as the terminal in dev-3.0 and it's been a pleasure to work with.
We hit a failure mode in production that I think is worth reporting separately from the individual crashes, because it turns any exception in the render path into a permanently dead terminal that the embedding app cannot recover from.
What happens
The pane goes blank and stays blank. The PTY, the shell and the process keep running, the DOM is fine, keyboard input still reaches the shell — nothing repaints, ever. Neither resizing the window nor toggling fullscreen brings it back. Only recreating the Terminal (or reloading the page) helps.
Why it is permanent
lib/terminal.ts → startRenderLoop() (current main):
private startRenderLoop(): void {
if (this.animationFrameId) return;
const loop = () => {
if (!this.isDisposed && this.isOpen) {
this.renderer!.render(this.wasmTerm!, false, this.viewportY, this, this.scrollbarOpacity);
// ...
this.animationFrameId = requestAnimationFrame(loop);
}
};
loop();
}
The next frame is scheduled only after render() returns, and the loop body has no try/catch. So one throw out of render() means no further frames — for the lifetime of that Terminal. And since startRenderLoop is private, an embedder has no way to restart it: there is no public "resume rendering" entry point and no error event to hook.
What we actually saw
Two different exceptions, both reaching us through term.resize(cols, rows), both leaving a permanently blank pane. From our logs (ghostty-web 0.4.0, macOS, Bun-based Electrobun app, WKWebView):
ERROR [refit] term.resize threw {"error":"RangeError: Arguments contain a value that is out of range of code points","cols":257,"rows":82}
ERROR [refit] term.resize threw {"error":"RuntimeError: Out of bounds memory access (evaluating 'this.exports.ghostty_terminal_resize(this.handle,e,t)')","cols":158,"rows":82}
In the second case the trap repeated ~116 times at frame cadence (a scrollbar fade animation kept calling render()), and eventually ghostty_terminal_resize itself trapped — so that WASM terminal was gone, not just one frame. Both were triggered by an ordinary resize: one right after we reattached to an existing PTY, one from a user dragging a side panel.
The first one has a plausible source that is still present on main — lib/renderer.ts:648:
char = String.fromCodePoint(cell.codepoint || 32); // Default to space if null
no range check, while the xterm-compat getChars() in the same codebase does guard the same value:
codepoint < 0 || codepoint > 0x10ffff || (codepoint >= 0xd800 && codepoint <= 0xdfff) ? "�" : String.fromCodePoint(codepoint)
So a garbage cell (for us most likely read around a resize realloc) is a replacement character in one path and a fatal, terminal-killing throw in the other.
Related, but not the same thing
- #132 fixes the resize/realloc race itself (pausing the loop around the WASM resize) — that removes one source of throws, and it is exactly what we are missing. We are on the latest npm release, 0.4.0, so we don't have it; I see #137 and #182 already cover getting a release out, so I'm not asking again here.
- #141 is another route to the same
Out of bounds memory access, fromfree()after multi-codepoint graphemes.
This report is about the layer above both: whatever the cause, one bad frame should not be able to end rendering forever with no way back.
What would help
- Survive a bad frame. Reschedule the next frame in a
finally(or wrap the body intry/catch), so an exception costs one frame instead of the terminal. Failing loudly is fine and welcome — logging or anonRenderErrorevent would be better than silence — as long as the loop stays alive. - Give embedders a way back. A public way to restart rendering (or a documented "the renderer is dead" signal) would let an app recover without throwing away the
Terminal. Today our only option is to dispose the terminal, build a new one and repaint from scratch, which is what we ended up shipping. - Range-guard the draw path in
renderer.tsthe waygetChars()already does, so a bad codepoint degrades to�instead of killing the terminal.
Happy to test a patch against our app — we see this in the wild often enough to tell quickly whether it's gone. Thanks again!
- Ngôn ngữ chính
- TypeScript
- Star
- 2.9k
- Fork
- 174
- Chỉ số merge pull request
- Không có pull request nào được merge trong 30 ngày
Chuẩn bị môi trường
Chúng tôi chưa kiểm tra các tệp thiết lập môi trường của dự án này. Hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của coder/ghostty-web
-
attachCustomKeyEventHandler inverts xterm.js's return-value contract (silently swallows all input)Đang mở
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 52/100
coder/ghostty-web#192 ·
-
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 70/100
coder/ghostty-web#188 ·
-
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 45/100
coder/ghostty-web#187 ·
-
Hyperlink arena exhaustion (StringAllocOutOfMemory) spins the write path instead of degradingĐang mở
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 48/100
coder/ghostty-web#186 · 1 bình luận ·
-
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 55/100
coder/ghostty-web#184 ·
Tất cả issue của coder/ghostty-web
Issue tương tự
-
Resources: New palettes of MacaoĐang mởresources
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
railmapgen/rmg-palette#2445 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
danielmiessler/LifeOS#2242 ·
Maintainer thường phản hồi trong vòng 5 ngày
-
good first issue hacktoberfest help wanted translation
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
callstackincubator/appduct#129 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 1/5 1-3 giờ Mức phù hợp với người mới 88/100