diff --git a/changelogs/unreleased/gh-9120-space-drop-rollback.md b/changelogs/unreleased/gh-9120-space-drop-rollback.md new file mode 100644 index 0000000000000000000000000000000000000000..10506a2d99748739fd29762acf796f3b7a23f2fc --- /dev/null +++ b/changelogs/unreleased/gh-9120-space-drop-rollback.md @@ -0,0 +1,4 @@ +## bugfix/box + +* Fixed a bug when local references to a space object got out of sync with the + space on the space drop rollback (gh-9120). diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc index c9da318b6264d848a5ba3054b7acd58d3162e8f9..691e9cdc9c6cebf1be629a0c10e2c34536b5477d 100644 --- a/src/box/lua/space.cc +++ b/src/box/lua/space.cc @@ -615,6 +615,26 @@ box_lua_space_new(struct lua_State *L, struct space *space) lua_setfield(L, -2, "space"); lua_getfield(L, -1, "space"); } + /* + * We can have the following conditions here: + * a) The space is totally new (e. g. on new space creation). + * b) The space is replaced (e. g. on index update). + * c) The space is updated (e. g. on sequence update). + * d) The space is reverted (e. g. on space drop rollback). + * + * - In case a) we need to create new box.space.<id|name> entries. + * - In cases b) and c) we need to update the existing box.space.<id> + * entry, drop the old box.space.<name> and create a new one. + * - In case d) we need to restore the original box.space.<id|name> + * entries, but let's only restore the box.space.<id> entry and + * perform the same actions as for b) and c) - it won't change the + * visible behavior but will make the code simpler. + */ + if (space->lua_ref != LUA_NOREF) { + /* We have either case c) or d). */ + lua_rawgeti(L, LUA_REGISTRYINDEX, space->lua_ref); + lua_rawseti(L, -2, space_id(space)); + } lua_rawgeti(L, -1, space_id(space)); if (lua_isnil(L, -1)) { /* @@ -636,6 +656,18 @@ box_lua_space_new(struct lua_State *L, struct space *space) lbox_fillspace(L, space, lua_gettop(L)); lua_setfield(L, -2, space_name(space)); + if (space->lua_ref == LUA_NOREF) { + /* + * Save the reference to the box.space[id] to restore + * the exactly same object on the space drop rollback. + * It prevents the situations when old references to + * the space go out of sync with the space which had + * been rolled back. For more information see #9120. + */ + lua_rawgeti(L, -1, space_id(space)); + space->lua_ref = luaL_ref(L, LUA_REGISTRYINDEX); + } + lua_pop(L, 2); /* box, space */ } diff --git a/src/box/space.c b/src/box/space.c index ccb827e5923bc640838c2299c56c2f608267d4ad..3b7b095cf975230e1c4e17409ed3d3d6932b7859 100644 --- a/src/box/space.c +++ b/src/box/space.c @@ -56,6 +56,7 @@ #include "wal_ext.h" #include "coll_id_cache.h" #include "func_adapter.h" +#include "lua/utils.h" int access_check_space(struct space *space, user_access_t access) @@ -536,6 +537,7 @@ space_create(struct space *space, struct engine *engine, space->constraint_ids = mh_strnptr_new(); rlist_create(&space->memtx_stories); rlist_create(&space->alter_stmts); + space->lua_ref = LUA_NOREF; return 0; fail_free_indexes: @@ -642,6 +644,7 @@ space_delete(struct space *space) mh_strnptr_delete(space->constraint_ids); assert(space->sql_triggers == NULL); assert(rlist_empty(&space->space_cache_pin_list)); + luaL_unref(tarantool_L, LUA_REGISTRYINDEX, space->lua_ref); space->vtab->destroy(space); } diff --git a/src/box/space.h b/src/box/space.h index 604009ce1a242a662e32382b8c2f2b63be7e914a..b6d5f882ae6159f8d9c6f97a93c624e992bf9aeb 100644 --- a/src/box/space.h +++ b/src/box/space.h @@ -280,6 +280,12 @@ struct space { * Linked by `coll_id_cache_holder::in_space`. */ struct rlist coll_id_holders; + /** + * A reference to the lua table representing this space. Only used + * on space drop rollback in order to keep the lua references to + * this object in sync. For more information see #9120. + */ + int lua_ref; }; /** Space alter statement. */ diff --git a/test/box-luatest/gh_9120_space_drop_rollback_test.lua b/test/box-luatest/gh_9120_space_drop_rollback_test.lua new file mode 100644 index 0000000000000000000000000000000000000000..a0716d0e1dd141eab5b7675be3bb8513d4aed33c --- /dev/null +++ b/test/box-luatest/gh_9120_space_drop_rollback_test.lua @@ -0,0 +1,99 @@ +local server = require('luatest.server') +local t = require('luatest') + +local g = t.group() + +g.before_all(function(cg) + cg.server = server:new() + cg.server:start() +end) + +g.after_all(function(cg) + cg.server:drop() +end) + +g.after_each(function(cg) + cg.server:exec(function() + if box.space.test ~= nil then + box.space.test:drop() + end + if box.space.renamed ~= nil then + box.space.renamed:drop() + end + end) +end) + +-- Checks that the space object of a dropped and rolled back space +-- is kept in sync with the space from the box.space namespace. +g.test_drop_rollback = function(cg) + cg.server:exec(function() + local s = box.schema.space.create('test') + + -- Drop the space and roll it back. + box.begin() + box.space.test:drop() + box.rollback() + + -- The local variable and the entry from the box.space + -- namespace reference the same object. + t.assert(s == box.space.test) + end) +end + +-- The same check but with data inserts and rename involved. +g.test_rename_drop_rollback = function(cg) + cg.server:exec(function() + local s = box.schema.space.create('test') + + s:create_index('pk') + s:insert({1, 1}) + + -- Rename and drop the space and roll it back. + -- Do some inserts in the way. + box.begin() + box.space.test:insert({2, 2}) + box.space.test:rename('renamed') + box.space.renamed:insert({3, 3}) + box.space.renamed:drop() + box.rollback() + + -- The local variable and the entry from the box.space + -- namespace reference the same object. + t.assert(s == box.space.test) + + -- The space still has the same name. + t.assert_equals(s.name, 'test') + + -- Contents hadn't changed. + t.assert_equals(s:select(), {{1, 1}}) + end) +end + +-- The same check but with more complex transaction with an attempt to +-- trick a system by creating another space with the same ID and name. +g.test_sophisticated_transaction = function(cg) + cg.server:exec(function() + local s = box.schema.space.create('test', {format = {'id'}}) + + s:create_index('pk') + s:insert({1, 1}) + + -- More sophisticated transaction: drop the old space, create a new one + -- with the same name but with a different format, roll everything back. + box.begin() + box.space.test:insert({2, 2}) + box.space.test:drop() + box.schema.space.create('test', {format = {'identifier'}}) + box.space.test:create_index('pk') + box.space.test:insert({3, 3}) + box.rollback() + + -- The local variable and the entry from the + -- box.space namespace are the same objects. + t.assert(s == box.space.test) + + -- The space format and contents still the same. + t.assert_equals(s:format(), {{name = 'id', type = 'any'}}) + t.assert_equals(s:select(), {{1, 1}}) + end) +end diff --git a/test/box/errinj.result b/test/box/errinj.result index fe5e9923c450f9562c07a69bc0c8709a8c5c70e4..8c4c8150f1a504cd28a35d5e29eb6b87b8ca3e82 100644 --- a/test/box/errinj.result +++ b/test/box/errinj.result @@ -418,11 +418,6 @@ s_withdata:drop() --- - error: Failed to write to disk ... --- FIXME(gh-9120): The space is recreated, so the s_withdata object is not --- valid anymore. -s_withdata = box.space.withdata ---- -... box.space['withdata'].enabled --- - true @@ -1343,11 +1338,6 @@ s:drop() --- - error: Failed to write to disk ... --- FIXME(gh-9120): The space object is invalidated on reverted drop, so we set --- it to the current one. -s = box.space.test ---- -... s:truncate() --- - error: Failed to write to disk diff --git a/test/box/errinj.test.lua b/test/box/errinj.test.lua index e2219529fa63bb13616a1947a8dbcea42825fd36..78047408d658ecca8a98ab04352b2d26e13c21f6 100644 --- a/test/box/errinj.test.lua +++ b/test/box/errinj.test.lua @@ -100,9 +100,6 @@ s_withindex.index.secondary s_withdata.index.secondary:drop() s_withdata.index.secondary.unique s_withdata:drop() --- FIXME(gh-9120): The space is recreated, so the s_withdata object is not --- valid anymore. -s_withdata = box.space.withdata box.space['withdata'].enabled index4 = s_withdata:create_index('another', { type = 'tree', parts = { 5, 'unsigned' }, unique = false}) s_withdata.index.another @@ -450,9 +447,6 @@ errinj.set('ERRINJ_WAL_IO', true) s:drop() s:truncate() s:drop() --- FIXME(gh-9120): The space object is invalidated on reverted drop, so we set --- it to the current one. -s = box.space.test s:truncate() errinj.set('ERRINJ_WAL_IO', false) for i = 1, 10 do s:replace{i + 10} end