{"record":{"id":"dde5384d454cc553","repo":"sgl-project/sglang","slug":"startexternalcorpusload-called-while-another-load","errorCode":null,"errorMessage":"startExternalCorpusLoad called while another load is in progress","messagePattern":"startExternalCorpusLoad called while another load is in progress","errorType":"exception","errorClass":"std::runtime_error","httpStatus":null,"severity":"error","filePath":"python/sglang/kernels/jit/csrc/ngram_corpus/ngram.cpp","lineNumber":74,"sourceCode":"}\n\nvoid Ngram::asyncInsert(std::vector<std::vector<int32_t>>&& tokens) {\n  {\n    std::lock_guard<std::mutex> lock(mutex_);\n    pending_count_ += tokens.size();\n  }\n  for (auto&& token : tokens) {\n    insert_queue_.enqueue(std::move(token));\n  }\n}\n\n// NOTE: staging operations (start/append/finish) are called from a background\n// thread during async corpus loading. They do NOT hold mutex_ because\n// staging_sam_ is disjoint from sams_ / trie_. Only finishExternalCorpusLoad\n// briefly acquires mutex_ when moving the completed SAM into sams_.\nvoid Ngram::startExternalCorpusLoad() {\n  if (staging_sam_) {\n    throw std::runtime_error(\"startExternalCorpusLoad called while another load is in progress\");\n  }\n  staging_sam_ = std::make_unique<SuffixAutomaton>();\n}\n\nvoid Ngram::appendExternalCorpusTokens(const std::vector<int32_t>& tokens) {\n  if (!staging_sam_) {\n    throw std::runtime_error(\"appendExternalCorpusTokens called without startExternalCorpusLoad\");\n  }\n  staging_sam_->appendTokens(tokens);\n}\n\nvoid Ngram::finishExternalCorpusLoad(const std::string& corpus_id) {\n  if (!staging_sam_) {\n    throw std::runtime_error(\"finishExternalCorpusLoad called without startExternalCorpusLoad\");\n  }\n  staging_sam_->finalize();\n  if (staging_sam_->empty()) {\n    staging_sam_.reset();","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/kernels/jit/csrc/ngram_corpus/ngram.cpp#L56-L92","documentation":"The external-corpus staging API is single-slot: only one background corpus load may be active at a time. startExternalCorpusLoad throws if a staging suffix automaton already exists, i.e. a previous start was not finished or abandoned.","triggerScenarios":"Calling startExternalCorpusLoad() twice without an intervening finishExternalCorpusLoad() or a failed load that reset staging_sam_.","commonSituations":"Starting two async corpus downloads/loads concurrently against the same Ngram instance; a previous load that errored mid-way (e.g. empty corpus) leaving staging state; retry loops that call start again without finishing.","solutions":["Serialize corpus loads: finish (or let fail) the in-flight load before starting the next","If the previous load was abandoned, call finishExternalCorpusLoad (it resets staging on error) to clear the slot","Guard with your own in-progress flag around the start/append/finish sequence"],"exampleFix":"// before\nngram.startExternalCorpusLoad();\n// other thread\nngram.startExternalCorpusLoad(); // throws\n// after\nstd::lock_guard<std::mutex> g(corpus_load_mu);\nngram.startExternalCorpusLoad();","handlingStrategy":"validation","validationCode":"std::atomic<bool> load_in_progress{false};\nif (load_in_progress.exchange(true)) { /* skip or queue */ return; }\nngram.startExternalCorpusLoad();","typeGuard":null,"tryCatchPattern":"try { ngram.startExternalCorpusLoad(); } catch (const std::runtime_error& e) { if (strstr(e.what(), \"in progress\")) { /* wait for current load */ } else throw; }","preventionTips":["Serialize corpus loads with a mutex/flag in the caller","Always pair start with finish (even on error) so the slot clears"],"tags":["ngram","corpus-loading","concurrency","state-machine"],"backgroundTag":"operation-already-in-progress","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}