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
feat(ds): list disconnected persistent sessions in clients API #12500
Conversation
eb2703e
to
3615cec
Compare
%% Internal exports | ||
%%-------------------------------------------------------------------- | ||
|
||
lookup_clientid_ets(ClientId, FormatFun) -> |
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.
Rename to lookup_running_client
?
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.
👍🏼
no_persistent_sessions() -> | ||
case emqx_persistent_message:is_persistence_enabled() of | ||
true -> | ||
emqx_persistent_session_ds_state:is_end_of_table(init_persistent_session_iterator()); |
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.
Q: Why use separate function here while there's a simple comparison with $end_of_table
down below?
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.
Good question. In my first attempt, it was just like that. But dialyzer complained about such a comparison here (because the iterator is opaque), so I had to introduce this function...
Curiously enough, it's not complaining about the use case in the function guard, in this same file. 🤔
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.
Maybe we could make that typespec:
-opaque session_iterator_inner() :: emqx_persistent_session_ds:id().
-type session_iterator() :: session_iterator_inner() | '$end_of_table'.
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.
This spec feels a bit odd to me: hard to make sense of a union of opaque type and non-opaque type. I think it's better either to use is_end_of_table/1
everywhere, to turn it into a regular type, or to change iteration function's contract into something like next(iterator()) -> {_Items, {more, iterator()} | fin}
.
17fea76
to
c1a7180
Compare
|
||
ok |
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.
Nit: not needed?
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.
Those ok
s are just for ease of adding new lines without adding commas to the diff. e.g.: ?assert(...).
would become ?assert(...),
just to add a newline.
I think those ok
s are innocuous, hence I prefer to leave them there.
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.
Not sure if we have to optimize code for "minimal diffs" metric. I would prefer to optimize for "readability at any given moment of time".
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.
Sure, no need to optimize. Still, in my opinion, this extra line does not hinder readability. 🤔
|
||
ok |
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.
Nit: not needed?
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.
Those ok
s are just for ease of adding new lines without adding commas to the diff. e.g.: ?assert(...).
would become ?assert(...),
just to add a newline.
I think those ok
s are innocuous, hence I prefer to leave them there.
|
||
ok |
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.
Same here?
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.
Those ok
s are just for ease of adding new lines without adding commas to the diff. e.g.: ?assert(...).
would become ?assert(...),
just to add a newline.
I think those ok
s are innocuous, hence I prefer to leave them there.
{error, _} -> X | ||
end. | ||
|
||
list_request(Port) -> |
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.
Is it possible to get IP and port from the CT Config variable or emqx config?
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.
Could be, I think it's a matter of style/choice.
|
||
t_persistent_sessions5(Config) -> | ||
[N1, N2] = ?config(nodes, Config), | ||
APIPort = 18084, |
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.
Why is it using non-standard port?
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.
Because the suite starts the management on the default port for the other tests.
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.
And why can't we reuse the default setup?
Also, is it possible to call the Erlang interface directly, bypassing conversion from/to JSON and the HTTP?
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.
And why can't we reuse the default setup?
Those tests are using an isolated, clustered setup. To avoid having to stop the running applications from the suite, it's simpler to just set another port here.
Also, is it possible to call the Erlang interface directly, bypassing conversion from/to JSON and the HTTP?
I believe it's best to test the user-facing API in this case, since it's the object of the test. Specially during the bridge refactoring tasks, we observed that tests that don't use such APIs tend to hinder future changes to the code.
c1a7180
to
4206931
Compare
%% N.B.: this is potentially costly. Should not be called in hot paths. | ||
%% `mnesia:table_info(_, size)' is always zero for rocksdb, so we need to traverse... | ||
do_session_count(make_session_iterator(), 0). | ||
|
||
-spec make_session_iterator() -> session_iterator(). | ||
make_session_iterator() -> | ||
case mnesia:dirty_first(?session_tab) of |
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.
Q: What this case clause is for?
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.
Good question. I didn't introduce it, but seems like we could "eta reduce" it. 🤔
-opaque session_iterator_inner() :: emqx_persistent_session_ds:id(). | ||
-type session_iterator() :: session_iterator_inner() | '$end_of_table'. |
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.
At first glance it honestly looks essentially equivalent to -type session_iterator() :: emqx_persistent_session_ds:id() | '$end_of_table'.
.
e161491
to
3db3fd6
Compare
@@ -53,7 +59,7 @@ | |||
|
|||
-type subscriptions() :: emqx_topic_gbt:t(_SubId, emqx_persistent_session_ds:subscription()). | |||
|
|||
-opaque session_iterator() :: emqx_persistent_session_ds:id() | '$end_of_table'. | |||
-type session_iterator() :: emqx_persistent_session_ds:id() | '$end_of_table'. |
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.
What triggered the change from opaque to transparent? Does the new code rely on the internal structure of the iterator? Ideally, thatt should be avoided, because I am planning to change the storage from mnesia to DS in nearest future, so it would be helpful to contain the algorithm for iteration in one place.
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.
What triggered this was the check for no persistent sessions, and also if the iteration has reached the end of the table. Basically, dialyzer complained about equality comparison with $end_of_table
.
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.
P.S. I think the return type of session_interator_next
can be changed like this:
-spec session_iterator_next(session_iterator(), pos_integer()) ->
{[{emqx_persistent_session_ds:id(), metadata()}], session_iterator() | '$end_of_table'}.
...Hopefully it will satisfy dialyzer.
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.
Good idea, I'll adjust it.
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.
Actually, checking for an empty table won't work with this, but I'll try another approach.
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.
This check is just comparing the first batch with []
and batch size = 1? I think this can be done in the mgmt code to keep the interface of the CRUD module lean and clean.
3db3fd6
to
8078684
Compare
do_session_count('$end_of_table', N) -> | ||
N; | ||
do_session_count(Cursor, N) -> | ||
do_session_count(mnesia:dirty_next(?session_tab, Cursor), N + 1). |
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.
Can be implemented via session_iterator_next
instead of mnesia:dirty_next()
? The parent function already uses make_session_iterator()
...
8078684
to
977690a
Compare
Fixes https://emqx.atlassian.net/browse/EMQX-11540 Note that not all information provided by disconnected in-memory sessions is available to disconnected persistent sessions, nor does all of them make sense.
977690a
to
3a4c7f6
Compare
@@ -359,17 +369,18 @@ del_rank(Key, Rec) -> | |||
fold_ranks(Fun, Acc, Rec) -> | |||
gen_fold(ranks, Fun, Acc, Rec). | |||
|
|||
-spec session_count() -> non_neg_integer(). |
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.
Ideally, I would prefer to move this function to mgmt. This module was designed to encapsulate access to the session states, completely devoid of the business logic, rather than a convenient place for the helper functions.
If we expose a session count function from this module, to me it means that we commit ourselves to providing session count to business logic. But since the current implementation requires a full sweep of the table, perhaps it's best to avoid it for now.
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.
Good point. I'll fix this in a follow up.
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.
Fixes https://emqx.atlassian.net/browse/EMQX-11540
Note that not all information provided by disconnected in-memory sessions is available to
disconnected persistent sessions, nor does all of them make sense.
Release version: v/e5.7
Summary
PR Checklist
Please convert it to a draft if any of the following conditions are not met. Reviewers may skip over until all the items are checked:
changes/(ce|ee)/(feat|perf|fix|breaking)-<PR-id>.en.md
filesChecklist for CI (.github/workflows) changes
changes/
dir for user-facing artifacts update