-
Notifications
You must be signed in to change notification settings - Fork 1.1k
chore: remove locks from acl validation in the hot path #1765
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
Changes from 2 commits
90b623f
627581f
a3bfb88
d0b8752
ed4484c
01efc46
739581e
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 |
---|---|---|
|
@@ -3,6 +3,8 @@ | |
|
||
#include "server/acl/acl_family.h" | ||
|
||
#include <glog/logging.h> | ||
|
||
#include <cctype> | ||
#include <optional> | ||
#include <variant> | ||
|
@@ -12,6 +14,7 @@ | |
#include "absl/strings/match.h" | ||
#include "absl/strings/str_cat.h" | ||
#include "core/overloaded.h" | ||
#include "facade/dragonfly_connection.h" | ||
#include "facade/facade_types.h" | ||
#include "server/acl/acl_commands_def.h" | ||
#include "server/command_registry.h" | ||
|
@@ -46,6 +49,9 @@ static std::string AclToString(uint32_t acl_category) { | |
return tmp; | ||
} | ||
|
||
AclFamily::AclFamily(util::ProactorPool& pp) : pp_(pp) { | ||
} | ||
|
||
void AclFamily::Acl(CmdArgList args, ConnectionContext* cntx) { | ||
(*cntx)->SendError("Wrong number of arguments for acl command"); | ||
} | ||
|
@@ -157,12 +163,31 @@ std::variant<User::UpdateRequest, ErrorReply> ParseAclSetUser(CmdArgList args) { | |
|
||
} // namespace | ||
|
||
void AclFamily::StreamUpdatesToAllProactorConnections(std::string_view user, uint32_t update_cat) { | ||
auto update_cb = [user, update_cat]([[maybe_unused]] size_t id, util::Connection* conn) { | ||
DCHECK(conn); | ||
auto connection = static_cast<facade::Connection*>(conn); | ||
auto ctx = static_cast<ConnectionContext*>(connection->cntx()); | ||
if (ctx && user == ctx->authed_username) { | ||
ctx->acl_categories = update_cat; | ||
} | ||
}; | ||
|
||
pp_.AwaitFiberOnAll([this, update_cb](util::ProactorBase* pb) { | ||
for (auto& listener : listeners_) { | ||
listener->TraverseConnections(update_cb); | ||
} | ||
}); | ||
} | ||
|
||
void AclFamily::SetUser(CmdArgList args, ConnectionContext* cntx) { | ||
std::string_view username = facade::ToSV(args[0]); | ||
auto req = ParseAclSetUser(args.subspan(1)); | ||
auto error_case = [cntx](ErrorReply&& error) { (*cntx)->SendError(error); }; | ||
auto update_case = [username, cntx](User::UpdateRequest&& req) { | ||
auto update_case = [username, cntx, this](User::UpdateRequest&& req) { | ||
ServerState::tlocal()->user_registry->MaybeAddAndUpdate(username, std::move(req)); | ||
auto cred = ServerState::tlocal()->user_registry->GetCredentials(username); | ||
StreamUpdatesToAllProactorConnections(username, cred.acl_categories); | ||
|
||
(*cntx)->SendOk(); | ||
}; | ||
|
||
|
@@ -171,8 +196,15 @@ void AclFamily::SetUser(CmdArgList args, ConnectionContext* cntx) { | |
|
||
using CI = dfly::CommandId; | ||
|
||
#define HFUNC(x) SetHandler(&AclFamily::x) | ||
using MemberFunc = void (AclFamily::*)(CmdArgList args, ConnectionContext* cntx); | ||
|
||
inline CommandId::Handler HandlerFunc(AclFamily* acl, MemberFunc f) { | ||
return [=](CmdArgList args, ConnectionContext* cntx) { return (acl->*f)(args, cntx); }; | ||
} | ||
|
||
#define HFUNC(x) SetHandler(HandlerFunc(this, &AclFamily::x)) | ||
|
||
constexpr uint32_t kAcl = acl::CONNECTION; | ||
constexpr uint32_t kList = acl::ADMIN | acl::SLOW | acl::DANGEROUS; | ||
constexpr uint32_t kSetUser = acl::ADMIN | acl::SLOW | acl::DANGEROUS; | ||
|
||
|
@@ -184,7 +216,7 @@ constexpr uint32_t kSetUser = acl::ADMIN | acl::SLOW | acl::DANGEROUS; | |
// easy to handle that case explicitly in `DispatchCommand`. | ||
|
||
void AclFamily::Register(dfly::CommandRegistry* registry) { | ||
*registry << CI{"ACL", CO::NOSCRIPT | CO::LOADING, 0, 0, 0, 0, acl::kList}.HFUNC(Acl); | ||
*registry << CI{"ACL", CO::NOSCRIPT | CO::LOADING, 0, 0, 0, 0, acl::kAcl}.HFUNC(Acl); | ||
*registry << CI{"ACL LIST", CO::ADMIN | CO::NOSCRIPT | CO::LOADING, 1, 0, 0, 0, acl::kList}.HFUNC( | ||
List); | ||
*registry << CI{"ACL SETUSER", CO::ADMIN | CO::NOSCRIPT | CO::LOADING, -2, 0, 0, 0, acl::kSetUser} | ||
|
@@ -193,4 +225,8 @@ void AclFamily::Register(dfly::CommandRegistry* registry) { | |
|
||
#undef HFUNC | ||
|
||
void AclFamily::Init(std::vector<facade::Listener*> listeners) { | ||
|
||
listeners_ = std::move(listeners); | ||
} | ||
|
||
} // namespace dfly::acl |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,8 +4,6 @@ | |
|
||
#pragma once | ||
|
||
#include <string_view> | ||
|
||
#include "facade/command_id.h" | ||
#include "server/conn_context.h" | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -608,7 +608,7 @@ optional<ShardId> GetRemoteShardToRunAt(const Transaction& tx) { | |
} // namespace | ||
|
||
Service::Service(ProactorPool* pp) | ||
: pp_(*pp), server_family_(this), cluster_family_(&server_family_) { | ||
: pp_(*pp), acl_family_(*pp), server_family_(this), cluster_family_(&server_family_) { | ||
CHECK(pp); | ||
CHECK(shard_set == NULL); | ||
|
||
|
@@ -665,6 +665,7 @@ void Service::Init(util::AcceptServer* acceptor, std::vector<facade::Listener*> | |
|
||
shard_set->Init(shard_num, !opts.disable_time_update); | ||
|
||
acl_family_.Init(listeners); | ||
|
||
request_latency_usec.Init(&pp_); | ||
StringFamily::Init(&pp_); | ||
GenericFamily::Init(&pp_); | ||
|
@@ -2127,7 +2128,7 @@ void Service::RegisterCommands() { | |
BitOpsFamily::Register(®istry_); | ||
HllFamily::Register(®istry_); | ||
SearchFamily::Register(®istry_); | ||
acl::AclFamily::Register(®istry_); | ||
acl_family_.Register(®istry_); | ||
|
||
server_family_.Register(®istry_); | ||
cluster_family_.Register(®istry_); | ||
|
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.
SetUser is called so rarely that we can neglect the cost of traversing all connections? (of which there are also just a few usually?)
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.
Damn... What if the context is performing squashing? 😭
We can copy the value (instead of referencing the parent), but then how do we propagate changes? On each batch? 😕
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.
If you want you can drop support for squashing and I will think how to enable support for your features
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.
Btw, because Redis is single-threaded it's impossible for ACL rules to change while its executing a script / a multi transaction, right?
Uh oh!
There was an error while loading. Please reload this page.
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.
Yes that's true but it doesn't hurt to not traverse if the user was just created :)
I already thought of this 👨🍳 ! It won't affect us. Why? Because, sure we update the "stub" contexts as well BUT validation is done on the
parent
and not on thestub
context. Therefore, we don't really care, since the update done to stub contexts won't affect anything(think of it astub
update -- we mocked the mock 🔪 ). That is before a batch executes we callValidateCommandExecution
which will see the updated changes since it promts the parent and not the stub contextIt's all fine and if it wasn't we would fix it 🚀 No rush here
Redis is single threaded indeed but connections can be multiplexed just like on a single thread you can run multiple tasks interleaved with each other (to the user this looks like multihreading, in reality it's just a single thread that interleaves task execution). That being said, this example works on redis:
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.
t1 to t7 is monotonically increasing
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.
I was referring to the case when acl rules change while exec is running
Exactly. When you change a connection from its own thread, you assume no other thread has access to it. So it is safe to mutate variables. BUT with squashing, the parent connection is accessed from multiple threads for validation, so its not possible to mutate it (because
ValidateCommandExecution
might be running on another thread)