{"record":{"id":"52e75925fa9eba96","repo":"NationalSecurityAgency/ghidra","slug":"no-batch-to-end","errorCode":null,"errorMessage":"No batch to end","messagePattern":"No batch to end","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Ghidra/Debug/Debugger-rmi-trace/src/main/py/src/ghidratrace/client.py","lineNumber":965,"sourceCode":"        return f\"<ghidratrace.Client {self.s}>\"\n\n    def close(self) -> None:\n        self.s.close()\n        self.receiver.shutdown()\n\n    def start_batch(self) -> Batch:\n        with self._block:\n            if self.cur_batch is None:\n                self.cur_batch = Batch()\n            self.cur_batch.inc()\n            return self.cur_batch\n\n    def end_batch(self) -> Optional[List[Any]]:\n        cb = None\n        with self._block:\n            cb = self.cur_batch\n            if cb is None:\n                raise ValueError(\"No batch to end\")\n            if 0 == cb.dec():\n                self.cur_batch = None\n                return cb.results()\n        return None\n\n    @contextmanager\n    def batch(self) -> Generator[Batch, None, Optional[List[Any]]]:\n        \"\"\"Execute a number of RMI calls in an asynchronous batch.\n\n        This returns a context manager, meant to be used as follows:\n\n           with client.batch():\n               trace.set_value(...)\n               trace.set_value(...)\n               ...\n\n        This is highly recommended when you know you will be making many rapid\n        RMI calls. All calls to the API that could involve RMI will instead","sourceCodeStart":947,"sourceCodeEnd":983,"githubUrl":"https://github.com/NationalSecurityAgency/ghidra/blob/d5f144c24d6bc53c9cbf4448c6d11143e7696206/Ghidra/Debug/Debugger-rmi-trace/src/main/py/src/ghidratrace/client.py#L947-L983","documentation":"Raised by Client.end_batch() when there is no current batch (cur_batch is None). Batching is reference-counted via start_batch/end_batch; end_batch may only be called when a batch is active. Mismatched calls corrupt the counter, so the library refuses rather than silently misbehave.","triggerScenarios":"Calling end_batch() without a prior start_batch(); calling end_batch() more times than start_batch(); an exception path that double-ends a batch.","commonSituations":"Manual batch management instead of the provided context manager; error handling that calls end_batch in a finally without checking start succeeded; copy-paste across code paths.","solutions":["Prefer the context manager: with client.batch(): ... which balances start/end automatically.","If managing manually, ensure every start_batch() has exactly one matching end_batch(), including in except/finally.","Track the batch handle returned by start_batch() and only end when you hold one."],"exampleFix":"# before\ncb = client.start_batch()\ntry:\n    do_work()\nfinally:\n    client.end_batch()  # fails if start_batch wasn't reached or already ended\n# after\nwith client.batch():\n    do_work()","handlingStrategy":"try-catch","validationCode":"cb = client.start_batch()\nassert cb is not None  # then end once","typeGuard":null,"tryCatchPattern":"try:\n    client.end_batch()\nexcept ValueError:\n    # batch already ended/not started; log and continue","preventionTips":["Prefer the with client.batch(): context manager.","Balance start_batch/end_batch exactly, including in finally."],"tags":["api-misuse","trace-rmi","batching","lifecycle"],"backgroundTag":null,"analyzedSha":"d5f144c24d6bc53c9cbf4448c6d11143e7696206","analyzedAt":"2026-08-14T01:00:57.564Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}