{"record":{"id":"267f8260ef862aa5","repo":"sgl-project/sglang","slug":"appendexternalcorpustokens-called-without-startext","errorCode":null,"errorMessage":"appendExternalCorpusTokens called without startExternalCorpusLoad","messagePattern":"appendExternalCorpusTokens called without startExternalCorpusLoad","errorType":"exception","errorClass":"std::runtime_error","httpStatus":null,"severity":"error","filePath":"python/sglang/kernels/jit/csrc/ngram_corpus/ngram.cpp","lineNumber":81,"sourceCode":"  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();\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.\");","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/kernels/jit/csrc/ngram_corpus/ngram.cpp#L63-L99","documentation":"appendExternalCorpusTokens is a staging API that only works between startExternalCorpusLoad and finishExternalCorpusLoad. It throws when staging_sam_ is null, i.e. no load was started or the previous one already completed/failed.","triggerScenarios":"Calling appendExternalCorpusTokens(tokens) without a prior successful startExternalCorpusLoad(), or after finishExternalCorpusLoad() already consumed the staging automaton.","commonSituations":"Background loader thread racing ahead of the starter thread; retrying appends after a failed finish; reordered async pipeline where chunk streaming begins before start.","solutions":["Ensure startExternalCorpusLoad() is called (and awaited) before any appendExternalCorpusTokens call","Add a state check / boolean flag in your loader before appending","Serialize the start/append/finish sequence on one thread or with a mutex"],"exampleFix":"// before\nngram.appendExternalCorpusTokens(chunk); // no start yet\n// after\nngram.startExternalCorpusLoad();\nngram.appendExternalCorpusTokens(chunk);","handlingStrategy":"validation","validationCode":"bool load_started = false; // set true right after startExternalCorpusLoad()\nif (load_started) ngram.appendExternalCorpusTokens(chunk);","typeGuard":null,"tryCatchPattern":"try { ngram.appendExternalCorpusTokens(chunk); } catch (const std::runtime_error& e) { /* restart load: start() then append */ }","preventionTips":["Own a small state machine (idle/loading/done) around the staging API","Run start/append/finish on a single loader thread"],"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-28T06:17:29.519Z"}