MsgPack-RPC dispatch based on function array lookup #864

This simplifies the generated msgpack_rpc_dispatch() function, separates the
code for each RPC method more clearly and allows easy implementation of
alternative dispatching methods (e.g. string method id dispatch).
This commit is contained in:
Felipe Oliveira Carvalho 2014-06-18 20:04:39 -03:00 committed by Justin M. Keyes
parent 11653ce2d7
commit 9a2b2d4a64
2 changed files with 74 additions and 46 deletions

View File

@ -108,7 +108,7 @@ const uint8_t msgpack_metadata[] = {
]]) ]])
-- serialize the API metadata using msgpack and embed into the resulting -- serialize the API metadata using msgpack and embed into the resulting
-- binary for easy querying by clients -- binary for easy querying by clients
packed = msgpack.pack(api) packed = msgpack.pack(api)
for i = 1, #packed do for i = 1, #packed do
output:write(string.byte(packed, i)..', ') output:write(string.byte(packed, i)..', ')
@ -116,38 +116,32 @@ for i = 1, #packed do
output:write('\n ') output:write('\n ')
end end
end end
-- start the dispatch function. number 0 is reserved for querying the metadata,
-- usually it is the first function called by clients.
output:write([[ output:write([[
}; };
const unsigned int msgpack_metadata_size = sizeof(msgpack_metadata); const unsigned int msgpack_metadata_size = sizeof(msgpack_metadata);
Object msgpack_rpc_dispatch(uint64_t channel_id,
uint64_t method_id,
msgpack_object *req,
Error *error)
{
Object ret = NIL;
switch (method_id) {
]]) ]])
-- Visit each function metadata to build the case label with code generated -- start the handler functions. First handler (method_id=0) is reserved for
-- for validating arguments and calling to the real API -- querying the metadata, usually it is the first function called by clients.
-- Visit each function metadata to build the handler function with code
-- generated for validating arguments and calling to the real API.
for i = 1, #api.functions do for i = 1, #api.functions do
local fn = api.functions[i] local fn = api.functions[i]
local args = {} local args = {}
local cleanup_label = 'cleanup_'..i
output:write('\n case '..fn.id..': {')
output:write('\n if (req->via.array.ptr[3].via.array.size != '..#fn.parameters..') {') output:write('static Object handle_'..fn.name..'(uint64_t channel_id, msgpack_object *req, Error *error)')
output:write('\n snprintf(error->msg, sizeof(error->msg), "Wrong number of arguments: expecting '..#fn.parameters..' but got %u", req->via.array.ptr[3].via.array.size);') output:write('\n{')
output:write('\n goto '..cleanup_label..';') output:write('\n if (req->via.array.ptr[3].via.array.size != '..#fn.parameters..') {')
output:write('\n }\n') output:write('\n snprintf(error->msg, sizeof(error->msg), "Wrong number of arguments: expecting '..#fn.parameters..' but got %u", req->via.array.ptr[3].via.array.size);')
output:write('\n goto cleanup;')
output:write('\n }\n')
-- Declare/initialize variables that will hold converted arguments -- Declare/initialize variables that will hold converted arguments
for j = 1, #fn.parameters do for j = 1, #fn.parameters do
local param = fn.parameters[j] local param = fn.parameters[j]
local converted = 'arg_'..j local converted = 'arg_'..j
output:write('\n '..param[1]..' '..converted..' msgpack_rpc_init_'..string.lower(param[1])..';') output:write('\n '..param[1]..' '..converted..' msgpack_rpc_init_'..string.lower(param[1])..';')
end end
output:write('\n') output:write('\n')
-- Validation/conversion for each argument -- Validation/conversion for each argument
@ -157,18 +151,17 @@ for i = 1, #api.functions do
arg = '(req->via.array.ptr[3].via.array.ptr + '..(j - 1)..')' arg = '(req->via.array.ptr[3].via.array.ptr + '..(j - 1)..')'
converted = 'arg_'..j converted = 'arg_'..j
convert_arg = 'msgpack_rpc_to_'..string.lower(param[1]) convert_arg = 'msgpack_rpc_to_'..string.lower(param[1])
output:write('\n if (!'..convert_arg..'('..arg..', &'..converted..')) {') output:write('\n if (!'..convert_arg..'('..arg..', &'..converted..')) {')
output:write('\n snprintf(error->msg, sizeof(error->msg), "Wrong type for argument '..j..', expecting '..param[1]..'");') output:write('\n snprintf(error->msg, sizeof(error->msg), "Wrong type for argument '..j..', expecting '..param[1]..'");')
output:write('\n error->set = true;')
output:write('\n error->set = true;') output:write('\n goto cleanup;')
output:write('\n goto '..cleanup_label..';') output:write('\n }\n')
output:write('\n }\n')
args[#args + 1] = converted args[#args + 1] = converted
end end
-- function call -- function call
local call_args = table.concat(args, ', ') local call_args = table.concat(args, ', ')
output:write('\n ') output:write('\n ')
if fn.return_type ~= 'void' then if fn.return_type ~= 'void' then
-- has a return value, prefix the call with a declaration -- has a return value, prefix the call with a declaration
output:write(fn.return_type..' rv = ') output:write(fn.return_type..' rv = ')
@ -196,37 +189,67 @@ for i = 1, #api.functions do
output:write('error);\n') output:write('error);\n')
end end
-- and check for the error -- and check for the error
output:write('\n if (error->set) {') output:write('\n if (error->set) {')
output:write('\n goto '..cleanup_label..';') output:write('\n goto cleanup;')
output:write('\n }\n') output:write('\n }\n')
else else
output:write(');\n') output:write(');\n')
end end
if fn.return_type ~= 'void' then if fn.return_type ~= 'void' then
output:write('\n ret = '..string.upper(fn.return_type)..'_OBJ(rv);') output:write('\n Object ret = '..string.upper(fn.return_type)..'_OBJ(rv);')
end end
-- Now generate the cleanup label for freeing memory allocated for the -- Now generate the cleanup label for freeing memory allocated for the
-- arguments -- arguments
output:write('\n\n'..cleanup_label..':'); output:write('\n\ncleanup:');
for j = 1, #fn.parameters do for j = 1, #fn.parameters do
local param = fn.parameters[j] local param = fn.parameters[j]
output:write('\n msgpack_rpc_free_'..string.lower(param[1])..'(arg_'..j..');') output:write('\n msgpack_rpc_free_'..string.lower(param[1])..'(arg_'..j..');')
end
if fn.return_type ~= 'void' then
output:write('\n return ret;\n}\n\n');
else
output:write('\n return NIL;\n}\n\n');
end end
output:write('\n break;');
output:write('\n };\n');
end end
output:write([[ output:write([[
static Object handle_missing_method(uint64_t channel_id,
msgpack_object *req,
default: Error *error)
snprintf(error->msg, sizeof(error->msg), "Invalid function id"); {
error->set = true; snprintf(error->msg, sizeof(error->msg), "Invalid function id");
} error->set = true;
return ret; return NIL;
} }
]]) ]])
-- Generate the table of handler functions indexed by method id
output:write([[
static const rpc_method_handler_fn rpc_method_handlers[] = {
[0] = (rpc_method_handler_fn)NULL]])
for i = 1, #api.functions do
local fn = api.functions[i]
output:write(',\n ['..i..'] = handle_'..fn.name..'')
end
output:write('\n};\n\n')
output:write([[
Object msgpack_rpc_dispatch(uint64_t channel_id,
uint64_t method_id,
msgpack_object *req,
Error *error)
{
]])
output:write('\n // method_id=0 is specially handled')
output:write('\n assert(method_id > 0);')
output:write('\n');
output:write('\n rpc_method_handler_fn handler = (method_id <= '..#api.functions..') ?')
output:write('\n rpc_method_handlers[method_id] : handle_missing_method;')
output:write('\n return handler(channel_id, req, error);')
output:write('\n}\n')
output:close() output:close()

View File

@ -16,6 +16,12 @@ typedef enum {
kUnpackResultNeedMore /// Need more data kUnpackResultNeedMore /// Need more data
} UnpackResult; } UnpackResult;
/// The rpc_method_handlers table, used in msgpack_rpc_dispatch(), stores
/// functions of this type.
typedef Object (*rpc_method_handler_fn)(uint64_t channel_id,
msgpack_object *req,
Error *error);
/// Dispatches to the actual API function after basic payload validation by /// Dispatches to the actual API function after basic payload validation by
/// `msgpack_rpc_call`. It is responsible for validating/converting arguments /// `msgpack_rpc_call`. It is responsible for validating/converting arguments
/// to C types, and converting the return value back to msgpack types. /// to C types, and converting the return value back to msgpack types.
@ -25,12 +31,12 @@ typedef enum {
/// @param channel_id The channel id /// @param channel_id The channel id
/// @param method_id The method id /// @param method_id The method id
/// @param req The parsed request object /// @param req The parsed request object
/// @param err Pointer to error structure /// @param error Pointer to error structure
/// @return Some object /// @return Some object
Object msgpack_rpc_dispatch(uint64_t channel_id, Object msgpack_rpc_dispatch(uint64_t channel_id,
uint64_t method_id, uint64_t method_id,
msgpack_object *req, msgpack_object *req,
Error *err) Error *error)
FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_NONNULL_ARG(3); FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_NONNULL_ARG(3);
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
@ -38,4 +44,3 @@ Object msgpack_rpc_dispatch(uint64_t channel_id,
#endif #endif
#endif // NVIM_OS_MSGPACK_RPC_H #endif // NVIM_OS_MSGPACK_RPC_H