4251 lines · 145.6 KB · Zig
RawDownload
const std = @import("std");const builtin = @import("builtin");const io_mod = @import("../shared/io.zig");const debug_trace = @import("../shared/debug_trace.zig");const background_launch_identity = @import("background_launch_identity.zig");const background_launch_output = @import("background_launch_output.zig");const background_record_liveness = @import("background_record_liveness.zig");const background_record_restore = @import("background_record_restore.zig");const background_store = @import("background_store.zig");const process_supervisor = @import("process_supervisor.zig");const server_detection = @import("server_detection.zig");const command_contract = @import("../execution/command_contract.zig");const background_process_provider = @import(    "../execution/background_process_provider.zig",);const session_child_store = @import("../session/session_child_store.zig");const session = @import("../session/session.zig");const session_codec = @import("../session/session_codec.zig");const types = @import("../shared/types.zig");const session_store = @import("../session/session_store.zig");const task_helpers = @import("../tasks/task_helpers.zig"); const Allocator = std.mem.Allocator; test {    _ = background_launch_identity;    _ = background_launch_output;    _ = background_record_liveness;    _ = background_record_restore;} pub const RuntimeContextSnapshot = process_supervisor.RuntimeContextSnapshot;pub const TaskSnapshot = process_supervisor.TaskSnapshot;pub const TaskListSnapshot = process_supervisor.TaskListSnapshot;pub const TaskState = process_supervisor.TaskState;pub const TaskSelection = process_supervisor.TaskSelection;pub const StopSelection = process_supervisor.StopSelection;pub const TaskCompletion = process_supervisor.TaskCompletion;pub const BackgroundLaunchPolicy =    process_supervisor.BackgroundLaunchPolicy;pub const StableBackgroundRecordId =    process_supervisor.StableBackgroundRecordId; pub const PreparedBackgroundLaunch = struct {    identity: background_launch_identity.Identity,    output: background_launch_output.Output,    consumed: bool = false,}; pub const BackgroundLaunchOutcome = enum {    process_local_started,    durable_started,    durable_started_degraded,}; pub const RegisteredBackground = struct {    process_id: u64,    outcome: BackgroundLaunchOutcome,    command: command_contract.BackgroundCommand,     pub fn deinit(self: *RegisteredBackground, alloc: Allocator) void {        alloc.free(@constCast(self.command.pid));        alloc.free(@constCast(self.command.command));        alloc.free(@constCast(self.command.cwd));        alloc.free(@constCast(self.command.log_path));        if (self.command.url) |url| alloc.free(@constCast(url));        self.* = undefined;    }}; pub const UrlReadyCallback = *const fn (ctx: *anyopaque, task_id: u64, url: []const u8) void;pub const WatcherContextDeinit = *const fn (alloc: Allocator, ctx: *anyopaque) void;pub const TaskCompletionCallback = *const fn (ctx: *anyopaque, completion: TaskCompletion) void; const BackgroundUrlWatchJob = struct {    alloc: Allocator,    runtime: *BackgroundRuntime,    process_id: u64,    callback_ctx: *anyopaque,    on_url_ready: UrlReadyCallback,    on_context_deinit: ?WatcherContextDeinit,    done: *std.atomic.Value(bool),}; const BackgroundWatcherHandle = struct {    thread: std.Thread,    done: *std.atomic.Value(bool),}; const RetainedSourceCapability = struct {    source_session_id: []u8,    capability: *session_child_store.SessionChildCapability,     fn deinit(self: *RetainedSourceCapability, alloc: Allocator) void {        self.capability.deinit();        alloc.destroy(self.capability);        alloc.free(self.source_session_id);        self.* = undefined;    }}; const OwnedBackgroundChild = struct {    process_id: u64,    process: background_process_provider.OwnedProcess,}; const watcher_retry_interval_ns = 200 * std.time.ns_per_ms;const process_identity_capture_attempts: usize = 3;const process_identity_capture_retry_delay_ns = 20 * std.time.ns_per_ms;const blocked_wrapper_cleanup_timeout_ms: i64 = 2000;var blocked_wrapper_cleanup_timeout_ms_for_test: ?i64 = null; const StableRecordIdFn =    *const fn () anyerror!StableBackgroundRecordId;var stable_record_id_for_test: ?StableRecordIdFn = null; pub const BackgroundRuntime = struct {    process_provider: background_process_provider.Provider =        background_process_provider.unavailable_provider,    mutex: std.Io.Mutex = .init,    supervisor: process_supervisor.ProcessSupervisor = .{},    persisted_store: ?background_store.Store = null,    owned_session_capability: ?*session_child_store.SessionChildCapability = null,    borrowed_session_capability: ?*session_child_store.SessionChildCapability = null,    source_session_id: ?[]u8 = null,    retained_source_capabilities: std.ArrayList(RetainedSourceCapability) = .empty,    retained_indeterminate_identities: std.ArrayList(background_launch_identity.Identity) = .empty,    prepared_launch_reservations: usize = 0,    owned_children: std.ArrayList(OwnedBackgroundChild) = .empty,    watchers: std.ArrayList(BackgroundWatcherHandle) = .empty,    stop_requested: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),    last_refresh_ms: i64 = 0,     pub fn init(        process_provider: background_process_provider.Provider,    ) BackgroundRuntime {        return .{ .process_provider = process_provider };    }     pub fn processProvider(        self: *const BackgroundRuntime,    ) background_process_provider.Provider {        return self.process_provider;    }     pub fn deinit(self: *BackgroundRuntime, alloc: Allocator) void {        if (self.source_session_id) |source_session_id| {            self.retryDegradedRecordsForSource(                alloc,                source_session_id,            );        }        self.requestStop();        self.pruneWatchers(alloc, true);        self.terminateOwnedUnrecordedProcesses(alloc);        self.watchers.deinit(alloc);        for (self.owned_children.items) |*owned| {            owned.process.forget();        }        self.owned_children.deinit(alloc);        if (self.persisted_store) |*store| store.deinit(alloc);        for (self.retained_source_capabilities.items) |*retained| {            retained.deinit(alloc);        }        self.retained_source_capabilities.deinit(alloc);        for (self.retained_indeterminate_identities.items) |*identity| {            identity.deinit(alloc);        }        self.retained_indeterminate_identities.deinit(alloc);        if (self.owned_session_capability) |capability| {            capability.deinit();            alloc.destroy(capability);        }        if (self.source_session_id) |source_session_id| {            alloc.free(source_session_id);        }        self.supervisor.deinit(alloc);        self.* = .{};    }     pub fn enablePersistence(self: *BackgroundRuntime, alloc: Allocator, background_dir: []const u8) !void {        const session_dir_path = std.fs.path.dirname(background_dir) orelse            return error.SessionChildStoreFailed;        const source_session_id = std.fs.path.basename(session_dir_path);        const capability = try alloc.create(            session_child_store.SessionChildCapability,        );        var capability_owned = true;        errdefer if (capability_owned) alloc.destroy(capability);        capability.* = try session_child_store.SessionChildCapability.initLegacyBackgroundRoutes(            alloc,            background_dir,            .writable,        );        errdefer if (capability_owned) capability.deinit();        self.installPersistence(            alloc,            capability,            source_session_id,            true,        ) catch |err| {            if (self.activeSessionCapability() == capability) {                capability_owned = false;            }            return err;        };        capability_owned = false;    }     pub fn enableManagedPersistence(        self: *BackgroundRuntime,        alloc: Allocator,        capability: *session_child_store.SessionChildCapability,        source_session_id: []const u8,    ) !void {        try self.installPersistence(            alloc,            capability,            source_session_id,            false,        );    }     fn installPersistence(        self: *BackgroundRuntime,        alloc: Allocator,        capability: *session_child_store.SessionChildCapability,        source_session_id: []const u8,        owns_capability: bool,    ) !void {        var store = background_store.Store.initManaged(capability);        const owned_source_session_id = try alloc.dupe(            u8,            source_session_id,        );        errdefer alloc.free(owned_source_session_id);         if (self.source_session_id) |existing_source_session_id| {            if (std.mem.eql(                u8,                existing_source_session_id,                owned_source_session_id,            ) and self.activeSessionCapability() == capability) {                alloc.free(owned_source_session_id);                return;            }            self.invalidateSourceAuthority(                alloc,                existing_source_session_id,            );        }         self.mutex.lockUncancelable(io_mod.getIo());        errdefer self.mutex.unlock(io_mod.getIo());         const next_id = try store.nextId();        if (self.persisted_store) |*existing| existing.deinit(alloc);        if (self.source_session_id) |closing_source_session_id| {            for (self.supervisor.tasks.items) |*task| {                const task_source = task.source_session_id orelse continue;                if (!std.mem.eql(                    u8,                    task_source,                    closing_source_session_id,                )) continue;                task.record_authority = .none;            }        }        if (self.owned_session_capability) |existing| {            existing.deinit();            alloc.destroy(existing);        }        self.borrowed_session_capability = null;        if (self.source_session_id) |existing| alloc.free(existing);        self.persisted_store = store;        if (owns_capability) {            self.owned_session_capability = capability;        } else {            self.owned_session_capability = null;            self.borrowed_session_capability = capability;        }        self.source_session_id = owned_source_session_id;        self.supervisor.next_background_process_id = @max(self.supervisor.next_background_process_id, next_id);         for (self.supervisor.tasks.items) |*task| {            const task_source = task.source_session_id orelse continue;            const stable_id = task.background_record_id orelse continue;            if (!std.mem.eql(                u8,                task_source,                owned_source_session_id,            )) continue;            if (store.loadByStableId(alloc, stable_id)) |record| {                var current = record;                defer current.deinit(alloc);                const token = current.process_token orelse continue;                const expected = process_supervisor.ProcessInstanceToken.parse(                    token,                ) catch continue;                const token_match = self.process_provider.matchToken(                    alloc,                    current.pid,                    expected,                );                switch (token_match) {                    .matched => {                        task.record_authority = .{                            .writable = capability,                        };                    },                    .missing, .mismatched => {                        task.record_authority = .{                            .writable = capability,                        };                        if (task.state == .running) {                            task.state = .stale;                            task.expect_url = false;                            task.exit_code = null;                        }                    },                    .unavailable => {},                }            } else |_| {}        }         const snapshots = self.supervisor.snapshotTasks(alloc) catch |err| {            if (self.persisted_store) |*existing| existing.deinit(alloc);            self.persisted_store = null;            return err;        };        self.mutex.unlock(io_mod.getIo());         defer snapshots.deinit(alloc);        self.saveSnapshotsOrDisablePersistence(alloc, "enable background persistence", snapshots.items);    }     pub fn invalidateSourceAuthority(        self: *BackgroundRuntime,        alloc: Allocator,        source_session_id: []const u8,    ) void {        self.retryDegradedRecordsForSource(            alloc,            source_session_id,        );        self.mutex.lockUncancelable(io_mod.getIo());        defer self.mutex.unlock(io_mod.getIo());        for (self.supervisor.tasks.items) |*task| {            const task_source = task.source_session_id orelse continue;            if (!std.mem.eql(                u8,                task_source,                source_session_id,            )) continue;            task.record_authority = .none;        }    }     pub fn detachManagedPersistence(        self: *BackgroundRuntime,        alloc: Allocator,        source_session_id: []const u8,    ) void {        const current = self.source_session_id orelse return;        if (!std.mem.eql(u8, current, source_session_id)) return;        self.invalidateSourceAuthority(alloc, source_session_id);         self.mutex.lockUncancelable(io_mod.getIo());        defer self.mutex.unlock(io_mod.getIo());        if (self.persisted_store) |*store| store.deinit(alloc);        self.persisted_store = null;        if (self.owned_session_capability) |capability| {            capability.deinit();            alloc.destroy(capability);        }        self.owned_session_capability = null;        self.borrowed_session_capability = null;        if (self.source_session_id) |owned| alloc.free(owned);        self.source_session_id = null;    }     fn activeSessionCapability(        self: *BackgroundRuntime,    ) ?*session_child_store.SessionChildCapability {        return self.borrowed_session_capability orelse            self.owned_session_capability;    }     fn retryDegradedRecordsForSource(        self: *BackgroundRuntime,        alloc: Allocator,        source_session_id: []const u8,    ) void {        self.mutex.lockUncancelable(io_mod.getIo());        const snapshots = self.supervisor.snapshotTasks(alloc) catch {            self.mutex.unlock(io_mod.getIo());            return;        };        self.mutex.unlock(io_mod.getIo());        defer snapshots.deinit(alloc);         for (snapshots.items) |task_snapshot| {            const task_source = task_snapshot.source_session_id orelse                continue;            if (!std.mem.eql(                u8,                task_source,                source_session_id,            )) continue;            if (task_snapshot.record_persistence !=                .initial_record_degraded and                task_snapshot.record_persistence !=                    .record_update_degraded)            {                continue;            }            self.saveDurableSnapshotIfAuthorized(                alloc,                "final source authority retry",                task_snapshot,            );        }    }     pub fn prepareBackgroundLaunch(        self: *BackgroundRuntime,        alloc: Allocator,        policy: BackgroundLaunchPolicy,    ) !PreparedBackgroundLaunch {        self.mutex.lockUncancelable(io_mod.getIo());        var locked = true;        errdefer if (locked) self.mutex.unlock(io_mod.getIo());        const reserved_retention_slots =            std.math.add(                usize,                self.prepared_launch_reservations,                1,            ) catch return error.BackgroundIdentityUnavailable;        try self.owned_children.ensureUnusedCapacity(            alloc,            reserved_retention_slots,        );        try self.retained_indeterminate_identities.ensureUnusedCapacity(            alloc,            reserved_retention_slots,        );        const display_id = try self.supervisor.reserveDisplayId();        errdefer self.supervisor.releaseDisplayId(display_id);         var identity: background_launch_identity.Identity = undefined;        switch (policy) {            .process_local_long_lived => {                identity = .{ .process_local_long_lived = .{                    .display_id = display_id,                } };            },            .durable_long_lived, .saved_headless => {                const source_session_id = self.source_session_id orelse                    return error.BackgroundPersistenceUnavailable;                const store = self.persisted_store orelse                    return error.BackgroundPersistenceUnavailable;                const stable_id = try self.generateStableRecordIdLocked(                    alloc,                    store,                    source_session_id,                );                const owned_source_session_id = try alloc.dupe(                    u8,                    source_session_id,                );                identity = switch (policy) {                    .durable_long_lived => .{                        .durable_long_lived = .{                            .display_id = display_id,                            .source_session_id = owned_source_session_id,                            .background_record_id = stable_id,                        },                    },                    .saved_headless => .{                        .saved_headless = .{                            .display_id = display_id,                            .source_session_id = owned_source_session_id,                            .background_record_id = stable_id,                        },                    },                    .process_local_long_lived => unreachable,                };            },        }        self.prepared_launch_reservations += 1;        self.mutex.unlock(io_mod.getIo());        locked = false;        errdefer self.releasePreparedLaunchReservation();        errdefer identity.deinit(alloc);         const output = switch (policy) {            .process_local_long_lived => try background_launch_output.prepareExternal(alloc),            .durable_long_lived, .saved_headless => blk: {                const capability = self.activeSessionCapability() orelse                    return error.BackgroundPersistenceUnavailable;

Lines 1–500 of 4251.