diff --git a/doc/Makefile b/doc/Makefile index cb3b3d411..527b47bca 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -9,6 +9,7 @@ MANPAGES := doc/lightning-cli.1 \ doc/lightningd-config.5 \ doc/lightning-addgossip.7 \ doc/lightning-autoclean-status.7 \ + doc/lightning-batching.7 \ doc/lightning-bkpr-channelsapy.7 \ doc/lightning-bkpr-dumpincomecsv.7 \ doc/lightning-bkpr-inspect.7 \ diff --git a/doc/index.rst b/doc/index.rst index 13bdbb48a..ad6b847f9 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -31,6 +31,7 @@ Core Lightning Documentation lightning-addgossip lightning-autoclean-status + lightning-batching lightning-bkpr-channelsapy lightning-bkpr-dumpincomecsv lightning-bkpr-inspect diff --git a/doc/lightning-batching.7.md b/doc/lightning-batching.7.md new file mode 100644 index 000000000..d69d8b290 --- /dev/null +++ b/doc/lightning-batching.7.md @@ -0,0 +1,55 @@ +lightning-batching -- Command to allow database batching. +========================================================= + +SYNOPSIS +-------- + +**batching** *enable* + +DESCRIPTION +----------- + +The **batching** RPC command allows (but does not guarantee!) database +commitments to be deferred when multiple commands are issued on this RPC +connection. This is only useful if many commands are being given at once, in +which case it can offer a performance improvement (the cost being that if +there is a crash, it's unclear how many of the commands will have been +persisted). + +*enable* is *true* to enable batching, *false* to disable it (the +default). + +EXAMPLE JSON REQUEST +-------------------- +```json +{ + "id": 82, + "method": "batching", + "params": { + "enable": true + } +} +``` + +RETURN VALUE +------------ + +[comment]: # (GENERATE-FROM-SCHEMA-START) +On success, an empty object is returned. + +[comment]: # (GENERATE-FROM-SCHEMA-END) + +On failure, one of the following error codes may be returned: + +- -32602: Error in given parameters. + +AUTHOR +------ + +Rusty Russell <> wrote the initial version of this man page. + +RESOURCES +--------- + +Main web site: +[comment]: # ( SHA256STAMP:326e5801f65998e13e909d8b682e9fbc9824f3a43aa7da1d76b871882e52f293) diff --git a/doc/schemas/batching.request.json b/doc/schemas/batching.request.json new file mode 100644 index 000000000..03c794d4a --- /dev/null +++ b/doc/schemas/batching.request.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "required": [ + "enable" + ], + "properties": { + "enable": { + "type": "boolean", + "description": "Whether to enable or disable transaction batching" + } + } +} diff --git a/doc/schemas/batching.schema.json b/doc/schemas/batching.schema.json new file mode 100644 index 000000000..1aad2dcae --- /dev/null +++ b/doc/schemas/batching.schema.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "properties": {} +} diff --git a/lightningd/jsonrpc.c b/lightningd/jsonrpc.c index 474928409..5c799e4ea 100644 --- a/lightningd/jsonrpc.c +++ b/lightningd/jsonrpc.c @@ -83,6 +83,9 @@ struct json_connection { /* Are notifications enabled? */ bool notifications_enabled; + /* Are we allowed to batch database commitments? */ + bool db_batching; + /* Our json_streams (owned by the commands themselves while running). * Since multiple streams could start returning data at once, we * always service these in order, freeing once empty. */ @@ -1006,6 +1009,7 @@ static struct io_plan *read_json(struct io_conn *conn, { bool complete; bool in_transaction = false; + struct timemono start_time = time_mono(); if (jcon->len_read) log_io(jcon->log, LOG_IO_IN, NULL, "", @@ -1063,8 +1067,24 @@ again: jsmn_init(&jcon->input_parser); toks_reset(jcon->input_toks); - if (jcon->used) + /* Do we have more already read? */ + if (jcon->used) { + if (!jcon->db_batching) { + db_commit_transaction(jcon->ld->wallet->db); + in_transaction = false; + } else { + /* FIXME: io_always() should interleave with + * real IO, and then we should rotate order we + * service fds in, to avoid starvation. */ + if (time_greater(timemono_between(time_mono(), + start_time), + time_from_msec(250))) { + db_commit_transaction(jcon->ld->wallet->db); + return io_always(conn, read_json, jcon); + } + } goto again; + } read_more: if (in_transaction) @@ -1090,6 +1110,7 @@ static struct io_plan *jcon_connected(struct io_conn *conn, jsmn_init(&jcon->input_parser); jcon->input_toks = toks_alloc(jcon); jcon->notifications_enabled = false; + jcon->db_batching = false; list_head_init(&jcon->commands); /* We want to log on destruction, so we free this in destructor. */ @@ -1436,7 +1457,6 @@ static const struct json_command check_command = { "Don't run {command_to_check}, just verify parameters.", .verbose = "check command_to_check [parameters...]\n" }; - AUTODATA(json_command, &check_command); static struct command_result *json_notifications(struct command *cmd, @@ -1461,7 +1481,32 @@ static const struct json_command notifications_command = { "notifications", "utility", json_notifications, - "Enable notifications for {level} (or 'false' to disable)", + "{enable} notifications", }; - AUTODATA(json_command, ¬ifications_command); + +static struct command_result *json_batching(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) +{ + bool *enable; + + if (!param(cmd, buffer, params, + p_req("enable", param_bool, &enable), + NULL)) + return command_param_failed(); + + /* Catch the case where they sent this command then hung up. */ + if (cmd->jcon) + cmd->jcon->db_batching = *enable; + return command_success(cmd, json_stream_success(cmd)); +} + +static const struct json_command batching_command = { + "batching", + "utility", + json_batching, + "Database transaction batching {enable}", +}; +AUTODATA(json_command, &batching_command); diff --git a/plugins/autoclean.c b/plugins/autoclean.c index 8993e1078..dcbb5a64d 100644 --- a/plugins/autoclean.c +++ b/plugins/autoclean.c @@ -552,6 +552,9 @@ static const char *init(struct plugin *p, rpc_scan_datastore_str(plugin, datastore_path(tmpctx, i, "num"), JSON_SCAN(json_to_u64, &total_cleaned[i])); } + + /* Optimization FTW! */ + rpc_enable_batching(p); return NULL; } diff --git a/plugins/libplugin.c b/plugins/libplugin.c index 36d187798..ef68530eb 100644 --- a/plugins/libplugin.c +++ b/plugins/libplugin.c @@ -520,17 +520,16 @@ static const jsmntok_t *read_rpc_reply(const tal_t *ctx, return toks; } -static const char *rpc_scan_core(const tal_t *ctx, +/* Send request, return response, set resp/len to reponse */ +static const jsmntok_t *sync_req(const tal_t *ctx, struct plugin *plugin, const char *method, const struct json_out *params TAKES, - const char *guide, - va_list ap) + const char **resp) { bool error; const jsmntok_t *contents; int reqlen; - const char *p; struct json_out *jout = json_out_new(tmpctx); json_out_start(jout, NULL, '{'); @@ -542,12 +541,27 @@ static const char *rpc_scan_core(const tal_t *ctx, tal_free(params); finish_and_send_json(plugin->rpc_conn->fd, jout); - read_rpc_reply(tmpctx, plugin, &contents, &error, &reqlen); + read_rpc_reply(ctx, plugin, &contents, &error, &reqlen); if (error) plugin_err(plugin, "Got error reply to %s: '%.*s'", - method, reqlen, membuf_elems(&plugin->rpc_conn->mb)); + method, reqlen, membuf_elems(&plugin->rpc_conn->mb)); - p = membuf_consume(&plugin->rpc_conn->mb, reqlen); + *resp = membuf_consume(&plugin->rpc_conn->mb, reqlen); + return contents; +} + +/* Returns contents of scanning guide on 'result' */ +static const char *rpc_scan_core(const tal_t *ctx, + struct plugin *plugin, + const char *method, + const struct json_out *params TAKES, + const char *guide, + va_list ap) +{ + const jsmntok_t *contents; + const char *p; + + contents = sync_req(tmpctx, plugin, method, params, &p); return json_scanv(ctx, p, contents, guide, ap); } @@ -631,6 +645,21 @@ bool rpc_scan_datastore_hex(struct plugin *plugin, return ret; } +void rpc_enable_batching(struct plugin *plugin) +{ + const char *p; + struct json_out *params; + + params = json_out_new(NULL); + json_out_start(params, NULL, '{'); + json_out_add(params, "enable", false, "true"); + json_out_end(params, '}'); + json_out_finished(params); + + /* We don't actually care about (empty) response */ + sync_req(tmpctx, plugin, "batching", take(params), &p); +} + static struct command_result *datastore_fail(struct command *command, const char *buf, const jsmntok_t *result, diff --git a/plugins/libplugin.h b/plugins/libplugin.h index 4131f4567..3e9b0f295 100644 --- a/plugins/libplugin.h +++ b/plugins/libplugin.h @@ -283,6 +283,8 @@ bool rpc_scan_datastore_hex(struct plugin *plugin, const char *path, ...); +/* This sets batching of database commitments */ +void rpc_enable_batching(struct plugin *plugin); /* Send an async rpc request to lightningd. */ struct command_result *send_outreq(struct plugin *plugin,