termio: color change operations must gracefully handle renderer mailbox full (#9224)

Fixes #9191

This changes our color change operations from writing to the renderer
mailbox directly to using our `rendererMailboxWriter` function which
handles the scenario where the mailbox is full by yielding the lock,
waking up the renderer, and retrying later.

This is a known deadlock scenario we've worked around since the private
beta days, but unfortunately this slipped through and I didn't catch it
in review.

What happens here is it's possible with certain escape sequences for the
IO thread to saturate other mailboxes with messages while holding the
terminal state lock. This can happen to any thread. This ultimately
leads to starvation and all threads deadlock.

Our IO thread is the only thread that produces this kind of massive
stream of events while holding the lock, so we have helpers in it to
attempt to queue (cheap, fast) and if that fails then to yield the lock,
wakeup the target thread, requeue, and grab the lock again (expensive,
slow).
pull/9226/head
Mitchell Hashimoto 2025-10-15 16:01:26 -07:00 committed by GitHub
parent 3665040b59
commit 014a2e0042
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 12 additions and 12 deletions

View File

@ -1192,21 +1192,21 @@ pub const StreamHandler = struct {
.dynamic => |dynamic| switch (dynamic) {
.foreground => {
self.foreground_color = set.color;
_ = self.renderer_mailbox.push(.{
self.rendererMessageWriter(.{
.foreground_color = set.color,
}, .{ .forever = {} });
});
},
.background => {
self.background_color = set.color;
_ = self.renderer_mailbox.push(.{
self.rendererMessageWriter(.{
.background_color = set.color,
}, .{ .forever = {} });
});
},
.cursor => {
self.cursor_color = set.color;
_ = self.renderer_mailbox.push(.{
self.rendererMessageWriter(.{
.cursor_color = set.color,
}, .{ .forever = {} });
});
},
.pointer_foreground,
.pointer_background,
@ -1246,9 +1246,9 @@ pub const StreamHandler = struct {
.dynamic => |dynamic| switch (dynamic) {
.foreground => {
self.foreground_color = null;
_ = self.renderer_mailbox.push(.{
self.rendererMessageWriter(.{
.foreground_color = self.foreground_color,
}, .{ .forever = {} });
});
self.surfaceMessageWriter(.{ .color_change = .{
.target = target,
@ -1257,9 +1257,9 @@ pub const StreamHandler = struct {
},
.background => {
self.background_color = null;
_ = self.renderer_mailbox.push(.{
self.rendererMessageWriter(.{
.background_color = self.background_color,
}, .{ .forever = {} });
});
self.surfaceMessageWriter(.{ .color_change = .{
.target = target,
@ -1269,9 +1269,9 @@ pub const StreamHandler = struct {
.cursor => {
self.cursor_color = null;
_ = self.renderer_mailbox.push(.{
self.rendererMessageWriter(.{
.cursor_color = self.cursor_color,
}, .{ .forever = {} });
});
if (self.default_cursor_color) |color| {
self.surfaceMessageWriter(.{ .color_change = .{