Skip to content

Commit 62f0916

Browse files
committed
lightningd: unify dynamic and static plugin initialization.
This means we now clean up options in startup plugins (that was only done by dynamic code!), and now they both share the 60 second timeout instead of 20 seconds for dynamic. For the dynamic case though, it's 60 seconds to both complete getmanifest and init, which seems fair. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
1 parent 1c668b9 commit 62f0916

File tree

3 files changed

+42
-189
lines changed

3 files changed

+42
-189
lines changed

lightningd/plugin.c

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ void plugin_kill(struct plugin *plugin, char *fmt, ...)
190190
{
191191
char *msg;
192192
va_list ap;
193+
struct plugin_opt *opt;
193194

194195
va_start(ap, fmt);
195196
msg = tal_vfmt(plugin, fmt, ap);
@@ -201,8 +202,18 @@ void plugin_kill(struct plugin *plugin, char *fmt, ...)
201202
kill(plugin->pid, SIGKILL);
202203
list_del(&plugin->list);
203204

204-
if (plugin->start_cmd)
205+
/* FIXME: This cleans up as it goes because plugin_kill called twice! */
206+
while ((opt = list_top(&plugin->plugin_opts, struct plugin_opt, list))) {
207+
if (!opt_unregister(opt->name))
208+
fatal("Could not unregister %s from plugin %s",
209+
opt->name, plugin->cmd);
210+
list_del_from(&plugin->plugin_opts, &opt->list);
211+
}
212+
213+
if (plugin->start_cmd) {
205214
plugin_cmd_killed(plugin->start_cmd, plugin, msg);
215+
plugin->start_cmd = NULL;
216+
}
206217

207218
check_plugins_resolved(plugin->plugins);
208219
}
@@ -494,13 +505,15 @@ static struct io_plan *plugin_write_json(struct io_conn *conn,
494505
*/
495506
static void plugin_conn_finish(struct io_conn *conn, struct plugin *plugin)
496507
{
508+
struct plugins *plugins = plugin->plugins;
497509
plugin->stdout_conn = NULL;
498510
if (plugin->start_cmd) {
499-
plugin_cmd_succeeded(plugin->start_cmd, plugin);
511+
plugin_cmd_killed(plugin->start_cmd, plugin,
512+
"Plugin exited before completing handshake.");
500513
plugin->start_cmd = NULL;
501514
}
502515
tal_free(plugin);
503-
check_plugins_resolved(plugin->plugins);
516+
check_plugins_resolved(plugins);
504517
}
505518

506519
struct io_plan *plugin_stdin_conn_init(struct io_conn *conn,

lightningd/plugin_control.c

Lines changed: 24 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -27,32 +27,13 @@ static struct command_result *plugin_dynamic_list_plugins(struct command *cmd,
2727
json_add_string(response, "name", p->cmd);
2828
json_add_bool(response, "active",
2929
p->plugin_state == INIT_COMPLETE);
30+
json_add_u32(response, "state", p->plugin_state);
3031
json_object_end(response);
3132
}
3233
json_array_end(response);
3334
return command_success(cmd, response);
3435
}
3536

36-
/* Mutual recursion. */
37-
static void plugin_dynamic_crash(struct plugin *plugin, struct dynamic_plugin *dp);
38-
39-
/**
40-
* Returned by all subcommands on error.
41-
*/
42-
static struct command_result *
43-
plugin_dynamic_error(struct dynamic_plugin *dp, const char *error)
44-
{
45-
if (dp->plugin)
46-
plugin_kill(dp->plugin, "%s", error);
47-
else
48-
log_info(dp->cmd->ld->log, "%s", error);
49-
50-
tal_del_destructor2(dp->plugin, plugin_dynamic_crash, dp);
51-
return command_fail(dp->cmd, JSONRPC2_INVALID_PARAMS,
52-
"%s: %s", dp->plugin ? dp->plugin->cmd : "unknown plugin",
53-
error);
54-
}
55-
5637
struct command_result *plugin_cmd_killed(struct command *cmd,
5738
struct plugin *plugin, const char *msg)
5839
{
@@ -71,143 +52,27 @@ struct command_result *plugin_cmd_all_complete(struct plugins *plugins,
7152
return plugin_dynamic_list_plugins(cmd, plugins);
7253
}
7354

74-
static void plugin_dynamic_timeout(struct dynamic_plugin *dp)
75-
{
76-
plugin_dynamic_error(dp, "Timed out while waiting for plugin response");
77-
}
78-
79-
static void plugin_dynamic_crash(struct plugin *p, struct dynamic_plugin *dp)
80-
{
81-
plugin_dynamic_error(dp, "Plugin exited before completing handshake.");
82-
}
83-
84-
static void plugin_dynamic_config_callback(const char *buffer,
85-
const jsmntok_t *toks,
86-
const jsmntok_t *idtok,
87-
struct dynamic_plugin *dp)
88-
{
89-
struct plugin *p;
90-
91-
dp->plugin->plugin_state = INIT_COMPLETE;
92-
/* Reset the timer only now so that we are either configured, or
93-
* killed. */
94-
tal_free(dp->plugin->timeout_timer);
95-
tal_del_destructor2(dp->plugin, plugin_dynamic_crash, dp);
96-
97-
list_for_each(&dp->plugin->plugins->plugins, p, list) {
98-
if (p->plugin_state != INIT_COMPLETE)
99-
return;
100-
}
101-
102-
/* No plugin unconfigured left, return the plugin list */
103-
was_pending(plugin_dynamic_list_plugins(dp->cmd, dp->plugin->plugins));
104-
}
105-
106-
/**
107-
* Send the init message to the plugin. We don't care about its response,
108-
* but it's considered the last part of the handshake : once it responds
109-
* it is considered configured.
110-
*/
111-
static void plugin_dynamic_config(struct dynamic_plugin *dp)
112-
{
113-
struct jsonrpc_request *req;
114-
115-
req = jsonrpc_request_start(dp->plugin, "init", dp->plugin->log,
116-
plugin_dynamic_config_callback, dp);
117-
plugin_populate_init_request(dp->plugin, req);
118-
jsonrpc_request_end(req);
119-
dp->plugin->plugin_state = AWAITING_INIT_RESPONSE;
120-
plugin_request_send(dp->plugin, req);
121-
}
122-
123-
static void plugin_dynamic_manifest_callback(const char *buffer,
124-
const jsmntok_t *toks,
125-
const jsmntok_t *idtok,
126-
struct dynamic_plugin *dp)
127-
{
128-
if (!plugin_parse_getmanifest_response(buffer, toks, idtok, dp->plugin))
129-
return was_pending(plugin_dynamic_error(dp, "Gave a bad response to getmanifest"));
130-
131-
if (!dp->plugin->dynamic)
132-
return was_pending(plugin_dynamic_error(dp, "Not a dynamic plugin"));
133-
134-
/* We got the manifest, now send the init message */
135-
dp->plugin->plugin_state = NEEDS_INIT;
136-
plugin_dynamic_config(dp);
137-
}
138-
139-
/**
140-
* This starts a plugin : spawns the process, connect its stdout and stdin,
141-
* then sends it a getmanifest request.
142-
*/
143-
static struct command_result *plugin_start(struct dynamic_plugin *dp)
144-
{
145-
int stdin, stdout;
146-
mode_t prev_mask;
147-
char **p_cmd;
148-
struct jsonrpc_request *req;
149-
struct plugin *p = dp->plugin;
150-
151-
p->dynamic = false;
152-
p_cmd = tal_arrz(NULL, char *, 2);
153-
p_cmd[0] = p->cmd;
154-
/* In case the plugin create files, this is a better default. */
155-
prev_mask = umask(dp->cmd->ld->initial_umask);
156-
p->pid = pipecmdarr(&stdin, &stdout, &pipecmd_preserve, p_cmd);
157-
umask(prev_mask);
158-
if (p->pid == -1)
159-
return plugin_dynamic_error(dp, "Error running command");
160-
else
161-
log_debug(dp->cmd->ld->plugins->log, "started(%u) %s", p->pid, p->cmd);
162-
tal_free(p_cmd);
163-
p->buffer = tal_arr(p, char, 64);
164-
p->stop = false;
165-
/* Give the plugin 20 seconds to respond to `getmanifest`, so we don't hang
166-
* too long on the RPC caller. */
167-
p->timeout_timer = new_reltimer(dp->cmd->ld->timers, dp,
168-
time_from_sec((20)),
169-
plugin_dynamic_timeout, dp);
170-
171-
/* Besides the timeout we could also have the plugin crash before
172-
* completing the handshake. In that case we'll get notified and we
173-
* can clean up the `struct dynamic_plugin` and return an appropriate
174-
* error.
175-
*
176-
* The destructor is deregistered in the following places:
177-
*
178-
* - plugin_dynamic_error in case of a timeout or a crash
179-
* - plugin_dynamic_config_callback if the handshake completes
180-
*/
181-
tal_add_destructor2(p, plugin_dynamic_crash, dp);
182-
183-
/* Create two connections, one read-only on top of the plugin's stdin, and one
184-
* write-only on its stdout. */
185-
io_new_conn(p, stdout, plugin_stdout_conn_init, p);
186-
io_new_conn(p, stdin, plugin_stdin_conn_init, p);
187-
req = jsonrpc_request_start(p, "getmanifest", p->log,
188-
plugin_dynamic_manifest_callback, dp);
189-
jsonrpc_request_end(req);
190-
plugin_request_send(p, req);
191-
p->plugin_state = AWAITING_GETMANIFEST_RESPONSE;
192-
return command_still_pending(dp->cmd);
193-
}
194-
19555
/**
19656
* Called when trying to start a plugin through RPC, it starts the plugin and
19757
* will give a result 20 seconds later at the most.
19858
*/
19959
static struct command_result *
20060
plugin_dynamic_start(struct command *cmd, const char *plugin_path)
20161
{
202-
struct dynamic_plugin *dp;
62+
struct plugin *p = plugin_register(cmd->ld->plugins, plugin_path, cmd);
20363

204-
dp = tal(cmd, struct dynamic_plugin);
205-
dp->cmd = cmd;
206-
dp->plugin = plugin_register(cmd->ld->plugins, plugin_path, NULL);
207-
if (!dp->plugin)
208-
return plugin_dynamic_error(dp, "Is already registered");
64+
if (!p)
65+
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
66+
"%s: already registered",
67+
plugin_path);
20968

210-
return plugin_start(dp);
69+
/* This will come back via plugin_cmd_killed or plugin_cmd_succeeded */
70+
if (!plugin_send_getmanifest(p))
71+
return command_fail(cmd, PLUGIN_ERROR,
72+
"%s: failed to open: %s",
73+
plugin_path, strerror(errno));
74+
75+
return command_still_pending(cmd);
21176
}
21277

21378
/**
@@ -218,38 +83,23 @@ static struct command_result *
21883
plugin_dynamic_startdir(struct command *cmd, const char *dir_path)
21984
{
22085
const char *err;
221-
struct plugin *p;
222-
/* If the directory is empty */
223-
bool found;
86+
struct command_result *res;
22487

22588
err = add_plugin_dir(cmd->ld->plugins, dir_path, false);
22689
if (err)
22790
return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "%s", err);
22891

229-
found = false;
230-
list_for_each(&cmd->ld->plugins->plugins, p, list) {
231-
if (p->plugin_state == UNCONFIGURED) {
232-
found = true;
233-
struct dynamic_plugin *dp = tal(cmd, struct dynamic_plugin);
234-
dp->plugin = p;
235-
dp->cmd = cmd;
236-
plugin_start(dp);
237-
}
238-
}
239-
if (!found)
240-
plugin_dynamic_list_plugins(cmd, cmd->ld->plugins);
92+
/* If none added, this calls plugin_cmd_all_complete immediately */
93+
res = plugin_register_all_complete(cmd->ld, cmd);
94+
if (res)
95+
return res;
24196

97+
plugins_send_getmanifest(cmd->ld->plugins);
24298
return command_still_pending(cmd);
24399
}
244100

245101
static void clear_plugin(struct plugin *p, const char *name)
246102
{
247-
struct plugin_opt *opt;
248-
249-
list_for_each(&p->plugin_opts, opt, list)
250-
if (!opt_unregister(opt->name))
251-
fatal("Could not unregister %s from plugin %s",
252-
opt->name, name);
253103
plugin_kill(p, "%s stopped by lightningd via RPC", name);
254104
tal_free(p);
255105
}
@@ -290,25 +140,16 @@ plugin_dynamic_stop(struct command *cmd, const char *plugin_name)
290140
static struct command_result *
291141
plugin_dynamic_rescan_plugins(struct command *cmd)
292142
{
293-
bool found;
294-
struct plugin *p;
143+
struct command_result *res;
295144

296145
/* This will not fail on "already registered" error. */
297146
plugins_add_default_dir(cmd->ld->plugins);
298147

299-
found = false;
300-
list_for_each(&cmd->ld->plugins->plugins, p, list) {
301-
if (p->plugin_state == UNCONFIGURED) {
302-
struct dynamic_plugin *dp = tal(cmd, struct dynamic_plugin);
303-
dp->plugin = p;
304-
dp->cmd = cmd;
305-
plugin_start(dp);
306-
found = true;
307-
}
308-
}
148+
/* If none added, this calls plugin_cmd_all_complete immediately */
149+
res = plugin_register_all_complete(cmd->ld, cmd);
150+
if (res)
151+
return res;
309152

310-
if (!found)
311-
return plugin_dynamic_list_plugins(cmd, cmd->ld->plugins);
312153
return command_still_pending(cmd);
313154
}
314155

tests/test_plugin.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,10 @@ def test_plugin_dir(node_factory):
194194

195195
def test_plugin_slowinit(node_factory):
196196
"""Tests that the 'plugin' RPC command times out if plugin doesnt respond"""
197-
os.environ['SLOWINIT_TIME'] = '21'
197+
os.environ['SLOWINIT_TIME'] = '61'
198198
n = node_factory.get_node()
199199

200-
with pytest.raises(RpcError, match="Timed out while waiting for plugin response"):
200+
with pytest.raises(RpcError, match='failed to respond to "init" in time, terminating.'):
201201
n.rpc.plugin_start(os.path.join(os.getcwd(), "tests/plugins/slow_init.py"))
202202

203203
# It's not actually configured yet, see what happens;
@@ -206,7 +206,6 @@ def test_plugin_slowinit(node_factory):
206206
n.rpc.plugin_list()
207207

208208

209-
@pytest.mark.xfail(strict=True)
210209
def test_plugin_command(node_factory):
211210
"""Tests the 'plugin' RPC command"""
212211
n = node_factory.get_node()

0 commit comments

Comments
 (0)