feat(msgpack_rpc): support out-of-order responses on msgpack-rpc

Added to support MessagePack-RPC fully compliant clients that do
not return responses in request order.

Although it is currently not an efficient implementation for full
compliance and full compliance cannot be guaranteed, the addition
of the new client type `msgpack-rpc` creates a situation where "if
the client type is `msgpack-rpc`, then backward compatibility is
ignored and full compliance with MessagePack- RPC compliance is
justified even if backward compatibility is ignored if the client
type is `msgpack-rpc`.
This commit is contained in:
Alisue 2023-08-06 23:19:29 +09:00
parent deb6fd6704
commit 01fe6b9e6a
No known key found for this signature in database
GPG Key ID: 74D5B4A7F3E1B28C
3 changed files with 51 additions and 7 deletions

View File

@ -6730,7 +6730,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
const char *name = NULL; const char *name = NULL;
Channel *chan = find_channel(chan_id); Channel *chan = find_channel(chan_id);
if (chan) { if (chan) {
name = rpc_client_name(chan); name = get_client_info(chan, "name");
} }
msg_ext_set_kind("rpc_error"); msg_ext_set_kind("rpc_error");
if (name) { if (name) {

View File

@ -306,6 +306,17 @@ end:
channel_decref(channel); channel_decref(channel);
} }
static ChannelCallFrame *find_call_frame(RpcState *rpc, uint32_t request_id)
{
for (size_t i = 0; i < kv_size(rpc->call_stack); i++) {
ChannelCallFrame *frame = kv_Z(rpc->call_stack, i);
if (frame->request_id == request_id) {
return frame;
}
}
return NULL;
}
static void parse_msgpack(Channel *channel) static void parse_msgpack(Channel *channel)
{ {
Unpacker *p = channel->rpc.unpacker; Unpacker *p = channel->rpc.unpacker;
@ -321,13 +332,15 @@ static void parse_msgpack(Channel *channel)
} }
arena_mem_free(arena_finish(&p->arena)); arena_mem_free(arena_finish(&p->arena));
} else if (p->type == kMessageTypeResponse) { } else if (p->type == kMessageTypeResponse) {
ChannelCallFrame *frame = kv_last(channel->rpc.call_stack); ChannelCallFrame *frame = channel->rpc.client_type == kClientTypeMsgpackRpc
if (p->request_id != frame->request_id) { ? find_call_frame(&channel->rpc, p->request_id)
: kv_last(channel->rpc.call_stack);
if (frame == NULL || p->request_id != frame->request_id) {
char buf[256]; char buf[256];
snprintf(buf, sizeof(buf), snprintf(buf, sizeof(buf),
"ch %" PRIu64 " returned a response with an unknown request " "ch %" PRIu64 " (type=%" PRIu32 ") returned a response with an unknown request "
"id %" PRIu32 ". Ensure the client is properly synchronized", "id %" PRIu32 ". Ensure the client is properly synchronized",
channel->id, p->request_id); channel->id, (unsigned)channel->rpc.client_type, p->request_id);
chan_close_with_error(channel, buf, LOGLVL_ERR); chan_close_with_error(channel, buf, LOGLVL_ERR);
} }
frame->returned = true; frame->returned = true;
@ -691,6 +704,25 @@ void rpc_set_client_info(uint64_t id, Dictionary info)
api_free_dictionary(chan->rpc.info); api_free_dictionary(chan->rpc.info);
chan->rpc.info = info; chan->rpc.info = info;
// Parse "type" on "info" and set "client_type"
const char *type = get_client_info(chan, "type");
if (type == NULL || strequal(type, "remote")) {
chan->rpc.client_type = kClientTypeRemote;
} else if (strequal(type, "msgpack-rpc")) {
chan->rpc.client_type = kClientTypeMsgpackRpc;
} else if (strequal(type, "ui")) {
chan->rpc.client_type = kClientTypeUi;
} else if (strequal(type, "embedder")) {
chan->rpc.client_type = kClientTypeEmbedder;
} else if (strequal(type, "host")) {
chan->rpc.client_type = kClientTypeHost;
} else if (strequal(type, "plugin")) {
chan->rpc.client_type = kClientTypePlugin;
} else {
chan->rpc.client_type = kClientTypeUnknown;
}
channel_info_changed(chan, false); channel_info_changed(chan, false);
} }
@ -699,14 +731,15 @@ Dictionary rpc_client_info(Channel *chan)
return copy_dictionary(chan->rpc.info, NULL); return copy_dictionary(chan->rpc.info, NULL);
} }
const char *rpc_client_name(Channel *chan) const char *get_client_info(Channel *chan, const char *key)
FUNC_ATTR_NONNULL_ALL
{ {
if (!chan->is_rpc) { if (!chan->is_rpc) {
return NULL; return NULL;
} }
Dictionary info = chan->rpc.info; Dictionary info = chan->rpc.info;
for (size_t i = 0; i < info.size; i++) { for (size_t i = 0; i < info.size; i++) {
if (strequal("name", info.items[i].key.data) if (strequal(key, info.items[i].key.data)
&& info.items[i].value.type == kObjectTypeString) { && info.items[i].value.type == kObjectTypeString) {
return info.items[i].value.data.string.data; return info.items[i].value.data.string.data;
} }

View File

@ -14,6 +14,16 @@
typedef struct Channel Channel; typedef struct Channel Channel;
typedef struct Unpacker Unpacker; typedef struct Unpacker Unpacker;
typedef enum {
kClientTypeUnknown = -1,
kClientTypeRemote = 0,
kClientTypeMsgpackRpc = 5,
kClientTypeUi = 1,
kClientTypeEmbedder = 2,
kClientTypeHost = 3,
kClientTypePlugin = 4,
} ClientType;
typedef struct { typedef struct {
uint32_t request_id; uint32_t request_id;
bool returned, errored; bool returned, errored;
@ -37,6 +47,7 @@ typedef struct {
uint32_t next_request_id; uint32_t next_request_id;
kvec_t(ChannelCallFrame *) call_stack; kvec_t(ChannelCallFrame *) call_stack;
Dictionary info; Dictionary info;
ClientType client_type;
} RpcState; } RpcState;
#endif // NVIM_MSGPACK_RPC_CHANNEL_DEFS_H #endif // NVIM_MSGPACK_RPC_CHANNEL_DEFS_H