{"record":{"id":"9b0267129496e802","repo":"apache/druid","slug":"cannot-set-mergers-for-final-level-more-than-once","errorCode":null,"errorMessage":"Cannot set mergers for final level more than once","messagePattern":"Cannot set mergers for final level more than once","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/frame/processor/SuperSorterProgressTracker.java","lineNumber":146,"sourceCode":"          totalMergingLevels - 1\n      );\n    }\n    if (totalMergingLevels != SuperSorter.UNKNOWN_LEVEL\n        && level < totalMergingLevels - 1 // This condition is only present for levels excluding the ultimate level\n        && levelToTotalBatches.containsKey(level)) {\n      throw new ISE(\"Total mergers are already present for the level %d\", level);\n    }\n    levelToTotalBatches.put(level, totalMergers);\n  }\n\n  /**\n   * Sets the number of mergers in the ultimate level (number of mergers = number of output partitions).\n   * Can only be set once\n   */\n  public synchronized void setTotalMergersForUltimateLevel(final long totalMergersForUltimateLevel)\n  {\n    if (this.totalMergersForUltimateLevel != SuperSorter.UNKNOWN_TOTAL) {\n      throw new ISE(\"Cannot set mergers for final level more than once\");\n    }\n    this.totalMergersForUltimateLevel = totalMergersForUltimateLevel;\n  }\n\n  /**\n   * This method is designed to be called during the course of the sorting. The batches once merged for a particular\n   * level can be marked as such through this.\n   */\n  public synchronized void addMergedBatchesForLevel(final int level, final long additionalMergedBatches)\n  {\n    if (totalMergingLevels != SuperSorter.UNKNOWN_LEVEL && level >= totalMergingLevels) {\n      throw new ISE(\n          \"Cannot add merged batches for level %d. Valid levels range from 0 to %d\",\n          level,\n          totalMergingLevels - 1\n      );\n    }\n    levelToMergedBatches.compute(level, (l, mergedBatchesSoFar) -> mergedBatchesSoFar == null","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/frame/processor/SuperSorterProgressTracker.java#L128-L164","documentation":"setTotalMergersForUltimateLevel is write-once: if the ultimate-level merger count is already set (different from SuperSorter.UNKNOWN_TOTAL), calling it again throws 'Cannot set mergers for final level more than once'. The ultimate level's merger count equals the number of output partitions and is finalized once partitioning is known; re-setting indicates the partition-count computation ran twice or two components both attempt to register it.","triggerScenarios":"Calling setTotalMergersForUltimateLevel a second time — e.g., outputPartitionsFuture completing twice, both the direct-merger and the middle-merger code paths registering output partitions, or a retry re-running sorter initialization on the same tracker.","commonSituations":"Reused tracker fixtures in tests; sorters re-initialized after cancellation without replacing the tracker; concurrent completion paths in custom merge drivers that both resolve the output partition count.","solutions":["Guard the call: only invoke setTotalMergersForUltimateLevel when the current value is still SuperSorter.UNKNOWN_TOTAL.","Ensure only one code path resolves output partitions and registers the ultimate-level count.","Construct a fresh tracker when retrying or reinitializing a sort.","If the output partition count genuinely changed, that requires a new sort run with a new tracker — the count is immutable once set."],"exampleFix":"// before\ntracker.setTotalMergersForUltimateLevel(outputPartitions.size()); // may be called on every future completion\n// after\nif (tracker.getTotalMergersForUltimateLevel() == SuperSorter.UNKNOWN_TOTAL) {\n  tracker.setTotalMergersForUltimateLevel(outputPartitions.size());\n}","handlingStrategy":"validation","validationCode":"if (tracker.getTotalMergersForUltimateLevel() == SuperSorter.UNKNOWN_TOTAL) {\n  tracker.setTotalMergersForUltimateLevel(outputPartitions.size());\n}","typeGuard":null,"tryCatchPattern":"try {\n  tracker.setTotalMergersForUltimateLevel(n);\n} catch (IllegalStateException e) {\n  if (!e.getMessage().contains(\"more than once\")) {\n    throw e;\n  } // already set — ignore duplicates\n}","preventionTips":["Resolve output partitions in exactly one code path.","Check for UNKNOWN_TOTAL before setting the ultimate-level count.","Replace the tracker on any retry; the ultimate count is immutable once set."],"tags":["internal-state","write-once","sorting"],"backgroundTag":"invalid-state-transition","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}