Skip to content

Commit 0077857

Browse files
committed
askrene: fork before calling the route solver.
This is fairly simple. We do all the prep work, fire off the child, and it continues all the way to producing JSON output (or an error). The parent then forwards it. Limitations (fixed in successive patches): 1. Child logging currently gets lost. 2. We wait for the child, so this code is not a speedup. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
1 parent 0017315 commit 0077857

File tree

1 file changed

+136
-38
lines changed

1 file changed

+136
-38
lines changed

plugins/askrene/askrene.c

Lines changed: 136 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
*/
99
#include "config.h"
1010
#include <ccan/array_size/array_size.h>
11+
#include <ccan/json_out/json_out.h>
12+
#include <ccan/noerr/noerr.h>
13+
#include <ccan/read_write_all/read_write_all.h>
14+
#include <ccan/tal/grab_file/grab_file.h>
1115
#include <ccan/tal/str/str.h>
1216
#include <common/clock_time.h>
1317
#include <common/dijkstra.h>
@@ -24,6 +28,8 @@
2428
#include <plugins/askrene/layer.h>
2529
#include <plugins/askrene/mcf.h>
2630
#include <plugins/askrene/reserve.h>
31+
#include <sys/wait.h>
32+
#include <unistd.h>
2733

2834
/* "spendable" for a channel assumes a single HTLC: for additional HTLCs,
2935
* the need to pay for fees (if we're the owner) reduces it */
@@ -561,18 +567,106 @@ void get_constraints(const struct route_query *rq,
561567
reserve_sub(rq->reserved, &scidd, rq->layers, max);
562568
}
563569

570+
/* Returns fd to child */
571+
static int fork_router_child(struct route_query *rq,
572+
enum algorithm algo,
573+
struct timemono deadline,
574+
const struct gossmap_node *srcnode,
575+
const struct gossmap_node *dstnode,
576+
struct amount_msat amount,
577+
struct amount_msat maxfee,
578+
u32 finalcltv, u32 maxdelay,
579+
const char *cmd_id,
580+
struct json_filter *cmd_filter,
581+
int *child_pid)
582+
{
583+
int replyfds[2];
584+
double probability;
585+
struct flow **flows;
586+
struct route **routes;
587+
struct amount_msat *amounts;
588+
const char *err, *p;
589+
size_t len;
590+
591+
if (pipe(replyfds) != 0)
592+
return -1;
593+
*child_pid = fork();
594+
if (*child_pid < 0) {
595+
close_noerr(replyfds[0]);
596+
close_noerr(replyfds[1]);
597+
return -1;
598+
}
599+
if (*child_pid != 0) {
600+
close(replyfds[1]);
601+
return replyfds[0];
602+
}
603+
604+
/* We are the child. Run the algo */
605+
close(replyfds[0]);
606+
if (algo == ALGO_SINGLE_PATH) {
607+
err = single_path_routes(rq, rq, deadline, srcnode, dstnode,
608+
amount, maxfee, finalcltv,
609+
maxdelay, &flows, &probability);
610+
} else {
611+
assert(algo == ALGO_DEFAULT);
612+
err = default_routes(rq, rq, deadline, srcnode, dstnode,
613+
amount, maxfee, finalcltv, maxdelay,
614+
&flows, &probability);
615+
}
616+
if (err) {
617+
write_all(replyfds[1], err, strlen(err));
618+
/* Non-zero exit tells parent this is an error string. */
619+
exit(1);
620+
}
621+
622+
/* otherwise we continue */
623+
assert(tal_count(flows) > 0);
624+
rq_log(tmpctx, rq, LOG_DBG, "Final answer has %zu flows",
625+
tal_count(flows));
626+
627+
/* convert flows to routes */
628+
routes = convert_flows_to_routes(rq, rq, finalcltv, flows,
629+
&amounts);
630+
assert(tal_count(routes) == tal_count(flows));
631+
assert(tal_count(amounts) == tal_count(flows));
632+
633+
/* output the results */
634+
struct json_stream *js = new_json_stream(tmpctx, NULL, NULL);
635+
json_object_start(js, NULL);
636+
json_add_string(js, "jsonrpc", "2.0");
637+
json_add_id(js, cmd_id);
638+
json_object_start(js, "result");
639+
if (cmd_filter)
640+
json_stream_attach_filter(js, cmd_filter);
641+
json_add_getroutes(js, routes, amounts, probability, finalcltv);
642+
643+
/* Detach filter before it complains about closing object it never saw */
644+
if (cmd_filter) {
645+
err = json_stream_detach_filter(tmpctx, js);
646+
if (err)
647+
json_add_string(js, "warning_parameter_filter", err);
648+
}
649+
/* "result" object */
650+
json_object_end(js);
651+
/* Global object */
652+
json_object_end(js);
653+
json_stream_close(js, NULL);
654+
655+
p = json_out_contents(js->jout, &len);
656+
if (!write_all(replyfds[1], p, len))
657+
abort();
658+
exit(0);
659+
}
660+
564661
static struct command_result *do_getroutes(struct command *cmd,
565662
struct gossmap_localmods *localmods,
566663
struct getroutes_info *info)
567664
{
568665
struct askrene *askrene = get_askrene(cmd->plugin);
569666
struct route_query *rq = tal(cmd, struct route_query);
570-
const char *err;
571-
double probability;
572-
struct amount_msat *amounts;
573-
struct route **routes;
574-
struct flow **flows;
575-
struct json_stream *response;
667+
const char *err, *json;
668+
struct timemono time_start, deadline;
669+
int child_fd, child_pid, child_status;
576670

577671
/* update the gossmap */
578672
if (gossmap_refresh(askrene->gossmap)) {
@@ -655,50 +749,54 @@ static struct command_result *do_getroutes(struct command *cmd,
655749
"maxparts == 1: switching to a single path algorithm.");
656750
}
657751

658-
/* Compute the routes. At this point we might select between multiple
659-
* algorithms. Right now there is only one algorithm available. */
660-
struct timemono time_start = time_mono();
661-
struct timemono deadline = timemono_add(time_start,
662-
time_from_sec(askrene->route_seconds));
663-
if (info->dev_algo == ALGO_SINGLE_PATH) {
664-
err = single_path_routes(rq, rq, deadline, srcnode, dstnode, info->amount,
665-
info->maxfee, info->finalcltv,
666-
info->maxdelay, &flows, &probability);
667-
} else {
668-
assert(info->dev_algo == ALGO_DEFAULT);
669-
err = default_routes(rq, rq, deadline, srcnode, dstnode, info->amount,
670-
info->maxfee, info->finalcltv,
671-
info->maxdelay, &flows, &probability);
752+
time_start = time_mono();
753+
deadline = timemono_add(time_start,
754+
time_from_sec(askrene->route_seconds));
755+
child_fd = fork_router_child(rq, info->dev_algo,
756+
deadline, srcnode, dstnode, info->amount,
757+
info->maxfee, info->finalcltv, info->maxdelay,
758+
cmd->id, cmd->filter, &child_pid);
759+
if (child_fd == -1) {
760+
err = tal_fmt(tmpctx, "failed to fork: %s", strerror(errno));
761+
goto fail_broken;
672762
}
763+
764+
/* FIXME: Go async! */
765+
json = grab_fd_str(cmd, child_fd);
766+
close(child_fd);
767+
waitpid(child_pid, &child_status, 0);
768+
673769
struct timerel time_delta = timemono_between(time_mono(), time_start);
674770

675771
/* log the time of computation */
676772
rq_log(tmpctx, rq, LOG_DBG, "get_routes %s %" PRIu64 " ms",
677-
err ? "failed after" : "completed in",
773+
WEXITSTATUS(child_status) != 0 ? "failed after" : "completed in",
678774
time_to_msec(time_delta));
679-
if (err)
680-
goto fail;
681775

682-
/* otherwise we continue */
683-
assert(tal_count(flows) > 0);
684-
rq_log(tmpctx, rq, LOG_DBG, "Final answer has %zu flows",
685-
tal_count(flows));
686-
687-
/* convert flows to routes */
688-
routes = convert_flows_to_routes(rq, rq, info->finalcltv, flows,
689-
&amounts);
690-
assert(tal_count(routes) == tal_count(flows));
691-
assert(tal_count(amounts) == tal_count(flows));
776+
if (WIFSIGNALED(child_status)) {
777+
err = tal_fmt(tmpctx, "child died with signal %u",
778+
WTERMSIG(child_status));
779+
goto fail_broken;
780+
}
781+
/* This is how it indicates an error message */
782+
if (WEXITSTATUS(child_status) != 0 && json) {
783+
err = json;
784+
goto fail;
785+
}
786+
if (!json) {
787+
err = tal_fmt(tmpctx, "child produced no output (exited %i)?",
788+
WEXITSTATUS(child_status));
789+
goto fail_broken;
790+
}
692791

693792
/* At last we remove the localmods from the gossmap. */
694793
gossmap_remove_localmods(askrene->gossmap, localmods);
695794

696-
/* output the results */
697-
response = jsonrpc_stream_success(cmd);
698-
json_add_getroutes(response, routes, amounts, probability,
699-
info->finalcltv);
700-
return command_finished(cmd, response);
795+
/* Child already created this fully formed. We just paste it */
796+
return command_finish_rawstr(cmd, json, strlen(json));
701797

798+
fail_broken:
799+
plugin_log(cmd->plugin, LOG_BROKEN, "%s", err);
702800
fail:
703801
assert(err);
704802
gossmap_remove_localmods(askrene->gossmap, localmods);

0 commit comments

Comments
 (0)