This is an automated email from the ASF dual-hosted git repository.

wangdan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-pegasus.git


The following commit(s) were added to refs/heads/master by this push:
     new 7ceef5d2d fix(meta): fix null pointer while deleting environment 
variables after table was dropped (#2170)
7ceef5d2d is described below

commit 7ceef5d2d23eaf71e76a7c81ded7d3afe9ed060b
Author: Dan Wang <[email protected]>
AuthorDate: Tue Dec 17 15:03:25 2024 +0800

    fix(meta): fix null pointer while deleting environment variables after 
table was dropped (#2170)
    
    https://github.com/apache/incubator-pegasus/issues/2149.
    
    Previously we've fixed the problem that meta server failed due to null 
pointer while
    deleting environment variables locally immediately after a table was 
dropped in
    https://github.com/apache/incubator-pegasus/pull/2148. There's the same 
problem
    while deleting environment variables.
---
 src/meta/server_state.cpp           | 133 +++++++++++++++++++++++++-----------
 src/meta/test/server_state_test.cpp |  94 ++++++++++++++++++-------
 2 files changed, 162 insertions(+), 65 deletions(-)

diff --git a/src/meta/server_state.cpp b/src/meta/server_state.cpp
index 8e68113f7..30d5df6b0 100644
--- a/src/meta/server_state.cpp
+++ b/src/meta/server_state.cpp
@@ -25,6 +25,7 @@
  */
 
 // IWYU pragma: no_include <boost/detail/basic_pointerbuf.hpp>
+#include <boost/algorithm/string/join.hpp>
 #include <boost/lexical_cast.hpp>
 // IWYU pragma: no_include <ext/alloc_traits.h>
 #include <fmt/core.h>
@@ -33,6 +34,7 @@
 #include <chrono>
 #include <cstdint>
 #include <cstring>
+#include <iterator>
 #include <set>
 #include <sstream> // IWYU pragma: keep
 #include <string>
@@ -2986,17 +2988,17 @@ void server_state::do_update_app_info(const std::string 
&app_path,
 
 void server_state::set_app_envs(const app_env_rpc &env_rpc)
 {
-    const configuration_update_app_env_request &request = env_rpc.request();
+    const auto &request = env_rpc.request();
     if (!request.__isset.keys || !request.__isset.values ||
-        request.keys.size() != request.values.size() || request.keys.size() <= 
0) {
+        request.keys.size() != request.values.size() || request.keys.empty()) {
         env_rpc.response().err = ERR_INVALID_PARAMETERS;
         LOG_WARNING("set app envs failed with invalid request");
         return;
     }
 
-    const std::vector<std::string> &keys = request.keys;
-    const std::vector<std::string> &values = request.values;
-    const std::string &app_name = request.app_name;
+    const auto &keys = request.keys;
+    const auto &values = request.values;
+    const auto &app_name = request.app_name;
 
     std::ostringstream os;
     for (size_t i = 0; i < keys.size(); ++i) {
@@ -3078,7 +3080,7 @@ void server_state::set_app_envs(const app_env_rpc 
&env_rpc)
             }
         });
 
-        std::shared_ptr<app_state> app = get_app(app_name);
+        auto app = get_app(app_name);
 
         // The table might be removed just before the callback function is 
invoked, thus we must
         // check if this table still exists.
@@ -3095,6 +3097,8 @@ void server_state::set_app_envs(const app_env_rpc 
&env_rpc)
             return;
         }
 
+        env_rpc.response().err = ERR_OK;
+
         const auto &old_envs = dsn::utils::kv_map_to_string(app->envs, ',', 
'=');
 
         // Update envs of local memory.
@@ -3109,70 +3113,119 @@ void server_state::set_app_envs(const app_env_rpc 
&env_rpc)
 
 void server_state::del_app_envs(const app_env_rpc &env_rpc)
 {
-    const configuration_update_app_env_request &request = env_rpc.request();
-    if (!request.__isset.keys || request.keys.size() <= 0) {
+    const auto &request = env_rpc.request();
+    if (!request.__isset.keys || request.keys.empty()) {
         env_rpc.response().err = ERR_INVALID_PARAMETERS;
         LOG_WARNING("del app envs failed with invalid request");
         return;
     }
-    const std::vector<std::string> &keys = request.keys;
-    const std::string &app_name = request.app_name;
 
-    std::ostringstream os;
-    for (int i = 0; i < keys.size(); i++) {
-        if (i != 0)
-            os << ",";
-        os << keys[i];
-    }
+    const auto &keys = request.keys;
+    const auto &app_name = request.app_name;
+
     LOG_INFO("del app envs for app({}) from remote({}): keys = {}",
              app_name,
              env_rpc.remote_address(),
-             os.str());
+             boost::join(keys, ","));
 
     app_info ainfo;
     std::string app_path;
     {
+        FAIL_POINT_INJECT_NOT_RETURN_F("del_app_envs_failed", [app_name, 
this](std::string_view s) {
+            zauto_write_lock l(_lock);
+
+            if (s == "not_found") {
+                CHECK_EQ(_exist_apps.erase(app_name), 1);
+                return;
+            }
+
+            if (s == "dropping") {
+                gutil::FindOrDie(_exist_apps, app_name)->status = 
app_status::AS_DROPPING;
+                return;
+            }
+        });
+
         zauto_read_lock l(_lock);
-        std::shared_ptr<app_state> app = get_app(app_name);
-        if (app == nullptr) {
-            LOG_WARNING("del app envs failed with invalid app_name({})", 
app_name);
-            env_rpc.response().err = ERR_INVALID_PARAMETERS;
-            env_rpc.response().hint_message = "invalid app name";
+
+        const auto &app = get_app(app_name);
+        if (!app) {
+            LOG_WARNING("del app envs failed since app_name({}) cannot be 
found", app_name);
+            env_rpc.response().err = ERR_APP_NOT_EXIST;
+            env_rpc.response().hint_message = "app cannot be found";
             return;
-        } else {
-            ainfo = *(reinterpret_cast<app_info *>(app.get()));
-            app_path = get_app_path(*app);
         }
+
+        if (app->status == app_status::AS_DROPPING) {
+            LOG_WARNING("del app envs failed since app(name={}, id={}) is 
being dropped",
+                        app_name,
+                        app->app_id);
+            env_rpc.response().err = ERR_BUSY_DROPPING;
+            env_rpc.response().hint_message = "app is being dropped";
+            return;
+        }
+
+        ainfo = *app;
+        app_path = get_app_path(*app);
     }
 
-    std::ostringstream oss;
-    oss << "deleted keys:";
-    int deleted = 0;
+    std::string deleted_keys_info("deleted keys:");
+    size_t deleted_count = 0;
     for (const auto &key : keys) {
-        if (ainfo.envs.erase(key) > 0) {
-            oss << std::endl << "    " << key;
-            deleted++;
+        if (ainfo.envs.erase(key) == 0) {
+            continue;
         }
+
+        fmt::format_to(std::back_inserter(deleted_keys_info), "\n    {}", key);
+        ++deleted_count;
     }
 
-    if (deleted == 0) {
-        LOG_INFO("no key need to delete");
-        env_rpc.response().hint_message = "no key need to delete";
+    if (deleted_count == 0) {
+        LOG_INFO("no key needs to be deleted for app({})", app_name);
+        env_rpc.response().err = ERR_OK;
+        env_rpc.response().hint_message = "no key needs to be deleted";
         return;
-    } else {
-        env_rpc.response().hint_message = oss.str();
     }
 
+    env_rpc.response().hint_message = std::move(deleted_keys_info);
+
     do_update_app_info(app_path, ainfo, [this, app_name, keys, 
env_rpc](error_code ec) {
-        CHECK_EQ_MSG(ec, ERR_OK, "update app info to remote storage failed");
+        CHECK_EQ_MSG(ec, ERR_OK, "update app({}) info to remote storage 
failed", app_name);
 
         zauto_write_lock l(_lock);
-        std::shared_ptr<app_state> app = get_app(app_name);
-        std::string old_envs = dsn::utils::kv_map_to_string(app->envs, ',', 
'=');
+
+        FAIL_POINT_INJECT_NOT_RETURN_F("del_app_envs_failed", [app_name, 
this](std::string_view s) {
+            if (s == "dropped_after") {
+                CHECK_EQ(_exist_apps.erase(app_name), 1);
+                return;
+            }
+        });
+
+        auto app = get_app(app_name);
+
+        // The table might be removed just before the callback function is 
invoked, thus we must
+        // check if this table still exists.
+        //
+        // TODO(wangdan): should make updates to remote storage sequential by 
supporting atomic
+        // set, otherwise update might be missing. For example, an update is 
setting the envs
+        // while another is dropping a table. The update setting the envs does 
not contain the
+        // dropped state. Once it is applied by remote storage after another 
update dropping
+        // the table, the state of the table would always be non-dropped on 
remote storage.
+        if (!app) {
+            LOG_ERROR("del app envs failed since app({}) has just been 
dropped", app_name);
+            env_rpc.response().err = ERR_APP_DROPPED;
+            env_rpc.response().hint_message = "app has just been dropped";
+            return;
+        }
+
+        env_rpc.response().err = ERR_OK;
+
+        const auto &old_envs = dsn::utils::kv_map_to_string(app->envs, ',', 
'=');
+
         for (const auto &key : keys) {
             app->envs.erase(key);
         }
-        std::string new_envs = dsn::utils::kv_map_to_string(app->envs, ',', 
'=');
+
+        const auto &new_envs = dsn::utils::kv_map_to_string(app->envs, ',', 
'=');
         LOG_INFO("app envs changed: old_envs = {}, new_envs = {}", old_envs, 
new_envs);
     });
 }
diff --git a/src/meta/test/server_state_test.cpp 
b/src/meta/test/server_state_test.cpp
index d84d586fe..90af81185 100644
--- a/src/meta/test/server_state_test.cpp
+++ b/src/meta/test/server_state_test.cpp
@@ -132,6 +132,21 @@ public:
         return rpc;
     }
 
+    void test_set_app_envs(const std::string &app_name,
+                           const std::vector<std::string> &env_keys,
+                           const std::vector<std::string> &env_vals,
+                           const error_code expected_err)
+    {
+        configuration_update_app_env_request request;
+        request.__set_app_name(app_name);
+        request.__set_op(app_env_operation::type::APP_ENV_OP_SET);
+        request.__set_keys(env_keys);
+        request.__set_values(env_vals);
+
+        auto rpc = set_app_envs(request);
+        ASSERT_EQ(expected_err, rpc.response().err);
+    }
+
     app_env_rpc del_app_envs(const configuration_update_app_env_request 
&request)
     {
         auto rpc = create_app_env_rpc(request);
@@ -141,6 +156,19 @@ public:
         return rpc;
     }
 
+    void test_del_app_envs(const std::string &app_name,
+                           const std::vector<std::string> &env_keys,
+                           const error_code expected_err)
+    {
+        configuration_update_app_env_request request;
+        request.__set_app_name(app_name);
+        request.__set_op(app_env_operation::type::APP_ENV_OP_DEL);
+        request.__set_keys(env_keys);
+
+        auto rpc = del_app_envs(request);
+        ASSERT_EQ(expected_err, rpc.response().err);
+    }
+
     app_env_rpc clear_app_envs(const configuration_update_app_env_request 
&request)
     {
         auto rpc = create_app_env_rpc(request);
@@ -220,22 +248,21 @@ void meta_service_test_app::app_envs_basic_test()
     test.load_apps({"test_app1",
                     "test_set_app_envs_not_found",
                     "test_set_app_envs_dropping",
-                    "test_set_app_envs_dropped_after"});
+                    "test_set_app_envs_dropped_after",
+                    "test_del_app_envs_not_found",
+                    "test_del_app_envs_dropping",
+                    "test_del_app_envs_dropped_after"});
 
 #define TEST_SET_APP_ENVS_FAILED(action, err_code)                             
                    \
     std::cout << "test server_state::set_app_envs(" #action ")..." << 
std::endl;                   \
     do {                                                                       
                    \
-        configuration_update_app_env_request request;                          
                    \
-        request.__set_app_name("test_set_app_envs_" #action);                  
                    \
-        request.__set_op(app_env_operation::type::APP_ENV_OP_SET);             
                    \
-        request.__set_keys({replica_envs::ROCKSDB_WRITE_BUFFER_SIZE});         
                    \
-        request.__set_values({"67108864"});                                    
                    \
-                                                                               
                    \
         fail::setup();                                                         
                    \
         fail::cfg("set_app_envs_failed", "void(" #action ")");                 
                    \
                                                                                
                    \
-        auto rpc = test.set_app_envs(request);                                 
                    \
-        ASSERT_EQ(err_code, rpc.response().err);                               
                    \
+        test.test_set_app_envs("test_set_app_envs_" #action,                   
                    \
+                               {replica_envs::ROCKSDB_WRITE_BUFFER_SIZE},      
                    \
+                               {"67108864"},                                   
                    \
+                               err_code);                                      
                    \
                                                                                
                    \
         fail::teardown();                                                      
                    \
     } while (0)
@@ -255,14 +282,7 @@ void meta_service_test_app::app_envs_basic_test()
     // Normal case for setting envs.
     std::cout << "test server_state::set_app_envs(success)..." << std::endl;
     {
-        configuration_update_app_env_request request;
-        request.__set_app_name("test_app1");
-        request.__set_op(app_env_operation::type::APP_ENV_OP_SET);
-        request.__set_keys(keys);
-        request.__set_values(values);
-
-        auto rpc = test.set_app_envs(request);
-        ASSERT_EQ(ERR_OK, rpc.response().err);
+        test.test_set_app_envs("test_app1", keys, values, ERR_OK);
 
         const auto &app = test.get_app("test_app1");
         ASSERT_TRUE(app);
@@ -276,15 +296,39 @@ void meta_service_test_app::app_envs_basic_test()
         }
     }
 
-    std::cout << "test server_state::del_app_envs()..." << std::endl;
-    {
-        configuration_update_app_env_request request;
-        request.__set_app_name("test_app1");
-        request.__set_op(app_env_operation::type::APP_ENV_OP_DEL);
-        request.__set_keys(del_keys);
+#define TEST_DEL_APP_ENVS_FAILED(action, err_code)                             
                    \
+    std::cout << "test server_state::del_app_envs(" #action ")..." << 
std::endl;                   \
+    do {                                                                       
                    \
+        test.test_set_app_envs("test_del_app_envs_" #action,                   
                    \
+                               {replica_envs::ROCKSDB_WRITE_BUFFER_SIZE},      
                    \
+                               {"67108864"},                                   
                    \
+                               ERR_OK);                                        
                    \
+                                                                               
                    \
+        fail::setup();                                                         
                    \
+        fail::cfg("del_app_envs_failed", "void(" #action ")");                 
                    \
+                                                                               
                    \
+        test.test_del_app_envs(                                                
                    \
+            "test_del_app_envs_" #action, 
{replica_envs::ROCKSDB_WRITE_BUFFER_SIZE}, err_code);    \
+                                                                               
                    \
+        fail::teardown();                                                      
                    \
+    } while (0)
+
+    // Failed to deleting envs while table was not found.
+    TEST_DEL_APP_ENVS_FAILED(not_found, ERR_APP_NOT_EXIST);
+
+    // Failed to deleting envs while table was being dropped as the 
intermediate state.
+    TEST_DEL_APP_ENVS_FAILED(dropping, ERR_BUSY_DROPPING);
+
+    // The table was found dropped after the new envs had been persistent on 
the remote
+    // meta storage.
+    TEST_DEL_APP_ENVS_FAILED(dropped_after, ERR_APP_DROPPED);
 
-        auto rpc = test.del_app_envs(request);
-        ASSERT_EQ(ERR_OK, rpc.response().err);
+#undef TEST_DEL_APP_ENVS_FAILED
+
+    // Normal case for deleting envs.
+    std::cout << "test server_state::del_app_envs(success)..." << std::endl;
+    {
+        test.test_del_app_envs("test_app1", del_keys, ERR_OK);
 
         const auto &app = test.get_app("test_app1");
         ASSERT_TRUE(app);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to