Skip to content

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

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

martin-belanger
Copy link
Contributor

This pull request is mainly to add support for REDIS asynchronous APIs (from the hiredis library).

I also fixed a potential memory leak in common/rediscommand.cpp.

Martin Belanger added 2 commits October 6, 2020 14:49
…a memory leak would have occured if the methods format() or formatArgv() were to be used more than once on the same object. This change simply ensures that currently allocated memory gets freed before new memory gets allocated.
@lguohan
Copy link
Contributor

lguohan commented Oct 7, 2020

can you separate the bug fix from this pr? we can review and merge it separately.

{
redisFreeCommand(temp);
temp = nullptr;
}
Copy link
Contributor

@qiluo-msft qiluo-msft Oct 7, 2020

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 RedisCommand::format() (or RedisCommand::formatArgv()) more than once. The pointer to the leaked memory, temp, is a private member and cannot be tested outside the class RedisCommand itself. I'm running out of ideas on how to write a unit test for this. Any suggestions would be greatly appreciated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 tests/redis_state_ut.cpp, line 738, as follows:

738)  RedisCommand keys;
739)  keys.format("KEYS %s*", (c.getStateHashPrefix() + tableName).c_str());
740)  RedisReply r(&db, keys, REDIS_REPLY_ARRAY);
741)  EXPECT_EQ(r.getContext()->elements, (size_t) 0);
742)  // Verify number of objects
743)  keys.format("KEYS %s:*", tableName.c_str());
744)  RedisReply r2(&db, keys, REDIS_REPLY_ARRAY);
745)  EXPECT_EQ(r2.getContext()->elements, (size_t) numOfKeys);

At line 739 we invoke keys.format() a first time. At line 743 we invoke keys.format() a second time. The second time the function is invoked, the memory that was allocated the first time the function was invoked is not freed and will leak.

Copy link
Contributor

Choose a reason for hiding this comment

The 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.
We need more effort to review the new feature.


In reply to: 501238042 [](ancestors = 501238042)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I will submit a separate pull request

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Submitted this pull request that only contains the memory leak fix.
#392

@lguohan
Copy link
Contributor

lguohan commented Nov 26, 2020

@qiluo-msft, any update on this pr?

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;
Copy link
Contributor

@qiluo-msft qiluo-msft Nov 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

db_name_m [](start = 25, length = 9)

We are using m_cameCase as member variable name convention. For this case, suggest m_dbName #Closed

*
* @return User context pointer.
*/
void * get_user_ctx() const { return user_ctx_pm; }
Copy link
Contributor

@qiluo-msft qiluo-msft Nov 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_user_ctx [](start = 11, length = 12)

Naming convention: cameCaseForFunctionName. So suggest getUserContext() #Closed

* @endcode
*
*/
redisAsyncContext * context() const { return ac_pm; }
Copy link
Contributor

@qiluo-msft qiluo-msft Nov 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[](start = 23, length = 1)

Suggest remove extra blank after * #Closed

*
* @return The DB name associated with this connector.
*/
const std::string & db_name() const { return db_name_m; }
Copy link
Contributor

@qiluo-msft qiluo-msft Nov 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[](start = 21, length = 1)

Suggest remove extra blank before & #Closed

*
* @return The DB name associated with this connector.
*/
const std::string & db_name() const { return db_name_m; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string [](start = 15, length = 6)

Better return non reference string, so caller does not worry about scope.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for misleading, I propose to return basic std::string.


In reply to: 532125270 [](ancestors = 532125270)

* that allows the event loop to dispatch the "events" (i.e. replies
* from the REDIS server) to their corresponding callback functions.
*/
class DBConnector_async
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DBConnector_async [](start = 6, length = 17)

Could you provide use cases? We also require unit test cases for new classes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks I read the Doxygen text.
I am asking about how do you plan to use the new classes in downstream repoes? Do you already have a PR following?
Require unit test cases in this PR. That also serves as use case.


In reply to: 532125398 [](ancestors = 532125398)

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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:
https://github.com/martin-belanger/sonic-buildimage/blob/master/src/ham/hamd/hamd_redis.h
https://github.com/martin-belanger/sonic-buildimage/blob/master/src/ham/hamd/hamd_redis.cpp

This shows how hamd's GLib Main-loop is created:
https://github.com/martin-belanger/sonic-buildimage/blob/master/src/ham/hamd/hamd_main.cpp

return status;
}

int DBConnector_async::formatted_command(redisCallbackFn * cb_func_p, void * cb_data_p, const char * cmd_p, size_t len)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatted_command [](start = 23, length = 17)

command() and formatted_command fit better in the class RedisReply, or its subclass.
Is it a good idea to have a redisCallbackFn member in RedisReply, or its subclass?

Copy link
Contributor Author

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.

const std::string db_name_m;
const int db_id_m = -1;
std::string sock_addr_m;
redisAsyncContext * ac_pm = nullptr;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redisAsyncContext [](start = 4, length = 17)

Suggest create a wrapper class: RedisAsyncContext derived from RedisContext.
m_conn should point to ac_pm->c

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot derive RedisAsyncContext from RedisContext. RedisAsyncContext is not my definition. It is defined in the hiredis library (a 3rd party library).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for misleading. I did not mean redisAsyncContext in hiredis repo.
I mean in this repo, create a wrapper class: RedisAsyncContext derived from RedisContext (not redisContext in hiredis repo).


In reply to: 532126556 [](ancestors = 532126556)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that a new class named RedisContext was introduced after I initially submitted my pull request. I understand what you are asking.

By the way, since I submitted this pull request (2 months ago) I have been reassigned to a new project. I have very little time for SONiC now, but let me see see what I can do. Maybe one of my colleagues can take this over.

common/table.cpp Outdated
cmd.formatHSET(key, field_r, value_r);
return dbconn_rm.formatted_command(cb_func_p, cb_data_p, cmd.c_str(), cmd.length());
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove extra blank line

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)
Copy link
Contributor

@qiluo-msft qiluo-msft Nov 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hget [](start = 17, length = 4)

Functions in this file fit better in the class DBConnector or its subclass.
It has less relationship with the Table concept. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just following the same model that was used for class Table. Just like class Table defines Table::hget(), Table::hset(), etc., class Table_async defines Table_async::hget(), Table_async::hset(), etc.

Copy link
Contributor

@qiluo-msft qiluo-msft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As comments. Please also resolve conflicts and add testcase.


int Table_async::hget(redisCallbackFn * cb_func_p, void * cb_data_p, const std::string & key_r, const std::string & field_r)
{
std::string key = getKeyName(key_r);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[](start = 15, length = 2)

Use one blank character

@martin-belanger
Copy link
Contributor Author

I just added a new commit to address the syntax issues (camel-casing, blanks, etc.)

I will look into adding a class RedisAsyncContext derived from RedisContext when I find the time or I will find someone else to do that.

prgeor added a commit to prgeor/sonic-swss-common that referenced this pull request Feb 27, 2025
* Add new SSD type support

* Fix review comment

---------

Co-authored-by: Prince George <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants