mirror of
https://github.com/4cuck/teapawt.git
synced 2026-10-27 09:04:35 +00:00
Skip an account that already has two calls in flight so a crawl keeps moving.
An unlimited per-account cap lets one burst pile onto the few accounts already known to work. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
d00136558a
commit
44b006821d
2 files changed
+57
-5
No files matched your search
@@ -95,10 +95,12 @@ second search does not pay a new SOCKS + TLS handshake.
|
||||
|
||||
### Rate limits and cache
|
||||
|
||||
- `maxConcurrentReqs` is **per cookie session**, not global. A busy public
|
||||
instance should set it to `1000000`. At that size the cap never queues a
|
||||
call, which matters once the instance is popular enough that every account
|
||||
would otherwise sit at a small limit.
|
||||
- `maxConcurrentReqs` is **per cookie session**, not global. At `2`, an
|
||||
account that already has two calls in flight is skipped and the next
|
||||
account is used immediately. A crawl stays at two in flight per account
|
||||
across the whole pool. Raising it into the millions lets one burst pile
|
||||
onto the few accounts already known to work, which is what produces a
|
||||
cliff of 429s.
|
||||
- `clientBudget` caps how fast one visitor IP can spend uncached X calls.
|
||||
Cache hits, media, and static files are free. SearchTimeline costs 2 tokens.
|
||||
- First-page search is cached for 60 seconds, then served stale for 4 more
|
||||
|
||||
+51
-1
@@ -356,7 +356,11 @@ impl SessionPool {
|
||||
}
|
||||
|
||||
// Spend the local window under the write lock, then take a permit.
|
||||
// A session that is only busy is remembered so we can wait on it.
|
||||
// An account already at its concurrency cap is skipped. The call
|
||||
// goes to the next account that still has a free slot, so a crawl
|
||||
// keeps moving instead of queueing behind the account that filled up.
|
||||
// Waiting is only for the case where every usable account is already
|
||||
// at the cap.
|
||||
let mut wait_on = None;
|
||||
for index in eligible {
|
||||
let api = api_for(self.sessions[index].credentials.kind);
|
||||
@@ -1056,6 +1060,52 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_full_account_hands_the_next_call_over_immediately() {
|
||||
unsafe { env::set_var("TEAPOT_SESSION_STATE_FILE", "") };
|
||||
let path = env::temp_dir().join(format!(
|
||||
"teapot-session-pool-spread-{}.jsonl",
|
||||
process::id()
|
||||
));
|
||||
fs::write(
|
||||
&path,
|
||||
concat!(
|
||||
r#"{"id":1,"username":"one","kind":"cookie","auth_token":"a","ct0":"c"}"#,
|
||||
"\n",
|
||||
r#"{"id":2,"username":"two","kind":"cookie","auth_token":"b","ct0":"d"}"#,
|
||||
"\n"
|
||||
),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let pool = SessionPool::load(path.to_str().unwrap(), 2).await.unwrap();
|
||||
|
||||
let first = pool
|
||||
.acquire("TweetDetail", Some(SessionKind::Cookie))
|
||||
.await
|
||||
.unwrap();
|
||||
let other = if first.id == 1 { 2 } else { 1 };
|
||||
let second = pool
|
||||
.acquire_excluding("TweetDetail", Some(SessionKind::Cookie), Some(other))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(first.id, second.id);
|
||||
|
||||
let next = timeout(
|
||||
Duration::from_millis(50),
|
||||
pool.acquire("TweetDetail", Some(SessionKind::Cookie)),
|
||||
)
|
||||
.await
|
||||
.expect("a full account should be skipped, not waited on")
|
||||
.unwrap();
|
||||
assert_eq!(next.id, other);
|
||||
|
||||
drop(first);
|
||||
drop(second);
|
||||
drop(next);
|
||||
fs::remove_file(path).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn pool_filters_kinds_rotates_and_enforces_concurrency() {
|
||||
let path = env::temp_dir().join(format!("teapot-session-pool-{}.jsonl", process::id()));
|
||||
|
||||
Reference in new issue
Block a user