From b05550fe550cafb61cbb0214fb43d8bc93898ec0 Mon Sep 17 00:00:00 2001 From: wm4 Date: Mon, 24 Feb 2020 00:14:54 +0100 Subject: 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. --- DOCS/man/ipc.rst | 35 +++++++++++++++++++++++++++--- input/ipc.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 90 insertions(+), 11 deletions(-) diff --git a/DOCS/man/ipc.rst b/DOCS/man/ipc.rst index 17bd373e2c..6d0f9c0d9c 100644 --- a/DOCS/man/ipc.rst +++ b/DOCS/man/ipc.rst @@ -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 -------- diff --git a/input/ipc.c b/input/ipc.c index 6568feacfa..fd971cf315 100644 --- a/input/ipc.c +++ b/input/ipc.c @@ -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; } -- cgit v1.2.3