{"record":{"id":"238f1d7950672307","repo":"sgl-project/sglang","slug":"finishexternalcorpusload-called-without-startexter","errorCode":null,"errorMessage":"finishExternalCorpusLoad called without startExternalCorpusLoad","messagePattern":"finishExternalCorpusLoad called without startExternalCorpusLoad","errorType":"exception","errorClass":"std::runtime_error","httpStatus":null,"severity":"error","filePath":"python/sglang/kernels/jit/csrc/ngram_corpus/ngram.cpp","lineNumber":88,"sourceCode":"// 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();\n    throw std::runtime_error(\"External corpus is empty — no tokens were loaded.\");\n  }\n  // Only lock briefly to install the completed SAM.\n  std::unique_lock<std::mutex> lock(mutex_);\n  if (sams_.find(corpus_id) != sams_.end()) {\n    throw std::runtime_error(\n        \"External corpus '\" + corpus_id + \"' already exists. Remove it before adding a new corpus with the same id.\");\n  }\n  sams_.emplace(corpus_id, std::move(staging_sam_));\n}\n\nvoid Ngram::removeExternalCorpus(const std::string& corpus_id) {\n  std::unique_lock<std::mutex> lock(mutex_);\n  sams_.erase(corpus_id);","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/kernels/jit/csrc/ngram_corpus/ngram.cpp#L70-L106","documentation":"finishExternalCorpusLoad finalizes the staged suffix automaton and installs it under a corpus id. It throws if no staging automaton exists, meaning startExternalCorpusLoad was never called or the staging slot was already consumed by a prior finish.","triggerScenarios":"Calling finishExternalCorpusLoad(id) without a successful startExternalCorpusLoad(), or calling finish twice.","commonSituations":"Double-finish in an async loader (e.g. cleanup path plus normal path); exception handler that finishes an already-finished load; start failed or was skipped due to earlier error.","solutions":["Call finish exactly once per successful start","Track load state (started/finished) in the caller and skip redundant finish calls","On error paths, only reset via the library's own semantics — do not re-finish"],"exampleFix":"// before\nngram.finishExternalCorpusLoad(\"wiki\");\nngram.finishExternalCorpusLoad(\"wiki\"); // throws\n// after\nngram.finishExternalCorpusLoad(\"wiki\");\nfinished = true; // guard later calls","handlingStrategy":"validation","validationCode":"if (load_started && !load_finished) ngram.finishExternalCorpusLoad(id);","typeGuard":null,"tryCatchPattern":"try { ngram.finishExternalCorpusLoad(id); } catch (const std::runtime_error& e) { /* treat missing-start as no-op, log */ }","preventionTips":["Track started/finished booleans per load","Never call finish from both success and cleanup paths"],"tags":["ngram","corpus-loading","api-misuse","state-machine"],"backgroundTag":"invalid-operation-sequence","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}