apprt/gtk: cgroup initialization

This commit is contained in:
Mitchell Hashimoto
2024-06-04 18:59:44 -07:00
parent 0a5f3fa0a4
commit 409c958b7e
2 changed files with 149 additions and 1 deletions

View File

@ -23,6 +23,7 @@ const CoreSurface = @import("../../Surface.zig");
const build_options = @import("build_options");
const cgroup = @import("cgroup.zig");
const Surface = @import("Surface.zig");
const Window = @import("Window.zig");
const ConfigErrorsWindow = @import("ConfigErrorsWindow.zig");
@ -373,9 +374,27 @@ pub fn wakeup(self: App) void {
/// Run the event loop. This doesn't return until the app exits.
pub fn run(self: *App) !void {
// Running will be false when we're not the primary instance and should
// exit (GTK single instance mode). If we're not running, we're done
// right away.
if (!self.running) return;
// If we're not remote, then we also setup our actions and menus.
// If we are running, then we proceed to setup our app.
// Setup our cgroup configurations for our tabs.
if (cgroup.init(self)) |cgroup_path| {
self.core_app.alloc.free(cgroup_path);
} else |err| {
// If we can't initialize cgroups then that's okay. We
// want to continue to run so we just won't isolate surfaces.
// NOTE(mitchellh): do we want a config to force it?
log.warn(
"failed to initialize cgroups, terminals will not be isolated err={}",
.{err},
);
}
// Setup our menu items
self.initActions();
self.initMenu();

129
src/apprt/gtk/cgroup.zig Normal file
View File

@ -0,0 +1,129 @@
/// Contains all the logic for putting the Ghostty process and
/// each individual surface into its own cgroup.
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const c = @import("c.zig");
const App = @import("App.zig");
const internal_os = @import("../../os/main.zig");
const log = std.log.scoped(.gtk_systemd_cgroup);
/// Initialize the cgroup for the app. This will create our
/// transient scope, initialize the cgroups we use for the app,
/// configure them, and return the cgroup path for the app.
pub fn init(app: *App) ![]const u8 {
const pid = std.os.linux.getpid();
const alloc = app.core_app.alloc;
const connection = c.g_application_get_dbus_connection(@ptrCast(app.app)) orelse
return error.DbusConnectionRequired;
// Get our initial cgroup. We need this so we can compare
// and detect when we've switched to our transient group.
const original = try internal_os.linux.cgroupPath(
alloc,
pid,
) orelse "";
defer alloc.free(original);
// Create our transient scope. If this succeeds then the unit
// was created, but we may not have moved into it yet, so we need
// to do a dumb busy loop to wait for the move to complete.
try createScope(connection);
const transient = transient: while (true) {
const current = try internal_os.linux.cgroupPath(
alloc,
pid,
) orelse "";
if (!std.mem.eql(u8, original, current)) break :transient current;
std.time.sleep(25 * std.time.ns_per_ms);
};
errdefer alloc.free(transient);
log.info("transient scope created cgroup={s}", .{transient});
return transient;
}
/// Create a transient systemd scope unit for the current process.
///
/// On success this will return the name of the transient scope
/// cgroup prefix, allocated with the given allocator.
fn createScope(connection: *c.GDBusConnection) !void {
// Our pid that we will move into the cgroup
const pid: c.guint32 = @intCast(std.os.linux.getpid());
// The unit name needs to be unique. We use the pid for this.
var name_buf: [256]u8 = undefined;
const name = std.fmt.bufPrintZ(
&name_buf,
"app-ghostty-transient-{}.scope",
.{pid},
) catch unreachable;
// Initialize our builder to build up our parameters
var builder: c.GVariantBuilder = undefined;
c.g_variant_builder_init(&builder, c.G_VARIANT_TYPE("(ssa(sv)a(sa(sv)))"));
c.g_variant_builder_add(&builder, "s", name.ptr);
c.g_variant_builder_add(&builder, "s", "fail");
{
// Properties
c.g_variant_builder_open(&builder, c.G_VARIANT_TYPE("a(sv)"));
defer c.g_variant_builder_close(&builder);
// https://www.freedesktop.org/software/systemd/man/latest/systemd-oomd.service.html
c.g_variant_builder_add(
&builder,
"(sv)",
"ManagedOOMMemoryPressure",
c.g_variant_new_string("kill"),
);
// Delegate
c.g_variant_builder_add(
&builder,
"(sv)",
"Delegate",
c.g_variant_new_boolean(1),
);
// Pid to move into the unit
c.g_variant_builder_add(
&builder,
"(sv)",
"PIDs",
c.g_variant_new_fixed_array(
c.G_VARIANT_TYPE("u"),
&pid,
1,
@sizeOf(c.guint32),
),
);
}
{
// Aux
c.g_variant_builder_open(&builder, c.G_VARIANT_TYPE("a(sa(sv))"));
defer c.g_variant_builder_close(&builder);
}
var err: ?*c.GError = null;
defer if (err) |e| c.g_error_free(e);
_ = c.g_dbus_connection_call_sync(
connection,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"StartTransientUnit",
c.g_variant_builder_end(&builder),
c.G_VARIANT_TYPE("(o)"),
c.G_DBUS_CALL_FLAGS_NONE,
-1,
null,
&err,
) orelse {
if (err) |e| log.err(
"creating transient cgroup scope failed err={s}",
.{e.message},
);
return error.DbusCallFailed;
};
}