|
8 | 8 | */ |
9 | 9 | #include "config.h" |
10 | 10 | #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> |
11 | 15 | #include <ccan/tal/str/str.h> |
12 | 16 | #include <common/clock_time.h> |
13 | 17 | #include <common/dijkstra.h> |
|
24 | 28 | #include <plugins/askrene/layer.h> |
25 | 29 | #include <plugins/askrene/mcf.h> |
26 | 30 | #include <plugins/askrene/reserve.h> |
| 31 | +#include <sys/wait.h> |
| 32 | +#include <unistd.h> |
27 | 33 |
|
28 | 34 | /* "spendable" for a channel assumes a single HTLC: for additional HTLCs, |
29 | 35 | * 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, |
561 | 567 | reserve_sub(rq->reserved, &scidd, rq->layers, max); |
562 | 568 | } |
563 | 569 |
|
| 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 | + |
564 | 661 | static struct command_result *do_getroutes(struct command *cmd, |
565 | 662 | struct gossmap_localmods *localmods, |
566 | 663 | struct getroutes_info *info) |
567 | 664 | { |
568 | 665 | struct askrene *askrene = get_askrene(cmd->plugin); |
569 | 666 | 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; |
576 | 670 |
|
577 | 671 | /* update the gossmap */ |
578 | 672 | if (gossmap_refresh(askrene->gossmap)) { |
@@ -655,50 +749,54 @@ static struct command_result *do_getroutes(struct command *cmd, |
655 | 749 | "maxparts == 1: switching to a single path algorithm."); |
656 | 750 | } |
657 | 751 |
|
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; |
672 | 762 | } |
| 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 | + |
673 | 769 | struct timerel time_delta = timemono_between(time_mono(), time_start); |
674 | 770 |
|
675 | 771 | /* log the time of computation */ |
676 | 772 | 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", |
678 | 774 | time_to_msec(time_delta)); |
679 | | - if (err) |
680 | | - goto fail; |
681 | 775 |
|
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 | + } |
692 | 791 |
|
693 | 792 | /* At last we remove the localmods from the gossmap. */ |
694 | 793 | gossmap_remove_localmods(askrene->gossmap, localmods); |
695 | 794 |
|
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)); |
701 | 797 |
|
| 798 | +fail_broken: |
| 799 | + plugin_log(cmd->plugin, LOG_BROKEN, "%s", err); |
702 | 800 | fail: |
703 | 801 | assert(err); |
704 | 802 | gossmap_remove_localmods(askrene->gossmap, localmods); |
|
0 commit comments