grpc: run authenticate's PBKDF2 verify off the engine lock
All checks were successful
CI / check (push) Successful in 3m54s
All checks were successful
CI / check (push) Successful in 3m54s
The Authenticate RPC held the engine Mutex across scram verify_plain, which at production iteration counts (1.2M) is ~0.8s of PBKDF2 on the async reactor — freezing the whole daemon (IRC link, gossip, every RPC) for the duration and serializing all logins. Fetch the verifier under the lock (cheap), then run verify_plain on spawn_blocking, lock released. 6 concurrent logins: 823ms (was ~4460ms serialized). set_password/register already offload this way; drop the now-unused authority_authenticate.
This commit is contained in:
parent
2e92f75a90
commit
63fc2fb2c0
2 changed files with 23 additions and 5 deletions
|
|
@ -47,8 +47,13 @@ impl Engine {
|
|||
status
|
||||
}
|
||||
|
||||
pub fn authority_authenticate(&self, name: &str, password: &str) -> Option<String> {
|
||||
self.db.authenticate(name, password).map(str::to_string)
|
||||
/// The account's canonical name and its SHA-256 verifier (owned), so a caller
|
||||
/// can run the PBKDF2 verify OFF the engine lock. At production iteration
|
||||
/// counts that derivation is ~1s of CPU — never run it while holding the lock
|
||||
/// (it would freeze the whole daemon); fetch here, then `scram::verify_plain`
|
||||
/// on a blocking thread.
|
||||
pub fn scram_verifier(&self, name: &str) -> Option<(String, String)> {
|
||||
self.db.scram_lookup(name, "SCRAM-SHA-256").map(|(a, v)| (a.to_string(), v.to_string()))
|
||||
}
|
||||
|
||||
pub fn authority_set_password(&mut self, account: &str, creds: Option<db::Credentials>) -> AuthorityStatus {
|
||||
|
|
|
|||
19
src/grpc.rs
19
src/grpc.rs
|
|
@ -291,9 +291,22 @@ impl Accounts for AccountsService {
|
|||
async fn authenticate(&self, req: Request<AuthenticateRequest>) -> Result<Response<AuthenticateReply>, Status> {
|
||||
authorize(&req, &self.token)?;
|
||||
let msg = req.into_inner();
|
||||
match self.engine.lock().await.authority_authenticate(&msg.name, &msg.password) {
|
||||
Some(account) => Ok(Response::new(AuthenticateReply { status: PbStatus::Ok as i32, account })),
|
||||
None => Ok(Response::new(AuthenticateReply { status: PbStatus::Invalid as i32, account: String::new() })),
|
||||
// Fetch the verifier under the lock (cheap), then run the ~1s PBKDF2 on a
|
||||
// blocking thread — never hold the engine lock across it, or every login
|
||||
// freezes the whole daemon (IRC link, gossip, all RPCs).
|
||||
let Some((account, verifier)) = self.engine.lock().await.scram_verifier(&msg.name) else {
|
||||
return Ok(Response::new(AuthenticateReply { status: PbStatus::Invalid as i32, account: String::new() }));
|
||||
};
|
||||
let password = msg.password;
|
||||
let ok = tokio::task::spawn_blocking(move || {
|
||||
crate::engine::scram::verify_plain(crate::engine::scram::Hash::Sha256, &verifier, &password)
|
||||
})
|
||||
.await
|
||||
.unwrap_or(false);
|
||||
if ok {
|
||||
Ok(Response::new(AuthenticateReply { status: PbStatus::Ok as i32, account }))
|
||||
} else {
|
||||
Ok(Response::new(AuthenticateReply { status: PbStatus::Invalid as i32, account: String::new() }))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue