{"record":{"id":"58c7de1b4e95d9ed","repo":"pinpoint-apm/pinpoint","slug":"scanner-is-exhausted-cannot-call-next-again","errorCode":null,"errorMessage":"Scanner is exhausted, cannot call next() again","messagePattern":"Scanner is exhausted, cannot call next\\(\\) again","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"commons-hbase/src/main/java/com/navercorp/pinpoint/common/hbase/wd/LocalScannerImpl.java","lineNumber":50,"sourceCode":" * callers to peek at the next {@link Result} and query exhaustion state before\n * consuming it.\n */\npublic class LocalScannerImpl implements Closeable, LocalScanner {\n\n    private final ResultScanner scanner;\n\n    private boolean exhausted = false;\n\n    private Result localBuffer;\n\n    public LocalScannerImpl(ResultScanner scanner) {\n        this.scanner = Objects.requireNonNull(scanner, \"scanner\");\n    }\n\n    @Override\n    public Result next() throws IOException {\n        if (exhausted) {\n            throw new IllegalStateException(\"Scanner is exhausted, cannot call next() again\");\n        }\n        if (localBuffer != null) {\n            return localBuffer;\n        }\n        Result next = scanner.next();\n        if (next == null) {\n            exhausted = true;\n        }\n        localBuffer = next;\n        return localBuffer;\n    }\n\n    @Override\n    public boolean isExhausted() {\n        return exhausted;\n    }\n\n    @Override","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/pinpoint-apm/pinpoint/blob/744c3d3075e595656abb1ae331ad2c0e4c9eb996/commons-hbase/src/main/java/com/navercorp/pinpoint/common/hbase/wd/LocalScannerImpl.java#L32-L68","documentation":"LocalScannerImpl wraps a per-region ResultScanner for the row-distributor (WD) scanner. Once the underlying scanner returns null it is marked exhausted; calling next() again is a contract violation for ResultScanner semantics and throws IllegalStateException to prevent returning stale or bogus rows.","triggerScenarios":"Calling next() on a LocalScannerImpl after a previous next() returned null (end of the wrapped region's results), e.g. a loop that doesn't break on null or retries after exhaustion.","commonSituations":"Consumer loops calling next() once more to 'confirm' the end; code caching the scanner and reusing it after completion; bugs in aggregate logic over WD-scanned regions.","solutions":["Stop iterating as soon as next() returns null","Obtain a new scanner (via the WD scanner factory) instead of reusing an exhausted one","Check for duplicate next() calls in retry/loop logic around ResultScanner usage"],"exampleFix":"// before\nResult r = scanner.next();\nprocess(r);\nResult r2 = scanner.next(); // may throw if already exhausted\n// after\nResult r;\nwhile ((r = scanner.next()) != null) { process(r); }","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    Result r = localScanner.next();\n} catch (IllegalStateException e) {\n    logger.error(\"Scanner misuse: {}\", e.getMessage());\n    scanner = wdScanner.getScanner(region); // reacquire instead of reusing\n}","preventionTips":["Treat next()==null as terminal; never call next() again afterwards","Always use while ((r = scanner.next()) != null) loops","Don't cache and reuse ResultScanner instances after completion","Reset local scanner state when reusing wrapper objects"],"tags":["hbase","scanner","invalid-state","iteration"],"backgroundTag":"invalid-state-transition","analyzedSha":"744c3d3075e595656abb1ae331ad2c0e4c9eb996","analyzedAt":"2026-09-07T18:48:45.289Z","contentChangedAt":"2026-09-07T18:48:45.289Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}