diff --git a/lightningd/dual_open_control.c b/lightningd/dual_open_control.c index 9b3a05ad8..fb7df13c7 100644 --- a/lightningd/dual_open_control.c +++ b/lightningd/dual_open_control.c @@ -3026,6 +3026,8 @@ static unsigned int dual_opend_msg(struct subd *dualopend, case WIRE_DUALOPEND_SEND_TX_SIGS: case WIRE_DUALOPEND_SEND_SHUTDOWN: case WIRE_DUALOPEND_DEPTH_REACHED: + case WIRE_DUALOPEND_DEV_MEMLEAK: + case WIRE_DUALOPEND_DEV_MEMLEAK_REPLY: break; } diff --git a/lightningd/memdump.c b/lightningd/memdump.c index 02ffe2102..226341a90 100644 --- a/lightningd/memdump.c +++ b/lightningd/memdump.c @@ -129,9 +129,19 @@ static void finish_report(const struct leak_detect *leaks) struct htable *memtable; const tal_t *i; const uintptr_t *backtrace; - struct command *cmd = leaks->cmd; - struct lightningd *ld = cmd->ld; - struct json_stream *response = json_stream_success(cmd); + struct command *cmd; + struct lightningd *ld; + struct json_stream *response; + + /* If it timed out, we free ourselved and exit! */ + if (!leaks->cmd) { + tal_free(leaks); + return; + } + + /* Convenience variables */ + cmd = leaks->cmd; + ld = cmd->ld; /* Enter everything, except this cmd and its jcon */ memtable = memleak_find_allocations(cmd, cmd, cmd->jcon); @@ -146,6 +156,7 @@ static void finish_report(const struct leak_detect *leaks) /* Now delete ld and those which it has pointers to. */ memleak_remove_region(memtable, ld, sizeof(*ld)); + response = json_stream_success(cmd); json_array_start(response, "leaks"); while ((i = memleak_get(memtable, &backtrace)) != NULL) { const tal_t *p; @@ -176,6 +187,12 @@ static void finish_report(const struct leak_detect *leaks) was_pending(command_success(cmd, response)); } +static void leak_detect_timeout(struct leak_detect *leak_detect) +{ + finish_report(leak_detect); + leak_detect->cmd = NULL; +} + static void leak_detect_req_done(const struct subd_req *req, struct leak_detect *leak_detect) { @@ -274,6 +291,10 @@ static struct command_result *json_memleak(struct command *cmd, /* Ask all per-peer daemons */ peer_dev_memleak(ld, leaks); + + /* Set timer: dualopend doesn't always listen! */ + notleak(new_reltimer(ld->timers, leaks, time_from_sec(20), + leak_detect_timeout, leaks)); return command_still_pending(cmd); } diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index a742b5a4b..5c4d43a34 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -62,6 +62,7 @@ #include #include #include +#include #include #include #include @@ -2363,6 +2364,19 @@ static void openingd_memleak_req_done(struct subd *open_daemon, report_subd_memleak(leaks, open_daemon); } +static void dualopend_memleak_req_done(struct subd *dualopend, + const u8 *msg, const int *fds UNUSED, + struct leak_detect *leaks) +{ + bool found_leak; + + if (!fromwire_dualopend_dev_memleak_reply(msg, &found_leak)) + fatal("Bad dualopend_dev_memleak"); + + if (found_leak) + report_subd_memleak(leaks, dualopend); +} + void peer_dev_memleak(struct lightningd *ld, struct leak_detect *leaks) { struct peer *p; @@ -2390,9 +2404,12 @@ void peer_dev_memleak(struct lightningd *ld, struct leak_detect *leaks) take(towire_onchaind_dev_memleak(NULL)), -1, 0, onchaind_memleak_req_done, leaks), leaks); + } else if (streq(c->owner->name, "dualopend")) { + start_leak_request(subd_req(c, c->owner, + take(towire_dualopend_dev_memleak(NULL)), + -1, 0, dualopend_memleak_req_done, leaks), + leaks); } - /* FIXME: dualopend doesn't support memleak - * when we ask */ } } } diff --git a/lightningd/test/run-invoice-select-inchan.c b/lightningd/test/run-invoice-select-inchan.c index 51cc8a420..7796b2f87 100644 --- a/lightningd/test/run-invoice-select-inchan.c +++ b/lightningd/test/run-invoice-select-inchan.c @@ -207,6 +207,9 @@ bool fromwire_channeld_dev_memleak_reply(const void *p UNNEEDED, bool *leak UNNE /* Generated stub for fromwire_connectd_peer_connected */ bool fromwire_connectd_peer_connected(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct node_id *id UNNEEDED, struct wireaddr_internal *addr UNNEEDED, struct wireaddr **remote_addr UNNEEDED, bool *incoming UNNEEDED, u8 **features UNNEEDED) { fprintf(stderr, "fromwire_connectd_peer_connected called!\n"); abort(); } +/* Generated stub for fromwire_dualopend_dev_memleak_reply */ +bool fromwire_dualopend_dev_memleak_reply(const void *p UNNEEDED, bool *leak UNNEEDED) +{ fprintf(stderr, "fromwire_dualopend_dev_memleak_reply called!\n"); abort(); } /* Generated stub for fromwire_hsmd_sign_bolt12_reply */ bool fromwire_hsmd_sign_bolt12_reply(const void *p UNNEEDED, struct bip340sig *sig UNNEEDED) { fprintf(stderr, "fromwire_hsmd_sign_bolt12_reply called!\n"); abort(); } @@ -634,6 +637,9 @@ u8 *towire_channeld_specific_feerates(const tal_t *ctx UNNEEDED, u32 feerate_bas /* Generated stub for towire_connectd_peer_final_msg */ u8 *towire_connectd_peer_final_msg(const tal_t *ctx UNNEEDED, const struct node_id *id UNNEEDED, const u8 *msg UNNEEDED) { fprintf(stderr, "towire_connectd_peer_final_msg called!\n"); abort(); } +/* Generated stub for towire_dualopend_dev_memleak */ +u8 *towire_dualopend_dev_memleak(const tal_t *ctx UNNEEDED) +{ fprintf(stderr, "towire_dualopend_dev_memleak called!\n"); abort(); } /* Generated stub for towire_errorfmt */ u8 *towire_errorfmt(const tal_t *ctx UNNEEDED, const struct channel_id *channel UNNEEDED, diff --git a/openingd/dualopend.c b/openingd/dualopend.c index 00017d4c0..d5ab273b1 100644 --- a/openingd/dualopend.c +++ b/openingd/dualopend.c @@ -881,18 +881,23 @@ static bool is_segwit_output(struct wally_tx_output *output, * at closing time, rather than when it askes. */ #if DEVELOPER -static void dualopend_dev_memleak(struct state *state) +static void handle_dev_memleak(struct state *state, const u8 *msg) { struct htable *memtable; + bool found_leak; - /* Populate a hash table with all our allocations. */ - memtable = memleak_find_allocations(tmpctx, NULL, NULL); + /* Populate a hash table with all our allocations (except msg, which + * is in use right now). */ + memtable = memleak_find_allocations(tmpctx, msg, msg); /* Now delete state and things it has pointers to. */ memleak_remove_region(memtable, state, tal_bytelen(state)); /* If there's anything left, dump it to logs, and return true. */ - dump_memleak(memtable, memleak_status_broken); + found_leak = dump_memleak(memtable, memleak_status_broken); + wire_sync_write(REQ_FD, + take(towire_dualopend_dev_memleak_reply(NULL, + found_leak))); } #endif /* DEVELOPER */ @@ -1053,7 +1058,14 @@ fetch_psbt_changes(struct state *state, psbt); wire_sync_write(REQ_FD, take(msg)); + msg = wire_sync_read(tmpctx, REQ_FD); +#if DEVELOPER + while (fromwire_dualopend_dev_memleak(msg)) { + handle_dev_memleak(state, msg); + msg = wire_sync_read(tmpctx, REQ_FD); + } +#endif if (fromwire_dualopend_fail(msg, msg, &err)) { open_err_warn(state, "%s", err); @@ -3601,6 +3613,11 @@ static u8 *handle_master_in(struct state *state) enum dualopend_wire t = fromwire_peektype(msg); switch (t) { + case WIRE_DUALOPEND_DEV_MEMLEAK: +#if DEVELOPER + handle_dev_memleak(state, msg); +#endif + return NULL; case WIRE_DUALOPEND_OPENER_INIT: opener_start(state, msg); return NULL; @@ -3627,6 +3644,7 @@ static u8 *handle_master_in(struct state *state) case WIRE_DUALOPEND_GOT_RBF_OFFER_REPLY: case WIRE_DUALOPEND_RBF_VALID: case WIRE_DUALOPEND_VALIDATE_LEASE_REPLY: + case WIRE_DUALOPEND_DEV_MEMLEAK_REPLY: /* Messages we send */ case WIRE_DUALOPEND_GOT_OFFER: @@ -3949,11 +3967,6 @@ int main(int argc, char *argv[]) dualopend_wire_name(fromwire_peektype(msg))); tal_free(msg); -#if DEVELOPER - /* Now look for memory leaks. */ - dualopend_dev_memleak(state); -#endif /* DEVELOPER */ - /* This frees the entire tal tree. */ tal_free(state); daemon_shutdown(); diff --git a/openingd/dualopend_wire.csv b/openingd/dualopend_wire.csv index 6c6543487..7f49f37fe 100644 --- a/openingd/dualopend_wire.csv +++ b/openingd/dualopend_wire.csv @@ -220,6 +220,12 @@ msgtype,dualopend_fail_fallen_behind,1028 # Shutdown is complete, ready for closing negotiation. + peer_fd & gossip_fd. msgtype,dualopend_shutdown_complete,7025 +# master -> dualopend: do you have a memleak? +msgtype,dualopend_dev_memleak,7033 + +msgtype,dualopend_dev_memleak_reply,7133 +msgdata,dualopend_dev_memleak_reply,leak,bool, + # dualopend -> master: this was a dry run, here's some info about this open msgtype,dualopend_dry_run,7026 msgdata,dualopend_dry_run,channel_id,channel_id, diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c index 7b2e30ec6..ef7c21d65 100644 --- a/wallet/test/run-wallet.c +++ b/wallet/test/run-wallet.c @@ -151,6 +151,9 @@ bool fromwire_channeld_sending_commitsig(const tal_t *ctx UNNEEDED, const void * /* Generated stub for fromwire_connectd_peer_connected */ bool fromwire_connectd_peer_connected(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct node_id *id UNNEEDED, struct wireaddr_internal *addr UNNEEDED, struct wireaddr **remote_addr UNNEEDED, bool *incoming UNNEEDED, u8 **features UNNEEDED) { fprintf(stderr, "fromwire_connectd_peer_connected called!\n"); abort(); } +/* Generated stub for fromwire_dualopend_dev_memleak_reply */ +bool fromwire_dualopend_dev_memleak_reply(const void *p UNNEEDED, bool *leak UNNEEDED) +{ fprintf(stderr, "fromwire_dualopend_dev_memleak_reply called!\n"); abort(); } /* Generated stub for fromwire_hsmd_get_output_scriptpubkey_reply */ bool fromwire_hsmd_get_output_scriptpubkey_reply(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, u8 **script UNNEEDED) { fprintf(stderr, "fromwire_hsmd_get_output_scriptpubkey_reply called!\n"); abort(); } @@ -723,6 +726,9 @@ u8 *towire_connectd_peer_disconnected(const tal_t *ctx UNNEEDED, const struct no /* Generated stub for towire_connectd_peer_final_msg */ u8 *towire_connectd_peer_final_msg(const tal_t *ctx UNNEEDED, const struct node_id *id UNNEEDED, const u8 *msg UNNEEDED) { fprintf(stderr, "towire_connectd_peer_final_msg called!\n"); abort(); } +/* Generated stub for towire_dualopend_dev_memleak */ +u8 *towire_dualopend_dev_memleak(const tal_t *ctx UNNEEDED) +{ fprintf(stderr, "towire_dualopend_dev_memleak called!\n"); abort(); } /* Generated stub for towire_errorfmt */ u8 *towire_errorfmt(const tal_t *ctx UNNEEDED, const struct channel_id *channel UNNEEDED,