nd,ngui: remove memleaks and deadlocks mostly from v0.4.0
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline was successful Details

the offending memleak printed out at program exit was introduced in
116fb3b5, specifically line 140 in src/bitcoindrpc.zig using incorrect
allocator.

while there, improved a bit overall memory handling by making sure
heap-alloc'ed messages are always deinit'ed.

another source of trouble was logging from within a sighandler: the
logger uses a thread mutex which may deadlock when the handler is
invoked by the OS.
pull/31/head v0.5.1
alex 1 year ago
parent 280bea40df
commit 29f6975601
Signed by: x1ddos
GPG Key ID: FDEFB4A63CBD8460

@ -137,7 +137,7 @@ pub const Client = struct {
fn parseResponse(self: Client, comptime m: Method, b: []const u8) !Result(m) { fn parseResponse(self: Client, comptime m: Method, b: []const u8) !Result(m) {
var resp = try types.Deinitable(RpcResponse(m)).init(self.allocator); var resp = try types.Deinitable(RpcResponse(m)).init(self.allocator);
errdefer resp.deinit(); errdefer resp.deinit();
resp.value = try std.json.parseFromSliceLeaky(RpcResponse(m), self.allocator, b, .{ resp.value = try std.json.parseFromSliceLeaky(RpcResponse(m), resp.arena.allocator(), b, .{
.ignore_unknown_fields = true, .ignore_unknown_fields = true,
.allocate = .alloc_always, .allocate = .alloc_always,
}); });
@ -150,6 +150,7 @@ pub const Client = struct {
return .{ .value = resp.value.result.?, .arena = resp.arena }; return .{ .value = resp.value.result.?, .arena = resp.arena };
} }
/// callers own returned value.
fn formatreq(self: *Client, comptime m: Method, args: MethodArgs(m)) ![]const u8 { fn formatreq(self: *Client, comptime m: Method, args: MethodArgs(m)) ![]const u8 {
const req = RpcRequest(m){ const req = RpcRequest(m){
.id = self.reqid.fetchAdd(1, .Monotonic), .id = self.reqid.fetchAdd(1, .Monotonic),
@ -163,7 +164,7 @@ pub const Client = struct {
const auth = try self.getAuthBase64(); const auth = try self.getAuthBase64();
defer self.allocator.free(auth); defer self.allocator.free(auth);
var bytes = std.ArrayList(u8).init(self.allocator); var bytes = std.ArrayList(u8).init(self.allocator); // return value as owned slice
const w = bytes.writer(); const w = bytes.writer();
try w.writeAll("POST / HTTP/1.0\r\n"); try w.writeAll("POST / HTTP/1.0\r\n");
//try w.writeAll("Host: 127.0.0.1\n", .{}); //try w.writeAll("Host: 127.0.0.1\n", .{});

@ -234,13 +234,13 @@ pub fn read(allocator: mem.Allocator, reader: anytype) !ParsedMessage {
else => Error.CommReadZeroLenInNonVoidTag, else => Error.CommReadZeroLenInNonVoidTag,
}; };
} }
switch (tag) {
.ping, .pong, .poweroff, .standby, .wakeup => unreachable, // handled above
inline else => |t| {
var bytes = try allocator.alloc(u8, len); var bytes = try allocator.alloc(u8, len);
defer allocator.free(bytes); defer allocator.free(bytes);
try reader.readNoEof(bytes); try reader.readNoEof(bytes);
return switch (tag) {
.ping, .pong, .poweroff, .standby, .wakeup => unreachable, // handled above
inline else => |t| {
var arena = try allocator.create(std.heap.ArenaAllocator); var arena = try allocator.create(std.heap.ArenaAllocator);
arena.* = std.heap.ArenaAllocator.init(allocator); arena.* = std.heap.ArenaAllocator.init(allocator);
errdefer { errdefer {
@ -255,7 +255,7 @@ pub fn read(allocator: mem.Allocator, reader: anytype) !ParsedMessage {
}; };
return parsed; return parsed;
}, },
}; }
} }
/// outputs the message msg using writer. /// outputs the message msg using writer.

@ -166,7 +166,7 @@ pub const Client = struct {
.listchannels => .{ .listchannels => .{
.httpmethod = .GET, .httpmethod = .GET,
.url = blk: { .url = blk: {
var buf = std.ArrayList(u8).init(arena); var buf = std.ArrayList(u8).init(arena); // free'ed when arena is deinit'ed, formatreq call site
const w = buf.writer(); const w = buf.writer();
try std.fmt.format(w, "{s}/v1/channels?peer_alias_lookup={}", .{ self.apibase, args.peer_alias_lookup }); try std.fmt.format(w, "{s}/v1/channels?peer_alias_lookup={}", .{ self.apibase, args.peer_alias_lookup });
if (args.status) |v| switch (v) { if (args.status) |v| switch (v) {
@ -181,7 +181,7 @@ pub const Client = struct {
// TODO: sanitize; Uri.writeEscapedQuery(w, q); // TODO: sanitize; Uri.writeEscapedQuery(w, q);
try std.fmt.format(w, "&peer={s}", .{v}); try std.fmt.format(w, "&peer={s}", .{v});
} }
break :blk try std.Uri.parse(buf.items); break :blk try std.Uri.parse(buf.items); // uri point to the original buf
}, },
.headers = blk: { .headers = blk: {
var h = std.http.Headers{ .allocator = arena }; var h = std.http.Headers{ .allocator = arena };

@ -132,7 +132,9 @@ fn parseArgs(gpa: std.mem.Allocator) !NdArgs {
var sigquit: std.Thread.ResetEvent = .{}; var sigquit: std.Thread.ResetEvent = .{};
fn sighandler(sig: c_int) callconv(.C) void { fn sighandler(sig: c_int) callconv(.C) void {
logger.info("received signal {}", .{sig}); if (sigquit.isSet()) {
return;
}
switch (sig) { switch (sig) {
os.SIG.INT, os.SIG.TERM => sigquit.set(), os.SIG.INT, os.SIG.TERM => sigquit.set(),
else => {}, else => {},
@ -215,6 +217,7 @@ pub fn main() !void {
try os.sigaction(os.SIG.INT, &sa, null); try os.sigaction(os.SIG.INT, &sa, null);
try os.sigaction(os.SIG.TERM, &sa, null); try os.sigaction(os.SIG.TERM, &sa, null);
sigquit.wait(); sigquit.wait();
logger.info("sigquit: terminating ...", .{});
// reached here due to sig TERM or INT. // reached here due to sig TERM or INT.
// tell deamon to terminate threads. // tell deamon to terminate threads.

@ -376,9 +376,8 @@ fn commThreadLoop(self: *Daemon) void {
}; };
defer res.deinit(); defer res.deinit();
const msg = res.value; logger.debug("got msg: {s}", .{@tagName(res.value)});
logger.debug("got msg: {s}", .{@tagName(msg)}); switch (res.value) {
switch (msg) {
.pong => { .pong => {
logger.info("received pong from ngui", .{}); logger.info("received pong from ngui", .{});
}, },
@ -408,7 +407,7 @@ fn commThreadLoop(self: *Daemon) void {
// TODO: send err back to ngui // TODO: send err back to ngui
}; };
}, },
else => logger.warn("unhandled msg tag {s}", .{@tagName(msg)}), else => |v| logger.warn("unhandled msg tag {s}", .{@tagName(v)}),
} }
self.mu.lock(); self.mu.lock();
@ -579,7 +578,7 @@ fn sendBitcoindReport(self: *Daemon) !void {
const mempool = try client.call(.getmempoolinfo, {}); const mempool = try client.call(.getmempoolinfo, {});
defer mempool.deinit(); defer mempool.deinit();
const balance: ?lndhttp.WalletBalance = blk: { const balance: ?lndhttp.Client.Result(.walletbalance) = blk: { // lndhttp.WalletBalance
var lndc = lndhttp.Client.init(.{ var lndc = lndhttp.Client.init(.{
.allocator = self.allocator, .allocator = self.allocator,
.tlscert_path = "/home/lnd/.lnd/tls.cert", .tlscert_path = "/home/lnd/.lnd/tls.cert",
@ -587,9 +586,9 @@ fn sendBitcoindReport(self: *Daemon) !void {
}) catch break :blk null; }) catch break :blk null;
defer lndc.deinit(); defer lndc.deinit();
const res = lndc.call(.walletbalance, {}) catch break :blk null; const res = lndc.call(.walletbalance, {}) catch break :blk null;
defer res.deinit(); break :blk res;
break :blk res.value;
}; };
defer if (balance) |bal| bal.deinit();
const btcrep: comm.Message.BitcoinReport = .{ const btcrep: comm.Message.BitcoinReport = .{
.blocks = bcinfo.value.blocks, .blocks = bcinfo.value.blocks,
@ -617,11 +616,11 @@ fn sendBitcoindReport(self: *Daemon) !void {
}, },
.balance = if (balance) |bal| .{ .balance = if (balance) |bal| .{
.source = .lnd, .source = .lnd,
.total = bal.total_balance, .total = bal.value.total_balance,
.confirmed = bal.confirmed_balance, .confirmed = bal.value.confirmed_balance,
.unconfirmed = bal.unconfirmed_balance, .unconfirmed = bal.value.unconfirmed_balance,
.locked = bal.locked_balance, .locked = bal.value.locked_balance,
.reserved = bal.reserved_balance_anchor_chan, .reserved = bal.value.reserved_balance_anchor_chan,
} else null, } else null,
}; };

@ -226,15 +226,24 @@ fn commThreadLoopCycle() !void {
defer ui_mutex.unlock(); defer ui_mutex.unlock();
switch (state) { switch (state) {
.standby => switch (msg.value) { .standby => switch (msg.value) {
.ping => try comm.pipeWrite(comm.Message.pong), .ping => {
defer msg.deinit();
try comm.pipeWrite(comm.Message.pong);
},
.network_report, .network_report,
.bitcoind_report, .bitcoind_report,
.lightning_report, .lightning_report,
=> last_report.replace(msg), => last_report.replace(msg),
else => logger.debug("ignoring {s}: in standby", .{@tagName(msg.value)}), else => {
logger.debug("ignoring {s}: in standby", .{@tagName(msg.value)});
msg.deinit();
},
}, },
.active, .alert => switch (msg.value) { .active, .alert => switch (msg.value) {
.ping => try comm.pipeWrite(comm.Message.pong), .ping => {
defer msg.deinit();
try comm.pipeWrite(comm.Message.pong);
},
.poweroff_progress => |rep| { .poweroff_progress => |rep| {
ui.poweroff.updateStatus(rep) catch |err| logger.err("poweroff.updateStatus: {any}", .{err}); ui.poweroff.updateStatus(rep) catch |err| logger.err("poweroff.updateStatus: {any}", .{err});
msg.deinit(); msg.deinit();
@ -253,8 +262,12 @@ fn commThreadLoopCycle() !void {
}, },
.settings => |sett| { .settings => |sett| {
ui.settings.update(sett) catch |err| logger.err("settings.update: {any}", .{err}); ui.settings.update(sett) catch |err| logger.err("settings.update: {any}", .{err});
msg.deinit();
},
else => {
logger.warn("unhandled msg tag {s}", .{@tagName(msg.value)});
msg.deinit();
}, },
else => logger.warn("unhandled msg tag {s}", .{@tagName(msg.value)}),
}, },
} }
} }
@ -342,7 +355,9 @@ fn usage(prog: []const u8) !void {
/// handles sig TERM and INT: makes the program exit. /// handles sig TERM and INT: makes the program exit.
fn sighandler(sig: c_int) callconv(.C) void { fn sighandler(sig: c_int) callconv(.C) void {
logger.info("received signal {}", .{sig}); if (sigquit.isSet()) {
return;
}
switch (sig) { switch (sig) {
os.SIG.INT, os.SIG.TERM => sigquit.set(), os.SIG.INT, os.SIG.TERM => sigquit.set(),
else => {}, else => {},
@ -399,9 +414,13 @@ pub fn main() anyerror!void {
try os.sigaction(os.SIG.INT, &sa, null); try os.sigaction(os.SIG.INT, &sa, null);
try os.sigaction(os.SIG.TERM, &sa, null); try os.sigaction(os.SIG.TERM, &sa, null);
sigquit.wait(); sigquit.wait();
logger.info("sigquit: terminating ...", .{});
// assuming nd won't ever send any more messages,
// so should be safe to touch last_report without a fence.
last_report.deinit(); last_report.deinit();
logger.info("main terminated", .{});
// let the OS rip off UI and comm threads
} }
test "tick" { test "tick" {