feat(pool): no machine-wide width - 128 passes per available account
Jorijn: "there is no need to do a global cap anymore, the cap should be 128 per available account". `--concurrency` no longer defaults to 128: the limits are each OAuth account's 128 (and the local login's), every driver's counted, an API seat's own REVIEW_GATES_API_CONCURRENCY, and free memory. `--concurrency N` still caps the machine when given; a value below 1 is a usage error. The dump's args.concurrency is null then. The board's driver settings read `128 per account`, which every driver's `seats` shows as that driver's width, and the `seats` header says a width is that driver's cap. The Scheduler test runs 300 passes on two accounts: 256 at once, 128 each. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
c28307601b
commit
61ee4602f4
12 changed files with 98 additions and 19 deletions
|
|
@ -341,11 +341,21 @@ echo "{\"type\":\"result\",\"subtype\":\"success\",\"is_error\":false,\"result\"
|
|||
// Stopped by `crafter-review stop`'s file, and by SIGTERM, while the
|
||||
// first gate's whole pass is under way: the pass is killed, no later
|
||||
// gate starts, nothing is left running.
|
||||
std::vector<std::string> widths;
|
||||
for (const bool bySignal : {false, true}) {
|
||||
before = calls();
|
||||
const std::string name = bySignal ? "term" : "file";
|
||||
const Process::Child child = start({"--no-reuse", "--no-salvage"}, {{"FAKE_SLEEP", "573"}}, name);
|
||||
const bool running = WaitFor([&] { return calls() > before; }, std::chrono::seconds(30));
|
||||
// While it runs, the board shows its width: per account, there
|
||||
// being no machine-wide cap.
|
||||
std::string width;
|
||||
try {
|
||||
const Json drivers = Dyn::LoadJsonFile(base / "board.json").at("drivers");
|
||||
for (auto it = drivers.begin(); it != drivers.end(); ++it) width = Text(Dyn::Get(Dyn::Get(it.value(), "settings"), "concurrency"));
|
||||
} catch (const std::exception&) {
|
||||
}
|
||||
widths.push_back(width);
|
||||
std::string scratch;
|
||||
for (const std::string& line : Lines(base / std::format("{}.out", name))) {
|
||||
if (const std::size_t at = line.rfind("; scratch "); at != std::string::npos) scratch = line.substr(at + 10);
|
||||
|
|
@ -368,6 +378,15 @@ echo "{\"type\":\"result\",\"subtype\":\"success\",\"is_error\":false,\"result\"
|
|||
dump.is_object() ? Text(dump.at("stoppedAt")) : Read(base / std::format("{}.err", name))));
|
||||
}
|
||||
|
||||
// No machine-wide width by default (2026-10-31): the dump's args record
|
||||
// none, a live run's board entry shows the per-account one, and a
|
||||
// width below 1 is refused.
|
||||
const Process::Captured noWidth = run({"--concurrency", "0"});
|
||||
Check("no global cap by default: the dump records none, a live run shows `128 per account` on the board; --concurrency below 1 is a usage error",
|
||||
dumpB.is_object() && dumpB.at("args").at("concurrency").is_null() && widths == std::vector<std::string>{"128 per account", "128 per account"} && noWidth.returnCode == 3
|
||||
&& noWidth.err.contains("argument --concurrency: a number from 1 up"),
|
||||
std::format("{} | {}", Dyn::Join(widths, ", "), noWidth.err));
|
||||
|
||||
// An env file that is not UTF-8: the preflight stops the run.
|
||||
Write(base / "bad.env", std::string("\xff\n", 2));
|
||||
const Process::Captured badEnv = run({"--no-reuse", "--no-salvage", "--env-file", (base / "bad.env").string()});
|
||||
|
|
|
|||
|
|
@ -145,6 +145,43 @@ namespace {
|
|||
Check("a no-room wait ends on the next completion, not at the window reset",
|
||||
r.DoneList().size() == 3 && Seconds(t0) < 5 && AnyContains(waited, "no seat has room") && AnyContains(waited, "seat room is re-checked") && r.PauseUntil() == 0, Dyn::Join(waited, " | "));
|
||||
|
||||
// 2026-10-31: no machine-wide width by default - each account takes at
|
||||
// most 128 passes at once, every driver's counted.
|
||||
{
|
||||
std::vector<Json> two;
|
||||
for (const std::string_view id : {std::string_view("1"), std::string_view("2")}) {
|
||||
Json s = SeatPool::NewSeat(std::string(id), "oauth", std::format("t{}", id));
|
||||
s["state"] = "ok";
|
||||
s["windows"] = {{"five_hour", {{"utilization", 10}, {"resetsAt", now + 3600}}}, {"seven_day", {{"utilization", 10}, {"resetsAt", now + 86400}}}};
|
||||
two.push_back(std::move(s));
|
||||
}
|
||||
std::mutex lock;
|
||||
std::map<std::string, std::int64_t> inSeat;
|
||||
std::map<std::string, std::int64_t> peakSeat;
|
||||
std::int64_t inFlight = 0;
|
||||
std::int64_t peak = 0;
|
||||
Pool::Scheduler wide({.log = [](const std::string&) {}, .seats = &two, .seatConfig = SeatPool::PoolConfig({}), .apiConfig = SeatPool::ApiConfig({})});
|
||||
std::vector<Json> many;
|
||||
for (std::int32_t i = 0; i < 300; ++i) many.push_back({{"label", std::format("w{}", i)}});
|
||||
wide.Run(many, [&](const Json& j, std::int64_t) {
|
||||
const std::string seat = Dyn::Str(j.at("seat"));
|
||||
{
|
||||
const std::lock_guard guard(lock);
|
||||
peakSeat[seat] = std::max(peakSeat[seat], ++inSeat[seat]);
|
||||
peak = std::max(peak, ++inFlight);
|
||||
}
|
||||
std::this_thread::sleep_for(300ms);
|
||||
{
|
||||
const std::lock_guard guard(lock);
|
||||
--inSeat[seat];
|
||||
--inFlight;
|
||||
}
|
||||
return std::pair<Json, Json>({{"label", j.at("label")}, {"status", "ok"}}, nullptr);
|
||||
}, NoFollowUp);
|
||||
Check("no machine-wide width by default: two accounts take 128 passes each, 256 at once, and all 300 finish",
|
||||
wide.DoneList().size() == 300 && peak == 256 && peakSeat["1"] == 128 && peakSeat["2"] == 128, std::format("peak {}, per seat {} and {}", peak, peakSeat["1"], peakSeat["2"]));
|
||||
}
|
||||
|
||||
std::map<std::string, double> started;
|
||||
std::map<std::string, double> answered;
|
||||
logs.Clear();
|
||||
|
|
|
|||
Loading…
Reference in a new issue