ipc: implement asynchronous commands

I decided to make this explicit. The alternative would have been making
all commands asynchronous always, like a small note in the manpage
threatened. I think that could have caused compatibility issues.

As a design decision, this does not send a reply if an async command
started. This could be a good or bad idea, but in any case, it will make
async command look almost like synchronous ones, except they don't block
the IPC protocol.
This commit is contained in:
wm4 2020-02-24 00:14:54 +01:00
parent a9e6b9ea36
commit b05550fe55
2 changed files with 90 additions and 11 deletions

View File

@ -136,9 +136,6 @@ Would generate this response:
If you don't specify a ``request_id``, command replies will set it to 0.
Commands may run asynchronously in the future, instead of blocking the socket
until a reply is sent.
All commands, replies, and events are separated from each other with a line
break character (``\n``).
@ -150,6 +147,38 @@ with ``#`` and empty lines are ignored.
Currently, embedded 0 bytes terminate the current line, but you should not
rely on this.
Asynchronous commands
---------------------
Command can be run asynchronously. This behaves exactly as with normal command
execution, except that execution is not blocking. Other commands can be sent
while it's executing, and command completion can be arbitrarily reordered.
The ``async`` field controls this. If present, it must be a boolean. If missing,
``false`` is assumed.
For example, this initiates an asynchronous command:
::
{ "command": ["screenshot"], "request_id": 123, "async": true }
And this is the completion:
::
{"request_id":123,"error":"success","data":null}
By design, you will not get a confirmation that the command was started. If a
command is long running, sending the message will lead to any reply until much
later when the command finishes.
Some commands execute synchronously, but these will behave like asynchronous
commands that finished execution immediately.
Cancellation of asynchronous commands is available in the libmpv API, but has
not yet been implemented in the IPC protocol.
Commands
--------

View File

@ -109,8 +109,28 @@ static void mpv_node_map_add_string(void *ta_parent, mpv_node *src, const char *
mpv_node_map_add(ta_parent, src, key, &val_node);
}
// This is supposed to write a reply that looks like "normal" command execution.
static void mpv_format_command_reply(void *ta_parent, mpv_event *event,
mpv_node *dst)
{
assert(event->event_id == MPV_EVENT_COMMAND_REPLY);
mpv_event_command *cmd = event->data;
mpv_node_map_add_int64(ta_parent, dst, "request_id", event->reply_userdata);
mpv_node_map_add_string(ta_parent, dst, "error",
mpv_error_string(event->error));
mpv_node_map_add(ta_parent, dst, "data", &cmd->result);
}
static void mpv_event_to_node(void *ta_parent, mpv_event *event, mpv_node *dst)
{
if (event->event_id == MPV_EVENT_COMMAND_REPLY) {
mpv_format_command_reply(ta_parent, event, dst);
return;
}
mpv_node_map_add_string(ta_parent, dst, "event", mpv_event_name(event->event_id));
if (event->reply_userdata)
@ -193,6 +213,10 @@ static char *json_execute_command(struct mpv_handle *client, void *ta_parent,
mpv_node msg_node;
mpv_node reply_node = {.format = MPV_FORMAT_NODE_MAP, .u.list = NULL};
mpv_node *reqid_node = NULL;
int64_t reqid = 0;
mpv_node *async_node = NULL;
bool async = false;
bool send_reply = true;
rc = json_parse(ta_parent, &msg_node, &src, 50);
if (rc < 0) {
@ -206,10 +230,27 @@ static char *json_execute_command(struct mpv_handle *client, void *ta_parent,
goto error;
}
async_node = node_map_get(&msg_node, "async");
if (async_node) {
if (async_node->format != MPV_FORMAT_FLAG) {
rc = MPV_ERROR_INVALID_PARAMETER;
goto error;
}
async = async_node->u.flag;
}
reqid_node = node_map_get(&msg_node, "request_id");
if (reqid_node && reqid_node->format != MPV_FORMAT_INT64) {
mp_warn(log, "'request_id' must be an integer. Using other types is "
"deprecated and will trigger an error in the future!\n");
if (reqid_node) {
if (reqid_node->format == MPV_FORMAT_INT64) {
reqid = reqid_node->u.int64;
} else if (async) {
mp_err(log, "'request_id' must be an integer for async commands.\n");
rc = MPV_ERROR_INVALID_PARAMETER;
goto error;
} else {
mp_warn(log, "'request_id' must be an integer. Using other types is "
"deprecated and will trigger an error in the future!\n");
}
}
mpv_node *cmd_node = node_map_get(&msg_node, "command");
@ -396,9 +437,15 @@ static char *json_execute_command(struct mpv_handle *client, void *ta_parent,
} else {
mpv_node result_node;
rc = mpv_command_node(client, cmd_node, &result_node);
if (rc >= 0)
mpv_node_map_add(ta_parent, &reply_node, "data", &result_node);
if (async) {
rc = mpv_command_node_async(client, reqid, cmd_node);
if (rc >= 0)
send_reply = false;
} else {
rc = mpv_command_node(client, cmd_node, &result_node);
if (rc >= 0)
mpv_node_map_add(ta_parent, &reply_node, "data", &result_node);
}
}
error:
@ -415,8 +462,11 @@ error:
mpv_node_map_add_string(ta_parent, &reply_node, "error", mpv_error_string(rc));
char *output = talloc_strdup(ta_parent, "");
json_write(&output, &reply_node);
output = ta_talloc_strdup_append(output, "\n");
if (send_reply) {
json_write(&output, &reply_node);
output = ta_talloc_strdup_append(output, "\n");
}
return output;
}