Skip to content
Snippets Groups Projects
Commit 7ffae819 authored by Vladimir Davydov's avatar Vladimir Davydov
Browse files

net.box: allow to store user-defined fields in future object

Before commit 954194a1 ("net.box:
rewrite request implementation in C"), net.box future was a plain Lua
table so that the caller could attach extra information to it. Now it
isn't true anymore - a future is a userdata object, and it doesn't have
indexing methods.

For backward compatibility, let's add __index and __newindex fields and
store user-defined fields in a Lua table, which is created lazily on the
first __newindex invocation. __index falls back on the metatable methods
if a field isn't found in the table.

Follow-up #6241
Closes #6306
parent 7b4eb172
No related branches found
No related tags found
No related merge requests found
......@@ -124,6 +124,15 @@ struct netbox_request {
/** Lua references to on_push trigger and its context. */
int on_push_ref;
int on_push_ctx_ref;
/**
* Lua reference to a table with user-defined fields.
* We allow the user to attach extra information to a future object,
* e.g. a reference to a connection or the invoked method name/args.
* All the information is stored in this table, which is created
* lazily, on the first __newindex invocation. Until then, it's
* LUA_NOREF.
*/
int index_ref;
/**
* Lua reference to the request result or LUA_NOREF if the response
* hasn't been received yet. If the response was decoded to a
......@@ -167,6 +176,7 @@ netbox_request_destroy(struct netbox_request *request)
luaL_unref(tarantool_L, LUA_REGISTRYINDEX, request->on_push_ref);
luaL_unref(tarantool_L, LUA_REGISTRYINDEX, request->on_push_ctx_ref);
luaL_unref(tarantool_L, LUA_REGISTRYINDEX, request->result_ref);
luaL_unref(tarantool_L, LUA_REGISTRYINDEX, request->index_ref);
if (request->error != NULL)
error_unref(request->error);
}
......@@ -1422,6 +1432,48 @@ luaT_netbox_request_gc(struct lua_State *L)
return 0;
}
static int
luaT_netbox_request_index(struct lua_State *L)
{
struct netbox_request *request = luaT_check_netbox_request(L, 1);
if (request->index_ref != LUA_NOREF) {
lua_rawgeti(L, LUA_REGISTRYINDEX, request->index_ref);
/*
* Copy the key (2nd argument) to the top. Note, we don't move
* it with lua_insert, like we do in __newindex, because we want
* to save it for the fallback path below.
*/
lua_pushvalue(L, 2);
lua_rawget(L, -2);
if (lua_type(L, -1) != LUA_TNIL)
return 1;
/* Pop nil and the index table. */
lua_pop(L, 2);
}
/* Fall back on metatable methods. */
lua_getmetatable(L, 1);
/* Move the metatable before the key (2nd argument). */
lua_insert(L, 2);
lua_rawget(L, 2);
return 1;
}
static int
luaT_netbox_request_newindex(struct lua_State *L)
{
struct netbox_request *request = luaT_check_netbox_request(L, 1);
if (request->index_ref == LUA_NOREF) {
/* Lazily create the index table on the first invocation. */
lua_newtable(L);
request->index_ref = luaL_ref(L, LUA_REGISTRYINDEX);
}
lua_rawgeti(L, LUA_REGISTRYINDEX, request->index_ref);
/* Move the index table before the key (2nd argument). */
lua_insert(L, 2);
lua_rawset(L, 2);
return 0;
}
/**
* Returns true if the response was received for the given request.
*/
......@@ -1666,6 +1718,7 @@ netbox_make_request(struct lua_State *L, int idx,
request->format = tuple_format_runtime;
tuple_format_ref(request->format);
fiber_cond_create(&request->cond);
request->index_ref = LUA_NOREF;
request->result_ref = LUA_NOREF;
request->error = NULL;
if (netbox_request_register(request, registry) != 0) {
......@@ -2054,6 +2107,8 @@ luaopen_net_box(struct lua_State *L)
static const struct luaL_Reg netbox_request_meta[] = {
{ "__gc", luaT_netbox_request_gc },
{ "__index", luaT_netbox_request_index },
{ "__newindex", luaT_netbox_request_newindex },
{ "is_ready", luaT_netbox_request_is_ready },
{ "result", luaT_netbox_request_result },
{ "wait_result", luaT_netbox_request_wait_result },
......
......@@ -10,10 +10,7 @@ net = require('net.box')
cond = nil
---
...
box.schema.func.create('long_function')
---
...
box.schema.user.grant('guest', 'execute', 'function', 'long_function')
box.schema.user.grant('guest', 'execute', 'universe')
---
...
function long_function(...) cond = fiber.cond() cond:wait() return ... end
......@@ -152,7 +149,109 @@ assert_no_csw(future.wait_result, future)
---
- [1, 2, 3]
...
box.schema.func.drop('long_function')
-- Storing user data in future object.
future = c:eval('return 123', {}, {is_async = true})
---
...
future.abc -- nil
---
- null
...
future.abc = nil -- delete a key that has never been set
---
...
future.abc -- nil
---
- null
...
future.abc = 'abc' -- set a value for a key
---
...
future.abc -- abc
---
- abc
...
future.abc = nil -- delete a value for a key
---
...
future.abc -- nil
---
- null
...
future.abc = nil -- delete a key with no value
---
...
future.abc -- nil
---
- null
...
future:wait_result() -- 123
---
- [123]
...
-- Garbage collection of stored user data.
future = c:eval('return 123', {}, {is_async = true})
---
...
future.data1 = {1}
---
...
future.data2 = {2}
---
...
future.data3 = {3}
---
...
gc = setmetatable({}, {__mode = 'v'})
---
...
gc.data1 = future.data1
---
...
gc.data2 = future.data2
---
...
gc.data3 = future.data3
---
...
future.data1 = nil
---
...
_ = collectgarbage('collect')
---
...
gc.data1 == nil
---
- true
...
future.data2 = 123
---
...
_ = collectgarbage('collect')
---
...
gc.data2 == nil
---
- true
...
future:wait_result() -- 123
---
- [123]
...
future = nil
---
...
_ = collectgarbage('collect')
---
...
_ = collectgarbage('collect')
---
...
gc.data3 == nil
---
- true
...
box.schema.user.revoke('guest', 'execute', 'universe')
---
...
c:close()
......
......@@ -5,8 +5,7 @@ net = require('net.box')
-- gh-3107: fiber-async netbox.
--
cond = nil
box.schema.func.create('long_function')
box.schema.user.grant('guest', 'execute', 'function', 'long_function')
box.schema.user.grant('guest', 'execute', 'universe')
function long_function(...) cond = fiber.cond() cond:wait() return ... end
function finalize_long() while not cond do fiber.sleep(0.01) end cond:signal() cond = nil end
s = box.schema.create_space('test')
......@@ -56,7 +55,41 @@ assert_no_csw(future.is_ready, future)
assert_no_csw(future.result, future)
assert_no_csw(future.wait_result, future)
box.schema.func.drop('long_function')
-- Storing user data in future object.
future = c:eval('return 123', {}, {is_async = true})
future.abc -- nil
future.abc = nil -- delete a key that has never been set
future.abc -- nil
future.abc = 'abc' -- set a value for a key
future.abc -- abc
future.abc = nil -- delete a value for a key
future.abc -- nil
future.abc = nil -- delete a key with no value
future.abc -- nil
future:wait_result() -- 123
-- Garbage collection of stored user data.
future = c:eval('return 123', {}, {is_async = true})
future.data1 = {1}
future.data2 = {2}
future.data3 = {3}
gc = setmetatable({}, {__mode = 'v'})
gc.data1 = future.data1
gc.data2 = future.data2
gc.data3 = future.data3
future.data1 = nil
_ = collectgarbage('collect')
gc.data1 == nil
future.data2 = 123
_ = collectgarbage('collect')
gc.data2 == nil
future:wait_result() -- 123
future = nil
_ = collectgarbage('collect')
_ = collectgarbage('collect')
gc.data3 == nil
box.schema.user.revoke('guest', 'execute', 'universe')
c:close()
s:drop()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment