{"record":{"id":"742750cbdc15a107","repo":"EpicGames/lore","slug":"unsupported-pool-threads-var-value-error-expected-a-whole","errorCode":null,"errorMessage":"unsupported {POOL_THREADS_VAR} \"{value}\": {error} (expected a whole number of threads)","messagePattern":"unsupported (.+?) \"(.+?)\": (.+?) \\(expected a whole number of threads\\)","errorType":"validation","errorClass":"io::Error","httpStatus":null,"severity":"warning","filePath":"lore-io/src/pool.rs","lineNumber":51,"sourceCode":"/// stalling async worker threads, while keeping this pool's claim on the process-wide thread budget\n/// small enough to leave room for the populations it shares that budget with.\n///\n/// Doubling the cap buys 4–6% warm and 2–8% cold on Windows/NTFS, and nothing outside ±4% on\n/// Linux/ext4. No cap wins every phase, so this is a position on a curve rather than an optimum;\n/// `lore-io/BENCHMARKS.md` has the sweeps. What does cost is falling well below the workload's\n/// concurrency: 8 threads measured 0.54× against 32 on 16,384 evicted files read at 64-way\n/// concurrency, and 4 threads measured 0.65× on macOS/APFS cold reads offering 64 and 128. That\n/// ratio is pool size against in-flight requests rather than against core count, so it is what a\n/// machine small enough for `2 × cores` to reach those sizes runs into.\npub(crate) fn default_max_threads() -> usize {\n    let cores = std::thread::available_parallelism().map_or(2, |count| count.get());\n    std::cmp::min(2 * cores, 16)\n}\n\n/// Parses a [`POOL_THREADS_VAR`] value. Separate from reading the variable so the accepted range\n/// and the error are testable without a process-global environment.\nfn max_threads_from_value(value: &str) -> std::io::Result<usize> {\n    let invalid = |detail: String| std::io::Error::new(std::io::ErrorKind::InvalidInput, detail);\n    let count: usize = value.trim().parse().map_err(|error| {\n        invalid(format!(\n            \"unsupported {POOL_THREADS_VAR} \\\"{value}\\\": {error} \\\n             (expected a whole number of threads)\"\n        ))\n    })?;\n    if count == 0 {\n        return Err(invalid(format!(\n            \"{POOL_THREADS_VAR} must be at least 1; a pool of 0 threads runs nothing\"\n        )));\n    }\n    if count > MAX_POOL_THREADS {\n        return Err(invalid(format!(\n            \"{POOL_THREADS_VAR} of {count} exceeds the {MAX_POOL_THREADS}-thread ceiling\"\n        )));\n    }\n    Ok(count)\n}","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/EpicGames/lore/blob/074eb0b0d1194c997d7cf28b55519e3e197b3e23/lore-io/src/pool.rs#L33-L69","documentation":"max_threads_from_value failed to parse the POOL_THREADS_VAR environment variable as a whole number of threads, so the pool builder rejects the value with ErrorKind::InvalidInput. The library parses the variable eagerly so a bad override fails fast with a clear message instead of falling back silently.","triggerScenarios":"Setting the POOL_THREADS_VAR environment variable to something that is not a valid usize after trimming — e.g. \"abc\", \"3.5\", \"-2\", \"4 threads\", or a number exceeding usize on this platform — then creating a pool via requested_max_threads.","commonSituations":"Typo or unit suffix in an env var set in a shell profile, CI config, or Dockerfile; copy-pasting \"16 threads\" or \"16\\n\" values; locale-formatted numbers like \"1_000\" or \"1,000\".","solutions":["Set POOL_THREADS_VAR to a plain non-negative integer, e.g. `export POOL_THREADS_VAR=8`.","Unset the variable entirely to use the library default (min(2 * cores, 16)).","Strip whitespace/units before exporting: use `8` not `\"8 threads\"`.","If validating user config, parse it first with value.trim().parse::<usize>() and reject early."],"exampleFix":"// before\nexport POOL_THREADS_VAR=\"16 threads\"\n// after\nexport POOL_THREADS_VAR=16","handlingStrategy":"validation","validationCode":"// Rust\nfn valid_pool_threads(v: &str) -> Result<usize, String> {\n    v.trim().parse::<usize>().map_err(|e| format!(\"POOL_THREADS_VAR '{v}' invalid: {e} (expected a whole number of threads)\"))\n}","typeGuard":null,"tryCatchPattern":"// Rust\nmatch requested_max_threads() {\n    Ok(n) => build_pool(n),\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidInput => build_pool(default_threads()), // fallback\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Set POOL_THREADS_VAR to a bare integer like `16`, no units or punctuation","Unset the variable to accept the default (min(2*cores, 16))","Validate env-derived config at process startup, fail fast with a clear message","Test config parsing in CI with a representative env (the library exposes max_threads_from_value for this)"],"tags":["config","environment","parsing"],"backgroundTag":"invalid-env-var-value","analyzedSha":"074eb0b0d1194c997d7cf28b55519e3e197b3e23","analyzedAt":"2026-09-13T09:00:57.509Z","contentChangedAt":"2026-09-13T09:00:57.509Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}