From 2a6039c2f616a97735fde8c9645f993c3d40e7a1 Mon Sep 17 00:00:00 2001 From: Yuval Lifshitz Date: Mon, 23 May 2022 14:38:34 +0300 Subject: [PATCH] rgw/lua: add atomic increment/decrement to RGW table Signed-off-by: Yuval Lifshitz --- doc/radosgw/lua-scripting.rst | 20 +++-- src/rgw/rgw_lua_background.cc | 46 ++++++++++++ src/rgw/rgw_lua_background.h | 28 ++++++- src/test/rgw/test_rgw_lua.cc | 138 ++++++++++++++++++++++++++++++++++ 4 files changed, 226 insertions(+), 6 deletions(-) diff --git a/doc/radosgw/lua-scripting.rst b/doc/radosgw/lua-scripting.rst index 39268d8aae208..fe8c6e271653f 100644 --- a/doc/radosgw/lua-scripting.rst +++ b/doc/radosgw/lua-scripting.rst @@ -309,16 +309,26 @@ The ``Request.Log()`` function prints the requests into the operations log. This Background Context -------------------- The ``background`` context may be used for purposes that include analytics, monitoring, caching data for other context executions. +- Background script execution default interval is 5 seconds. +Global ``RGW`` Table +-------------------- The ``RGW`` Lua table is accessible from all contexts and saves data written to it during execution so that it may be read and used later during other executions, from the same context of a different one. - -- Background script execution default interval is 5 seconds. - - Each RGW instance has its own private and ephemeral ``RGW`` Lua table that is lost when the daemon restarts. Note that ``background`` context scripts will run on every instance. +- The maximum number of entries in the table is 100,000. Each entry has a string key a value with a combined length of no more than 1KB. +A Lua script will abort with an error if the number of entries or entry size exceeds these limits. +- The ``RGW`` Lua table uses string indeces and can store values of type: string, integer, double and boolean + +Increment/Decrement Functions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Since entries in the ``RGW`` table could be accessed from multiple places at the same time we need a way +to atomically increment and decrement numeric values in it. For that the following functions should be used: +- ``RGW.increment(, [value])`` would increment the value of ``key`` by ``value`` if value is provided or by 1 if not +- ``RGW.decrement(, [value])`` would decrement the value of ``key`` by ``value`` if value is provided or by 1 if not +- if the value of ``key`` is not numeric, the execution of the script would fail +- if we try to increment or decrement by non-numeric values, the execution of the script would fail -- The maximum number of entries in the table is 100,000. Each entry has a key and string value with a combined length of no more than 1KB. - A Lua script will abort with an error if the number of entries or entry size exceeds these limits. Lua Code Samples ---------------- diff --git a/src/rgw/rgw_lua_background.cc b/src/rgw/rgw_lua_background.cc index 21c5fcc1a3d9f..592848c0ff9ea 100644 --- a/src/rgw/rgw_lua_background.cc +++ b/src/rgw/rgw_lua_background.cc @@ -9,6 +9,52 @@ namespace rgw::lua { +const char* RGWTable::INCREMENT = "increment"; +const char* RGWTable::DECREMENT = "decrement"; + +int RGWTable::increment_by(lua_State* L) { + const auto map = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); + auto& mtx = *reinterpret_cast(lua_touserdata(L, lua_upvalueindex(2))); + auto decrement = lua_toboolean(L, lua_upvalueindex(3)); + + const auto args = lua_gettop(L); + const auto index = luaL_checkstring(L, 1); + + // by default we increment by 1/-1 + const auto default_inc = (decrement ? -1 : 1); + BackgroundMapValue inc_by = default_inc; + if (args == 2) { + if (lua_isinteger(L, 2)) { + inc_by = lua_tointeger(L, 2)*default_inc; + } else if (lua_isnumber(L, 2)){ + inc_by = lua_tonumber(L, 2)*static_cast(default_inc); + } else { + return luaL_error(L, "can increment only by numeric values"); + } + } + + std::unique_lock l(mtx); + + const auto it = map->find(std::string(index)); + if (it != map->end()) { + auto& value = it->second; + if (std::holds_alternative(value) && std::holds_alternative(inc_by)) { + value = std::get(value) + std::get(inc_by); + } else if (std::holds_alternative(value) && std::holds_alternative(inc_by)) { + value = std::get(value) + std::get(inc_by); + } else if (std::holds_alternative(value) && std::holds_alternative(inc_by)) { + value = std::get(value) + static_cast(std::get(inc_by)); + } else if (std::holds_alternative(value) && std::holds_alternative(inc_by)) { + value = static_cast(std::get(value)) + std::get(inc_by); + } else { + mtx.unlock(); + return luaL_error(L, "can increment only numeric values"); + } + } + + return 0; +} + Background::Background(rgw::sal::Store* store, CephContext* cct, const std::string& luarocks_path, diff --git a/src/rgw/rgw_lua_background.h b/src/rgw/rgw_lua_background.h index e2fa9a195e98f..e8ebc1cebc2b1 100644 --- a/src/rgw/rgw_lua_background.h +++ b/src/rgw/rgw_lua_background.h @@ -32,15 +32,37 @@ inline void pushvalue(lua_State* L, bool value) { lua_pushboolean(L, value); } + struct RGWTable : EmptyMetaTable { + + static const char* INCREMENT; + static const char* DECREMENT; + static std::string TableName() {return "RGW";} static std::string Name() {return TableName() + "Meta";} + static int increment_by(lua_State* L); + static int IndexClosure(lua_State* L) { - auto& mtx = *reinterpret_cast(lua_touserdata(L, lua_upvalueindex(2))); const auto map = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); + auto& mtx = *reinterpret_cast(lua_touserdata(L, lua_upvalueindex(2))); const char* index = luaL_checkstring(L, 2); + if (strcasecmp(index, INCREMENT) == 0) { + lua_pushlightuserdata(L, map); + lua_pushlightuserdata(L, &mtx); + lua_pushboolean(L, false /*increment*/); + lua_pushcclosure(L, increment_by, THREE_UPVALS); + return ONE_RETURNVAL; + } + if (strcasecmp(index, DECREMENT) == 0) { + lua_pushlightuserdata(L, map); + lua_pushlightuserdata(L, &mtx); + lua_pushboolean(L, true /*decrement*/); + lua_pushcclosure(L, increment_by, THREE_UPVALS); + return ONE_RETURNVAL; + } + std::lock_guard l(mtx); const auto it = map->find(std::string(index)); @@ -67,6 +89,10 @@ struct RGWTable : EmptyMetaTable { auto& mtx = *reinterpret_cast(lua_touserdata(L, lua_upvalueindex(2))); const auto map = reinterpret_cast(lua_touserdata(L, lua_upvalueindex(1))); const auto index = luaL_checkstring(L, 2); + + if (strcasecmp(index, INCREMENT) == 0 || strcasecmp(index, DECREMENT) == 0) { + return luaL_error(L, "increment/decrement are reserved function names for RGW"); + } std::unique_lock l(mtx); diff --git a/src/test/rgw/test_rgw_lua.cc b/src/test/rgw/test_rgw_lua.cc index 1d38619712777..3a2d43af2d35d 100644 --- a/src/test/rgw/test_rgw_lua.cc +++ b/src/test/rgw/test_rgw_lua.cc @@ -1023,3 +1023,141 @@ TEST(TestRGWLuaBackground, TableIterate) EXPECT_EQ(get_table_value(lua_background, "size"), 5); } +TEST(TestRGWLuaBackground, TableIncrement) +{ + TestBackground lua_background(""); + + const std::string request_script = R"( + RGW["key1"] = 42 + RGW["key2"] = 42.2 + RGW.increment("key1") + assert(RGW["key1"] == 43) + RGW.increment("key2") + assert(RGW["key2"] == 43.2) + )"; + + DEFINE_REQ_STATE; + + const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", request_script, &lua_background); + ASSERT_EQ(rc, 0); +} + +TEST(TestRGWLuaBackground, TableIncrementBy) +{ + TestBackground lua_background(""); + + const std::string request_script = R"( + RGW["key1"] = 42 + RGW["key2"] = 42.2 + RGW.increment("key1", 10) + assert(RGW["key1"] == 52) + RGW.increment("key2", 10) + assert(RGW["key2"] == 52.2) + RGW.increment("key1", 0.2) + assert(RGW["key1"] == 52.2) + )"; + + DEFINE_REQ_STATE; + + const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", request_script, &lua_background); + ASSERT_EQ(rc, 0); +} + +TEST(TestRGWLuaBackground, TableDecrement) +{ + TestBackground lua_background(""); + + const std::string request_script = R"( + RGW["key1"] = 42 + RGW["key2"] = 42.2 + RGW.decrement("key1") + assert(RGW["key1"] == 41) + RGW.decrement("key2") + assert(RGW["key2"] == 41.2) + )"; + + DEFINE_REQ_STATE; + + const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", request_script, &lua_background); + ASSERT_EQ(rc, 0); +} + +TEST(TestRGWLuaBackground, TableDecrementBy) +{ + TestBackground lua_background(""); + + const std::string request_script = R"( + RGW["key1"] = 42 + RGW["key2"] = 42.2 + RGW.decrement("key1", 10) + assert(RGW["key1"] == 32) + RGW.decrement("key2", 10) + assert(RGW["key2"] == 32.2) + RGW.decrement("key1", 0.8) + assert(RGW["key1"] == 31.2) + )"; + + DEFINE_REQ_STATE; + + const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", request_script, &lua_background); + ASSERT_EQ(rc, 0); +} + +TEST(TestRGWLuaBackground, TableIncrementValueError) +{ + TestBackground lua_background(""); + + std::string request_script = R"( + -- cannot increment string values + RGW["key1"] = "hello" + RGW.increment("key1") + )"; + + DEFINE_REQ_STATE; + + auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", request_script, &lua_background); + ASSERT_NE(rc, 0); + + request_script = R"( + -- cannot increment bool values + RGW["key1"] = true + RGW.increment("key1") + )"; + + rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", request_script, &lua_background); + ASSERT_NE(rc, 0); + + request_script = R"( + -- cannot increment by string values + RGW["key1"] = 99 + RGW.increment("key1", "kaboom") + )"; + + rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", request_script, &lua_background); + ASSERT_NE(rc, 0); +} + +TEST(TestRGWLuaBackground, TableIncrementError) +{ + TestBackground lua_background(""); + + std::string request_script = R"( + -- missing argument + RGW["key1"] = 11 + RGW.increment() + )"; + + DEFINE_REQ_STATE; + + auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", request_script, &lua_background); + ASSERT_NE(rc, 0); + + request_script = R"( + -- used as settable field + RGW.increment = 11 + )"; + + rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", request_script, &lua_background); + ASSERT_NE(rc, 0); +} + -- 2.39.5