mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
termio/exec: detect abnormally short runtime and show an error message
This commit is contained in:
@ -38,6 +38,11 @@ const c = @cImport({
|
|||||||
/// correct granularity of events.
|
/// correct granularity of events.
|
||||||
const disable_kitty_keyboard_protocol = apprt.runtime == apprt.glfw;
|
const disable_kitty_keyboard_protocol = apprt.runtime == apprt.glfw;
|
||||||
|
|
||||||
|
/// The number of milliseconds below which we consider a process
|
||||||
|
/// exit to be abnormal. This is used to show an error message
|
||||||
|
/// when the process exits too quickly.
|
||||||
|
const abnormal_runtime_threshold_ms = 250;
|
||||||
|
|
||||||
/// Allocator
|
/// Allocator
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
|
|
||||||
@ -230,13 +235,16 @@ pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData {
|
|||||||
self.execFailedInChild() catch {};
|
self.execFailedInChild() catch {};
|
||||||
std.os.exit(1);
|
std.os.exit(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
errdefer self.subprocess.stop();
|
errdefer self.subprocess.stop();
|
||||||
const pid = pid: {
|
const pid = pid: {
|
||||||
const command = self.subprocess.command orelse return error.ProcessNotStarted;
|
const command = self.subprocess.command orelse return error.ProcessNotStarted;
|
||||||
break :pid command.pid orelse return error.ProcessNoPid;
|
break :pid command.pid orelse return error.ProcessNoPid;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Track our process start time so we know how long it was
|
||||||
|
// running for.
|
||||||
|
const process_start = try std.time.Instant.now();
|
||||||
|
|
||||||
// Create our pipe that we'll use to kill our read thread.
|
// Create our pipe that we'll use to kill our read thread.
|
||||||
// pipe[0] is the read end, pipe[1] is the write end.
|
// pipe[0] is the read end, pipe[1] is the write end.
|
||||||
const pipe = try internal_os.pipe();
|
const pipe = try internal_os.pipe();
|
||||||
@ -268,6 +276,7 @@ pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData {
|
|||||||
.renderer_wakeup = self.renderer_wakeup,
|
.renderer_wakeup = self.renderer_wakeup,
|
||||||
.renderer_mailbox = self.renderer_mailbox,
|
.renderer_mailbox = self.renderer_mailbox,
|
||||||
.process = process,
|
.process = process,
|
||||||
|
.process_start = process_start,
|
||||||
.data_stream = stream,
|
.data_stream = stream,
|
||||||
.loop = &thread.loop,
|
.loop = &thread.loop,
|
||||||
.terminal_stream = .{
|
.terminal_stream = .{
|
||||||
@ -538,6 +547,15 @@ pub fn jumpToPrompt(self: *Exec, delta: isize) !void {
|
|||||||
pub inline fn queueWrite(self: *Exec, data: []const u8, linefeed: bool) !void {
|
pub inline fn queueWrite(self: *Exec, data: []const u8, linefeed: bool) !void {
|
||||||
const ev = self.data.?;
|
const ev = self.data.?;
|
||||||
|
|
||||||
|
// If our process is exited then we send our surface a message
|
||||||
|
// about it but we don't queue any more writes.
|
||||||
|
if (ev.process_exited) {
|
||||||
|
_ = ev.surface_mailbox.push(.{
|
||||||
|
.child_exited = {},
|
||||||
|
}, .{ .forever = {} });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// We go through and chunk the data if necessary to fit into
|
// We go through and chunk the data if necessary to fit into
|
||||||
// our cached buffers that we can queue to the stream.
|
// our cached buffers that we can queue to the stream.
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
@ -640,6 +658,7 @@ const EventData = struct {
|
|||||||
|
|
||||||
/// The process watcher
|
/// The process watcher
|
||||||
process: xev.Process,
|
process: xev.Process,
|
||||||
|
process_start: std.time.Instant,
|
||||||
process_exited: bool = false,
|
process_exited: bool = false,
|
||||||
|
|
||||||
/// This is used for both waiting for the process to exit and then
|
/// This is used for both waiting for the process to exit and then
|
||||||
@ -703,11 +722,59 @@ fn processExit(
|
|||||||
r: xev.Process.WaitError!u32,
|
r: xev.Process.WaitError!u32,
|
||||||
) xev.CallbackAction {
|
) xev.CallbackAction {
|
||||||
const code = r catch unreachable;
|
const code = r catch unreachable;
|
||||||
log.debug("child process exited status={}", .{code});
|
|
||||||
|
|
||||||
const ev = ev_.?;
|
const ev = ev_.?;
|
||||||
ev.process_exited = true;
|
ev.process_exited = true;
|
||||||
|
|
||||||
|
// Determine how long the process was running for.
|
||||||
|
const runtime_ms: ?u64 = runtime: {
|
||||||
|
const process_end = std.time.Instant.now() catch break :runtime null;
|
||||||
|
const runtime_ns = process_end.since(ev.process_start);
|
||||||
|
const runtime_ms = runtime_ns / std.time.ns_per_ms;
|
||||||
|
break :runtime runtime_ms;
|
||||||
|
};
|
||||||
|
log.debug(
|
||||||
|
"child process exited status={} runtime={}ms",
|
||||||
|
.{ code, runtime_ms orelse 0 },
|
||||||
|
);
|
||||||
|
|
||||||
|
// If our runtime was below some threshold then we assume that this
|
||||||
|
// was an abnormal exit and we show an error message.
|
||||||
|
if (runtime_ms) |runtime| runtime: {
|
||||||
|
if (runtime > abnormal_runtime_threshold_ms) break :runtime;
|
||||||
|
log.warn("abnormal process exit detected, showing error message", .{});
|
||||||
|
|
||||||
|
// Modify the terminal to show our error message. This
|
||||||
|
// requires grabbing the renderer state lock.
|
||||||
|
ev.renderer_state.mutex.lock();
|
||||||
|
defer ev.renderer_state.mutex.unlock();
|
||||||
|
const alloc = ev.terminal_stream.handler.alloc;
|
||||||
|
const t = ev.renderer_state.terminal;
|
||||||
|
|
||||||
|
// Reset the terminal completely.
|
||||||
|
t.fullReset(alloc);
|
||||||
|
|
||||||
|
// Write our message out.
|
||||||
|
const view = std.unicode.Utf8View.init(
|
||||||
|
\\ Ghostty failed to launch the requested command.
|
||||||
|
\\ Please check your "command" configuration.
|
||||||
|
\\
|
||||||
|
\\ Press any key to exit.
|
||||||
|
) catch break :runtime;
|
||||||
|
var it = view.iterator();
|
||||||
|
while (it.nextCodepoint()) |cp| {
|
||||||
|
if (cp == '\n') {
|
||||||
|
t.carriageReturn();
|
||||||
|
t.linefeed() catch break :runtime;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
t.print(cp) catch break :runtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
return .disarm;
|
||||||
|
}
|
||||||
|
|
||||||
// Notify our surface we want to close
|
// Notify our surface we want to close
|
||||||
_ = ev.surface_mailbox.push(.{
|
_ = ev.surface_mailbox.push(.{
|
||||||
.child_exited = {},
|
.child_exited = {},
|
||||||
|
Reference in New Issue
Block a user