-
Notifications
You must be signed in to change notification settings - Fork 342
Add REDIS async support #390
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |
| #include <memory> | ||
|
|
||
| #include <hiredis/hiredis.h> | ||
| #include <hiredis/async.h> // redisAsyncContext | ||
| #include "rediscommand.h" | ||
| #include "redisreply.h" | ||
| #define EMPTY_NAMESPACE std::string() | ||
|
|
@@ -171,5 +172,148 @@ void DBConnector::hmset(const std::string &key, InputIterator start, InputIterat | |
| RedisReply r(this, shmset, REDIS_REPLY_STATUS); | ||
| } | ||
|
|
||
|
|
||
|
|
||
| /******************************************************************************* | ||
| / \ ___ _ _ _ __ ___ | ||
| / _ \ / __| | | | '_ \ / __| | ||
| / ___ \\__ \ |_| | | | | (__ | ||
| /_/ \_\___/\__, |_| |_|\___| | ||
| |___/ | ||
| *******************************************************************************/ | ||
|
|
||
| /** | ||
| * @brief This uses hiredis asynchronous APIs (hiredis/async.h) to connect | ||
| * to the REDIS server. | ||
| * | ||
| * @details Asyncronous architecture means that the application is designed | ||
| * around an event loop that calls callback functions for each event. | ||
| * The hiredis library provides a number of adapters for 3rd party | ||
| * event loop libraries such as GLib, libevent, qt, etc. | ||
| * Ref: <a href="https://github.com/redis/hiredis/tree/master/adapters">hiredis/adapters/[*.h]</a> | ||
| * | ||
| * No additional thread is created when using the async hiredis APIs. | ||
| * The hiredis context is simply "hooked up" to the event loop and | ||
| * that allows the event loop to dispatch the "events" (i.e. replies | ||
| * from the REDIS server) to their corresponding callback functions. | ||
| */ | ||
| class DBConnector_async | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Could you provide use cases? We also require unit test cases for new classes.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use cases are described in the Doxygen text above. Basically, this allows designing processes around an event loop such as GLib, libevent, qt, etc. as described in hiredis documentation: https://github.com/redis/hiredis/tree/master/adapters
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks I read the Doxygen text. In reply to: 532125398 [](ancestors = 532125398)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will be used by hamd (pull request: sonic-net/sonic-buildimage#5553). hamd is the Host Account Management Daemon. It is designed as an asynchronous, non-blocking, event-driven process. It uses the GLib event-driven main-loop framework (ref. https://www.freedesktop.org/software/gstreamer-sdk/data/docs/latest/glib/glib-The-Main-Event-Loop.html). The hiredis library provides GLib bindings. hamd uses the hiredis asynchronous APIs to make non-blocking, asynchronous, requests to the REDIS server. When responses or events are received from the REDIS server, they get dispatched to callback functions by the GLib main-loop. The following files show how hamd uses the new async APIs: This shows how hamd's GLib Main-loop is created: |
||
| { | ||
| public: | ||
| /** | ||
| * @brief Asynchronous Connector object to the REDIS server. | ||
| * | ||
| * @param db_name_p Name of the DB we want to connect to (e.g. CONFIG_DB, | ||
| * APPL_DB, ...) | ||
| * | ||
| * @param user_ctx_p Optional user context (future use). | ||
| */ | ||
| DBConnector_async(const std::string & db_name_p, | ||
| void * user_ctx_p=nullptr); | ||
| ~DBConnector_async(); | ||
|
|
||
| /** | ||
| * @brief Get the hiredis context | ||
| * | ||
| * @return Return the hiredis async connection context. This API should be | ||
| * used when your application needs to hook up the hiredis context | ||
| * to the event loop. For example, if your application uses the | ||
| * GLib main loop, you would hook up the hiredis context as in the | ||
| * example below: | ||
| * | ||
| * @code | ||
| * // -------------------------------------------------------------------- | ||
| * // Example showing how to hook up the hiredis context retrived | ||
| * // from a DBConnector_async object to a GLib main event loop. | ||
| * -------------------------------------------------------------------- | ||
| * #include <glib.h> // g_main_context_default() | ||
| * #include <hiredis/adapters/glib.h> // redis_source_new() | ||
| * #include "dbconnector.h" // class DBConnector_async | ||
| * | ||
| * GMainContext * main_ctx_p = g_main_context_default(); | ||
| * GMainLoop * loop_p = g_main_loop_new(main_ctx_p, FALSE); | ||
| * . | ||
| * . | ||
| * | ||
| * DBConnector_async db("APPL_DB"); | ||
| * . | ||
| * . | ||
| * | ||
| * // Hook up hiredis context to GLib main loop | ||
| * g_source_attach(redis_source_new(db.context()), main_ctx_p); | ||
| * . | ||
| * . | ||
| * | ||
| * g_main_loop_run(loop_p); | ||
| * | ||
| * @endcode | ||
| * | ||
| */ | ||
| redisAsyncContext * context() const { return ac_pm; } | ||
|
||
|
|
||
| /** | ||
| * @brief Return the user context that was provided in the constructor. | ||
| * | ||
| * @return User context pointer. | ||
| */ | ||
| void * get_user_ctx() const { return user_ctx_pm; } | ||
|
||
|
|
||
| /** | ||
| * @brief Get the DB name (e.g. "CONFIG_DB", "APPL_DB", etc.). This is the | ||
| * same name that was provided to the constructor. | ||
| * | ||
| * @return The DB name associated with this connector. | ||
| */ | ||
| const std::string & db_name() const { return db_name_m; } | ||
|
||
|
|
||
| /** | ||
| * @brief Get the DB ID (e.g. 0 for "APPL_DB", 4 for "CONFIG_DB", etc.) | ||
| * | ||
| * @return The DB ID associated with this connector. | ||
| */ | ||
| int db_id() const { return db_id_m; } | ||
|
|
||
| /** | ||
| * @brief Get the socket address associated with this connector. | ||
| * | ||
| * @return The Unix Domain Socket name. | ||
| */ | ||
| const std::string & sock_addr() const { return sock_addr_m; } | ||
|
|
||
| /** | ||
| * @brief Invoke a REDIS a command | ||
| * | ||
| * @param cb_func_p Callback function to be invoked when reply is received | ||
| * @param cb_data_p User data passed to the callback function | ||
| * @param format A format string similar to printf(), but specific to | ||
| * hiredis. For more info refer to the documentation for <a | ||
| * href="https://github.com/redis/hiredis/blob/master/hiredis.c#L531">redisFormatCommand()</a>. | ||
| * | ||
| * @return 0 on success, otherwise the status returned by | ||
| * redisvAsyncCommand() | ||
| */ | ||
| int command(redisCallbackFn * cb_func_p, void * cb_data_p, const char * format, ...); | ||
|
|
||
| /** | ||
| * @brief Invoke a REDIS a command | ||
| * | ||
| * @param cb_func_p Callback function to be invoked when reply is received | ||
| * @param cb_data_p User data passed to the callback function | ||
| * @param cmd A formatted command to be sent to the REDIS server | ||
| * @param len The length of %cmd | ||
| * | ||
| * @return 0 on success, otherwise the status returned by | ||
| * redisAsyncFormattedCommand() | ||
| */ | ||
| int formatted_command(redisCallbackFn * cb_func_p, void * cb_data_p, const char * cmd_p, size_t len); | ||
|
|
||
| private: | ||
| const std::string db_name_m; | ||
|
||
| const int db_id_m = -1; | ||
| std::string sock_addr_m; | ||
| redisAsyncContext * ac_pm = nullptr; | ||
|
||
| void * user_ctx_pm = nullptr; | ||
| }; | ||
|
|
||
| } | ||
| #endif | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,12 @@ RedisCommand::~RedisCommand() | |
|
|
||
| void RedisCommand::format(const char *fmt, ...) | ||
| { | ||
| if (temp != nullptr) | ||
| { | ||
| redisFreeCommand(temp); | ||
| temp = nullptr; | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a valid bug fix. Could you please add a unit test, so it will fail on old code but pass with your fix? #Closed
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi @qiluo-msft . I'm not sure how to add a unit test for this. The leak will only happen if someone calls
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BTW, while looking at the existing unit tests, I found a place where a memory leak would occur. The leak is in file At line 739 we invoke
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Understand the difficulty. Could you separate the bug fix from this pr? The bug fix could be merged sooner. In reply to: 501238042 [](ancestors = 501238042)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok I will submit a separate pull request
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Submitted this pull request that only contains the memory leak fix. |
||
|
|
||
| va_list ap; | ||
| va_start(ap, fmt); | ||
| int len = redisvFormatCommand(&temp, fmt, ap); | ||
|
|
@@ -31,6 +37,12 @@ void RedisCommand::format(const char *fmt, ...) | |
|
|
||
| void RedisCommand::formatArgv(int argc, const char **argv, const size_t *argvlen) | ||
| { | ||
| if (temp != nullptr) | ||
| { | ||
| redisFreeCommand(temp); | ||
| temp = nullptr; | ||
| } | ||
|
|
||
| int len = redisFormatCommandArgv(&temp, argc, argv, argvlen); | ||
| if (len == -1) { | ||
| throw std::bad_alloc(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -241,3 +241,46 @@ string Table::stripSpecialSym(const string &key) | |
|
|
||
| return key; | ||
| } | ||
|
|
||
|
|
||
| /******************************************************************************* | ||
| / \ ___ _ _ _ __ ___ | ||
| / _ \ / __| | | | '_ \ / __| | ||
| / ___ \\__ \ |_| | | | | (__ | ||
| /_/ \_\___/\__, |_| |_|\___| | ||
| |___/ | ||
| *******************************************************************************/ | ||
| Table_async::Table_async(DBConnector_async & dbconn_r, const std::string & table_name_r) : | ||
| TableBase(table_name_r, SonicDBConfig::getSeparator(dbconn_r.db_name())), | ||
| dbconn_rm(dbconn_r) | ||
| { | ||
| } | ||
|
|
||
| Table_async::~Table_async() | ||
| { | ||
| } | ||
|
|
||
| int Table_async::hdel(redisCallbackFn * cb_func_p, void * cb_data_p, const std::string & key_r, const std::string & field_r) | ||
| { | ||
| std::string key = getKeyName(key_r); | ||
| swss::RedisCommand cmd; | ||
| cmd.formatHDEL(key, field_r); | ||
| return dbconn_rm.formatted_command(cb_func_p, cb_data_p, cmd.c_str(), cmd.length()); | ||
| } | ||
|
|
||
| int Table_async::hget(redisCallbackFn * cb_func_p, void * cb_data_p, const std::string & key_r, const std::string & field_r) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Functions in this file fit better in the class
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm just following the same model that was used for |
||
| { | ||
| std::string key = getKeyName(key_r); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Use one blank character |
||
| swss::RedisCommand cmd; | ||
| cmd.formatHGET(key, field_r); | ||
| return dbconn_rm.formatted_command(cb_func_p, cb_data_p, cmd.c_str(), cmd.length()); | ||
| } | ||
|
|
||
| int Table_async::hset(redisCallbackFn * cb_func_p, void * cb_data_p, const std::string & key_r, const std::string & field_r, const std::string & value_r) | ||
| { | ||
| std::string key = getKeyName(key_r); | ||
| swss::RedisCommand cmd; | ||
| cmd.formatHSET(key, field_r, value_r); | ||
| return dbconn_rm.formatted_command(cb_func_p, cb_data_p, cmd.c_str(), cmd.length()); | ||
| } | ||
|
|
||
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
command()andformatted_commandfit better in the classRedisReply, or its subclass.Is it a good idea to have a
redisCallbackFnmember inRedisReply, or its subclass?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The RedisReply class is designed specifically for synchronous calls (i.e. blocking calls). When using the Async library (i.e. non-blocking) we do not wait for a reply. The reply comes back at a later time and is dispatched by the event loop (implemented by GLib, libevent, qt, or others) to a callback function.
Async is a completely different approach and the RedisReply does not apply.