http: bound spawn_http concurrency (http_max_concurrent, default 32) like spawn_crypto — it spawned one unbounded OS thread per call, so a pre-auth VERIFY/REGISTER flood could exhaust threads and hammer the accounts backend; at capacity the command now fails with TEMPORARILY_UNAVAILABLE instead
This commit is contained in:
parent
801614605f
commit
bbe4ee4567
2 changed files with 40 additions and 5 deletions
|
|
@ -203,13 +203,21 @@ impl Command for Register {
|
|||
urlencode(&ip),
|
||||
port
|
||||
);
|
||||
s.spawn_http(
|
||||
if !s.spawn_http(
|
||||
uid,
|
||||
format!("acctreg:register:{account}"),
|
||||
url,
|
||||
body,
|
||||
apikey_headers(s),
|
||||
);
|
||||
) {
|
||||
s.fail(
|
||||
uid,
|
||||
"REGISTER",
|
||||
"TEMPORARILY_UNAVAILABLE",
|
||||
"The server is busy; please try again in a moment.",
|
||||
);
|
||||
return CmdResult::Fail;
|
||||
}
|
||||
CmdResult::Ok
|
||||
}
|
||||
}
|
||||
|
|
@ -252,13 +260,21 @@ impl Command for Verify {
|
|||
urlencode(&account),
|
||||
urlencode(¶ms[1])
|
||||
);
|
||||
s.spawn_http(
|
||||
if !s.spawn_http(
|
||||
uid,
|
||||
format!("acctreg:verify:{account}"),
|
||||
url,
|
||||
body,
|
||||
apikey_headers(s),
|
||||
);
|
||||
) {
|
||||
s.fail(
|
||||
uid,
|
||||
"VERIFY",
|
||||
"TEMPORARILY_UNAVAILABLE",
|
||||
"The server is busy; please try again in a moment.",
|
||||
);
|
||||
return CmdResult::Fail;
|
||||
}
|
||||
CmdResult::Ok
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -462,6 +462,10 @@ impl Server {
|
|||
/// the core as `Event::HttpResult { uid, tag, .. }` — the same self-injection
|
||||
/// pattern as the DNS resolver, so a slow endpoint never blocks the main loop.
|
||||
/// `tag` is `"<module>:<detail>"`; the core routes the reply by its prefix.
|
||||
/// Returns `false` when at capacity (`http_max_concurrent`, default 32) so the
|
||||
/// caller can reject instead of spawning an unbounded number of threads — a
|
||||
/// pre-auth flood (e.g. VERIFY) would otherwise exhaust threads and hammer the
|
||||
/// backend. A `Drop` guard keeps the counter correct even if the task panics.
|
||||
pub fn spawn_http(
|
||||
&self,
|
||||
uid: Uid,
|
||||
|
|
@ -469,10 +473,24 @@ impl Server {
|
|||
url: String,
|
||||
body: String,
|
||||
headers: Vec<(String, String)>,
|
||||
) {
|
||||
) -> bool {
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
static ACTIVE: AtomicUsize = AtomicUsize::new(0);
|
||||
struct Guard;
|
||||
impl Drop for Guard {
|
||||
fn drop(&mut self) {
|
||||
ACTIVE.fetch_sub(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
let max = self.conf_num("http_max_concurrent", 32usize);
|
||||
if ACTIVE.fetch_add(1, Ordering::Relaxed) >= max {
|
||||
ACTIVE.fetch_sub(1, Ordering::Relaxed);
|
||||
return false;
|
||||
}
|
||||
let tx = self.event_tx.clone();
|
||||
let verify = self.conf_bool("http_tls_verify", true);
|
||||
std::thread::spawn(move || {
|
||||
let _guard = Guard; // decrements even on panic
|
||||
let (status, body) = crate::http::post(
|
||||
&url,
|
||||
"application/x-www-form-urlencoded",
|
||||
|
|
@ -489,6 +507,7 @@ impl Server {
|
|||
body,
|
||||
});
|
||||
});
|
||||
true
|
||||
}
|
||||
|
||||
/// Run an expensive credential operation (a KDF: bcrypt / pbkdf2) on a worker
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue