Merge PR #1398 'Tests and msgpack rpc improvements'

This commit is contained in:
Thiago de Arruda
2014-11-06 04:36:46 -03:00
5 changed files with 165 additions and 37 deletions

View File

@@ -6,7 +6,7 @@
fun:__nss_database_lookup fun:__nss_database_lookup
} }
{ {
ex_function_1 interior_ptr1
Memcheck:Leak Memcheck:Leak
fun:malloc fun:malloc
fun:try_malloc fun:try_malloc
@@ -14,7 +14,7 @@
fun:ex_function fun:ex_function
} }
{ {
ex_function_2 interior_ptr2
Memcheck:Leak Memcheck:Leak
fun:realloc fun:realloc
fun:xrealloc fun:xrealloc
@@ -22,7 +22,7 @@
fun:ex_function fun:ex_function
} }
{ {
ex_function_3 interior_ptr3
Memcheck:Leak Memcheck:Leak
fun:malloc fun:malloc
fun:strdup fun:strdup
@@ -30,6 +30,52 @@
fun:vim_strsave fun:vim_strsave
fun:ex_function fun:ex_function
} }
{
interior_ptr4
Memcheck:Leak
fun:malloc
fun:realloc
fun:xrealloc
fun:ga_grow
fun:ex_function
}
{
interior_ptr5
Memcheck:Leak
fun:malloc
fun:try_malloc
fun:xmalloc
fun:set_var
fun:set_var_lval
}
{
interior_ptr6
Memcheck:Leak
fun:malloc
fun:try_malloc
fun:xmalloc
fun:get_lit_string_tv
fun:eval7
}
{
interior_ptr7
Memcheck:Leak
fun:malloc
fun:strdup
fun:xstrdup
fun:vim_strsave
fun:copy_tv
fun:get_var_tv
fun:eval7
}
{
interior_ptr8
Memcheck:Leak
fun:malloc
fun:try_malloc
fun:xmalloc
fun:dictitem_alloc
}
{ {
uv_spawn_with_optimizations uv_spawn_with_optimizations
Memcheck:Leak Memcheck:Leak

View File

@@ -1,6 +1,8 @@
get_filename_component(BUSTED_DIR ${BUSTED_PRG} PATH) get_filename_component(BUSTED_DIR ${BUSTED_PRG} PATH)
set(ENV{PATH} "${BUSTED_DIR}:$ENV{PATH}") set(ENV{PATH} "${BUSTED_DIR}:$ENV{PATH}")
set(ENV{VIMRUNTIME} ${WORKING_DIR}/runtime)
if(NVIM_PRG) if(NVIM_PRG)
set(ENV{NVIM_PROG} "${NVIM_PRG}") set(ENV{NVIM_PROG} "${NVIM_PRG}")
endif() endif()

View File

@@ -66,14 +66,23 @@ typedef struct {
uint64_t request_id; uint64_t request_id;
} RequestEvent; } RequestEvent;
#define RequestEventFreer(x) typedef struct {
KMEMPOOL_INIT(RequestEventPool, RequestEvent, RequestEventFreer) Channel *channel;
kmempool_t(RequestEventPool) *request_event_pool = NULL; String method;
Array args;
} DelayedNotification;
#define _noop(x)
KMEMPOOL_INIT(RequestEventPool, RequestEvent, _noop)
KLIST_INIT(DelayedNotification, DelayedNotification, _noop)
static kmempool_t(RequestEventPool) *request_event_pool = NULL;
static klist_t(DelayedNotification) *delayed_notifications = NULL;
static uint64_t next_id = 1; static uint64_t next_id = 1;
static PMap(uint64_t) *channels = NULL; static PMap(uint64_t) *channels = NULL;
static PMap(cstr_t) *event_strings = NULL; static PMap(cstr_t) *event_strings = NULL;
static msgpack_sbuffer out_buffer; static msgpack_sbuffer out_buffer;
static size_t pending_requests = 0;
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "msgpack_rpc/channel.c.generated.h" # include "msgpack_rpc/channel.c.generated.h"
@@ -83,6 +92,7 @@ static msgpack_sbuffer out_buffer;
void channel_init(void) void channel_init(void)
{ {
request_event_pool = kmp_init(RequestEventPool); request_event_pool = kmp_init(RequestEventPool);
delayed_notifications = kl_init(DelayedNotification);
channels = pmap_new(uint64_t)(); channels = pmap_new(uint64_t)();
event_strings = pmap_new(cstr_t)(); event_strings = pmap_new(cstr_t)();
msgpack_sbuffer_init(&out_buffer); msgpack_sbuffer_init(&out_buffer);
@@ -173,15 +183,27 @@ bool channel_send_event(uint64_t id, char *name, Array args)
{ {
Channel *channel = NULL; Channel *channel = NULL;
if (id > 0) { if (id && (!(channel = pmap_get(uint64_t)(channels, id))
if (!(channel = pmap_get(uint64_t)(channels, id)) || channel->closed) { || channel->closed)) {
api_free_array(args); api_free_array(args);
return false; return false;
} }
if (pending_requests) {
DelayedNotification p = {
.channel = channel,
.method = cstr_to_string(name),
.args = args
};
// Pending request, queue the notification for sending later
*kl_pushp(DelayedNotification, delayed_notifications) = p;
} else {
if (channel) {
send_event(channel, name, args); send_event(channel, name, args);
} else { } else {
broadcast_event(name, args); broadcast_event(name, args);
} }
}
return true; return true;
} }
@@ -206,16 +228,6 @@ Object channel_send_call(uint64_t id,
return NIL; return NIL;
} }
if (kv_size(channel->call_stack) > 20) {
// 20 stack depth is more than anyone should ever need for RPC calls
api_set_error(err,
Exception,
_("Channel %" PRIu64 " crossed maximum stack depth"),
channel->id);
api_free_array(args);
return NIL;
}
uint64_t request_id = channel->next_request_id++; uint64_t request_id = channel->next_request_id++;
// Send the msgpack-rpc request // Send the msgpack-rpc request
send_request(channel, request_id, method_name, args); send_request(channel, request_id, method_name, args);
@@ -223,18 +235,24 @@ Object channel_send_call(uint64_t id,
// Push the frame // Push the frame
ChannelCallFrame frame = {request_id, false, false, NIL}; ChannelCallFrame frame = {request_id, false, false, NIL};
kv_push(ChannelCallFrame *, channel->call_stack, &frame); kv_push(ChannelCallFrame *, channel->call_stack, &frame);
pending_requests++;
event_poll_until(-1, frame.returned); event_poll_until(-1, frame.returned);
(void)kv_pop(channel->call_stack); (void)kv_pop(channel->call_stack);
pending_requests--;
if (frame.errored) { if (frame.errored) {
api_set_error(err, Exception, "%s", frame.result.data.string.data); api_set_error(err, Exception, "%s", frame.result.data.string.data);
return NIL; return NIL;
} }
if (channel->closed && !kv_size(channel->call_stack)) { if (!kv_size(channel->call_stack) && channel->closed) {
free_channel(channel); free_channel(channel);
} }
if (!pending_requests) {
send_delayed_notifications();
}
return frame.result; return frame.result;
} }
@@ -678,6 +696,7 @@ static void complete_call(msgpack_object *obj, Channel *channel)
static void call_set_error(Channel *channel, char *msg) static void call_set_error(Channel *channel, char *msg)
{ {
ELOG("Msgpack-RPC error: %s", msg);
for (size_t i = 0; i < kv_size(channel->call_stack); i++) { for (size_t i = 0; i < kv_size(channel->call_stack); i++) {
ChannelCallFrame *frame = kv_A(channel->call_stack, i); ChannelCallFrame *frame = kv_A(channel->call_stack, i);
frame->returned = true; frame->returned = true;
@@ -727,6 +746,20 @@ static WBuffer *serialize_response(uint64_t channel_id,
return rv; return rv;
} }
static void send_delayed_notifications(void)
{
DelayedNotification p;
while (kl_shift(DelayedNotification, delayed_notifications, &p) == 0) {
if (p.channel) {
send_event(p.channel, p.method.data, p.args);
} else {
broadcast_event(p.method.data, p.args);
}
free(p.method.data);
}
}
#if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL #if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL
#define REQ "[request] " #define REQ "[request] "
#define RES "[response] " #define RES "[response] "
@@ -764,3 +797,4 @@ static void log_msg_close(FILE *f, msgpack_object msg)
fclose(f); fclose(f);
} }
#endif #endif

View File

@@ -2,8 +2,10 @@
-- `rpcnotify`, to evaluate `rpcrequest` calls we need the client event loop to -- `rpcnotify`, to evaluate `rpcrequest` calls we need the client event loop to
-- be running. -- be running.
local helpers = require('test.functional.helpers') local helpers = require('test.functional.helpers')
local clear, nvim, eval, eq, run, stop = helpers.clear, helpers.nvim, local clear, nvim, eval = helpers.clear, helpers.nvim, helpers.eval
helpers.eval, helpers.eq, helpers.run, helpers.stop local eq, run, stop = helpers.eq, helpers.run, helpers.stop
local restart = helpers.restart
describe('server -> client', function() describe('server -> client', function()
@@ -13,6 +15,7 @@ describe('server -> client', function()
clear() clear()
cid = nvim('get_api_info')[1] cid = nvim('get_api_info')[1]
end) end)
teardown(restart)
describe('simple call', function() describe('simple call', function()
it('works', function() it('works', function()
@@ -65,4 +68,53 @@ describe('server -> client', function()
run(on_request, nil, on_setup) run(on_request, nil, on_setup)
end) end)
end) end)
describe('requests and notifications interleaved', function()
-- This tests that the following scenario won't happen:
--
-- server->client [request ] (1)
-- client->server [request ] (2) triggered by (1)
-- server->client [notification] (3) triggered by (2)
-- server->client [response ] (4) response to (2)
-- client->server [request ] (4) triggered by (3)
-- server->client [request ] (5) triggered by (4)
-- client->server [response ] (6) response to (1)
--
-- If the above scenario ever happens, the client connection will be closed
-- because (6) is returned after request (5) is sent, and nvim
-- only deals with one server->client request at a time. (In other words,
-- the client cannot send a response to a request that is not at the top
-- of nvim's request stack).
--
-- But above scenario shoudn't happen by the way notifications are dealt in
-- Nvim: they are only sent after there are no pending server->client
-- request(the request stack fully unwinds). So (3) is only sent after the
-- client returns (6).
it('works', function()
local expected = 300
local notified = 0
local function on_setup()
eq('notified!', eval('rpcrequest('..cid..', "notify")'))
end
local function on_request(method, args)
eq('notify', method)
eq(1, eval('rpcnotify('..cid..', "notification")'))
return 'notified!'
end
local function on_notification(method, args)
eq('notification', method)
if notified == expected then
stop()
return
end
notified = notified + 1
eq('notified', eval('rpcrequest('..cid..', "notify")'))
end
run(on_request, on_notification, on_setup)
eq(expected, notified)
end)
end)
end) end)

View File

@@ -1,20 +1,14 @@
-- Test for ":options". -- Test if ":options" throws any exception. The options window seems to mess
-- other tests, so restart nvim in the teardown hook
local helpers = require('test.functional.helpers') local helpers = require('test.functional.helpers')
local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert local restart, command, clear = helpers.restart, helpers.command, helpers.clear
local execute, expect = helpers.execute, helpers.expect
describe('options', function() describe('options', function()
setup(clear) setup(clear)
teardown(restart)
it('is working', function() it('is working', function()
insert('result') command('options')
execute("let caught = 'ok'")
execute('try', 'options', 'catch', 'let caught = v:throwpoint . "\n" . v:exception', 'endtry')
execute('buf 1')
execute('$put =caught')
expect("result\nok")
end) end)
end) end)