From 5be7ad083114f3b00a6b457f4ed7f8e52cca1a21 Mon Sep 17 00:00:00 2001 From: torque Date: Wed, 31 Jul 2024 12:07:29 -0700 Subject: [PATCH] main: handle various forms of process termination more safely This attempts to solve the problem where, if the rotator is actively rotating and the program is killed, the LabJack will not be reset and the rotator will keep running. We install various signal handlers to try to catch common cases (ctrl+c, terminal getting closed out from under us). There are still ways to end the process that leave the LabJack running (such as if it crashes, though there are currently no known crashes), but I don't think it's possible to completely avoid that. --- src/RotCtl.zig | 4 ++-- src/YaesuController.zig | 35 +++++++++++++++++++++------ src/main.zig | 53 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 81 insertions(+), 11 deletions(-) diff --git a/src/RotCtl.zig b/src/RotCtl.zig index 4520205..be08140 100644 --- a/src/RotCtl.zig +++ b/src/RotCtl.zig @@ -10,7 +10,7 @@ const log = std.log.scoped(.RotCtl); writer: std.io.BufferedWriter(512, std.net.Stream.Writer), running: bool, -rotator: YaesuController, +rotator: *YaesuController, pub fn run(allocator: std.mem.Allocator) !void { // var server = std.net.StreamServer.init(.{ .reuse_address = true }); @@ -30,7 +30,7 @@ pub fn run(allocator: std.mem.Allocator) !void { var interface: RotCtl = .{ .writer = undefined, .running = true, - .rotator = try YaesuController.init(allocator), + .rotator = try YaesuController.create(allocator), }; while (interface.running) { diff --git a/src/YaesuController.zig b/src/YaesuController.zig index 64628f6..e8079d1 100644 --- a/src/YaesuController.zig +++ b/src/YaesuController.zig @@ -8,6 +8,8 @@ const log = std.log.scoped(.yaesu_controller); const YaesuController = @This(); +pub var singleton: ?*YaesuController = null; + control_thread: std.Thread, lock: *std.Thread.Mutex, controller: *const Controller, @@ -23,7 +25,7 @@ pub const CalibrationRoutine = enum { }; pub fn calibrate(allocator: std.mem.Allocator, routine: CalibrationRoutine) !void { - const controller = try YaesuController.init(allocator); + const controller = try YaesuController.create(allocator); defer { controller.quit(); controller.control_thread.join(); @@ -35,7 +37,12 @@ pub fn calibrate(allocator: std.mem.Allocator, routine: CalibrationRoutine) !voi } } -pub fn init(allocator: std.mem.Allocator) !YaesuController { +pub fn create(allocator: std.mem.Allocator) !*YaesuController { + if (singleton) |_| { + log.err("Controller singleton already exists.", .{}); + return error.AlreadyInitialized; + } + const controller = try allocator.create(Controller); errdefer allocator.destroy(controller); controller.* = try Controller.init(allocator); @@ -43,11 +50,16 @@ pub fn init(allocator: std.mem.Allocator) !YaesuController { // do this in the main thread so we can throw the error about it synchronously. try controller.connectLabjack(); - return .{ + const self = try allocator.create(YaesuController); + errdefer allocator.destroy(self); + self.* = .{ .control_thread = try std.Thread.spawn(.{}, runController, .{controller}), .lock = &controller.lock, .controller = controller, }; + + singleton = self; + return self; } fn inRange(request: f64, comptime dof: enum { azimuth, elevation }) bool { @@ -88,7 +100,7 @@ pub fn setTarget(self: YaesuController, target: AzEl) error{OutOfRange}!void { const controller = @constCast(self.controller); controller.target = masked_target; - controller.requested_state = .running; + controller.requestState(.running); } pub fn currentPosition(self: YaesuController) AzEl { @@ -114,7 +126,7 @@ pub fn quit(self: YaesuController) void { defer self.lock.unlock(); const controller = @constCast(self.controller); - controller.requested_state = .stopped; + controller.requestState(.stopped); } pub fn stop(self: YaesuController) void { @@ -123,7 +135,7 @@ pub fn stop(self: YaesuController) void { const controller = @constCast(self.controller); controller.target = controller.position; - controller.requested_state = .idle; + controller.requestState(.idle); } pub fn startPark(self: YaesuController) void { @@ -254,6 +266,15 @@ const Controller = struct { self.labjack.id = info.local_id; } + fn requestState(self: *Controller, request: ControllerState) void { + // If stopped has been requested, don't allow anything to override it. We need + // to exit. + switch (self.requested_state) { + .stopped => {}, + else => self.requested_state = request, + } + } + fn lerpOne(input: f64, cal_points: Config.MinMax) f64 { return (input - cal_points.minimum.voltage) * cal_points.slope() + cal_points.minimum.angle; } @@ -383,7 +404,7 @@ const Controller = struct { // run calibration routine. psych, this does nothing. gottem self.current_state = .idle; - self.requested_state = self.current_state; + self.requestState(.idle); }, .running => { const pos_error: AzEl = blk: { diff --git a/src/main.zig b/src/main.zig index 8ab4eda..53938a1 100644 --- a/src/main.zig +++ b/src/main.zig @@ -10,6 +10,45 @@ const udev = @import("udev_rules"); const log = std.log.scoped(.main); +fn die() noreturn { + if (YaesuController.singleton) |controller| { + controller.quit(); + controller.control_thread.join(); + } + + std.process.exit(1); +} + +fn posixSignalHandler(signal: c_int) callconv(.C) void { + _ = signal; + die(); +} + +fn windowsEventHandler(code: std.os.windows.DWORD) callconv(std.os.windows.WINAPI) std.os.windows.BOOL { + _ = code; + die(); +} + +fn addAbortHandler() !void { + if (comptime builtin.os.tag == .windows) { + try std.os.windows.SetConsoleCtrlHandler(windowsEventHandler, true); + } else { + const action = std.posix.Sigaction{ + .handler = .{ .handler = posixSignalHandler }, + .mask = std.posix.empty_sigset, + .flags = 0, + }; + + for ([_]u6{ + std.posix.SIG.INT, + std.posix.SIG.HUP, + std.posix.SIG.QUIT, + }) |sig| { + try std.posix.sigaction(sig, &action, null); + } + } +} + fn printStderr(comptime fmt: []const u8, args: anytype) void { std.debug.print(fmt ++ "\n", args); } @@ -66,9 +105,14 @@ pub fn main() !u8 { defer Config.deinit(); + addAbortHandler() catch { + log.err("Could not install quit handler.", .{}); + return 1; + }; + RotCtl.run(allocator) catch |err| { log.err("rotator controller ceased unexpectedly! {s}", .{@errorName(err)}); - return 1; + die(); }; } else if (std.mem.eql(u8, args[1], commands.calibrate)) { if (args.len < 3 or args.len > 4) { @@ -86,9 +130,14 @@ pub fn main() !u8 { return 1; }; + addAbortHandler() catch { + log.err("Could not install quit handler.", .{}); + return 1; + }; + YaesuController.calibrate(allocator, routine) catch |err| { log.err("Calibration failed: {s}", .{@errorName(err)}); - return 1; + die(); }; } else if (std.mem.eql(u8, args[1], commands.help)) { if (args.len != 3) {