From bccf509ca0eceff5f9623507fc2cf24e967a748e Mon Sep 17 00:00:00 2001 From: Nitromelon Date: Mon, 25 Apr 2022 19:42:25 +0800 Subject: [PATCH] A workaround for redis sync exec (#1216) --- .../redis/inc/drogon/nosql/RedisClient.h | 59 ++++++++++- nosql_lib/redis/tests/redis_test.cc | 98 ++++++++++++++++++- 2 files changed, 155 insertions(+), 2 deletions(-) diff --git a/nosql_lib/redis/inc/drogon/nosql/RedisClient.h b/nosql_lib/redis/inc/drogon/nosql/RedisClient.h index 2f63fb38..590da5eb 100644 --- a/nosql_lib/redis/inc/drogon/nosql/RedisClient.h +++ b/nosql_lib/redis/inc/drogon/nosql/RedisClient.h @@ -21,6 +21,7 @@ #include #include #include +#include #ifdef __cpp_impl_coroutine #include #endif @@ -61,7 +62,7 @@ struct [[nodiscard]] RedisAwaiter : public CallbackAwaiter }; struct [[nodiscard]] RedisTransactionAwaiter - : public CallbackAwaiter > + : public CallbackAwaiter> { RedisTransactionAwaiter(RedisClient *client) : client_(client) { @@ -122,6 +123,62 @@ class DROGON_EXPORT RedisClient string_view command, ...) noexcept = 0; + /** + * @brief Execute a redis command synchronously + * + * @param processFunc Function to extract data from redis result. + * received successfully. + * @param command The command to be executed. the command string can contain + * some placeholders for parameters, such as '%s', '%d', etc. + * @param ... The command parameters. + * @return Returns the same value as process callback. + * For example: + * @code + try + { + std::string res = redisClientPtr->execCommandSync( + [](const RedisResult &r){ + return r.asString(); + }, + "get %s", + key.data() + ); + } + catch (const RedisException & err) + { + } + catch (const std::exception & err) + { + } + @endcode + */ + template + T execCommandSync(std::function &&processFunc, + string_view command, + Args &&...args) + { + std::shared_ptr> pro(new std::promise); + std::future f = pro->get_future(); + execCommandAsync( + [process = std::move(processFunc), pro](const RedisResult &result) { + try + { + pro->set_value(process(result)); + } + catch (...) + { + pro->set_exception(std::current_exception()); + } + }, + [pro](const RedisException &err) { + pro->set_exception(std::make_exception_ptr(err)); + }, + command, + std::forward(args)...); + + return f.get(); + } + /** * @brief Create a redis transaction object. * diff --git a/nosql_lib/redis/tests/redis_test.cc b/nosql_lib/redis/tests/redis_test.cc index 011fa578..dcb4014d 100644 --- a/nosql_lib/redis/tests/redis_test.cc +++ b/nosql_lib/redis/tests/redis_test.cc @@ -41,7 +41,7 @@ DROGON_TEST(RedisTest) MANDATE(r.asArray().size() == 1UL); }, [TEST_CTX](const std::exception &err) { MANDATE(err.what()); }, - "keys *"); + "keys id_*"); // 5 redisClient->execCommandAsync( [TEST_CTX](const drogon::nosql::RedisResult &r) { @@ -80,6 +80,102 @@ DROGON_TEST(RedisTest) }; drogon::sync_wait(coro_test()); #endif + + // 9. Test sync + try + { + auto res = redisClient->execCommandSync( + [](const RedisResult &result) { return result.asString(); }, + "set %s %s", + "sync_key", + "sync_value"); + MANDATE(res == "OK"); + } + catch (const RedisException &err) + { + MANDATE(err.what()); + } + + try + { + auto [isNull, str] = + redisClient->execCommandSync>( + [](const RedisResult &result) -> std::pair { + if (result.isNil()) + { + return {true, ""}; + } + return {false, result.asString()}; + }, + "get %s", + "sync_key"); + MANDATE(isNull == false); + MANDATE(str == "sync_value"); + } + catch (const RedisException &err) + { + MANDATE(err.what()); + } + + // 10. Test sync redis exception + try + { + auto [isNull, str] = + redisClient->execCommandSync>( + [](const RedisResult &result) -> std::pair { + if (result.isNil()) + { + return {true, ""}; + } + return {false, result.asString()}; + }, + "get %s %s", + "sync_key", + "sync_key"); + MANDATE(false); + } + catch (const RedisException &err) + { + LOG_INFO << "Successfully catch sync error: " << err.what(); + MANDATE(err.code() == RedisErrorCode::kRedisError); + SUCCESS(); + } + + // 11. Test sync process function exception + try + { + auto value = redisClient->execCommandSync( + [](const RedisResult &result) { + if (result.isNil()) + { + throw std::runtime_error("Key not exists"); + } + return result.asString(); + }, + "get %s", + "not_exists"); + MANDATE(false); + } + catch (const RedisException &err) + { + MANDATE(false); + } + catch (const std::runtime_error &err) + { + MANDATE(std::string("Key not exists") == err.what()); + SUCCESS(); + } + + try + { + redisClient->execCommandSync([](const RedisResult &) { return 1; }, + "del %s", + "sync_key"); + } + catch (const RedisException &err) + { + MANDATE(err.what()); + } } int main(int argc, char **argv)